This commit is contained in:
gotoeasy 2024-04-07 20:08:05 +08:00
parent 2eefa7ea8f
commit 0ed4b55c65
12 changed files with 335 additions and 96 deletions

View File

@ -2,6 +2,7 @@ package ldb
import ( import (
"glc/com" "glc/com"
"glc/conf"
"glc/ldb/search" "glc/ldb/search"
"glc/ldb/storage" "glc/ldb/storage"
"glc/ldb/storage/indexword" "glc/ldb/storage/indexword"
@ -152,7 +153,49 @@ func (e *Engine) Search(cond *search.SearchCondition) *search.SearchResult {
rs = search.SearchLogData(e.storeName, cond) rs = search.SearchLogData(e.storeName, cond)
} else { } else {
// 多关键词查询模式 // 多关键词查询模式
rs = search.SearchWordIndex(e.storeName, cond) if cond.NewNearId > 0 {
// 相邻检索模式的判断及条件准备
max := conf.GetPageSize()
leftSize := 20
cond.CurrentId = cond.NewNearId
isForward := cond.OldNearId < 1 || cond.NewNearId < cond.OldNearId // 是否向下检索更多的旧日志
// 搜索更多的新数据
// 反向检索前x条
cond.Forward = false
if isForward {
cond.SearchSize = leftSize
} else {
cond.SearchSize = max - leftSize
}
rs = search.SearchWordIndex(e.storeName, cond)
// 定位日志1条固定不管是否满足检索条件
ldm := search.GetLogDataModelById(cond.NearStoreName, cond.NewNearId)
// 正向检索剩余件数
cond.Forward = true
cond.SearchSize = max - len(rs.Data)
rsForward := search.SearchWordIndex(e.storeName, cond)
// 正向件数不够,反向重查来凑
if len(rsForward.Data) < cond.SearchSize && len(rs.Data) == leftSize {
cond.Forward = false
cond.SearchSize = max - len(rsForward.Data)
rs = search.SearchWordIndex(e.storeName, cond)
}
// 拼接检索结果
rs.Data = append(rs.Data, ldm)
rs.Data = append(rs.Data, rsForward.Data...)
// 检索结果最大匹配件数修正
if len(rs.Data)-1 < max {
rs.Count = cmn.IntToString(len(rs.Data) - 1)
}
} else {
// 普通检索
rs = search.SearchWordIndex(e.storeName, cond)
}
} }
if maxMatchCount < cmn.StringToUint32(rs.Count, 0) { if maxMatchCount < cmn.StringToUint32(rs.Count, 0) {

View File

@ -28,6 +28,9 @@ type SearchCondition struct {
CurrentStoreName string // 隐藏条件当前日志文档ID所属的日志仓 CurrentStoreName string // 隐藏条件当前日志文档ID所属的日志仓
CurrentId uint32 // 隐藏条件当前日志文档ID CurrentId uint32 // 隐藏条件当前日志文档ID
Forward bool // 隐藏条件,是否向前检索(玩下滚动查询) Forward bool // 隐藏条件,是否向前检索(玩下滚动查询)
OldNearId uint32 // 隐藏条件相邻检索时的旧ID
NewNearId uint32 // 隐藏条件相邻检索时的新ID
NearStoreName string // 隐藏条件相邻检索时新ID对应的日志仓
Kws []string // 【内部用】解析条件所得的检索关键词,非直接输入的检索文本 Kws []string // 【内部用】解析条件所得的检索关键词,非直接输入的检索文本
SearchSize int // 【内部用】需要查询多少件(跨仓检索时可能多次检索,中间会内部调整) SearchSize int // 【内部用】需要查询多少件(跨仓检索时可能多次检索,中间会内部调整)
Systems []string // 【内部用】有权限的系统名(一定有值,第一个元素是“*”时表示全部) Systems []string // 【内部用】有权限的系统名(一定有值,第一个元素是“*”时表示全部)
@ -98,6 +101,14 @@ func SearchWordIndex(storeName string, cond *SearchCondition) *SearchResult {
return findSame(cond, minDocumentId, maxDocumentId, storeLogData, widxs...) return findSame(cond, minDocumentId, maxDocumentId, storeLogData, widxs...)
} }
// 按ID查取日志
func GetLogDataModelById(storeName string, id uint32) *logdata.LogDataModel {
storeLogData := storage.NewLogDataStorageHandle(storeName) // 数据
ldm := storeLogData.GetLogDataModel(id)
ldm.StoreName = storeName // 日志仓名称未序列化,前端要使用,这里补足赋值
return ldm
}
// 无关键词时走全量检索 // 无关键词时走全量检索
func SearchLogData(storeName string, cond *SearchCondition) *SearchResult { func SearchLogData(storeName string, cond *SearchCondition) *SearchResult {
allloglevels := cmn.Join(cond.Loglevels, ",") // 合并多选的级别条件 allloglevels := cmn.Join(cond.Loglevels, ",") // 合并多选的级别条件
@ -193,6 +204,7 @@ func SearchLogData(storeName string, cond *SearchCondition) *SearchResult {
} }
md := storeLogData.GetLogDataDocument(i).ToLogDataModel() md := storeLogData.GetLogDataDocument(i).ToLogDataModel()
md.StoreName = storeLogData.GetStoreName() // 日志仓名称未序列化,前端要使用,这里补足赋值
if noLogLevels || cmn.ContainsIngoreCase(allloglevels, md.LogLevel) { if noLogLevels || cmn.ContainsIngoreCase(allloglevels, md.LogLevel) {
rs.Data = append(rs.Data, md) rs.Data = append(rs.Data, md)
rsCnt++ rsCnt++
@ -251,6 +263,7 @@ func SearchLogData(storeName string, cond *SearchCondition) *SearchResult {
} }
md := storeLogData.GetLogDataDocument(i).ToLogDataModel() md := storeLogData.GetLogDataDocument(i).ToLogDataModel()
md.StoreName = storeLogData.GetStoreName() // 日志仓名称未序列化,前端要使用,这里补足赋值
if noLogLevels || cmn.ContainsIngoreCase(allloglevels, md.LogLevel) { if noLogLevels || cmn.ContainsIngoreCase(allloglevels, md.LogLevel) {
rs.Data = append(rs.Data, md) rs.Data = append(rs.Data, md)
rsCnt++ rsCnt++
@ -306,6 +319,7 @@ func SearchLogData(storeName string, cond *SearchCondition) *SearchResult {
} }
md := storeLogData.GetLogDataDocument(i).ToLogDataModel() md := storeLogData.GetLogDataDocument(i).ToLogDataModel()
md.StoreName = storeLogData.GetStoreName() // 日志仓名称未序列化,前端要使用,这里补足赋值
if noLogLevels || cmn.ContainsIngoreCase(allloglevels, md.LogLevel) { if noLogLevels || cmn.ContainsIngoreCase(allloglevels, md.LogLevel) {
rs.Data = append(rs.Data, md) rs.Data = append(rs.Data, md)
rsCnt++ rsCnt++
@ -355,11 +369,52 @@ func findSame(cond *SearchCondition, minDocumentId uint32, maxDocumentId uint32,
} }
// 找匹配位置并排除没结果的情景 // 找匹配位置并排除没结果的情景
pos := totalCount // 默认检索最新第一页 var pos uint32
if cond.CurrentId > 0 { if cond.Forward {
pos = minIdx.idxdocStorage.GetWordDocSeq(minIdx.word, cond.CurrentId) // 有相对文档ID时找相对位置 pos = totalCount // 向后检索更加旧的日志,游标从大开始递降
if pos == 0 || (pos == 1 && cond.Forward) || (pos == totalCount && !cond.Forward) { if cond.NewNearId > 0 {
return rs // 找不到、或最后条还要向后、或最前条还要向前,都是找不到 // 相邻检索
nearId := cond.NewNearId - 1 // 查找比定位日志小的旧日志
pos = minIdx.idxdocStorage.GetWordDocSeq(minIdx.word, nearId) // 有相对文档ID时找相对位置
for pos == 0 && nearId >= minDocumentId {
nearId--
pos = minIdx.idxdocStorage.GetWordDocSeq(minIdx.word, nearId)
}
if pos == 0 {
return rs // 找不到
}
pos++ // 找到后续会减1排除本条找到的日志但相邻检索时本条日志是需要的所以加1处理
} else {
// 普通检索
if cond.CurrentId > 0 {
pos = minIdx.idxdocStorage.GetWordDocSeq(minIdx.word, cond.CurrentId) // 有相对文档ID时找相对位置
if pos <= 1 {
return rs // 找不到、或最后条还要向后
}
}
}
} else {
pos = 1 // 向前检索更加新的日志,游标从小开始递增
if cond.NewNearId > 0 {
// 相邻检索
nearId := cond.NewNearId + 1 // 查找比定位日志大的新日志
pos = minIdx.idxdocStorage.GetWordDocSeq(minIdx.word, nearId) // 有相对文档ID时找相对位置
for pos == 0 && nearId <= maxDocumentId {
nearId++
pos = minIdx.idxdocStorage.GetWordDocSeq(minIdx.word, nearId)
}
if pos == 0 {
return rs // 找不到
}
pos-- // 找到后续会加1排除本条找到的日志但相邻检索时本条日志是需要的所以减1处理
} else {
// 普通检索
if cond.CurrentId > 0 {
pos = minIdx.idxdocStorage.GetWordDocSeq(minIdx.word, cond.CurrentId) // 有相对文档ID时找相对位置
if pos == 0 || pos == totalCount {
return rs // 找不到、或最前条还要向前
}
}
} }
} }
@ -429,6 +484,7 @@ func findSame(cond *SearchCondition, minDocumentId uint32, maxDocumentId uint32,
// 找到则加入结果 // 找到则加入结果
if flg { if flg {
md := storeLogData.GetLogDataModel(docId) md := storeLogData.GetLogDataModel(docId)
md.StoreName = storeLogData.GetStoreName() // 日志仓名称未序列化,前端要使用,这里补足赋值
if noLogLevels || cmn.ContainsIngoreCase(allloglevels, md.LogLevel) { if noLogLevels || cmn.ContainsIngoreCase(allloglevels, md.LogLevel) {
rsCnt++ rsCnt++
rs.Data = append(rs.Data, md) rs.Data = append(rs.Data, md)
@ -444,10 +500,11 @@ func findSame(cond *SearchCondition, minDocumentId uint32, maxDocumentId uint32,
tmpMinPos-- // 当前最短索引可能不变得正常减1若变化则会被覆盖没有关系 tmpMinPos-- // 当前最短索引可能不变得正常减1若变化则会被覆盖没有关系
} }
} else { } else {
// 有相对文档ID且是前一页方向(注:暂未使用,未经测试) // 有相对文档ID且是前一页方向
pos++ pos++
var ary []*logdata.LogDataModel var ary []*logdata.LogDataModel
for i := pos; i <= totalCount; i++ { total := storeLogData.TotalCount() // 当前日志最大件数
for i := pos; i <= total; i++ {
// 取值 // 取值
docId := minIdx.idxwordStorage.GetDocId(minIdx.word, i) docId := minIdx.idxwordStorage.GetDocId(minIdx.word, i)
@ -500,6 +557,7 @@ func findSame(cond *SearchCondition, minDocumentId uint32, maxDocumentId uint32,
// 找到则加入结果 // 找到则加入结果
if flg { if flg {
md := storeLogData.GetLogDataModel(docId) md := storeLogData.GetLogDataModel(docId)
md.StoreName = storeLogData.GetStoreName() // 日志仓名称未序列化,前端要使用,这里补足赋值
if noLogLevels || cmn.ContainsIngoreCase(allloglevels, md.LogLevel) { if noLogLevels || cmn.ContainsIngoreCase(allloglevels, md.LogLevel) {
rsCnt++ rsCnt++
ary = append(ary, md) ary = append(ary, md)

View File

@ -166,7 +166,7 @@ func (s *WordIndexStorage) Add(word string, docId uint32) error {
} }
// 保存建好的索引数 // 保存建好的索引数
s.setTotalCount(word, seq) err = s.setTotalCount(word, seq)
if err != nil { if err != nil {
cmn.Error("保存关键词反向索引件数失败", err) cmn.Error("保存关键词反向索引件数失败", err)
return err // 忽略事务问题,可下回重建 return err // 忽略事务问题,可下回重建

View File

@ -25,6 +25,7 @@ type LogDataModel struct {
LogLevel string `json:"loglevel,omitempty"` // 日志级别debug、info、warn、error LogLevel string `json:"loglevel,omitempty"` // 日志级别debug、info、warn、error
User string `json:"user,omitempty"` // 用户 User string `json:"user,omitempty"` // 用户
Detail string `json:"detail,omitempty"` // 【内部字段】多行时的详细日志信息,通常是包含错误堆栈等的日志内容 Detail string `json:"detail,omitempty"` // 【内部字段】多行时的详细日志信息,通常是包含错误堆栈等的日志内容
StoreName string `json:"storename,omitempty"` // 日志仓名称(未存储,仅赋值给前端使用)
} }
func (d *LogDataModel) ToJson() string { func (d *LogDataModel) ToJson() string {

View File

@ -1,4 +1,4 @@
package ver package ver
// 版本号,升级版本时修改 // 版本号,升级版本时修改
const VERSION = "v0.14.2" const VERSION = "v0.15.0"

View File

@ -44,6 +44,9 @@ func LogSearchController(req *gweb.HttpRequest) *gweb.HttpResult {
cond.CurrentStoreName = req.GetFormParameter("currentStoreName") // 滚动查询时定位用日志仓 cond.CurrentStoreName = req.GetFormParameter("currentStoreName") // 滚动查询时定位用日志仓
cond.CurrentId = cmn.StringToUint32(req.GetFormParameter("currentId"), 0) // 滚动查询时定位用ID cond.CurrentId = cmn.StringToUint32(req.GetFormParameter("currentId"), 0) // 滚动查询时定位用ID
cond.Forward = cmn.StringToBool(req.GetFormParameter("forward"), true) // 是否向下滚动查询 cond.Forward = cmn.StringToBool(req.GetFormParameter("forward"), true) // 是否向下滚动查询
cond.OldNearId = cmn.StringToUint32(req.GetFormParameter("oldNearId"), 0) // 相邻检索旧ID
cond.NewNearId = cmn.StringToUint32(req.GetFormParameter("newNearId"), 0) // 相邻检索新ID
cond.NearStoreName = req.GetFormParameter("nearStoreName") // 相邻检索时新ID对应的日志仓
cond.DatetimeFrom = req.GetFormParameter("datetimeFrom") // 日期范围From cond.DatetimeFrom = req.GetFormParameter("datetimeFrom") // 日期范围From
cond.DatetimeTo = req.GetFormParameter("datetimeTo") // 日期范围To cond.DatetimeTo = req.GetFormParameter("datetimeTo") // 日期范围To
cond.OrgSystem = cmn.Trim(req.GetFormParameter("system")) // 系统 cond.OrgSystem = cmn.Trim(req.GetFormParameter("system")) // 系统

View File

@ -4,9 +4,9 @@
"editor.formatOnType": true, "editor.formatOnType": true,
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll": true, "source.fixAll": "explicit",
"source.fixAll.eslint": true, "source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": true "source.fixAll.stylelint": "explicit"
}, },
"eslint.validate": ["javascript", "javascriptvue", "vue"], "eslint.validate": ["javascript", "javascriptvue", "vue"],
"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass", "html"] "stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass", "html"]

View File

@ -9,7 +9,7 @@
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.1.0", "@element-plus/icons-vue": "^2.1.0",
"@gotoeasy/glcui": "^0.1.15", "@gotoeasy/glcui": "0.2.0",
"@imengyu/vue3-context-menu": "^1.3.0", "@imengyu/vue3-context-menu": "^1.3.0",
"@vueuse/core": "^10.2.1", "@vueuse/core": "^10.2.1",
"axios": "^1.4.0", "axios": "^1.4.0",

View File

@ -9,8 +9,8 @@ dependencies:
specifier: ^2.1.0 specifier: ^2.1.0
version: 2.1.0(vue@3.3.4) version: 2.1.0(vue@3.3.4)
'@gotoeasy/glcui': '@gotoeasy/glcui':
specifier: ^0.1.15 specifier: 0.2.0
version: 0.1.15 version: 0.2.0
'@imengyu/vue3-context-menu': '@imengyu/vue3-context-menu':
specifier: ^1.3.0 specifier: ^1.3.0
version: 1.3.0 version: 1.3.0
@ -772,8 +772,8 @@ packages:
resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
dev: true dev: true
/@gotoeasy/glcui@0.1.15: /@gotoeasy/glcui@0.2.0:
resolution: {integrity: sha512-Htk1iNsX46ROcCCxNZFjOY2fON33AN/H3Os5yApZA+r/2ljsXyYOCzyIFP5pGMHNkgfAH9HaEyynqnG5M+18dQ==} resolution: {integrity: sha512-GERMjGBaojq8L317uxGLtM7Aghbr5Xyzc7TG47nKw8jZKFKKtzAQLPVnXGFBll5InxDiDlS/bOmUY2Cb3NFJqA==}
dev: false dev: false
/@humanwhocodes/config-array@0.11.10: /@humanwhocodes/config-array@0.11.10:

View File

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M458.678857 730.672762c150.211048 0 271.993905-121.782857 271.993905-272.018286 0-3.047619-0.048762-6.095238-0.146286-9.142857h-107.471238a10.678857 10.678857 0 0 1-7.558095-18.212571l125.123047-125.074286c4.144762-4.169143 10.898286-4.169143 15.067429 0l125.098667 125.074286a10.654476 10.654476 0 0 1-7.558096 18.212571H789.211429a329.313524 329.313524 0 0 1-76.897524 221.305905l183.100952 183.100952-41.472 41.496381-183.100952-183.100952a329.337905 329.337905 0 0 1-212.163048 77.019428c-107.398095 0-202.849524-51.2-263.241143-130.511238l41.935238-41.935238a271.652571 271.652571 0 0 0 221.305905 113.785905zM458.678857 186.660571c92.769524 0 174.689524 46.445714 223.792762 117.369905l42.032762-42.057143A330.191238 330.191238 0 0 0 458.678857 128c-182.613333 0-330.678857 148.041143-330.678857 330.678857 0 4.071619 0.073143 8.118857 0.24381 12.190476H47.104a10.678857 10.678857 0 0 0-7.558095 18.188191l125.098666 125.098666c4.169143 4.144762 10.922667 4.144762 15.09181 0l125.074286-125.098666a10.678857 10.678857 0 0 0-7.533715-18.212572H186.928762a276.601905 276.601905 0 0 1-0.24381-12.190476c0-150.186667 121.758476-271.993905 271.993905-271.993905z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -5,8 +5,8 @@
<el-input v-model="formData.searchKeys" placeholder="请输入关键词检索,支持多关键词" input-style="width:500px;height: 30px" <el-input v-model="formData.searchKeys" placeholder="请输入关键词检索,支持多关键词" input-style="width:500px;height: 30px"
maxlength="1000" @keyup.enter="fnSearch"> maxlength="1000" @keyup.enter="fnSearch">
<template #append> <template #append>
<el-button type="primary" class="c-btn-search" style="height:30px;color: white; background-color:#0081dd" <el-button type="primary" class="c-btn-search" style="height:30px;color:white"
@click="fnSearch"> :style="{ backgroundColor: searchFormNormalMode ? '#0081dd' : '#e6a23c' }" @click="fnSearch">
<el-icon> <el-icon>
<Search /> <Search />
</el-icon> </el-icon>
@ -14,7 +14,10 @@
</el-button> </el-button>
</template> </template>
</el-input> </el-input>
<GxButton icon="refresh-left" style="margin-left:108px;" @click="fnReset"> </GxButton> <GxButton icon="refresh-left" style="margin-left:108px;" @click="fnReset">
<span v-if="searchFormNormalMode"> </span>
<span v-else title="取消定位相邻检索"> </span>
</GxButton>
<el-badge is-dot :hidden="noMoreSearchCondition" style="margin-left:12px;" type="primary" class="c-btn-badge"> <el-badge is-dot :hidden="noMoreSearchCondition" style="margin-left:12px;" type="primary" class="c-btn-badge">
<el-button circle @click="() => (moreVisible = !moreVisible)"> <el-button circle @click="() => (moreVisible = !moreVisible)">
<el-icon> <el-icon>
@ -32,7 +35,7 @@
<el-divider style="margin: 0 0 10px" /> <el-divider style="margin: 0 0 10px" />
<el-row justify="center"> <el-row justify="center">
<el-button type="primary" class="c-btn" @click="fnSearch"> <el-button :type="searchFormNormalMode ? 'primary' : 'warning'" class="c-btn" @click="fnSearch">
<el-icon size="14"> <el-icon size="14">
<Search /> <Search />
</el-icon> </el-icon>
@ -73,6 +76,24 @@ const moreVisible = ref(false);
$emitter.on("defaultStorageCondtion", v => defaultData.value.storage = v); $emitter.on("defaultStorageCondtion", v => defaultData.value.storage = v);
// (/)
const searchFormNormalMode = ref(true);
const tmpFormDataNormal = {};
const setSearchNormalMode = (normal = false) => {
if (searchFormNormalMode.value && !normal) {
//
tmpFormDataNormal.storage = formData.value.storage || '';
tmpFormDataNormal.system = formData.value.system || '';
tmpFormDataNormal.loglevel = [...(formData.value.loglevel || [])];
tmpFormDataNormal.datetime = [...(formData.value.datetime || [])];
tmpFormDataNormal.user = formData.value.user || '';
tmpFormDataNormal.searchKeys = formData.value.searchKeys || '';
}
searchFormNormalMode.value = normal;
moreVisible.value = false; //
}
$emitter.on("searchFormNormalMode", setSearchNormalMode);
const emit = defineEmits(['search']); const emit = defineEmits(['search']);
const fnSearch = () => { const fnSearch = () => {
@ -83,19 +104,33 @@ $emitter.on("fnSearch", fnSearch);
const fnReset = () => { const fnReset = () => {
const keys = Object.keys(formData.value); if (!searchFormNormalMode.value) {
for (const key of keys) { // -->
if (!formData.value[key] && !defaultData.value[key]) { searchFormNormalMode.value = true;
continue; $emitter.emit("searchNearMode", false);
} else if (!formData.value[key] && defaultData.value[key]) {
formData.value[key] = defaultData.value[key]; formData.value.storage = tmpFormDataNormal.storage;
} else if (formData.value[key] && !defaultData.value[key]) { formData.value.system = tmpFormDataNormal.system;
formData.value[key] = null; !formData.value.loglevel && (formData.value.loglevel = []);
} else if (Array.isArray(defaultData.value[key])) { formData.value.loglevel.splice(0, formData.value.loglevel.length);
formData.value[key] = defaultData.value[key].slice(0); formData.value.loglevel.push(...(tmpFormDataNormal.loglevel || []));
} else { !formData.value.datetime && (formData.value.datetime = []);
formData.value[key] = defaultData.value[key]; formData.value.datetime.splice(0, formData.value.datetime.length);
} formData.value.datetime.push(...(tmpFormDataNormal.datetime || []));
formData.value.user = tmpFormDataNormal.user;
formData.value.searchKeys = tmpFormDataNormal.searchKeys;
} else {
//
formData.value.storage = defaultData.value.storage;
formData.value.system = defaultData.value.system;
!formData.value.loglevel && (formData.value.loglevel = []);
formData.value.loglevel.splice(0, formData.value.loglevel.length);
formData.value.loglevel.push(...(defaultData.value.loglevel || []));
!formData.value.datetime && (formData.value.datetime = []);
formData.value.datetime.splice(0, formData.value.datetime.length);
formData.value.datetime.push(...(defaultData.value.datetime || []));
formData.value.user = defaultData.value.user;
formData.value.searchKeys = '';
} }
moreVisible.value = false; moreVisible.value = false;
@ -103,47 +138,50 @@ const fnReset = () => {
} }
const noMoreSearchCondition = computed(() => { const noMoreSearchCondition = computed(() => {
for (const [key, value] of Object.entries(formData.value)) {
if (key == 'searchKeys') {
continue; //
}
if (key == 'storage') {
if (!defaultData.value.storage) {
continue; //
} else if (defaultData.value.storage == value) {
continue; //
}
if (!value) return false; //
}
if (value) {
if (Array.isArray(value)) {
if (!defaultData.value[key]) {
if (!value.length) {
continue;
} else {
return false;
}
} else {
if (value.length != defaultData.value[key].length) { //
return false; if (!searchFormNormalMode.value) {
} if ((formData.value.loglevel || []).length) return false;
for (let i = 0; i < value.length; i++) { const ary = formData.value.datetime || ['', ''];
if (value[i] != defaultData.value[key][i]) { !ary.length && ary.push(...['', '']);
return false; if (ary.length != 2 || ary[0] || ary[1]) return false;
} if (formData.value.user) return false;
} return true;
} }
} else if (value != defaultData.value[key]) { //
return false; if ((formData.value.storage || '') != (defaultData.value.storage || '')) {
} return false;
} else if (defaultData.value[key]) { }
if ((formData.value.system || '') != (defaultData.value.system || '')) {
return false;
}
if ((formData.value.user || '') != (defaultData.value.user || '')) {
return false;
}
//
const formDataLoglevel = formData.value.loglevel || [];
const defaultDataLoglevel = defaultData.value.loglevel || [];
if (formDataLoglevel.length != defaultDataLoglevel.length) {
return false;
}
for (let i = 0; i < formDataLoglevel.length; i++) {
if (!defaultDataLoglevel.includes(formDataLoglevel[i])) {
return false; return false;
} }
} }
//
const formDataDatetime = formData.value.datetime || [];
!formDataDatetime.length && formDataDatetime.push(...['', '']);
const defaultDataDatetime = defaultData.value.datetime || [];
!defaultDataDatetime.length && defaultDataDatetime.push(...['', '']);
if (formDataDatetime.length != defaultDataDatetime.length || formDataDatetime[0] != defaultDataDatetime[0] || formDataDatetime[1] != defaultDataDatetime[1]) {
return false;
}
return true; return true;
}) })
</script> </script>

View File

@ -3,37 +3,43 @@
<GxToolbar style="margin-bottom: 13px" class="c-btn"> <GxToolbar style="margin-bottom: 13px" class="c-btn">
<template #left> <template #left>
<SearchForm :data="formData" class="c-search-form" @search="search"> <SearchForm :data="formData" class="c-search-form" @search="() => search()">
<el-row> <el-row>
<el-form-item label="选择日志仓"> <el-form-item label="选择日志仓">
<el-select v-model="formData.storage" clearable filterable placeholder="请选择" style="width:420px;" <el-select v-model="formData.storage" clearable filterable placeholder="请选择" style="width:420px;"
@clear="reGetStorageOptions"> :disabled="searchNearMode" @clear="reGetStorageOptions">
<el-option v-for="item in storageOptions" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in storageOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="系统名"> <el-form-item label="系统名">
<el-select v-model="formData.system" :multiple="false" filterable allow-create default-first-option <el-select v-model="formData.system" :multiple="false" filterable allow-create default-first-option
style="width:420px;" clearable :reserve-keyword="false" placeholder="请输入系统名"> :disabled="searchNearMode" style="width:420px;" clearable :reserve-keyword="false" placeholder="请输入系统名">
<el-option v-for="item in systemOptions" :key="item.value" :label="item.label" :value="item.value" /> <el-option v-for="item in systemOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="日志级别"> <el-form-item label="日志级别">
<el-select v-model="formData.loglevel" multiple clearable :reserve-keyword="true" style="width:420px;" <!-- <el-select v-model="formData.loglevel" multiple clearable :reserve-keyword="true" style="width:420px;"
placeholder="请选择..."> placeholder="请选择...">
<el-option label="ERROR" value="error" /> <el-option label="ERROR" value="error" />
<el-option label="WARN" value="warn" /> <el-option label="WARN" value="warn" />
<el-option label="INFO" value="info" /> <el-option label="INFO" value="info" />
<el-option label="DEBUG" value="debug" /> <el-option label="DEBUG" value="debug" />
</el-select> </el-select> -->
<el-checkbox-group v-model="formData.loglevel">
<el-checkbox label="DEBUG" value="debug" border />
<el-checkbox label="INFO" value="info" border style="margin-left: 6px;" />
<el-checkbox label="WARN" value="warn" border style="margin-left: 6px;" />
<el-checkbox label="ERROR" value="error" border style="margin-left: 6px;" />
</el-checkbox-group>
</el-form-item> </el-form-item>
<el-form-item label="时间范围"> <el-form-item label="时间范围">
<el-date-picker v-model="formData.datetime" type="datetimerange" :shortcuts="shortcuts" range-separator="" <el-date-picker v-model="formData.datetime" type="datetimerange" :shortcuts="shortcuts"
value-format="YYYY-MM-DD HH:mm:ss" start-placeholder="开始时间" end-placeholder="结束时间" range-separator="" value-format="YYYY-MM-DD HH:mm:ss" start-placeholder="开始时间" end-placeholder="结束时间"
popper-class="c-datapicker" /> popper-class="c-datapicker" />
</el-form-item> </el-form-item>
<el-form-item label="用户"> <el-form-item label="用户">
<el-input v-model="formData.user" placeholder="请输入用户" maxlength="100" style="width:420px;" <el-input v-model="formData.user" placeholder="请输入用户" maxlength="100" style="width:420px;"
@keyup.enter="() => $emitter.emit('fnSearch')" /> @keyup.enter="() => { $emitter.emit('fnSearch'); }" />
</el-form-item> </el-form-item>
</el-row> </el-row>
</SearchForm> </SearchForm>
@ -46,7 +52,7 @@
</el-button> </el-button>
</el-tooltip> </el-tooltip>
<el-tooltip :content="autoSearchMode ? '停止自动查询' : '开始自动查询'" placement="top"> <el-tooltip :content="autoSearchMode ? '停止自动查询' : '开始自动查询'" placement="top">
<el-button circle @click="switchAutoSearchMode"> <el-button circle :disabled="searchNearMode" @click="switchAutoSearchMode">
<SvgIcon v-if="!autoSearchMode" name="play" /> <SvgIcon v-if="!autoSearchMode" name="play" />
<SvgIcon v-if="autoSearchMode" name="stop" /> <SvgIcon v-if="autoSearchMode" name="stop" />
</el-button> </el-button>
@ -65,17 +71,21 @@
</template> </template>
</GxToolbar> </GxToolbar>
<GxTable ref="table" v-loading="showTableLoadding" scrollbar-always-on :enable-header-contextmenu="false" <GxTable ref="table" v-loading="showTableLoadding" :row-class-name="tableRowClassName" scrollbar-always-on
:enable-first-expand="true" stripe :tid="tid" :data="tableData" :height="tableHeight" class="c-gx-table c-glc-table" :enable-header-contextmenu="true" :enable-first-expand="true" stripe :tid="tid" :data="tableData"
row-key="id"> :height="tableHeight" class="c-gx-table c-glc-table" row-key="id">
<template #$operation="{ row }">
<div title="定位相邻检索">
<SvgIcon name="near-search" class="hand" @click="fnSearchNear(row)" />
</div>
</template>
</GxTable> </GxTable>
<div> <div>
<div style="display:flex;justify-content:space-between;"> <div style="display:flex;justify-content:space-between;">
<div style="min-height:30px;padding-top:5px; line-height:30px; color: #909399;" v-text="info"></div> <div style="min-height:30px;padding-top:5px; line-height:30px; color: #909399;" v-text="info"> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
@ -96,7 +106,7 @@ const { formData, visible, tableData, tableHeight, pageSettingStore, showTableLo
const showTestBtn = ref(false); // const showTestBtn = ref(false); //
const autoSearchMode = ref(false); // const autoSearchMode = ref(false); //
const table = ref(); // const table = ref(); //
const tid = ref('searchMain231126'); // ID const tid = ref('v001500GlcMain'); // ID
const info = ref(''); // const info = ref(''); //
const storageOptions = ref([]) // const storageOptions = ref([]) //
const systemSet = new Set(); const systemSet = new Set();
@ -188,6 +198,51 @@ const shortcuts = ref([
}, },
]); ]);
//
const searchNearMode = ref(false);
const setSearchNearMode = (near = false) => {
searchNearMode.value = near;
}
$emitter.on("searchNearMode", setSearchNearMode);
const oldNearId = ref(0);
const newNearId = ref(0);
const nearStoreName = ref('');
const tableRowClassName = ({ row }) => {
if (searchNearMode.value && row.id == newNearId.value) {
return 'search-near-row';
}
return '';
}
//
const fnSearchNear = (row) => {
$emitter.emit("searchFormNormalMode", false); //
if (searchNearMode.value) {
// 使
oldNearId.value = newNearId.value;
newNearId.value = row.id;
} else {
//
oldNearId.value = 0;
newNearId.value = row.id;
nearStoreName.value = row.storename;
formData.value.storage = row.storename;
formData.value.system = row.system;
formData.value.loglevel = [];
formData.value.datetime = [];
formData.value.user = '';
formData.value.searchKeys = '';
$emitter.emit("searchNearMode", true);
}
//
search();
}
// //
onMounted(async () => { onMounted(async () => {
const configStore = $emitter.emit('$table:config', { id: tid.value }); const configStore = $emitter.emit('$table:config', { id: tid.value });
@ -276,7 +331,7 @@ function isAutoSearchMode() {
} }
function switchAutoSearchMode(changMode = true) { function switchAutoSearchMode(changMode = true) {
changMode && (autoSearchMode.value = !autoSearchMode.value); changMode && (autoSearchMode.value = !autoSearchMode.value);
if (autoSearchMode.value) { if (autoSearchMode.value && !searchNearMode.value) {
search(); search();
setTimeout(() => { setTimeout(() => {
isAutoSearchMode() && switchAutoSearchMode(false); isAutoSearchMode() && switchAutoSearchMode(false);
@ -297,30 +352,58 @@ function search() {
data.datetimeFrom = (formData.value.datetime || ['', ''])[0]; data.datetimeFrom = (formData.value.datetime || ['', ''])[0];
data.datetimeTo = (formData.value.datetime || ['', ''])[1]; data.datetimeTo = (formData.value.datetime || ['', ''])[1];
data.user = formData.value.user; data.user = formData.value.user;
if (searchNearMode.value && newNearId.value) {
data.oldNearId = oldNearId.value;
data.newNearId = newNearId.value;
data.nearStoreName = newNearId.value ? nearStoreName.value : '';
}
// //
moreConditon.value = data; moreConditon.value = data;
$post(url, data, null, { 'Content-Type': 'application/x-www-form-urlencoded' }).then(rs => { $post(url, data, null, { 'Content-Type': 'application/x-www-form-urlencoded' }).then(rs => {
console.log(rs)
if (rs.success) { if (rs.success) {
const resultData = rs.result.data || []; const resultData = rs.result.data || [];
const pagesize = rs.result.pagesize - 0; const pagesize = rs.result.pagesize - 0;
tableData.value.splice(0, tableData.value.length); // nextTick tableData.value.splice(0, tableData.value.length); // nextTick
document.querySelector('.c-glc-table .el-scrollbar__wrap').scrollTop = 0; // !searchNearMode.value && (document.querySelector('.c-glc-table .el-scrollbar__wrap').scrollTop = 0); //
rs.result.laststorename && (lastStoreName.value = rs.result.laststorename); // rs.result.laststorename && (lastStoreName.value = rs.result.laststorename); //
maxMatchCount.value = rs.result.count; // maxMatchCount.value = rs.result.count; //
nextTick(() => { nextTick(() => {
resultData.forEach(item => { if (searchNearMode.value) {
tableData.value.push(item); //
item.system && !systemSet.has(item.system) && systemSet.add(item.system) && systemOptions.value.push({ value: item.system, label: item.system }); tableData.value.push(...resultData);
}); info.value = `日志总量 ${rs.result.total} 条,当前条件最多匹配 ${maxMatchCount.value} 条,正展示相邻 ${tableData.value.length - 1} 条,查询${rs.result.timemessage}`
if (resultData.length < pagesize) { nextTick(() => {
info.value = `日志总量 ${rs.result.total} 条,当前条件最多匹配 ${tableData.value.length} 条,正展示前 ${tableData.value.length} 条,查询${rs.result.timemessage}` const step = 30;
const down = oldNearId.value == 0 || newNearId.value <= oldNearId.value; //
let scrollTop = down ? (-7 * step) : (-15 * step); // 7;15
for (let i = 0; i < tableData.value.length; i++) {
if (newNearId.value == tableData.value[i].id) {
break;
}
scrollTop += step;
}
// scrollTop <= (step * 15) && (scrollTop = 0);
document.querySelector('.c-glc-table .el-scrollbar__wrap').scrollTop = scrollTop; //
// if (oldNearId.value > 0 && newNearId.value > oldNearId.value) {
// document.querySelector('.c-glc-table .el-scrollbar__wrap').scrollTop = 20000; //
// }
});
} else { } else {
info.value = `日志总量 ${rs.result.total} 条,当前条件最多匹配 ${maxMatchCount.value} 条,正展示前 ${tableData.value.length} 条,查询${rs.result.timemessage}` //
resultData.forEach(item => {
tableData.value.push(item);
item.system && !systemSet.has(item.system) && systemSet.add(item.system) && systemOptions.value.push({ value: item.system, label: item.system });
});
if (resultData.length < pagesize) {
info.value = `日志总量 ${rs.result.total} 条,当前条件最多匹配 ${tableData.value.length} 条,正展示前 ${tableData.value.length} 条,查询${rs.result.timemessage}`
} else {
info.value = `日志总量 ${rs.result.total} 条,当前条件最多匹配 ${maxMatchCount.value} 条,正展示前 ${tableData.value.length} 条,查询${rs.result.timemessage}`
}
} }
}); });
@ -334,11 +417,15 @@ function search() {
} }
function searchMore() { function searchMore() {
if (searchNearMode.value) {
return; //
}
if (tableData.value.length >= 5000) { if (tableData.value.length >= 5000) {
if (info.value.indexOf('请考虑') < 0) { if (info.value.indexOf('请考虑') < 0) {
info.value += ` (数据太多不再自动加载,请考虑添加条件)` info.value += ` (数据太多不再自动加载,请考虑添加条件)`
} }
return return;
} }
const url = `/v1/log/search`; const url = `/v1/log/search`;
@ -399,6 +486,14 @@ function fnDownload() {
</script> </script>
<style> <style>
.el-table--striped .el-table__body tr.el-table__row--striped.search-near-row td.el-table__cell {
background: lemonchiffon;
}
.el-table__body tr.el-table__row.search-near-row td.el-table__cell {
background: lemonchiffon;
}
.c-glc-table .el-popper.is-dark { .c-glc-table .el-popper.is-dark {
display: none; display: none;
} }