Przeglądaj źródła

新增日志管理

zmj 1 tydzień temu
rodzic
commit
e96b592a48

+ 44 - 0
src/api/base/sysLog.js

@@ -0,0 +1,44 @@
+import request from '@/utils/request'
+
+// 查询运行日志列表
+export function listSysLog(query) {
+  return request({
+    url: '/base/sysLog/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询运行日志详细
+export function getSysLog(id) {
+  return request({
+    url: '/base/sysLog/' + id,
+    method: 'get'
+  })
+}
+
+// 新增运行日志
+export function addSysLog(data) {
+  return request({
+    url: '/base/sysLog',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改运行日志
+export function updateSysLog(data) {
+  return request({
+    url: '/base/sysLog',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除运行日志
+export function delSysLog(id) {
+  return request({
+    url: '/base/sysLog/' + id,
+    method: 'delete'
+  })
+}

+ 205 - 2
src/views/base/opsScreenThemeConfig/index.vue

@@ -29,7 +29,32 @@
         <el-row :gutter="20">
           <el-col :span="24">
             <el-form-item label="Logo 图片" prop="logoUrl">
-              <image-upload v-model="form.logoUrl" :limit="1" :file-size="0.2" />
+              <div class="custom-upload-wrap">
+                <el-upload
+                  class="asset-uploader"
+                  drag
+                  :action="uploadUrl"
+                  :headers="uploadHeaders"
+                  :show-file-list="false"
+                  :before-upload="beforeLogoUpload"
+                  :on-success="(res, file) => handleLogoSuccess(res, file)"
+                  :on-error="handleUploadError">
+                  <div v-if="form.logoUrl" class="upload-preview">
+                    <img :src="form.logoUrl" class="preview-img" />
+                    <div class="upload-overlay">
+                      <el-icon><UploadFilled /></el-icon>
+                      <span>重新上传</span>
+                    </div>
+                  </div>
+                  <template v-else>
+                    <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
+                    <div class="el-upload__text">拖拽 Logo 到此处,或 <em>点击上传</em></div>
+                  </template>
+                </el-upload>
+                <div v-if="form.logoUrl" class="upload-actions">
+                  <el-button type="danger" size="small" @click="form.logoUrl = null">删除</el-button>
+                </div>
+              </div>
               <div class="form-tip">建议上传 png/jpg/jpeg 格式图片,大小不超过 200KB;未配置时屏幕端使用本地默认 Logo。</div>
             </el-form-item>
           </el-col>
@@ -50,7 +75,32 @@
         <el-row :gutter="20">
           <el-col :span="24">
             <el-form-item label="背景图" prop="backgroundImage">
-              <image-upload v-model="form.backgroundImage" :limit="1" :file-size="2" />
+              <div class="custom-upload-wrap">
+                <el-upload
+                  class="asset-uploader bg-uploader"
+                  drag
+                  :action="uploadUrl"
+                  :headers="uploadHeaders"
+                  :show-file-list="false"
+                  :before-upload="beforeBgUpload"
+                  :on-success="(res, file) => handleBgSuccess(res, file)"
+                  :on-error="handleUploadError">
+                  <div v-if="form.backgroundImage" class="upload-preview bg-preview">
+                    <img :src="form.backgroundImage" class="preview-img" />
+                    <div class="upload-overlay">
+                      <el-icon><UploadFilled /></el-icon>
+                      <span>重新上传</span>
+                    </div>
+                  </div>
+                  <template v-else>
+                    <el-icon class="el-icon--upload"><UploadFilled /></el-icon>
+                    <div class="el-upload__text">拖拽背景图到此处,或 <em>点击上传</em></div>
+                  </template>
+                </el-upload>
+                <div v-if="form.backgroundImage" class="upload-actions">
+                  <el-button type="danger" size="small" @click="form.backgroundImage = null">删除</el-button>
+                </div>
+              </div>
               <div class="form-tip">建议上传适配屏幕比例的横图,png/jpg/jpeg 格式,大小不超过 2MB;未配置时屏幕端使用本地默认背景。</div>
             </el-form-item>
           </el-col>
@@ -114,12 +164,19 @@
 
 <script setup name="OpsScreenThemeConfig">
 import { listOpsScreenThemeConfig, addOpsScreenThemeConfig, updateOpsScreenThemeConfig, delOpsScreenThemeConfig } from '@/api/base/opsScreenThemeConfig'
+import useUserStore from '@/store/modules/user'
 
 const { proxy } = getCurrentInstance()
 
 const loading = ref(false)
 const hasRemoteConfig = ref(false)
 
+// 上传配置
+const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + '/common/uploadMediaFile')
+const uploadHeaders = ref({
+  Authorization: 'Bearer ' + useUserStore()?.token
+})
+
 const DEFAULT_FORM = {
   id: null,
   configKey: 'default',
@@ -276,6 +333,78 @@ function submitForm() {
   })
 }
 
+/** Logo 上传前校验 */
+function beforeLogoUpload(file) {
+  const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
+  const isAllowed = allowedTypes.includes(file.type)
+  const isLt200KB = file.size / 1024 <= 200
+  if (!isAllowed) {
+    proxy.$modal.msgError('Logo 仅支持 jpg、png、webp 格式图片')
+    return false
+  }
+  if (!isLt200KB) {
+    proxy.$modal.msgError('Logo 文件大小不能超过 200KB')
+    return false
+  }
+  proxy.$modal.loading('正在上传 Logo...')
+  return true
+}
+
+/** Logo 上传成功 */
+function handleLogoSuccess(response, file) {
+  proxy.$modal.closeLoading()
+  if (response.code === 200 || response.code === undefined) {
+    const url = response.data?.fileUrl || response.fileUrl || response.url
+    if (url) {
+      form.value.logoUrl = url
+      proxy.$modal.msgSuccess('Logo 上传成功')
+    } else {
+      proxy.$modal.msgError('Logo 上传成功,但未获取到文件地址')
+    }
+  } else {
+    proxy.$modal.msgError(response.msg || 'Logo 上传失败')
+  }
+}
+
+/** 背景图上传前校验 */
+function beforeBgUpload(file) {
+  const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
+  const isAllowed = allowedTypes.includes(file.type)
+  const isLt2MB = file.size / 1024 / 1024 <= 2
+  if (!isAllowed) {
+    proxy.$modal.msgError('背景图仅支持 jpg、png、webp 格式图片')
+    return false
+  }
+  if (!isLt2MB) {
+    proxy.$modal.msgError('背景图文件大小不能超过 2MB')
+    return false
+  }
+  proxy.$modal.loading('正在上传背景图...')
+  return true
+}
+
+/** 背景图上传成功 */
+function handleBgSuccess(response, file) {
+  proxy.$modal.closeLoading()
+  if (response.code === 200 || response.code === undefined) {
+    const url = response.data?.fileUrl || response.fileUrl || response.url
+    if (url) {
+      form.value.backgroundImage = url
+      proxy.$modal.msgSuccess('背景图上传成功')
+    } else {
+      proxy.$modal.msgError('背景图上传成功,但未获取到文件地址')
+    }
+  } else {
+    proxy.$modal.msgError(response.msg || '背景图上传失败')
+  }
+}
+
+/** 上传失败 */
+function handleUploadError(error) {
+  proxy.$modal.closeLoading()
+  proxy.$modal.msgError('文件上传失败,请检查文件格式或稍后重试')
+}
+
 onMounted(() => {
   loadConfig()
 })
@@ -323,4 +452,78 @@ onMounted(() => {
   padding-top: 18px;
   border-top: 1px solid #ebeef5;
 }
+/* 自定义上传样式 */
+.custom-upload-wrap {
+  display: flex;
+  align-items: flex-start;
+  gap: 12px;
+}
+.asset-uploader {
+  width: 148px;
+}
+.asset-uploader :deep(.el-upload) {
+  border: 1px dashed #d9d9d9;
+  border-radius: 6px;
+  cursor: pointer;
+  position: relative;
+  overflow: hidden;
+  transition: border-color 0.2s;
+}
+.asset-uploader :deep(.el-upload:hover) {
+  border-color: #409eff;
+}
+.asset-uploader :deep(.el-upload-dragger) {
+  width: 100%;
+  height: 148px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  background-color: #fafafa;
+}
+.bg-uploader :deep(.el-upload-dragger) {
+  height: 120px;
+}
+.upload-preview {
+  width: 100%;
+  height: 100%;
+  position: relative;
+}
+.preview-img {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+  background: #fff;
+}
+.upload-overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  opacity: 0;
+  transition: opacity 0.2s;
+}
+.upload-preview:hover .upload-overlay {
+  opacity: 1;
+}
+.upload-overlay .el-icon {
+  font-size: 28px;
+  margin-bottom: 4px;
+}
+.upload-overlay span {
+  font-size: 12px;
+}
+.bg-preview .preview-img {
+  object-fit: cover;
+}
+.upload-actions {
+  padding-top: 4px;
+}
 </style>

+ 337 - 0
src/views/base/sysLog/index.vue

@@ -0,0 +1,337 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="机器人编号" prop="robotCode">
+        <el-input
+          v-model="queryParams.robotCode"
+          placeholder="请输入机器人编号"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="日志时间,设备侧或服务侧日志发生时间" prop="logTime">
+        <el-date-picker clearable
+          v-model="queryParams.logTime"
+          type="date"
+          value-format="YYYY-MM-DD"
+          placeholder="请选择日志时间,设备侧或服务侧日志发生时间">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item label="日志接收时间" prop="receiveTime">
+        <el-date-picker clearable
+          v-model="queryParams.receiveTime"
+          type="date"
+          value-format="YYYY-MM-DD"
+          placeholder="请选择日志接收时间">
+        </el-date-picker>
+      </el-form-item>
+      <el-form-item label="日志类型" prop="logType">
+        <el-select v-model="queryParams.logType" placeholder="请选择日志类型" clearable>
+          <el-option
+            v-for="dict in robot_log_type"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="日志级别" prop="logLevel">
+        <el-input
+          v-model="queryParams.logLevel"
+          placeholder="请输入日志级别"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="模块名称,如 screen、audio、camera、network、ota" prop="moduleName">
+        <el-input
+          v-model="queryParams.moduleName"
+          placeholder="请输入模块名称,如 screen、audio、camera、network、ota"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="结果状态" prop="resultStatus">
+        <el-select v-model="queryParams.resultStatus" placeholder="请选择结果状态" clearable>
+          <el-option
+            v-for="dict in common_result_status"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="链路追踪ID" prop="traceId">
+        <el-input
+          v-model="queryParams.traceId"
+          placeholder="请输入链路追踪ID"
+          clearable
+          @keyup.enter="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+        <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="Plus"
+          @click="handleAdd"
+          v-hasPermi="['base:sysLog:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="Edit"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['base:sysLog:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="Delete"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['base:sysLog:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="Download"
+          @click="handleExport"
+          v-hasPermi="['base:sysLog:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="sysLogList" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+      <el-table-column label="主键ID" align="center" prop="id" />
+      <el-table-column label="机器人编号" align="center" prop="robotCode" />
+      <el-table-column label="日志时间,设备侧或服务侧日志发生时间" align="center" prop="logTime" width="180">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.logTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="日志接收时间" align="center" prop="receiveTime" width="180">
+        <template #default="scope">
+          <span>{{ parseTime(scope.row.receiveTime, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="日志类型" align="center" prop="logType">
+        <template #default="scope">
+          <dict-tag :options="robot_log_type" :value="scope.row.logType"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="日志级别" align="center" prop="logLevel">
+        <template #default="scope">
+          <dict-tag :options="robot_log_level" :value="scope.row.logLevel"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="模块名称,如 screen、audio、camera、network、ota" align="center" prop="moduleName" />
+      <el-table-column label="日志摘要" align="center" prop="contentSummary" />
+      <el-table-column label="日志内容" align="center" prop="content" />
+      <el-table-column label="结果状态" align="center" prop="resultStatus">
+        <template #default="scope">
+          <dict-tag :options="common_result_status" :value="scope.row.resultStatus"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="链路追踪ID" align="center" prop="traceId" />
+      <el-table-column label="备注" align="center" prop="remark" />
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template #default="scope">
+          <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['base:sysLog:edit']">修改</el-button>
+          <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['base:sysLog:remove']">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    
+    <pagination
+      v-show="total>0"
+      :total="total"
+      v-model:page="queryParams.pageNum"
+      v-model:limit="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改运行日志对话框 -->
+    <el-dialog :title="title" v-model="open" width="500px" append-to-body>
+      <el-form ref="sysLogRef" :model="form" :rules="rules" label-width="100px">
+        <el-row>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="SysLog">
+import { listSysLog, getSysLog, delSysLog, addSysLog, updateSysLog } from "@/api/base/sysLog"
+
+const { proxy } = getCurrentInstance()
+const { robot_log_type, common_result_status } = useDict('robot_log_type', 'common_result_status')
+
+const sysLogList = ref([])
+const open = ref(false)
+const loading = ref(true)
+const showSearch = ref(true)
+const ids = ref([])
+const single = ref(true)
+const multiple = ref(true)
+const total = ref(0)
+const title = ref("")
+
+const data = reactive({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    robotCode: undefined,
+    logTime: undefined,
+    receiveTime: undefined,
+    logType: undefined,
+    logLevel: undefined,
+    moduleName: undefined,
+    contentSummary: undefined,
+    content: undefined,
+    resultStatus: undefined,
+    traceId: undefined,
+  },
+  rules: {
+  }
+})
+
+const { queryParams, form, rules } = toRefs(data)
+
+/** 查询运行日志列表 */
+function getList() {
+  loading.value = true
+  listSysLog(queryParams.value).then(response => {
+    sysLogList.value = response.rows
+    total.value = response.total
+    loading.value = false
+  })
+}
+
+/** 取消按钮 */
+function cancel() {
+  open.value = false
+  reset()
+}
+
+/** 表单重置 */
+function reset() {
+  form.value = {
+    id: null,
+    robotCode: null,
+    logTime: null,
+    receiveTime: null,
+    logType: null,
+    logLevel: null,
+    moduleName: null,
+    contentSummary: null,
+    content: null,
+    resultStatus: null,
+    traceId: null,
+    remark: null,
+    createTime: null
+  }
+  proxy.resetForm("sysLogRef")
+}
+
+/** 搜索按钮操作 */
+function handleQuery() {
+  queryParams.value.pageNum = 1
+  getList()
+}
+
+/** 重置按钮操作 */
+function resetQuery() {
+  proxy.resetForm("queryRef")
+  handleQuery()
+}
+
+/** 多选框选中数据 */
+function handleSelectionChange(selection) {
+  ids.value = selection.map(item => item.id)
+  single.value = selection.length != 1
+  multiple.value = !selection.length
+}
+
+/** 新增按钮操作 */
+function handleAdd() {
+  reset()
+  open.value = true
+  title.value = "添加运行日志"
+}
+
+/** 修改按钮操作 */
+function handleUpdate(row) {
+  reset()
+  const _id = row.id || ids.value
+  getSysLog(_id).then(response => {
+    form.value = response.data
+    open.value = true
+    title.value = "修改运行日志"
+  })
+}
+
+/** 提交按钮 */
+function submitForm() {
+  proxy.$refs["sysLogRef"].validate(valid => {
+    if (valid) {
+      if (form.value.id != null) {
+        updateSysLog(form.value).then(() => {
+          proxy.$modal.msgSuccess("修改成功")
+          open.value = false
+          getList()
+        })
+      } else {
+        addSysLog(form.value).then(() => {
+          proxy.$modal.msgSuccess("新增成功")
+          open.value = false
+          getList()
+        })
+      }
+    }
+  })
+}
+
+/** 删除按钮操作 */
+function handleDelete(row) {
+  const _ids = row.id || ids.value
+  proxy.$modal.confirm('是否确认删除运行日志编号为"' + _ids + '"的数据项?').then(function() {
+    return delSysLog(_ids)
+  }).then(() => {
+    getList()
+    proxy.$modal.msgSuccess("删除成功")
+  }).catch(() => {})
+}
+
+/** 导出按钮操作 */
+function handleExport() {
+  proxy.download('base/sysLog/export', {
+    ...queryParams.value
+  }, `sysLog_${new Date().getTime()}.xlsx`)
+}
+
+getList()
+</script>