ed5f2d577304ced4b32a2dc2060c426cef1abc35..ce1f8c5b0e8cd5d4050e79c3e02433aafce81b24
5 天以前 huangxiaoqiang
1
ce1f8c 对比 | 目录
5 天以前 huangxiaoqiang
代码提交
f81474 对比 | 目录
已添加2个文件
已修改1个文件
523 ■■■■■ 文件已修改
Code Management/WMS/WIDESEA_WMSClient/src/router/viewGird.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code Management/WMS/WIDESEA_WMSClient/src/views/system/Log.vue 346 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code Management/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/System/Sys_LogController.cs 171 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code Management/WMS/WIDESEA_WMSClient/src/router/viewGird.js
@@ -66,6 +66,12 @@
    component: () => import('@/views/widesea_wms/taskinfo/Dt_CL.vue'),
    meta: {
    }
  },{
    path: '/Log',
    name: 'Log',
    component: () => import('@/views/system/Log.vue'),
    meta: {
    }
  }
]
Code Management/WMS/WIDESEA_WMSClient/src/views/system/Log.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,346 @@
<template>
  <div class="tree-container">
    <el-input placeholder="输入关键字进行过滤" v-model="filterText" class="filter-input">
    </el-input>
    <div class="custom-tree-wrapper">
      <el-tree ref="tree" class="filter-tree" :filter-node-method="filterNode" :data="data" node-key="id" accordion>
        <template #default="{ node, data }">
          <div class="custom-tree-node">
            <span class="node-label">{{ node.label }}</span>
            <span v-if="data.hidden" class="node-actions">
              <el-button type="text" size="mini" @click="() => view(data)" class="action-btn">
                æŸ¥çœ‹
              </el-button>
              <el-button type="text" size="mini" @click="() => dowmload(node, data)" class="action-btn">
                ä¸‹è½½
              </el-button>
            </span>
          </div>
        </template>
      </el-tree>
    </div>
  </div>
  <div class="log-container">
    <el-card shadow="always" v-if="logName" class="log-card">
      <template #header>
        <div class="card-header">
          <el-tag type="info" size="small">日志文件</el-tag>
          <span class="log-title">{{ logName }}</span>
        </div>
      </template>
      <div class="log-content">
        <div v-for="(item, index) in log" :key="index" class="log-line">
          <span class="line-number">{{ index + 1 }}</span>
          <span class="line-content">{{ item }}</span>
        </div>
      </div>
    </el-card>
    <div v-else class="empty-log">
      <el-empty description="请选择日志文件进行查看" />
    </div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      data: [],
      defaultProps: {
        children: "children",
        label: "label",
      },
      filterText: "",
      logName: "",
      log: [],
    };
  },
  watch: {
    filterText(val) {
      this.$refs.tree.filter(val);
    },
  },
  created() {
    this.getLogName();
  },
  methods: {
    filterNode(value, data) {
      if (!value) return true;
      return data.label.indexOf(value) !== -1;
    },
    getLogName() {
      this.http
        .post("/api/Sys_Log/GetLogName", null, "正在执行中...")
        .then((x) => {
          if (x.status) {
            this.data = x.data;
          }
        });
    },
    view(data) {
      // var params = {
      //   MainData: { fileName: data.label },
      // };
      this.http
        .post("/api/Sys_Log/GetLog?fileName=" + data.label, "正在查询中...")
        .then((x) => {
          if (x.status) {
            this.logName = data.label;
            this.log = x.data;
          }
        });
    },
    dowmload(node, data) {
      let ipAddress = this.http.ipAddress;
      let url =
        "api/Sys_Log/DownLoadLog?fileName=" +
        data.fatherNode +
        "\\" +
        data.label;
      let fileName = data.label;
      let xmlResquest = new XMLHttpRequest();
      xmlResquest.open("GET", ipAddress + url, true);
      xmlResquest.setRequestHeader("Content-type", "application/json");
      xmlResquest.setRequestHeader(
        "Authorization",
        this.$store.getters.getToken()
      );
      let elink = this.$refs.template;
      xmlResquest.responseType = "blob";
      let $_vue = this;
      this.loadingStatus = true;
      xmlResquest.onload = function (e) {
        // è¯·æ±‚成功
        if (this.status == 200) {
          let blob = this.response;
          let a = document.createElement("a");
          //window.URL.createObjectURL() é™æ€æ–¹æ³•会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL。这个 URL çš„生命周期和创建它的窗口中的 document ç»‘定。这个新的URL å¯¹è±¡è¡¨ç¤ºæŒ‡å®šçš„ File å¯¹è±¡æˆ– Blob å¯¹è±¡ã€‚
          let url = window.URL.createObjectURL(blob);
          a.href = url;
          a.download = fileName;
          a.click();
          //URL.revokeObjectURL() é™æ€æ–¹æ³•用来释放一个之前已经存在的、通过调用 URL.createObjectURL() åˆ›å»ºçš„ URL å¯¹è±¡ã€‚当你结束使用某个 URL å¯¹è±¡ä¹‹åŽï¼Œåº”该通过调用这个方法来让浏览器知道不用在内存中继续保留对这个文件的引用了。
          window.URL.revokeObjectURL(url);
        } else {
          //下载失败处理
        }
      };
      xmlResquest.send();
    },
  },
};
</script>
<style scoped>
.tree-container {
  width: 30%;
  float: left;
  padding: 16px;
  background: #f8f9fa;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.filter-input {
  margin-bottom: 16px;
}
.filter-input :deep(.el-input__inner) {
  border-radius: 20px;
  border-color: #dcdfe6;
  transition: all 0.3s;
}
.filter-input :deep(.el-input__inner):focus {
  border-color: #409eff;
  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
.custom-tree-wrapper {
  height: 760px;
  overflow-y: auto;
  padding: 8px;
  background: white;
  border-radius: 6px;
  border: 1px solid #ebeef5;
}
.filter-tree :deep(.el-tree-node__content) {
  height: 40px;
  margin: 2px 0;
  border-radius: 4px;
  transition: all 0.2s;
}
.filter-tree :deep(.el-tree-node__content:hover) {
  background-color: #f0f9ff;
}
.filter-tree :deep(.el-tree-node.is-current > .el-tree-node__content) {
  background-color: #ecf5ff;
  font-weight: 600;
}
.custom-tree-node {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 0 8px;
}
.node-label {
  font-size: 14px;
  color: #606266;
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.node-actions {
  display: flex;
  gap: 8px;
  margin-left: 12px;
}
.action-btn {
  padding: 4px 8px;
  font-size: 12px;
  color: #409eff;
  border-radius: 3px;
}
.action-btn:hover {
  background-color: rgba(64, 158, 255, 0.1);
}
.log-container {
  width: 68%;
  float: right;
  padding: 16px;
}
.log-card {
  height: 800px;
  border-radius: 8px;
  border: 1px solid #ebeef5;
}
.log-card :deep(.el-card__header) {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  border-bottom: 1px solid #ebeef5;
  padding: 16px 20px;
}
.card-header {
  display: flex;
  align-items: center;
  gap: 12px;
}
.log-title {
  font-size: 16px;
  font-weight: 600;
  color: white;
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.log-content {
  height: 700px;
  overflow-y: auto;
  font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
  background: #f8f9fa;
  padding: 12px;
  border-radius: 4px;
}
.log-line {
  display: flex;
  align-items: flex-start;
  margin-bottom: 4px;
  line-height: 1.5;
  background: white;
  padding: 8px 12px;
  border-radius: 4px;
  border-left: 3px solid #409eff;
  transition: all 0.2s;
}
.log-line:hover {
  background: #f0f9ff;
  transform: translateX(2px);
}
.line-number {
  display: inline-block;
  min-width: 40px;
  padding-right: 12px;
  text-align: right;
  color: #909399;
  font-size: 12px;
  user-select: none;
}
.line-content {
  flex: 1;
  color: #303133;
  font-size: 13px;
  word-break: break-all;
  white-space: pre-wrap;
}
.empty-log {
  height: 800px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.empty-log :deep(.el-empty__description) {
  margin-top: 8px;
}
/* æ»šåŠ¨æ¡æ ·å¼ */
.custom-tree-wrapper::-webkit-scrollbar,
.log-content::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}
.custom-tree-wrapper::-webkit-scrollbar-track,
.log-content::-webkit-scrollbar-track {
  background: #f1f1f1;
  border-radius: 3px;
}
.custom-tree-wrapper::-webkit-scrollbar-thumb,
.log-content::-webkit-scrollbar-thumb {
  background: #c1c1c1;
  border-radius: 3px;
}
.custom-tree-wrapper::-webkit-scrollbar-thumb:hover,
.log-content::-webkit-scrollbar-thumb:hover {
  background: #a8a8a8;
}
/* æ¸…除浮动 */
.tree-container::after,
.log-container::after {
  content: "";
  display: table;
  clear: both;
}
</style>
Code Management/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/System/Sys_LogController.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,171 @@
using Microsoft.AspNetCore.Mvc;
using System.Text;
namespace WIDESEA_WMSServer.Controllers.System
{
    [Route("api/[controller]")]
    [ApiController]
    public class Sys_LogController : Controller
    {
        [HttpPost, Route("GetLogName"), AllowAnonymous]
        public WebResponseContent GetLogName()
        {
            WebResponseContent content = new WebResponseContent();
            try
            {
                List<object> data = new List<object>();
                DirectoryInfo folder = new DirectoryInfo(AppContext.BaseDirectory + "\\Logs\\");
                DirectoryInfo[] firstDirectoryInfos = folder.GetDirectories().OrderByDescending(x => x.CreationTime).ToArray();
                int k = 2020;
                for (int i = 0; i < firstDirectoryInfos.Length; i++)
                {
                    if (firstDirectoryInfos[i].Name != "Info")
                    {
                        FileInfo[] nextFileInfos = firstDirectoryInfos[i].GetFiles();
                        List<object> values = new List<object>();
                        for (int j = 0; j < nextFileInfos.Length; j++)
                        {
                            values.Add(new { label = nextFileInfos[j].Name, id = k, hidden = true, fatherNode = firstDirectoryInfos[i].Name });
                            k++;
                        }
                        data.Add(new { label = firstDirectoryInfos[i].Name, children = values, id = i, hidden = false });
                    }
                }
                return WebResponseContent.Instance.OK(data: data);
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error(ex.Message);
            }
        }
        [HttpPost, Route("GetLog"), AllowAnonymous]
        public WebResponseContent GetLog(string fileName)
        {
            WebResponseContent content = new WebResponseContent();
            try
            {
                List<FileInfo> files = new List<FileInfo>();
                DirectoryInfo folder = new DirectoryInfo(AppContext.BaseDirectory + "\\Logs\\");
                DirectoryInfo[] firstDirectoryInfos = folder.GetDirectories();
                for (int i = 0; i < firstDirectoryInfos.Length; i++)
                {
                    FileInfo[] nextFileInfos = firstDirectoryInfos[i].GetFiles();
                    files.AddRange(nextFileInfos);
                }
                if (files.Count > 0)
                {
                    FileInfo file = files.Where(x => x.Name == fileName).FirstOrDefault();
                    using StreamReader stream = new StreamReader(file.FullName);
                    StringBuilder text = new StringBuilder();
                    List<string> lines = new List<string>();
                    int i = 0;
                    while (stream.Peek() >= 0)
                    {
                        var line = stream.ReadLine();
                        lines.Add(line);
                    }
                    content = WebResponseContent.Instance.OK(data: lines);
                }
                else
                {
                    content = WebResponseContent.Instance.Error($"未找到日志文件,【{fileName}】");
                }
            }
            catch (Exception ex)
            {
                content = WebResponseContent.Instance.Error($"打开日志文件错误,{ex.Message}");
            }
            return content;
        }
        [HttpPost, HttpGet, Route("DownLoadLog"), AllowAnonymous]
        public virtual ActionResult DownLoadLog(string fileName)
        {
            try
            {
                // 1. å‚数验证
                if (string.IsNullOrWhiteSpace(fileName))
                {
                    return BadRequest("文件名不能为空");
                }
                string logDirectory = Path.Combine(AppContext.BaseDirectory, "Logs");
                if (!Directory.Exists(logDirectory))
                {
                    Directory.CreateDirectory(logDirectory);
                }
                string filePath = Path.Combine(logDirectory, fileName);
                if (Directory.Exists(filePath))
                {
                    return NotFound($"文件 {fileName} ä¸å­˜åœ¨");
                }
                string extension = Path.GetExtension(fileName).ToLowerInvariant();
                if (!IsAllowedFileType(extension))
                {
                    return BadRequest($"不支持的文件类型: {extension}");
                }
                FileInfo fileInfo = new FileInfo(filePath);
                if (fileInfo.Length > 50 * 1024 * 1024) // 50MB限制
                {
                    return BadRequest("文件过大,无法下载");
                }
                byte[] fileBytes;
                using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        fileStream.CopyTo(memoryStream);
                        fileBytes = memoryStream.ToArray();
                    }
                }
                string contentType = GetContentType(extension);
                return File(fileBytes, contentType, fileName);
            }
            catch (UnauthorizedAccessException)
            {
                return StatusCode(403, "没有访问该文件的权限");
            }
            catch (PathTooLongException)
            {
                return BadRequest("文件路径过长");
            }
            catch (IOException ex)
            {
                return StatusCode(500, "文件读取失败,可能正在被其他进程使用");
            }
            catch (Exception ex)
            {
                return StatusCode(500, $"服务器内部错误: {ex.Message}");
            }
        }
        private bool IsAllowedFileType(string extension)
        {
            var allowedTypes = new[] { ".txt", ".log", ".csv", ".json", ".xml" };
            return allowedTypes.Contains(extension);
        }
        private string GetContentType(string extension)
        {
            return extension.ToLowerInvariant() switch
            {
                ".txt" => "text/plain; charset=utf-8",
                ".log" => "text/plain; charset=utf-8",
                ".csv" => "text/csv; charset=utf-8",
                ".json" => "application/json; charset=utf-8",
                ".xml" => "application/xml; charset=utf-8",
                _ => "application/octet-stream"
            };
        }
    }
}