feat: 按点位分类巡检任务日志
This commit is contained in:
parent
f26519a824
commit
15f82efb49
@ -65,7 +65,10 @@
|
|||||||
:activeStep="taskRunningInfo[selectedRunTask.id]?.activeStep || 0" />
|
:activeStep="taskRunningInfo[selectedRunTask.id]?.activeStep || 0" />
|
||||||
<div ref="taskLogContainerRef" class="run-log-container">
|
<div ref="taskLogContainerRef" class="run-log-container">
|
||||||
<div ref="taskLogTitleRef">执行日志</div>
|
<div ref="taskLogTitleRef">执行日志</div>
|
||||||
<taskLog :height="taskLogHeight" :list="taskLogList[selectedRunTask.id]" />
|
<taskLog :height="taskLogHeight" :task-id="selectedRunTask.taskId"
|
||||||
|
:task-status="selectedRunTask.status"
|
||||||
|
:points="pointList_runningTask[selectedRunTask.taskId]"
|
||||||
|
:list="taskLogList[selectedRunTask.id]" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-empty v-else description="暂无任务" />
|
<el-empty v-else description="暂无任务" />
|
||||||
@ -143,8 +146,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog title="任务执行日志" v-model="logDialogVisible" width="500px" append-to-body>
|
<el-dialog title="任务执行日志" v-model="logDialogVisible" width="800px" append-to-body>
|
||||||
<taskLog v-loading="logDialogLoading" :list="logDialogData" :height="300" />
|
<taskLog v-loading="logDialogLoading" :task-id="logDialogTaskId" :task-status="logDialogTaskStatus"
|
||||||
|
: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>
|
||||||
@ -610,11 +614,18 @@ const subscribeDebounced = debounce(async (sub) => {
|
|||||||
|
|
||||||
const logDialogVisible = ref(false)
|
const logDialogVisible = ref(false)
|
||||||
const logDialogData = ref([])
|
const logDialogData = ref([])
|
||||||
|
// 历史日志组件需要任务定义 ID 查询该任务绑定的点位,并用任务终态推导未单独记录状态的点位结果。
|
||||||
|
const logDialogTaskId = ref('')
|
||||||
|
const logDialogTaskStatus = ref(null)
|
||||||
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
|
||||||
|
|
||||||
|
// 先切换任务上下文,再请求日志;点位请求和日志请求可以并行,避免弹窗一直沿用上一次任务的数据。
|
||||||
|
logDialogTaskId.value = row.taskId
|
||||||
|
logDialogTaskStatus.value = row.status
|
||||||
logDialogData.value = []
|
logDialogData.value = []
|
||||||
logDialogLoading.value = true
|
logDialogLoading.value = true
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,12 +1,469 @@
|
|||||||
<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.fullName">{{ group.name }}</span>
|
||||||
|
<span class="task-log__statuses">
|
||||||
|
<el-tag :type="group.status.type" size="small">{{ group.status.label }}</el-tag>
|
||||||
|
</span>
|
||||||
|
</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 { getWaypointByTaskId } from '@/api/inspection/task'
|
||||||
import TaskExecutionLog from '@/views/flow/components/TaskExecutionLog.vue'
|
import TaskExecutionLog from '@/views/flow/components/TaskExecutionLog.vue'
|
||||||
|
|
||||||
defineProps({
|
/**
|
||||||
|
* taskId: 任务定义 ID。当父组件没有传入 points 时,用它查询任务所属的全部点位。
|
||||||
|
* taskStatus: 任务实例状态,用于在任务结束、失败或终止后推导点位的最终状态。
|
||||||
|
* list: 当前任务实例产生的日志;巡检日志接口中的 itemId 是点位的 detectItemId。
|
||||||
|
* points: 父组件已经查询到的点位列表,传入后优先使用,避免当前任务重复请求点位接口。
|
||||||
|
* height: 整个点位日志区域的高度,超出的点位和节点日志在区域内部滚动。
|
||||||
|
*/
|
||||||
|
const props = defineProps({
|
||||||
|
taskId: { type: [String, Number], default: '' },
|
||||||
|
taskStatus: { type: [String, Number], default: null },
|
||||||
list: { type: Array, default: () => [] },
|
list: { type: Array, default: () => [] },
|
||||||
|
points: { type: Array, default: () => [] },
|
||||||
height: { type: Number, default: 150 },
|
height: { type: Number, default: 150 },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const taskPoints = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
// 默认全部折叠,页面初始只显示点位和状态,不提前占用节点日志空间。
|
||||||
|
const expandedKeys = ref(new Set())
|
||||||
|
|
||||||
|
// 请求序号用于防止快速切换任务时,旧任务的点位响应覆盖新任务。
|
||||||
|
let requestId = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 后端 ID 可能以数字或字符串返回。统一转成字符串后再建立索引,避免 1 和 "1" 匹配失败。
|
||||||
|
* 空值不参与点位和日志归类,后续会进入“未关联点位”兜底分组,避免日志静默丢失。
|
||||||
|
*/
|
||||||
|
const valueOf = (value) => value === null || value === undefined || value === '' ? '' : String(value)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点位的唯一归属键。业务约定点位 detectItemId 与点位 ID 一一对应,日志 itemId 保存的是
|
||||||
|
* detectItemId,因此日志归类必须使用这个字段,而不是直接拿日志 itemId 去匹配点位 id。
|
||||||
|
*/
|
||||||
|
const pointKeyOf = (point) => valueOf(point?.detectItemId)
|
||||||
|
|
||||||
|
// 巡检日志接口中的 itemId 就是点位的 detectItemId,这是唯一的主归类字段。
|
||||||
|
const logKeyOf = (log) => valueOf(log?.itemId)
|
||||||
|
|
||||||
|
// 点位名称兼容不同接口 DTO,正常情况下使用 waypointName。
|
||||||
|
const nameOf = (point) => point?.waypointName || point?.name || point?.pointName || '未命名点位'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从日志中提取节点名称,用于展开区域的节点摘要。
|
||||||
|
* 兼容节点名称位于顶层、node 对象或 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 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueValues = (values) => [...new Set(values.map(valueOf).filter(Boolean))]
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断日志是否表示该点位已经执行完成。
|
||||||
|
* 普通节点完成只能说明流程继续推进,进度达到 100 或明确的成功状态才作为完成候选;
|
||||||
|
* 顺序执行任务中,如果后面的点位已经有日志,前面的点位也会由 statusOf 统一判定完成。
|
||||||
|
*/
|
||||||
|
const isPointCompletionLog = (log) => {
|
||||||
|
const statuses = [
|
||||||
|
log?.itemStatus,
|
||||||
|
log?.executionStatus,
|
||||||
|
log?.runStatus,
|
||||||
|
log?.resultStatus,
|
||||||
|
log?.state,
|
||||||
|
log?.status,
|
||||||
|
].map(normalizedStatus)
|
||||||
|
|
||||||
|
return statuses.some((status) => ['0', 'success', 'succeeded', 'completed', 'complete', 'finished'].includes(status))
|
||||||
|
|| Number(log?.progress) >= 100
|
||||||
|
|| log?.completed === true
|
||||||
|
|| log?.finished === true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据点位日志和任务实例状态计算点位状态。
|
||||||
|
* 优先级为:失败 > 终止 > 暂停 > 已完成 > 执行中;没有日志的点位固定为未执行。
|
||||||
|
* 对失败、终止和暂停任务,按顺序执行规则把最后一个产生日志的点位视为中断点。
|
||||||
|
*/
|
||||||
|
const statusOf = (logs, execution = {}) => {
|
||||||
|
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' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskStatus = execution.taskStatus === null || execution.taskStatus === undefined || execution.taskStatus === ''
|
||||||
|
? Number.NaN
|
||||||
|
: Number(execution.taskStatus)
|
||||||
|
|
||||||
|
// 任务整体完成时,所有实际产生过日志的点位都视为完成。
|
||||||
|
if (taskStatus === 0) return { label: '已完成', type: 'success' }
|
||||||
|
|
||||||
|
// 失败、终止或暂停时,最后一个有日志的点位是任务中断位置。
|
||||||
|
if (taskStatus === 2 || taskStatus === 4 || taskStatus === 3) {
|
||||||
|
if (execution.groupIndex < execution.lastLoggedIndex) {
|
||||||
|
return { label: '已完成', type: 'success' }
|
||||||
|
}
|
||||||
|
if (taskStatus === 2) return { label: '执行失败', type: 'danger' }
|
||||||
|
if (taskStatus === 4) return { label: '已终止', type: 'warning' }
|
||||||
|
return { label: '已暂停', type: 'warning' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后续点位已经产生日志,说明当前点位已经执行完毕。
|
||||||
|
if (execution.groupIndex < execution.lastLoggedIndex) {
|
||||||
|
return { label: '已完成', type: 'success' }
|
||||||
|
}
|
||||||
|
if (logs.some(isPointCompletionLog)) {
|
||||||
|
return { label: '已完成', type: 'success' }
|
||||||
|
}
|
||||||
|
return { label: '执行中', type: 'primary' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 父组件传入点位时优先使用;历史日志弹窗没有现成点位时再使用 taskId 请求。
|
||||||
|
const taskPointsSource = computed(() => props.points.length ? props.points : taskPoints.value)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将点位和日志合并成页面分组:
|
||||||
|
* 1. 先为每个点位创建空日志分组,保证没有执行过的点位也展示为未执行;
|
||||||
|
* 2. 使用 point.detectItemId 建立索引;
|
||||||
|
* 3. 使用 log.itemId 查找对应分组,确保一个点位的节点日志不会进入其他点位;
|
||||||
|
* 4. 最后计算节点摘要和点位执行状态。
|
||||||
|
*/
|
||||||
|
const groups = computed(() => {
|
||||||
|
const result = taskPointsSource.value.map((point, index) => {
|
||||||
|
const pointKey = pointKeyOf(point)
|
||||||
|
const name = nameOf(point)
|
||||||
|
return {
|
||||||
|
key: `point-${pointKey || index}`,
|
||||||
|
name,
|
||||||
|
fullName: name,
|
||||||
|
pointKey,
|
||||||
|
logs: [],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const keyToGroup = new Map()
|
||||||
|
result.forEach((group) => {
|
||||||
|
if (group.pointKey) keyToGroup.set(group.pointKey, group)
|
||||||
|
})
|
||||||
|
|
||||||
|
const unmatchedLogs = []
|
||||||
|
props.list.forEach((log) => {
|
||||||
|
const group = keyToGroup.get(logKeyOf(log))
|
||||||
|
if (group) group.logs.push(log)
|
||||||
|
else unmatchedLogs.push(log)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 后端数据异常或历史日志字段不完整时保留日志,避免用户看不到已经产生的执行记录。
|
||||||
|
// if (unmatchedLogs.length) {
|
||||||
|
// result.push({
|
||||||
|
// key: 'unmatched',
|
||||||
|
// name: '未关联点位',
|
||||||
|
// fullName: '未关联点位',
|
||||||
|
// pointKey: '',
|
||||||
|
// logs: unmatchedLogs,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
const lastLoggedIndex = result.reduce((lastIndex, group, index) => (
|
||||||
|
group.logs.length ? index : lastIndex
|
||||||
|
), -1)
|
||||||
|
|
||||||
|
return result.map((group, index) => ({
|
||||||
|
...group,
|
||||||
|
nodeNames: uniqueValues(group.logs.map(nodeNameOf)),
|
||||||
|
status: statusOf(group.logs, {
|
||||||
|
groupIndex: index,
|
||||||
|
lastLoggedIndex,
|
||||||
|
taskStatus: props.taskStatus,
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
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 查询点位列表。父组件传入 points 时不重复请求;请求版本号保证快速切换
|
||||||
|
* 历史任务时,旧任务响应不会覆盖当前任务的点位列表。
|
||||||
|
*/
|
||||||
|
const loadTaskPoints = async (taskId) => {
|
||||||
|
const currentRequestId = ++requestId
|
||||||
|
if (!taskId || props.points.length) {
|
||||||
|
taskPoints.value = []
|
||||||
|
loading.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await getWaypointByTaskId(taskId)
|
||||||
|
if (currentRequestId === requestId) {
|
||||||
|
taskPoints.value = Array.isArray(res?.data)
|
||||||
|
? res.data
|
||||||
|
: Array.isArray(res?.rows)
|
||||||
|
? res.rows
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
if (currentRequestId === requestId) taskPoints.value = []
|
||||||
|
} finally {
|
||||||
|
if (currentRequestId === requestId) loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// taskId 或外部点位列表变化时重新加载数据;计算分组会自动响应日志 list 的实时更新。
|
||||||
|
watch(
|
||||||
|
() => [props.taskId, props.points.length],
|
||||||
|
([taskId]) => loadTaskPoints(taskId),
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
// 切换任务后清理旧任务的展开项,避免新任务复用旧任务的展开状态。
|
||||||
|
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__statuses {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
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