Sfoglia il codice sorgente

短信通知添加

master
suyanyan 5 giorni fa
parent
commit
e131022ec8
9 ha cambiato i file con 363 aggiunte e 4 eliminazioni
  1. +2
    -2
      SafeCampus.WEB/.env.development
  2. +1
    -0
      SafeCampus.WEB/src/api/modules/sys/audit/index.ts
  3. +44
    -0
      SafeCampus.WEB/src/api/modules/sys/audit/smsLog.ts
  4. +1
    -1
      SafeCampus.WEB/src/views/sys/audit/oplog/index.vue
  5. +70
    -0
      SafeCampus.WEB/src/views/sys/audit/smslog/components/detail.vue
  6. +73
    -0
      SafeCampus.WEB/src/views/sys/audit/smslog/components/lineChat.vue
  7. +47
    -0
      SafeCampus.WEB/src/views/sys/audit/smslog/components/pieChat.vue
  8. +124
    -0
      SafeCampus.WEB/src/views/sys/audit/smslog/index.vue
  9. +1
    -1
      SafeCampus.WEB/src/views/sys/audit/vislog/index.vue

+ 2
- 2
SafeCampus.WEB/.env.development Vedi File

@@ -16,12 +16,12 @@ VITE_PWA = false

# 开发环境接口地址
# VITE_API_URL = http://192.168.10.186:5566
VITE_API_URL = http://192.168.10.186:8003
VITE_API_URL = http://192.168.10.51:8003

# 否开启代理
VITE_HTTP_PROXY = true

# 开发环境跨域代理,支持配置多个
# VITE_PROXY = [["/api","http://192.168.10.186:5566","/Files"]]
VITE_PROXY = [["/api","http://192.168.10.186:8003"],["/Files","http://192.168.10.186:8003/Files"]]
VITE_PROXY = [["/api","http://192.168.10.51:8003"],["/Files","http://192.168.10.51:8003/Files"]]


+ 1
- 0
SafeCampus.WEB/src/api/modules/sys/audit/index.ts Vedi File

@@ -14,3 +14,4 @@
*/
export * from "./visLog";
export * from "./opLog";
export * from "./smsLog";

+ 44
- 0
SafeCampus.WEB/src/api/modules/sys/audit/smsLog.ts Vedi File

@@ -0,0 +1,44 @@
/**
* @description 操作日志
* @license Apache License Version 2.0
* @Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
* @remarks
* SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
* 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
* 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
* 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为不要删除和修改作者声明。
* 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关
* @see https://gitee.com/dotnetmoyu/SimpleAdmin
*/

import { ResPage, VisLog } from "@/api/interface";
import { moduleRequest } from "@/api/request";
const http = moduleRequest("/sys/audit/smsLog/");

/**
* @Description: 操作日志
* @Author: huguodong
* @Date: 2024-11-19 15:34:54
*/
const smsLogApi = {
/** 获取操作日志分页 */
page(params: VisLog.Page) {
return http.get<ResPage<VisLog.VisLogInfo>>("page", params);
},
/** 获取操作日志柱状图数据 */
lineChart() {
return http.get<VisLog.LineChart[]>("lineChartData", {}, { loading: false });
},
/** 获取操作日志饼状图数据 */
pieChart() {
return http.get<VisLog.PineChart[]>("pieChartData", {}, { loading: false });
},
/** 清空操作日志 */
delete(category: string) {
return http.post("delete", { category });
}
};

export { smsLogApi };

+ 1
- 1
SafeCampus.WEB/src/views/sys/audit/oplog/index.vue Vedi File

@@ -34,7 +34,7 @@
<el-radio-group v-model="initParam.category" class="mb-15px">
<el-radio-button v-for="(item, index) in visLogTypeOptions" :key="index" :label="item.value">{{ item.label }}</el-radio-button>
</el-radio-group>
<el-input v-model="initParam.searchKey" placeholder="请输入字典名称" class="mb-15px">
<el-input v-model="initParam.searchKey" placeholder="请输入关键字" class="mb-15px">
<template #append>
<el-button :icon="Search" class="el-input-button" />
</template>


+ 70
- 0
SafeCampus.WEB/src/views/sys/audit/smslog/components/detail.vue Vedi File

