feat: 日志
This commit is contained in:
parent
2fe732f1df
commit
43b07c94f4
@ -66,7 +66,8 @@
|
|||||||
:activeStep="taskRunningInfo[runTask.id]?.activeStep || 0" />
|
:activeStep="taskRunningInfo[runTask.id]?.activeStep || 0" />
|
||||||
<div class="run-log-container">
|
<div class="run-log-container">
|
||||||
<div>执行日志</div>
|
<div>执行日志</div>
|
||||||
<taskLog :height="500" :list="taskLogList[runTask.id]" />
|
<!-- 传入任务定义 ID,task-log 会据此查询检测项并按检测项归类当前实例日志。 -->
|
||||||
|
<taskLog :height="460" :task-id="runTask.taskId" :list="taskLogList[runTask.id]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -154,7 +155,8 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog title="任务执行日志" v-model="logDialogVisible" width="800px" append-to-body>
|
<el-dialog title="任务执行日志" v-model="logDialogVisible" width="800px" append-to-body>
|
||||||
<taskLog v-loading="logDialogLoading" :list="logDialogData" :height="500" />
|
<!-- 历史弹窗也传入对应任务定义 ID,确保没有实时进度数据时仍能展示完整检测项列表。 -->
|
||||||
|
<taskLog v-loading="logDialogLoading" :task-id="logDialogTaskId" :list="logDialogData" :height="500" />
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog title="本次执行安排" v-model="startDialogVisible" width="820px" append-to-body>
|
<el-dialog title="本次执行安排" v-model="startDialogVisible" width="820px" append-to-body>
|
||||||
@ -590,11 +592,17 @@ const subscribeDebounced = debounce(async (sub) => {
|
|||||||
|
|
||||||
const logDialogVisible = ref(false)
|
const logDialogVisible = ref(false)
|
||||||
const logDialogData = ref([])
|
const logDialogData = ref([])
|
||||||
|
|
||||||
|
// 历史表格行里的 taskId 是任务定义 ID;日志组件使用它查询本次任务包含的全部检测项。
|
||||||
|
const logDialogTaskId = ref('')
|
||||||
const logDialogLoading = ref(false)
|
const logDialogLoading = ref(false)
|
||||||
let logDialogRequestId = 0
|
let logDialogRequestId = 0
|
||||||
const handleOpenLog = async (row) => {
|
const handleOpenLog = async (row) => {
|
||||||
const requestId = ++logDialogRequestId
|
const requestId = ++logDialogRequestId
|
||||||
logDialogVisible.value = true
|
logDialogVisible.value = true
|
||||||
|
|
||||||
|
// 在请求实例日志前先更新 taskId,让检测项请求与日志请求并行执行,减少弹窗等待时间。
|
||||||
|
logDialogTaskId.value = row.taskId
|
||||||
logDialogData.value = []
|
logDialogData.value = []
|
||||||
logDialogLoading.value = true
|
logDialogLoading.value = true
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,12 +1,443 @@
|
|||||||
<template>
|
<template>
|
||||||
<TaskExecutionLog :list="list" :height="height" />
|
<div class="task-log" :style="{ height: `${height}px` }">
|
||||||
|
<!-- 折叠状态下只保留检测项名称和状态两列,节点日志不会提前占用页面空间。 -->
|
||||||
|
<div class="task-log__heading">
|
||||||
|
<span>检测项</span>
|
||||||
|
<span>状态</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="loading" class="task-log__empty">检测项加载中...</div>
|
||||||
|
<div v-else-if="!groups.length" class="task-log__empty">暂无检测项</div>
|
||||||
|
<div v-else class="task-log__groups">
|
||||||
|
<section v-for="(group, index) in groups" :key="group.key" class="task-log__group">
|
||||||
|
<!-- 整行都可以点击;aria-expanded 同步当前展开状态,便于键盘和辅助技术识别。 -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="task-log__group-header"
|
||||||
|
:aria-expanded="isExpanded(group.key)"
|
||||||
|
@click="toggleGroup(group.key)"
|
||||||
|
>
|
||||||
|
<el-icon class="task-log__expand-icon" :class="{ 'is-expanded': isExpanded(group.key) }">
|
||||||
|
<ArrowRight />
|
||||||
|
</el-icon>
|
||||||
|
<span class="task-log__order">{{ String(index + 1).padStart(2, '0') }}</span>
|
||||||
|
<span class="task-log__name" :title="group.name">{{ group.name }}</span>
|
||||||
|
<el-tag class="task-log__status" :type="group.status.type" size="small">
|
||||||
|
{{ group.status.label }}
|
||||||
|
</el-tag>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- 展开后只把当前检测项已经归类的节点日志交给通用日志组件渲染。 -->
|
||||||
|
<div v-if="isExpanded(group.key)" class="task-log__details">
|
||||||
|
<div class="task-log__details-heading">
|
||||||
|
<span>节点执行日志</span>
|
||||||
|
<span class="task-log__details-count">{{ group.logs.length }} 条</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="group.nodeNames.length" class="task-log__nodes">
|
||||||
|
节点:{{ group.nodeNames.join('、') }}
|
||||||
|
</div>
|
||||||
|
<TaskExecutionLog
|
||||||
|
v-if="group.logs.length"
|
||||||
|
:list="group.logs"
|
||||||
|
:height="logHeight(group.logs)"
|
||||||
|
/>
|
||||||
|
<div v-else class="task-log__details-empty">该检测项暂未产生节点日志</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import { ArrowRight } from '@element-plus/icons-vue'
|
||||||
|
import { getTestCaseByTaskId } from '@/api/intelligenceTest/taskManage'
|
||||||
import TaskExecutionLog from '@/views/flow/components/TaskExecutionLog.vue'
|
import TaskExecutionLog from '@/views/flow/components/TaskExecutionLog.vue'
|
||||||
|
|
||||||
defineProps({
|
/**
|
||||||
|
* taskId: 任务定义 ID,用于查询该任务绑定的全部检测项。
|
||||||
|
* list: 当前任务实例产生的日志,检测项加载完成后会按 itemId/itemOrder 分组。
|
||||||
|
* items: 可选的外部检测项数据;传入后优先使用,可避免父组件已经查询时重复请求。
|
||||||
|
* height: 整个检测项日志区域的固定高度,内部超出部分滚动显示。
|
||||||
|
*/
|
||||||
|
const props = defineProps({
|
||||||
|
taskId: { type: [String, Number], default: '' },
|
||||||
list: { type: Array, default: () => [] },
|
list: { type: Array, default: () => [] },
|
||||||
|
items: { type: Array, default: () => [] },
|
||||||
height: { type: Number, default: 150 },
|
height: { type: Number, default: 150 },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// taskItems 保存根据 taskId 查询到的检测项;items prop 存在时不会使用这里的数据。
|
||||||
|
const taskItems = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 默认 Set 为空,因此所有检测项初始都是折叠状态,符合“默认只展示检测项和状态”的要求。
|
||||||
|
const expandedKeys = ref(new Set())
|
||||||
|
|
||||||
|
// 每次检测项请求递增版本号,只允许最后一次请求更新页面,避免快速切换任务时旧结果覆盖新任务。
|
||||||
|
let requestId = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接口中的 ID 可能是数字或字符串。统一转为字符串后再比较,避免 1 与 "1" 无法匹配。
|
||||||
|
* null、undefined 和空字符串都视为无效 ID,不参与日志分组。
|
||||||
|
*/
|
||||||
|
const valueOf = (value) => value === null || value === undefined || value === '' ? '' : String(value)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* actualResult、extraInfo 在不同日志来源中可能是对象,也可能是 JSON 字符串。
|
||||||
|
* 解析失败时返回空对象,让单条异常日志不会中断整个检测项列表的渲染。
|
||||||
|
*/
|
||||||
|
const parseObject = (value) => {
|
||||||
|
if (!value) return {}
|
||||||
|
if (typeof value === 'object') return value
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value)
|
||||||
|
return parsed && typeof parsed === 'object' ? parsed : {}
|
||||||
|
} catch {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标准化、过滤空值并去重,防止同一个 ID 的多个字段别名重复建立索引。
|
||||||
|
const uniqueValues = (values) => [...new Set(values.map(valueOf).filter(Boolean))]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成检测项所有可能的关联键。
|
||||||
|
* 当前业务主要使用 detectItemId,但保留 itemId/testCaseId/caseId/id,兼容历史数据和不同接口 DTO。
|
||||||
|
*/
|
||||||
|
const itemKeysOf = (item) => uniqueValues([
|
||||||
|
item?.detectItemId,
|
||||||
|
item?.itemId,
|
||||||
|
item?.testCaseId,
|
||||||
|
item?.caseId,
|
||||||
|
item?.id,
|
||||||
|
])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成日志所有可能的检测项关联键。
|
||||||
|
* 除日志顶层字段外,也读取 actualResult 和 extraInfo,兼容节点执行器把上下文放进扩展字段的情况。
|
||||||
|
*/
|
||||||
|
const logKeysOf = (log) => {
|
||||||
|
const actualResult = parseObject(log?.actualResult)
|
||||||
|
const extraInfo = parseObject(log?.extraInfo)
|
||||||
|
return uniqueValues([
|
||||||
|
log?.itemId,
|
||||||
|
log?.detectItemId,
|
||||||
|
log?.testItemId,
|
||||||
|
log?.testCaseId,
|
||||||
|
log?.caseId,
|
||||||
|
log?.item?.id,
|
||||||
|
log?.item?.detectItemId,
|
||||||
|
actualResult.itemId,
|
||||||
|
actualResult.detectItemId,
|
||||||
|
extraInfo.itemId,
|
||||||
|
extraInfo.detectItemId,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测项名称优先使用任务用例名称,其他字段作为不同后端版本的兼容回退。
|
||||||
|
const nameOf = (item) => item?.caseName || item?.detectName || item?.itemName || item?.name || '未命名检测项'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取执行节点名称,用于展开区域的节点汇总。
|
||||||
|
* 如果没有可读名称,最后使用 nodeId,至少能让用户定位到具体流程节点。
|
||||||
|
*/
|
||||||
|
const nodeNameOf = (log) => {
|
||||||
|
const actualResult = parseObject(log?.actualResult)
|
||||||
|
const extraInfo = parseObject(log?.extraInfo)
|
||||||
|
return log?.nodeName
|
||||||
|
|| log?.nodeTitle
|
||||||
|
|| log?.node?.name
|
||||||
|
|| log?.node?.nodeName
|
||||||
|
|| actualResult.nodeName
|
||||||
|
|| extraInfo.nodeName
|
||||||
|
|| log?.nodeId
|
||||||
|
|| ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态统一转为小写字符串,使数字枚举和英文状态值可以使用同一套判断逻辑。
|
||||||
|
const normalizedStatus = (value) => String(value ?? '').trim().toLowerCase()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据一个检测项下的全部日志推导检测项状态。
|
||||||
|
* 优先级为:失败 > 终止 > 暂停 > 完成 > 执行中;没有日志时固定为“未执行”。
|
||||||
|
* 完成状态同时识别状态字段、progress=100 以及 completed/finished 布尔标记。
|
||||||
|
*/
|
||||||
|
const statusOf = (logs) => {
|
||||||
|
if (!logs.length) return { label: '未执行', type: 'info' }
|
||||||
|
|
||||||
|
const statuses = logs.flatMap((log) => [
|
||||||
|
log?.itemStatus,
|
||||||
|
log?.executionStatus,
|
||||||
|
log?.runStatus,
|
||||||
|
log?.resultStatus,
|
||||||
|
log?.state,
|
||||||
|
log?.status,
|
||||||
|
].map(normalizedStatus))
|
||||||
|
|
||||||
|
if (statuses.some((status) => ['2', 'failed', 'failure', 'error', 'exception'].includes(status))) {
|
||||||
|
return { label: '执行失败', type: 'danger' }
|
||||||
|
}
|
||||||
|
if (statuses.some((status) => ['4', 'stopped', 'terminated', 'aborted'].includes(status))) {
|
||||||
|
return { label: '已终止', type: 'warning' }
|
||||||
|
}
|
||||||
|
if (statuses.some((status) => ['3', 'paused', 'pause'].includes(status))) {
|
||||||
|
return { label: '已暂停', type: 'warning' }
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
statuses.some((status) => ['0', 'success', 'succeeded', 'completed', 'complete', 'finished'].includes(status))
|
||||||
|
|| logs.some((log) => Number(log?.progress) >= 100 || log?.completed === true || log?.finished === true)
|
||||||
|
) {
|
||||||
|
return { label: '已完成', type: 'success' }
|
||||||
|
}
|
||||||
|
return { label: '执行中', type: 'primary' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 父组件明确传入 items 时直接使用,否则使用 taskId 请求到的检测项。
|
||||||
|
const taskItemsSource = computed(() => props.items.length ? props.items : taskItems.value)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将“检测项列表”和“任务实例日志”合并成页面需要的分组结构:
|
||||||
|
* 1. 先为每个检测项创建空分组,保证尚未执行的检测项也能显示;
|
||||||
|
* 2. 建立检测项 ID 到分组的索引,再把每条日志放入对应分组;
|
||||||
|
* 3. 最后计算节点名称集合和检测项状态。
|
||||||
|
*/
|
||||||
|
const groups = computed(() => {
|
||||||
|
// 每个检测项先建立空日志数组,所以初始状态能够由 statusOf 正确显示为“未执行”。
|
||||||
|
const result = taskItemsSource.value.map((item, index) => ({
|
||||||
|
key: itemKeysOf(item)[0] || `item-${index}`,
|
||||||
|
name: nameOf(item),
|
||||||
|
logs: [],
|
||||||
|
}))
|
||||||
|
const keyToGroup = new Map()
|
||||||
|
|
||||||
|
// 一个检测项可能同时存在 detectItemId、testCaseId 等多个 ID,全部映射到同一个分组。
|
||||||
|
taskItemsSource.value.forEach((item, index) => {
|
||||||
|
const group = result[index]
|
||||||
|
itemKeysOf(item).forEach((key) => {
|
||||||
|
if (!keyToGroup.has(key)) keyToGroup.set(key, group)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const unmatchedLogs = []
|
||||||
|
props.list.forEach((log) => {
|
||||||
|
const order = Number(log?.itemOrder)
|
||||||
|
|
||||||
|
// itemOrder 是任务内的 1 基序号,存在时可以准确处理同一检测项被重复编排的情况。
|
||||||
|
const orderedGroup = Number.isInteger(order) && order > 0 ? result[order - 1] : null
|
||||||
|
|
||||||
|
// 没有有效序号时,再使用日志 itemId 与检测项 detectItemId 等字段进行匹配。
|
||||||
|
const matchedGroup = orderedGroup || logKeysOf(log).map((key) => keyToGroup.get(key)).find(Boolean)
|
||||||
|
if (matchedGroup) matchedGroup.logs.push(log)
|
||||||
|
else unmatchedLogs.push(log)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 无法关联的日志不能静默丢失,统一放入兜底分组,便于发现后端字段或历史数据问题。
|
||||||
|
if (unmatchedLogs.length) {
|
||||||
|
result.push({
|
||||||
|
key: 'unmatched',
|
||||||
|
name: '未关联检测项',
|
||||||
|
logs: unmatchedLogs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.map((group) => ({
|
||||||
|
...group,
|
||||||
|
nodeNames: uniqueValues(group.logs.map(nodeNameOf)),
|
||||||
|
status: statusOf(group.logs),
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
const isExpanded = (key) => expandedKeys.value.has(key)
|
||||||
|
|
||||||
|
// 使用新 Set 替换旧值,确保 Vue 能可靠追踪集合变化并立即更新展开区域。
|
||||||
|
const toggleGroup = (key) => {
|
||||||
|
const next = new Set(expandedKeys.value)
|
||||||
|
if (next.has(key)) next.delete(key)
|
||||||
|
else next.add(key)
|
||||||
|
expandedKeys.value = next
|
||||||
|
}
|
||||||
|
|
||||||
|
// 展开日志设置最小、最大高度;少量日志不会留出大块空白,大量日志则在内部滚动。
|
||||||
|
const logHeight = (logs) => Math.max(120, Math.min(320, 78 + logs.length * 58))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据任务定义 ID 查询其绑定的检测项。
|
||||||
|
* 当 taskId 为空或父组件已经传入 items 时不发请求;请求结束前再次切换任务时,
|
||||||
|
* 通过 currentRequestId 与全局 requestId 比较,忽略已经过期的响应和 loading 状态更新。
|
||||||
|
*/
|
||||||
|
const loadTaskItems = async (taskId) => {
|
||||||
|
const currentRequestId = ++requestId
|
||||||
|
if (!taskId || props.items.length) {
|
||||||
|
taskItems.value = []
|
||||||
|
loading.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getTestCaseByTaskId(taskId)
|
||||||
|
if (currentRequestId === requestId) {
|
||||||
|
taskItems.value = Array.isArray(res?.data) ? res.data : Array.isArray(res?.rows) ? res.rows : []
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
if (currentRequestId === requestId) taskItems.value = []
|
||||||
|
} finally {
|
||||||
|
if (currentRequestId === requestId) loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// immediate 保证组件首次拿到 taskId 时立即查询,而不需要父组件额外触发初始化方法。
|
||||||
|
watch(() => props.taskId, loadTaskItems, { immediate: true })
|
||||||
|
|
||||||
|
// 检测项切换后清除已经不存在的展开 key,避免新任务意外继承旧任务的展开状态。
|
||||||
|
watch(() => groups.value.map((group) => group.key), (keys) => {
|
||||||
|
expandedKeys.value = new Set([...expandedKeys.value].filter((key) => keys.includes(key)))
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.task-log {
|
||||||
|
margin-top: 12px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__heading,
|
||||||
|
.task-log__group-header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 22px 34px minmax(0, 1fr) auto;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__heading {
|
||||||
|
padding: 10px 14px;
|
||||||
|
color: #98a2b3;
|
||||||
|
font-size: 12px;
|
||||||
|
border-bottom: 1px solid #f0f2f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__heading span:first-child {
|
||||||
|
grid-column: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__heading span:last-child {
|
||||||
|
grid-column: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__groups {
|
||||||
|
padding: 0 8px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__group {
|
||||||
|
border-bottom: 1px solid #f0f2f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__group:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__group-header {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 8px 6px;
|
||||||
|
color: #344054;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__group-header:hover {
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__expand-icon {
|
||||||
|
color: #98a2b3;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__expand-icon.is-expanded {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__order {
|
||||||
|
color: #98a2b3;
|
||||||
|
font-size: 12px;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__name {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__status {
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__details {
|
||||||
|
margin: 0 6px 10px 64px;
|
||||||
|
padding: 10px 12px 12px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-left: 2px solid #d0d5dd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__details-heading {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
color: #475467;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__details-count,
|
||||||
|
.task-log__nodes {
|
||||||
|
color: #98a2b3;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__nodes {
|
||||||
|
margin-top: 6px;
|
||||||
|
line-height: 18px;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__details :deep(.task-log) {
|
||||||
|
margin-top: 8px;
|
||||||
|
border-color: #eaecf0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__empty,
|
||||||
|
.task-log__details-empty {
|
||||||
|
padding: 28px 12px;
|
||||||
|
color: #98a2b3;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__details-empty {
|
||||||
|
padding: 18px 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.task-log__details {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-log__heading,
|
||||||
|
.task-log__group-header {
|
||||||
|
grid-template-columns: 22px 26px minmax(0, 1fr) auto;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user