feat: add web application
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import { h } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: '模块',
|
||||
key: 'module',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.module == 'admin' ? 'info' : 'success',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.module,
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
key: 'memberName',
|
||||
render(row) {
|
||||
if (row.memberId === 0) {
|
||||
return row.memberName;
|
||||
}
|
||||
return row.memberName + '(' + row.memberId + ')';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '请求方式',
|
||||
key: 'method',
|
||||
},
|
||||
{
|
||||
title: '请求路径',
|
||||
key: 'url',
|
||||
},
|
||||
{
|
||||
title: '访问IP',
|
||||
key: 'ip',
|
||||
},
|
||||
// {
|
||||
// title: 'IP地区',
|
||||
// key: 'region',
|
||||
// },
|
||||
{
|
||||
title: '状态码',
|
||||
key: 'errorCode',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.errorCode == 0 ? 'success' : 'warning',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.errorMsg + '(' + row.errorCode + ')',
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Goroutine耗时',
|
||||
key: 'takeUpTime',
|
||||
render(row) {
|
||||
return row.takeUpTime + ' ms';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '访问时间',
|
||||
key: 'createdAt',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,292 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset">
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
<template #toolbar>
|
||||
<n-button type="primary" @click="reloadTable">ems-log刷新数据</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { getLogList, Delete } from '@/api/log/log';
|
||||
import { columns } from './columns';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { DeleteOutlined } from '@vicons/antd';
|
||||
|
||||
const dialog = useDialog();
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'member_id',
|
||||
component: 'NInput',
|
||||
label: '操作人',
|
||||
componentProps: {
|
||||
placeholder: '请输入操作人ID',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'url',
|
||||
component: 'NInput',
|
||||
label: '接口路径',
|
||||
componentProps: {
|
||||
placeholder: '请输入接口路径',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'ip',
|
||||
component: 'NInput',
|
||||
label: '访问IP',
|
||||
componentProps: {
|
||||
placeholder: '请输入IP地址',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'method',
|
||||
component: 'NSelect',
|
||||
label: '请求方式',
|
||||
componentProps: {
|
||||
placeholder: '请选择请求方式',
|
||||
options: [
|
||||
{
|
||||
label: 'GET',
|
||||
value: 'GET',
|
||||
},
|
||||
{
|
||||
label: 'POST',
|
||||
value: 'POST',
|
||||
},
|
||||
],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
component: 'NDatePicker',
|
||||
label: '访问时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
// defaultValue: [new Date() - 86400000 * 30, new Date()],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'take_up_time',
|
||||
component: 'NSelect',
|
||||
label: '请求耗时',
|
||||
componentProps: {
|
||||
placeholder: '请选择请求耗时',
|
||||
options: [
|
||||
{
|
||||
label: '50ms内',
|
||||
value: '50',
|
||||
},
|
||||
{
|
||||
label: '100ms内',
|
||||
value: '100',
|
||||
},
|
||||
{
|
||||
label: '200ms内',
|
||||
value: '200',
|
||||
},
|
||||
{
|
||||
label: '500ms内',
|
||||
value: '500',
|
||||
},
|
||||
],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'error_code',
|
||||
component: 'NSelect',
|
||||
label: '状态码',
|
||||
componentProps: {
|
||||
placeholder: '请选择状态码',
|
||||
options: [
|
||||
{
|
||||
label: '0 成功',
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
label: '-1 失败',
|
||||
value: '-1',
|
||||
},
|
||||
],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const router = useRouter();
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const formParams = ref({});
|
||||
|
||||
const params = ref({
|
||||
pageSize: 10,
|
||||
});
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
// fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '查看详情',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
if (rowKeys.length > 0) {
|
||||
batchDeleteDisabled.value = false;
|
||||
} else {
|
||||
batchDeleteDisabled.value = true;
|
||||
}
|
||||
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
console.log('点击了删除', record);
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record)
|
||||
.then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
// message.error(e.message ?? '操作失败');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function batchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value })
|
||||
.then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message ?? '操作失败');
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await getLogList({ ...formParams.value, ...params.value, ...res });
|
||||
};
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
console.log('点击了编辑', record);
|
||||
router.push({ name: 'ems_view', params: { id: record.id } });
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
console.log(values);
|
||||
formParams.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
console.log(values);
|
||||
formParams.value = {};
|
||||
reloadTable();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
:title="data.id ? '日志详情 ID:' + data.id : '日志详情'"
|
||||
>
|
||||
<n-descriptions label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="请求方式">{{ data.method }}</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>请求地址</template>
|
||||
{{ data.url }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="请求耗时">{{ data.takeUpTime }} ms</n-descriptions-item>
|
||||
<n-descriptions-item label="访问IP">{{ data.ip }}</n-descriptions-item>
|
||||
<n-descriptions-item label="IP归属地">河南 郑州</n-descriptions-item>
|
||||
<n-descriptions-item label="链路ID">{{ data.reqId }}</n-descriptions-item>
|
||||
<n-descriptions-item label="响应时间">{{
|
||||
timestampToTime(data.timestamp)
|
||||
}}</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item label="创建时间">{{ data.createdAt }}</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="访问代理"
|
||||
>
|
||||
{{ data.userAgent }}
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="报错信息"
|
||||
>
|
||||
<n-descriptions label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="报错状态码"> {{ data.errorCode }} </n-descriptions-item>
|
||||
<n-descriptions-item label="报错消息">
|
||||
<n-tag type="success"> {{ data.errorMsg }} </n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="报错日志">
|
||||
<n-tag type="success"> {{ data.errorData }} </n-tag>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="Header请求头"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(data.headerData ?? '{}')"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
style="width: 100%; min-width: 3.125rem"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="GET参数"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(data.getData ?? '{}')"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
style="width: 100%; min-width: 3.125rem"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="POST参数"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="JSON.parse(data.postData ?? '{}')"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
style="width: 100%; min-width: 3.125rem"
|
||||
/>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { JsonViewer } from 'vue3-json-viewer';
|
||||
import 'vue3-json-viewer/dist/vue3-json-viewer.css';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { View } from '@/api/log/log';
|
||||
import { timestampToTime } from '@/utils/dateUtil';
|
||||
|
||||
const message = useMessage();
|
||||
const router = useRouter();
|
||||
const logId = Number(router.currentRoute.value.params.id);
|
||||
|
||||
onMounted(async () => {
|
||||
if (logId === undefined || logId < 1) {
|
||||
message.error('ID不正确,请检查!');
|
||||
return;
|
||||
}
|
||||
|
||||
await getInfo();
|
||||
});
|
||||
|
||||
const data = ref({});
|
||||
|
||||
const getInfo = async () => {
|
||||
data.value = await View({ id: logId });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,48 @@
|
||||
import { h } from 'vue';
|
||||
import Column from './components/Column.vue';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: '记录ID',
|
||||
key: 'id',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '访客',
|
||||
key: 'name',
|
||||
width: 180,
|
||||
render(row) {
|
||||
return h(Column, {
|
||||
state: row,
|
||||
column: 'visitor',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '请求接口',
|
||||
key: 'name',
|
||||
width: 260,
|
||||
render(row) {
|
||||
return h(Column, {
|
||||
state: row,
|
||||
column: 'request',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '接口响应',
|
||||
key: 'name',
|
||||
width: 260,
|
||||
render(row) {
|
||||
return h(Column, {
|
||||
state: row,
|
||||
column: 'response',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '访问时间',
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div class="box">
|
||||
<template v-if="column === 'visitor'">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-row mb-1">
|
||||
<div class="text">
|
||||
{{
|
||||
state.memberId === 0
|
||||
? state.memberName
|
||||
: state.memberName + '(' + state.memberId + ')'
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div>访问IP:</div>
|
||||
<div class="text">{{ state.ip }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text">
|
||||
{{ state.cityLabel !== '' ? state.cityLabel : '局域网' }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column === 'request'">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-row">
|
||||
<div class="text mr-1 mb-1">
|
||||
<n-button :type="state.method === 'GET' ? 'tertiary' : 'primary'" size="tiny">
|
||||
{{ state.method }}
|
||||
</n-button>
|
||||
</div>
|
||||
<div class="text">{{ state.url }}</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="text">{{ state.tags }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{ state.summary }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column === 'response'">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-row">
|
||||
<div class="text mr-1 mb-1">
|
||||
<n-button v-if="state.errorCode === 0" type="tertiary" size="tiny">
|
||||
{{ state.errorMsg }}
|
||||
</n-button>
|
||||
<n-button v-else type="error" size="tiny">
|
||||
{{ state.errorCode }} → {{ state.errorMsg }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="text">处理耗时:{{ state.takeUpTime }}ms</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="text">响应时间:{{ timestampToTime(state.timestamp) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { basicProps } from './props';
|
||||
import { timestampToTime } from '@/utils/dateUtil';
|
||||
|
||||
const props = defineProps(basicProps);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.text {
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { State } from '@/views/log/log/model';
|
||||
|
||||
export const basicProps = {
|
||||
state: {
|
||||
type: State,
|
||||
default: null,
|
||||
},
|
||||
column: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="访问日志">
|
||||
全局访问日志记录了系统中后台人员和客户端的操作记录以及服务响应情况
|
||||
</n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset">
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-20000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { getLogList, Delete } from '@/api/log/log';
|
||||
import { columns } from './columns';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { DeleteOutlined } from '@vicons/antd';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
import { loadOptions, schemas } from './model';
|
||||
|
||||
const dialog = useDialog();
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
const router = useRouter();
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const formParams = ref({});
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 160,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '查看详情',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
batchDeleteDisabled.value = rowKeys.length <= 0;
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function batchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await getLogList({ ...formParams.value, ...res });
|
||||
};
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
router.push({ name: 'log_view', params: { id: record.id } });
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
formParams.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
formParams.value = {};
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,151 @@
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { ref } from 'vue';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
|
||||
const dict = useDictStore();
|
||||
|
||||
export class State {
|
||||
id = 0;
|
||||
reqId = '';
|
||||
appId = '';
|
||||
merchantId = 0;
|
||||
memberId = 0;
|
||||
method = '';
|
||||
module = '';
|
||||
url = '';
|
||||
getData: Record<string, any> | null = null;
|
||||
postData: Record<string, string[]> | null = null;
|
||||
headerData: Record<string, string[]> | null = null;
|
||||
ip = '';
|
||||
provinceId = 0;
|
||||
cityId = 0;
|
||||
errorCode = 0;
|
||||
errorMsg = '';
|
||||
errorData: string[] = [];
|
||||
userAgent = '';
|
||||
takeUpTime = 0;
|
||||
timestamp = 0;
|
||||
status = 0;
|
||||
createdAt = '';
|
||||
updatedAt = '';
|
||||
cityLabel = '';
|
||||
tags = '';
|
||||
summary = '';
|
||||
description = '';
|
||||
}
|
||||
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'reqId',
|
||||
component: 'NInput',
|
||||
label: '链路ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入链路ID',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'complexMemberId',
|
||||
component: 'ComplexMemberPicker',
|
||||
label: '操作人',
|
||||
componentProps: {
|
||||
placeholder: '请选择操作人',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'url',
|
||||
component: 'NInput',
|
||||
label: '接口路径',
|
||||
componentProps: {
|
||||
placeholder: '请输入接口路径',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'keyword',
|
||||
component: 'NInput',
|
||||
label: '关键词',
|
||||
componentProps: {
|
||||
placeholder: '请输入关键词',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'ip',
|
||||
component: 'NInput',
|
||||
label: '访问IP',
|
||||
componentProps: {
|
||||
placeholder: '请输入IP地址',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'method',
|
||||
component: 'NSelect',
|
||||
label: '请求方式',
|
||||
componentProps: {
|
||||
placeholder: '请选择请求方式',
|
||||
options: dict.getOption('HTTPMethod'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
label: '访问时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'takeUpTime',
|
||||
component: 'NSelect',
|
||||
label: '请求耗时',
|
||||
componentProps: {
|
||||
placeholder: '请选择请求耗时',
|
||||
options: dict.getOption('HTTPHandlerTime'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'errorCode',
|
||||
component: 'NSelect',
|
||||
label: '状态码',
|
||||
labelMessage: '支持填入自定义状态码',
|
||||
componentProps: {
|
||||
placeholder: '请选择状态码',
|
||||
options: dict.getOption('HTTPApiCode'),
|
||||
filterable: true,
|
||||
tag: true,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// 加载字典数据选项
|
||||
export function loadOptions() {
|
||||
dict.loadOptions(['HTTPMethod', 'HTTPHandlerTime', 'HTTPApiCode']);
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
:title="data.id ? '日志详情 ID:' + data.id : '日志详情'"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-button icon-placement="right" @click="goBackOrToPage({ name: 'log' })">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<ArrowRightOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
返回
|
||||
</n-button>
|
||||
</template>
|
||||
<n-descriptions label-placement="left" class="py-2" :column="settingStore.isMobile ? 1 : 3">
|
||||
<n-descriptions-item label="请求方式">{{ data.method }}</n-descriptions-item>
|
||||
<n-descriptions-item>
|
||||
<template #label>请求地址</template>
|
||||
{{ data.url }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="接口名称"
|
||||
>{{ data.tags }} / {{ data.summary }}</n-descriptions-item
|
||||
>
|
||||
<n-descriptions-item label="访问IP">{{ data.ip }}</n-descriptions-item>
|
||||
<n-descriptions-item label="IP归属地">{{ data.cityLabel }}</n-descriptions-item>
|
||||
<n-descriptions-item label="链路ID">{{ data.reqId }}</n-descriptions-item>
|
||||
<n-descriptions-item label="请求耗时">{{ data.takeUpTime }} ms</n-descriptions-item>
|
||||
<n-descriptions-item label="响应时间">{{
|
||||
data.timestamp > 0 ? timestampToTime(data.timestamp) : '--'
|
||||
}}</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item label="访问时间">{{ data.createdAt }}</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="访问代理"
|
||||
>
|
||||
{{ data.userAgent }}
|
||||
</n-card>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="报错信息"
|
||||
>
|
||||
<n-descriptions label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="错误状态码"> {{ data.errorCode }} </n-descriptions-item>
|
||||
<n-descriptions-item label="错误提示">
|
||||
<n-tag type="error"> {{ data.errorMsg }} </n-tag>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="堆栈打印"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="data.errorData"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
class="json-width"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="Header请求头"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="data.headerData"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
class="json-width"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="GET参数"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="data.getData"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
class="json-width"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="POST参数"
|
||||
>
|
||||
<JsonViewer
|
||||
:value="data.postData"
|
||||
:expand-depth="5"
|
||||
copyable
|
||||
boxed
|
||||
sort
|
||||
class="json-width"
|
||||
/>
|
||||
</n-card>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { JsonViewer } from 'vue3-json-viewer';
|
||||
import 'vue3-json-viewer/dist/vue3-json-viewer.css';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { View } from '@/api/log/log';
|
||||
import { timestampToTime } from '@/utils/dateUtil';
|
||||
import { ArrowRightOutlined } from '@vicons/antd';
|
||||
import { goBackOrToPage } from '@/utils/urlUtils';
|
||||
import { State } from '@/views/log/log/model';
|
||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||
|
||||
const message = useMessage();
|
||||
const router = useRouter();
|
||||
const settingStore = useProjectSettingStore();
|
||||
const params = router.currentRoute.value.params;
|
||||
const loading = ref(false);
|
||||
const data = ref<State>(new State());
|
||||
|
||||
const getInfo = () => {
|
||||
loading.value = true;
|
||||
View(params)
|
||||
.then((res) => {
|
||||
data.value = res as unknown as State;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (!params.id) {
|
||||
message.error('日志ID不正确,请检查!');
|
||||
return;
|
||||
}
|
||||
getInfo();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
::v-deep(.json-width) {
|
||||
width: 100%;
|
||||
min-width: 3.125rem;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="登录日志"> 在这里会记录管理后台所有的来访登录情况 </n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="reloadTable"
|
||||
@reset="reloadTable"
|
||||
@keyup.enter="reloadTable"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-20000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button
|
||||
type="error"
|
||||
@click="handleBatchDelete"
|
||||
:disabled="batchDeleteDisabled"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/loginLog/delete'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="handleExport"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/loginLog/export'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<ExportOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
导出
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import { List, Export, Delete } from '@/api/loginLog';
|
||||
import { columns, schemas, loadOptions } from './model';
|
||||
import { ExportOutlined, DeleteOutlined } from '@vicons/antd';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
const router = useRouter();
|
||||
const actionRef = ref();
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const searchFormRef = ref<any>({});
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 160,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '查看详情',
|
||||
onClick: handleView.bind(null, record),
|
||||
auth: ['/loginLog/view'],
|
||||
ifShow: () => {
|
||||
return record.sysLogId > 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
auth: ['/loginLog/delete'],
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...searchFormRef.value?.formModel, ...res });
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
batchDeleteDisabled.value = rowKeys.length <= 0;
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleView(record: Recordable) {
|
||||
router.push({ name: 'log_view', params: { id: record.sysLogId } });
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleBatchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要批量删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
message.loading('正在导出列表...', { duration: 1200 });
|
||||
Export(searchFormRef.value?.formModel);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,140 @@
|
||||
import { h, ref } from 'vue';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { NTag } from 'naive-ui';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { renderOptionTag } from '@/utils';
|
||||
|
||||
const dict = useDictStore();
|
||||
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'username',
|
||||
component: 'NInput',
|
||||
label: '用户名',
|
||||
componentProps: {
|
||||
placeholder: '请输入用户名',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'loginIp',
|
||||
component: 'NInput',
|
||||
label: 'IP地址',
|
||||
componentProps: {
|
||||
placeholder: '请输入IP地址',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '状态',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择状态',
|
||||
options: dict.getOption('sys_login_status'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'loginAt',
|
||||
component: 'NDatePicker',
|
||||
label: '登录时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: '记录ID',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '用户名',
|
||||
key: 'username',
|
||||
width: 120,
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: 'info',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.username,
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '登录IP',
|
||||
key: 'loginIp',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: 'IP归属地',
|
||||
key: 'cityLabel',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '浏览器',
|
||||
key: 'browser',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '操作系统',
|
||||
key: 'os',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
return renderOptionTag('sys_login_status', row.status);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '提示信息',
|
||||
key: 'errMsg',
|
||||
render(row) {
|
||||
if (row.errMsg !== '') {
|
||||
return row.errMsg;
|
||||
}
|
||||
|
||||
if (row.status === 1) {
|
||||
return '登录成功';
|
||||
}
|
||||
return ``;
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '登录时间',
|
||||
key: 'loginAt',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
// 加载字典数据选项
|
||||
export function loadOptions() {
|
||||
dict.loadOptions(['sys_login_status']);
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="短信记录">
|
||||
在这里,您可以方便地查看平台的所有短信发送记录
|
||||
</n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="handleSubmit"
|
||||
@reset="handleReset"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-20000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { getLogList, Delete } from '@/api/log/smslog';
|
||||
import { DeleteOutlined } from '@vicons/antd';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { renderOptionTag } from '@/utils';
|
||||
|
||||
const dict = useDictStore();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '事件模板',
|
||||
key: 'event',
|
||||
render(row) {
|
||||
return renderOptionTag('config_sms_template', row.event);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
key: 'mobile',
|
||||
render(row) {
|
||||
return row.mobile;
|
||||
},
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '验证码或短信内容',
|
||||
key: 'code',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '验证次数',
|
||||
key: 'times',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '发送者IP',
|
||||
key: 'ip',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
return renderOptionTag('CodeStatusOptions', row.status);
|
||||
},
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '发送时间',
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
key: 'updatedAt',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
const searchFormRef = ref<any>({});
|
||||
|
||||
const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'event',
|
||||
component: 'NSelect',
|
||||
label: '事件模板',
|
||||
componentProps: {
|
||||
placeholder: '请选择事件模板',
|
||||
options: dict.getOption('config_sms_template'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'mobile',
|
||||
component: 'NInput',
|
||||
label: '手机号',
|
||||
componentProps: {
|
||||
placeholder: '请输入手机号',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'ip',
|
||||
component: 'NInput',
|
||||
label: '发送者IP',
|
||||
componentProps: {
|
||||
placeholder: '请输入IP',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '状态',
|
||||
componentProps: {
|
||||
placeholder: '请选择状态',
|
||||
options: dict.getOption('CodeStatusOptions'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
label: '发送时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 80,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
batchDeleteDisabled.value = rowKeys.length <= 0;
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
console.log('点击了删除', record);
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function batchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await getLogList({ ...searchFormRef.value?.formModel, ...res });
|
||||
};
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
console.log(values);
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
console.log(values);
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function loadOptions() {
|
||||
dict.loadOptions(['config_sms_template', 'CodeStatusOptions']);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
Reference in New Issue
Block a user