@@ -0,0 +1,70 @@
<!--
* @Description: 详情
* @Author: Syy
* @Date: 2024-11-20 15:44:33
!-->
<template>
<form-container v-model="visible" title="日志详情" form-size="600px">
<el-descriptions :column="1" border :label-width="170" :label-style="{ 'text-align': 'right' }">
<el-descriptions-item label="名称" label-align="center">{{ logInfo.name }}</el-descriptions-item>
<el-descriptions-item label="发送状态" label-align="center" label-class-name="my-label">
<el-tag v-if="logInfo.exeStatus === 'FAIL'" type="danger">失败</el-tag>
<el-tag v-if="logInfo.exeStatus === 'SUCCESS'" type="success">成功</el-tag>
</el-descriptions-item>
<el-descriptions-item label="通知用户" label-align="center">{{ logInfo.opUser }}</el-descriptions-item>
<el-descriptions-item label="发送人" label-align="center">{{ logInfo.opAccount }}</el-descriptions-item>
<el-descriptions-item label="短信内容" label-align="center">{{ logInfo.opAddress }}</el-descriptions-item>
<el-descriptions-item label="发送时间" label-align="center">{{ logInfo.opTime }}</el-descriptions-item>
</el-descriptions>
<template #footer>
<el-button type="primary" @click="onClose"> 确定 </el-button>
</template>
</form-container>
</template>

<script setup lang="ts" name="detail">
import { VisLog } from "@/api";

const visible = ref(false); //是否显示表单

// 表单参数
const logInfo = ref<VisLog.VisLogInfo>({
name: "",
opIp: "",
opAddress: "",
opBrowser: "",
opOs: "",
opTime: "",
opUser: "",
opAccount: "",
id: 0,
category: "",
exeStatus: "",
createTime: ""
});

/**
* 打开表单
* @param props 表单参数
*/
function onOpen(record: VisLog.VisLogInfo) {
logInfo.value = record;
visible.value = true; //显示表单
}

/** 关闭表单*/
function onClose() {
visible.value = false;
}

// 暴露给父组件的方法
defineExpose({
onOpen
});
</script>

<style lang="scss" scoped>
:deep(.my-label) {
width: 100px !important;
}
</style>

+ 73
- 0
SafeCampus.WEB/src/views/sys/audit/smslog/components/lineChat.vue Vedi File

@@ -0,0 +1,73 @@
<!--
* @Description: 折线图
* @Author: Syy
* @Date: 2024-11-20 15:44:33
!-->
<template>
<div id="lineChat" class="h-150px"></div>
</template>

<script setup lang="ts" name="lineChart">
import { VisLog, smsLogApi } from "@/api";
import { Line } from "@antv/g2plot";

const seriesField = "series"; //分组字段
const yField = "value"; //图形在 y 方向对应的数据字段名,一般是纵向的坐标轴对应的字段

type alias = "用户登录" | "用户登出"; //别名

// lineMeta 用于处理数据,将数据中的字段名转换为别名
interface LineMeta {
[key: string]: {
alias: alias;
};
}
const lineMeta: LineMeta = {
successCount: {
alias: "发送成功"
},
failCount: {
alias: "发送失败"
}
};
onMounted(() => {
smsLogApi.lineChart().then(res => {
createLineChat(res.data);
});
});

/**
* 创建折线图,具体文档参考https://g2plot.antv.antgroup.com/api/plots/line
* @param data 数据
*/
function createLineChat(data: VisLog.LineChart[]) {
const line = new Line("lineChat", {
data: processData(data, lineMeta),
padding: "auto",
xField: "date",
yField: yField,
seriesField: seriesField,
color: ["#409EFF", "rgb(188, 189, 190)"],
appendPadding: [0, 8, 0, 0]
});
line.render(); //渲染
}

/**
* 处理数据,将数据中的字段名转换为别名
* @param data 数据
* @param yFields y轴字段
* @param meta 别名
*/
function processData(data: VisLog.LineChart[], meta: LineMeta) {
const result: any = [];
//将数据中的字段名转换为别名
data.forEach((item: VisLog.LineChart) => {
result.push({ ...item, [seriesField]: meta.successCount?.alias, [yField]: item.successCount });
result.push({ ...item, [seriesField]: meta.failCount?.alias, [yField]: item.failCount });
});
return result;
}
</script>

<style scoped lang="scss"></style>

+ 47
- 0
SafeCampus.WEB/src/views/sys/audit/smslog/components/pieChat.vue Vedi File

@@ -0,0 +1,47 @@
<!--
* @Description: 饼状图
* @Author: Syy
* @Date: 2024-11-20 15:44:33
!-->
<template>
<div id="pieChat" class="h-150px"></div>
</template>

<script setup lang="ts" name="pieChart">
import { VisLog, smsLogApi } from "@/api";
import { Pie } from "@antv/g2plot";

onMounted(() => {
smsLogApi.pieChart().then(res => {
createPieChat(res.data);
});
});

/**
* 创建折线图,具体文档参考https://g2plot.antv.antgroup.com/api/plots/pie
* @param data 数据
*/
function createPieChat(data: VisLog.PineChart[]) {
const piePlot = new Pie("pieChat", {
appendPadding: 10,
data,
angleField: "value",
colorField: "type",
radius: 0.9,
color: ["#409EFF", "rgb(188, 189, 190)"],
label: {
type: "inner",
offset: "-30%",
content: ({ percent }) => `${(percent * 100).toFixed(0)}%`,
style: {
fontSize: 14,
textAlign: "center"
}
},
interactions: [{ type: "element-active" }]
});
piePlot.render(); //渲染
}
</script>

