LoginLog.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. <!--
  2. * @since: 2025-02-07
  3. * LoginLog.vue
  4. -->
  5. <template>
  6. <div class="login-log">
  7. <el-card class="mb-3 proCard">
  8. <el-space >
  9. <el-form ref="searchFormRef" :inline="true" :model="requestParams.queryParam" class="form-inline" >
  10. <el-form-item prop="queryType">
  11. <el-select v-model="requestParams.queryParam.queryType" placeholder="选择类型" class="type-select" >
  12. <el-option
  13. v-for="item in queryTypeSelect"
  14. :key="item.value"
  15. :label="item.label"
  16. :value="item.value"
  17. />
  18. </el-select>
  19. <el-input v-model="requestParams.queryParam.queryTypeContent" clearable placeholder="请输入搜索关键字"
  20. @keyup.enter="queryLoginLogPage" :disabled="requestParams.queryParam.queryType === ''" />
  21. </el-form-item>
  22. <el-form-item label="终端类型" prop="device">
  23. <el-select v-model="requestParams.queryParam.device" placeholder="请选择终端类型" clearable>
  24. <el-option :label="item.label" :value="item.value" v-for="item in deviceList" :key="item.value" />
  25. </el-select>
  26. </el-form-item>
  27. <el-form-item label="登录时间" prop="date">
  28. <el-date-picker
  29. v-model="requestParams.queryParam.date"
  30. type="daterange"
  31. placeholder="请选择登录时间"
  32. range-separator="~"
  33. start-placeholder="开始时间"
  34. end-placeholder="结束时间"
  35. :disabled-date="disabledDate"
  36. clearable />
  37. </el-form-item>
  38. <el-form-item label="请选择组织:" label-position="left" prop="deptId">
  39. <el-tree-select type="daterange" range-separator="To" start-placeholder="开始时间" end-placeholder="结束时间"
  40. v-model="requestParams.queryParam.deptId" :data="departmentList" :render-after-expand="false"
  41. :default-expand-all="true" check-strictly placeholder="请选择组织" class="protocal-select" />
  42. </el-form-item>
  43. <el-form-item>
  44. <el-button type="primary" :icon="Search" @click="queryLoginLogPage">查询</el-button>
  45. <el-button :icon="Refresh" @click="hanleResetForm(searchFormRef)">重置</el-button>
  46. </el-form-item>
  47. </el-form>
  48. </el-space>
  49. </el-card>
  50. <el-card>
  51. <template #header>
  52. <el-button type="primary" @click="handleExport()">导出数据</el-button>
  53. </template>
  54. <el-table height="calc(100vh - 500px)" :data="loginLogList" v-loading="loading">
  55. <el-table-column type="index" label="序号" width="100" />
  56. <el-table-column label="登录账号" prop="userName" />
  57. <el-table-column label="姓名" prop="realName" />
  58. <el-table-column label="组织" prop="deptName" />
  59. <el-table-column label="客户端" prop="device" >
  60. <template #default="{row}">
  61. <el-icon :size="24" color="#409eff" v-if="row.device === ClientTypeEnum.PC"><Platform /></el-icon>
  62. <el-icon :size="24" color="#409eff" v-else><Iphone /></el-icon>
  63. </template>
  64. </el-table-column>
  65. <el-table-column label="操作类型" prop="type" >
  66. <template #default="{row}">
  67. <el-tag
  68. :type="row.type === OperationTypeEnum.LOGIN ? 'success' : 'danger'"
  69. round
  70. >
  71. {{ row.type === OperationTypeEnum.LOGIN ? '登录': '登出' }}
  72. </el-tag>
  73. </template>
  74. </el-table-column>
  75. <el-table-column label="操作结果" prop="loginStatus" >
  76. <template #default="{ row }">
  77. <el-icon :size="24" color="#67C23A" v-if="row.loginStatus === LoginStatusEnum.SUCCESS"><SuccessFilled /></el-icon>
  78. <el-icon :size="24" color="#F56C6C" v-else><CircleCloseFilled /></el-icon>
  79. </template>
  80. </el-table-column>
  81. <el-table-column label="登录IP" prop="loginIp" />
  82. <el-table-column label="操作时间" prop="createdAt" />
  83. </el-table>
  84. <section class="mt-4 flex justify-end">
  85. <el-pagination background layout="total, sizes, prev, pager, next" :page-sizes="[10, 30, 50]" :total="total"
  86. v-model:page-size="requestParams.pageSize" v-model:current-page="requestParams.pageNumber"
  87. @change="queryLoginLogPage" />
  88. </section>
  89. </el-card>
  90. </div>
  91. </template>
  92. <script setup lang="ts">
  93. import { ref, onMounted } from 'vue';
  94. import { Search, Refresh, SuccessFilled, CircleCloseFilled, Platform, Iphone } from '@element-plus/icons-vue';
  95. import { getAllDepartments } from '@/api/auth/dept';
  96. import type { FormInstance } from 'element-plus'
  97. import { OptionsProps, deviceList, queryTypeSelect, LoginStatusEnum, OperationTypeEnum, ClientTypeEnum } from '@/types/log/constants';
  98. import useSceneInfos from '@/hooks/useSceneInfos';
  99. import useLoginLogQuery from '../hooks/useLoginLogQuery';
  100. import { ElMessage, ElMessageBox } from 'element-plus';
  101. import { exportLoginLog } from '@/api/system/log';
  102. import { downloadByData } from '@/utils/file/download';
  103. const sceneInfos = useSceneInfos();
  104. const { calculateTreeData } = sceneInfos;
  105. const { requestParams, total, loginLogList, queryLoginLogPage, loading, resetRequestParams } = useLoginLogQuery();
  106. const departmentList = ref<OptionsProps[]>([]);
  107. onMounted(async () => {
  108. queryLoginLogPage();
  109. getAllDepartments().then((res) => {
  110. departmentList.value = calculateTreeData(
  111. res,
  112. { level: 3, valueKey: 'id', labelKey: 'deptName' },
  113. 1,
  114. );
  115. });
  116. });
  117. /* 导出数据 */
  118. const handleExport = () => {
  119. ElMessageBox.confirm('确定导出所查询数据?', '导出', {
  120. confirmButtonText: '确定',
  121. showCancelButton: true,
  122. type: 'warning',
  123. })
  124. .then(() => {
  125. exportLoginLog(requestParams).then(async (responnse) => {
  126. if (!responnse) {
  127. throw new Error('下载文件失败');
  128. }
  129. downloadByData(responnse, '登录日志.xlsx');
  130. ElMessage.success('下载文件成功');
  131. });
  132. })
  133. .catch(() => {
  134. ElMessage({
  135. type: 'info',
  136. message: '取消导出',
  137. });
  138. });
  139. }
  140. /* 重置 */
  141. const searchFormRef = ref<FormInstance>()
  142. const hanleResetForm = (formEl: FormInstance | undefined) => {
  143. if (!formEl) return
  144. resetRequestParams();
  145. formEl.resetFields();
  146. queryLoginLogPage();
  147. }
  148. /* 设置今天之后的时间不可选 */
  149. const disabledDate = (time) => {
  150. return time.getTime() > Date.now();
  151. }
  152. </script>
  153. <style scoped lang="scss">
  154. .form-inline .el-input {
  155. --el-input-width: 160px;
  156. }
  157. .form-inline .el-select {
  158. --el-select-width: 160px;
  159. }
  160. </style>