<style scoped lang="scss"></style>

+ 124
- 0
SafeCampus.WEB/src/views/sys/audit/smslog/index.vue Vedi File

@@ -0,0 +1,124 @@
<!--
* @Description: 短信日志
* @Author: Syy
* @Date: 2024-11-20 15:44:33
!-->
<template>
<div>
<el-row :gutter="10" class="mb-2">
<el-col :span="16">
<el-card header="周统计" shadow="never">
<line-chat />
</el-card>
</el-col>
<el-col :span="8">
<el-card header="总比例" shadow="never">
<pie-chat />
</el-card>
</el-col>
</el-row>
<el-row>
<div class="table-box min-h-400px">
<ProTable ref="proTable" title="系统字典" class="table" :tool-button="false" :columns="columns" :request-api="smsLogApi.page">
<!-- 表格 header 按钮 -->
<template #tableHeader>
<el-space>
<el-radio-group v-model="initParam.category" class="mb-15px">
<el-radio-button v-for="(item, index) in visLogTypeOptions" :key="index" :label="item.value">{{ item.label }}</el-radio-button>
</el-radio-group>
<el-input v-model="initParam.searchKey" placeholder="请输入关键字" class="mb-15px">
<template #append>
<el-button :icon="Search" class="el-input-button" />
</template>
</el-input>
<s-button type="danger" plain :opt="FormOptEnum.DELETE" @click="onDelete('清空当前日志')">清空</s-button>
</el-space>
</template>

<!-- 操作 -->
<template #operation="scope">
<s-button link :opt="FormOptEnum.VIEW" @click="onOpen(scope.row)"> 详情 </s-button>
</template>
</ProTable>
<!-- 日志详情 -->
<Detail ref="detailRef" />
</div>
</el-row>
</div>
</template>

<script setup lang="tsx" name="sysVisLog">
import LineChat from "./components/lineChat.vue";
import PieChat from "./components/pieChat.vue";
import Detail from "./components/detail.vue";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { Search } from "@element-plus/icons-vue";
import { VisLog, smsLogApi } from "@/api";
import { FormOptEnum } from "@/enums";
import { useHandleData } from "@/hooks/useHandleData";

// 左侧表格初始化条件
interface InitParam {
category?: string; //字典分类
searchKey?: string; //关键字
}
// 访问日志类型
let visLogTypeOptions = [
{
label: "短信日志",
value: "SUCCESS"
}
];
// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
const initParam = reactive<InitParam>({ category: visLogTypeOptions[0].value }); //主表格初始化参数

// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTable = ref<ProTableInstance>();

// 表格配置项
const columns: ColumnProps<VisLog.VisLogInfo>[] = [
{ prop: "name", label: "名称" },
{
prop: "exeStatus",
label: "发送状态",
render: scope => {
if (scope.row.exeStatus === "FAIL") {
return (<el-tag type="danger">失败</el-tag>);
}
if (scope.row.exeStatus === "SUCCESS") {
return (<el-tag type="success">成功</el-tag>);
}
}
},
{ prop: "opUser", label: "通知用户" },
{ prop: "opAccount", label: "发送人" },
{ prop: "opAddress", label: "短信内容" },
{ prop: "opTime", label: "发送时间" },
{ prop: "operation", label: "操作", fixed: "right", width: 100 }
];

const detailRef = ref<InstanceType<typeof Detail> | null>(null); //详情引用

/**
* 打开表单
* @param record 记录
*/
function onOpen(record: VisLog.VisLogInfo) {
detailRef.value?.onOpen(record);
}

/**
* 清空日志
*/
async function onDelete(msg: string) {
// 二次确认 => 请求api => 刷新表格
await useHandleData(smsLogApi.delete, initParam.category, msg);
proTable.value?.refresh(); //刷新主表格
}
</script>

<style lang="scss" scoped>
:deep(.el-card__header) {
padding: 9px;
}
</style>

+ 1
- 1
SafeCampus.WEB/src/views/sys/audit/vislog/index.vue Vedi File

@@ -34,7 +34,7 @@
<el-radio-group v-model="initParam.category" class="mb-15px">
<el-radio-button v-for="(item, index) in visLogTypeOptions" :key="index" :label="item.value">{{ item.label }}</el-radio-button>
</el-radio-group>
<el-input v-model="initParam.searchKey" placeholder="请输入字典名称" class="mb-15px">
<el-input v-model="initParam.searchKey" placeholder="请输入关键字" class="mb-15px">
<template #append>
<el-button :icon="Search" class="el-input-button" />
</template>


Caricamento…
Annulla
Salva