状态监控界面修复+导出操作日志+导出异常记录

This commit is contained in:
2026-05-27 15:13:58 +08:00
parent eaed9e214a
commit 3f488d2e72
6 changed files with 327 additions and 28 deletions

View File

@@ -37,3 +37,12 @@ export function managelogbyid(id: string | number) {
method: 'get', method: 'get',
}) })
} }
export function managelogexport(params: any) {
return request({
url: '/manage-log/export',
method: 'get',
params,
responseType: 'blob',
})
}

View File

@@ -37,3 +37,12 @@ export function recordInfoupd(data: any) {
data, data,
}) })
} }
export function recordInfoexport(params: any) {
return request({
url: '/recordInfo/export',
method: 'get',
params,
responseType: 'blob',
})
}

View File

@@ -105,7 +105,7 @@
</el-menu-item> </el-menu-item>
<el-menu-item index="/status-monitor"> <el-menu-item index="/status-monitor">
<el-icon><component :is="getMenuIcon('监控')" /></el-icon> <el-icon><component :is="getMenuIcon('监控')" /></el-icon>
<span>状态监控界面</span> <span>状态监控</span>
</el-menu-item> </el-menu-item>
<el-menu-item v-if="hasPermission('tb:goodrecord')" index="/goods-info"> <el-menu-item v-if="hasPermission('tb:goodrecord')" index="/goods-info">
<el-icon><component :is="getMenuIcon('样品管理')" /></el-icon> <el-icon><component :is="getMenuIcon('样品管理')" /></el-icon>

View File

@@ -59,6 +59,10 @@
<el-button type="primary" @click="handleSearch">查询</el-button> <el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button> <el-button @click="resetSearch">重置</el-button>
</el-form-item> </el-form-item>
<el-form-item style="margin-left: 84px">
<el-button v-permission="'sys:managelog:excel'" type="success" :loading="exportLoading" @click="handleExport">导出日志</el-button>
</el-form-item>
</el-form> </el-form>
</div> </div>
</el-card> </el-card>
@@ -131,7 +135,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, reactive, ref } from 'vue' import { onMounted, reactive, ref } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { managelogbyid, manageloglist } from '@/api/system/manage-log' import { managelogbyid, managelogexport, manageloglist } from '@/api/system/manage-log'
import { userselect } from '@/api/system/user' import { userselect } from '@/api/system/user'
interface LogItem { interface LogItem {
@@ -153,6 +157,7 @@ interface UserOption {
} }
const loading = ref(false) const loading = ref(false)
const exportLoading = ref(false)
const tableData = ref<LogItem[]>([]) const tableData = ref<LogItem[]>([])
const total = ref(0) const total = ref(0)
const userOptions = ref<UserOption[]>([]) const userOptions = ref<UserOption[]>([])
@@ -236,6 +241,22 @@ const normalizeDateTimeParam = (value?: string | Date) => {
return raw return raw
} }
const buildQueryParams = () => {
const params: Record<string, any> = {
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
}
if (queryForm.logType) params.logType = queryForm.logType
if (queryForm.createId !== '' && queryForm.createId !== null) params.createId = queryForm.createId
if (queryForm.logWritetimeRange?.length === 2) {
params.logWritetimeStart = normalizeDateTimeParam(queryForm.logWritetimeRange[0])
params.logWritetimeEnd = normalizeDateTimeParam(queryForm.logWritetimeRange[1])
}
return params
}
const loadUserOptions = async () => { const loadUserOptions = async () => {
try { try {
const res: any = await userselect({}) const res: any = await userselect({})
@@ -272,19 +293,7 @@ const loadUserOptions = async () => {
const loadList = async () => { const loadList = async () => {
loading.value = true loading.value = true
try { try {
const params: any = { const res: any = await manageloglist(buildQueryParams())
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
}
if (queryForm.logType) params.logType = queryForm.logType
if (queryForm.createId !== '' && queryForm.createId !== null) params.createId = queryForm.createId
if (queryForm.logWritetimeRange?.length === 2) {
params.logWritetimeStart = normalizeDateTimeParam(queryForm.logWritetimeRange[0])
params.logWritetimeEnd = normalizeDateTimeParam(queryForm.logWritetimeRange[1])
}
const res: any = await manageloglist(params)
const data = res?.data ?? res ?? {} const data = res?.data ?? res ?? {}
const recordsFromData = const recordsFromData =
@@ -353,6 +362,45 @@ const openDetailDialog = async (row: LogItem) => {
} }
} }
const getFileNameFromDisposition = (contentDisposition?: string) => {
if (!contentDisposition) return ''
const utf8Match = contentDisposition.match(/filename\*=UTF-8''([^;]+)/i)
if (utf8Match?.[1]) return decodeURIComponent(utf8Match[1])
const standardMatch = contentDisposition.match(/filename=([^;]+)/i)
if (standardMatch?.[1]) {
return standardMatch[1].replace(/['"]/g, '').trim()
}
return ''
}
const downloadBlobFile = (blob: Blob, fileName: string) => {
const downloadUrl = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = downloadUrl
link.download = fileName
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(downloadUrl)
}
const handleExport = async () => {
exportLoading.value = true
try {
const res: any = await managelogexport(buildQueryParams())
const blob = res instanceof Blob ? res : new Blob([res as BlobPart], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const fileName = getFileNameFromDisposition((res as any)?.headers?.['content-disposition']) || '操作日志.xlsx'
downloadBlobFile(blob, fileName)
ElMessage.success('导出成功')
} catch (error) {
console.error('export log error', error)
ElMessage.error('导出失败')
} finally {
exportLoading.value = false
}
}
onMounted(async () => { onMounted(async () => {
await loadUserOptions() await loadUserOptions()
loadList() loadList()

View File

@@ -29,6 +29,9 @@
<el-button type="primary" @click="handleSearch">查询</el-button> <el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetSearch">重置</el-button> <el-button @click="resetSearch">重置</el-button>
</el-form-item> </el-form-item>
<el-form-item style="margin-left: 276px" >
<el-button v-permission="'tb:exceptionrecord:excel'" type="success" :loading="exportLoading" @click="handleExport">导出记录</el-button>
</el-form-item>
</el-form> </el-form>
</div> </div>
</el-card> </el-card>
@@ -173,7 +176,7 @@ import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { islandInfolist } from '@/api/tb/islandinfo' import { islandInfolist } from '@/api/tb/islandinfo'
import { devselect } from '@/api/tb/devinfo' import { devselect } from '@/api/tb/devinfo'
import { recordInfoupd, recordInfolist } from '@/api/tb/recordinfo' import { recordInfoexport, recordInfoupd, recordInfolist } from '@/api/tb/recordinfo'
import { recordDealadd, recordDealupd, recordDeallistByRecordId, recordDeallist } from '@/api/tb/recorddeal' import { recordDealadd, recordDealupd, recordDeallistByRecordId, recordDeallist } from '@/api/tb/recorddeal'
interface RecordInfoItem { interface RecordInfoItem {
@@ -210,6 +213,7 @@ interface ActionItem {
} }
const loading = ref(false) const loading = ref(false)
const exportLoading = ref(false)
const tableData = ref<RecordInfoItem[]>([]) const tableData = ref<RecordInfoItem[]>([])
const total = ref(0) const total = ref(0)
const detailVisible = ref(false) const detailVisible = ref(false)
@@ -304,14 +308,7 @@ const loadLookups = async () => {
const loadTableData = async () => { const loadTableData = async () => {
loading.value = true loading.value = true
try { try {
const params = { const res = await recordInfolist(buildListParams())
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
recordName: queryForm.recordName || undefined,
recordType: queryForm.recordType === '' ? undefined : queryForm.recordType,
recordStatus: queryForm.recordStatus === '' ? undefined : queryForm.recordStatus,
}
const res = await recordInfolist(params)
tableData.value = getListFromResponse(res) tableData.value = getListFromResponse(res)
total.value = getTotalFromResponse(res) total.value = getTotalFromResponse(res)
} catch (error: any) { } catch (error: any) {
@@ -393,6 +390,43 @@ const initDealDialog = async (row: RecordInfoItem) => {
} }
} }
const buildListParams = () => ({
pageNum: pagination.pageNum,
pageSize: pagination.pageSize,
recordName: queryForm.recordName || undefined,
recordType: queryForm.recordType === '' ? undefined : queryForm.recordType,
recordStatus: queryForm.recordStatus === '' ? undefined : queryForm.recordStatus,
})
const buildExportParams = () => ({
recordName: queryForm.recordName || undefined,
recordType: queryForm.recordType === '' ? undefined : queryForm.recordType,
recordStatus: queryForm.recordStatus === '' ? undefined : queryForm.recordStatus,
})
const getFileNameFromDisposition = (disposition?: string) => {
if (!disposition) return '异常记录.xlsx'
const match = disposition.match(/filename\*=UTF-8''([^;]+)|filename=\"?([^\";]+)\"?/i)
const encodedName = match?.[1] || match?.[2]
if (!encodedName) return '异常记录.xlsx'
try {
return decodeURIComponent(encodedName)
} catch {
return encodedName
}
}
const downloadBlob = (blob: Blob, fileName: string) => {
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = fileName
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
}
const handleSearch = () => { const handleSearch = () => {
pagination.pageNum = 1 pagination.pageNum = 1
loadTableData() loadTableData()
@@ -417,6 +451,21 @@ const handleSizeChange = (size: number) => {
loadTableData() loadTableData()
} }
const handleExport = async () => {
exportLoading.value = true
try {
const res: any = await recordInfoexport(buildExportParams())
const blob = new Blob([res?.data ?? res], { type: 'application/vnd.ms-excel' })
const disposition = res?.headers?.['content-disposition'] || res?.headers?.['Content-Disposition']
downloadBlob(blob, getFileNameFromDisposition(disposition))
ElMessage.success('导出成功')
} catch (error: any) {
ElMessage.error(error?.message || '导出异常记录失败')
} finally {
exportLoading.value = false
}
}
const handleView = (row: RecordInfoItem) => { const handleView = (row: RecordInfoItem) => {
detailData.value = { ...row } detailData.value = { ...row }
detailVisible.value = true detailVisible.value = true

View File

@@ -3,7 +3,7 @@
<el-breadcrumb separator="/" class="page-breadcrumb"> <el-breadcrumb separator="/" class="page-breadcrumb">
<el-breadcrumb-item><router-link to="/home">首页</router-link></el-breadcrumb-item> <el-breadcrumb-item><router-link to="/home">首页</router-link></el-breadcrumb-item>
<el-breadcrumb-item>业务流程控制</el-breadcrumb-item> <el-breadcrumb-item>业务流程控制</el-breadcrumb-item>
<el-breadcrumb-item>状态监控界面</el-breadcrumb-item> <el-breadcrumb-item>状态监控</el-breadcrumb-item>
</el-breadcrumb> </el-breadcrumb>
<div class="monitor-layout"> <div class="monitor-layout">
@@ -36,6 +36,7 @@
<el-tag class="island-status-tag" :type="getStatusTag(getIslandStatus(item))" size="small" effect="light"> <el-tag class="island-status-tag" :type="getStatusTag(getIslandStatus(item))" size="small" effect="light">
{{ getStatusText(getIslandStatus(item)) }} {{ getStatusText(getIslandStatus(item)) }}
</el-tag> </el-tag>
<div v-if="getIslandRecordCount(item) > 0" class="record-dot island-list-record-dot">{{ getIslandRecordCount(item) }}</div>
<div class="action-badges" v-if="getIslandActionList(item).length > 0"> <div class="action-badges" v-if="getIslandActionList(item).length > 0">
<div <div
@@ -83,6 +84,7 @@
<el-tag :type="getStatusTag(getIslandStatus(selectedIsland))" effect="light" size="small"> <el-tag :type="getStatusTag(getIslandStatus(selectedIsland))" effect="light" size="small">
{{ getStatusText(getIslandStatus(selectedIsland)) }} {{ getStatusText(getIslandStatus(selectedIsland)) }}
</el-tag> </el-tag>
<div v-if="getIslandRecordCount(selectedIsland) > 0" class="record-dot island-detail-record-dot">{{ getIslandRecordCount(selectedIsland) }}</div>
</div> </div>
</div> </div>
@@ -104,6 +106,7 @@
</div> </div>
<el-tag :type="getStatusTag(getActionStatus(group.action))" effect="light" size="small"> <el-tag :type="getStatusTag(getActionStatus(group.action))" effect="light" size="small">
{{ getStatusText(getActionStatus(group.action)) }}</el-tag> {{ getStatusText(getActionStatus(group.action)) }}</el-tag>
<div v-if="getActionRecordCount(group.action) > 0" class="record-dot action-record-dot">{{ getActionRecordCount(group.action) }}</div>
</div> </div>
<div class="arrow-down" aria-hidden="true"> <div class="arrow-down" aria-hidden="true">
@@ -145,6 +148,31 @@
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="功能岛描述">{{ getIslandDesc(dialogIsland) }}</el-descriptions-item> <el-descriptions-item label="功能岛描述">{{ getIslandDesc(dialogIsland) }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
<div class="record-section">
<div class="record-section-title">待处理异常记录</div>
<div v-if="dialogIslandRecords.length === 0" class="record-empty-text">当前功能岛暂无待处理异常记录</div>
<el-scrollbar v-else max-height="220px">
<div class="record-list">
<div v-for="record in dialogIslandRecords" :key="record.id" class="record-item">
<div class="record-item-header">
<span class="record-item-name">{{ record.recordName || '未命名异常' }}</span>
<el-tag type="warning" size="small">待处理</el-tag>
</div>
<div class="record-item-content">{{ record.recordContent || '暂无异常内容' }}</div>
</div>
</div>
</el-scrollbar>
</div>
<template #footer>
<div class="dialog-footer-actions">
<el-button @click="islandDialogVisible = false">关闭</el-button>
<el-button v-if="dialogIslandRecords.length > 0" type="warning" @click="goToExceptionRecords">
异常处理
</el-button>
</div>
</template>
</el-dialog> </el-dialog>
<el-dialog v-model="actionDialogVisible" title="动作状态详情" width="640px" destroy-on-close> <el-dialog v-model="actionDialogVisible" title="动作状态详情" width="640px" destroy-on-close>
@@ -158,17 +186,44 @@
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="动作描述">{{ getActionDesc(dialogAction) }}</el-descriptions-item> <el-descriptions-item label="动作描述">{{ getActionDesc(dialogAction) }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
<div class="record-section">
<div class="record-section-title">待处理异常记录</div>
<div v-if="dialogActionRecords.length === 0" class="record-empty-text">当前动作暂无待处理异常记录</div>
<el-scrollbar v-else max-height="220px">
<div class="record-list">
<div v-for="record in dialogActionRecords" :key="record.id" class="record-item">
<div class="record-item-header">
<span class="record-item-name">{{ record.recordName || '未命名异常' }}</span>
<el-tag type="warning" size="small">待处理</el-tag>
</div>
<div class="record-item-content">{{ record.recordContent || '暂无异常内容' }}</div>
</div>
</div>
</el-scrollbar>
</div>
<template #footer>
<div class="dialog-footer-actions">
<el-button @click="actionDialogVisible = false">关闭</el-button>
<el-button v-if="dialogActionRecords.length > 0" type="warning" @click="goToExceptionRecords">
异常处理
</el-button>
</div>
</template>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { Bottom } from '@element-plus/icons-vue' import { Bottom } from '@element-plus/icons-vue'
import { islandInfolist } from '@/api/tb/islandinfo' import { islandInfolist } from '@/api/tb/islandinfo'
import { devInfolist } from '@/api/tb/devinfo' import { devInfolist } from '@/api/tb/devinfo'
import { devparamselect } from '@/api/tb/devparam' import { devparamselect } from '@/api/tb/devparam'
import { recordInfolist } from '@/api/tb/recordinfo'
interface AnyItem { interface AnyItem {
id?: string | number id?: string | number
@@ -193,14 +248,19 @@ interface AnyItem {
status?: number | string status?: number | string
state?: number | string state?: number | string
onlineStatus?: number | string onlineStatus?: number | string
recordName?: string
recordContent?: string
recordStatus?: boolean | number | string
[key: string]: any [key: string]: any
} }
const router = useRouter()
const islandLoading = ref(false) const islandLoading = ref(false)
const detailLoading = ref(false) const detailLoading = ref(false)
const islands = ref<AnyItem[]>([]) const islands = ref<AnyItem[]>([])
const actions = ref<AnyItem[]>([]) const actions = ref<AnyItem[]>([])
const params = ref<AnyItem[]>([]) const params = ref<AnyItem[]>([])
const abnormalRecords = ref<AnyItem[]>([])
const selectedIslandId = ref<string | number | ''>('') const selectedIslandId = ref<string | number | ''>('')
const selectedActionId = ref<string | number | ''>('') const selectedActionId = ref<string | number | ''>('')
@@ -218,6 +278,11 @@ const getResponseList = (res: any) => {
return data.records || data.list || data.rows || data.items || data.data || [] return data.records || data.list || data.rows || data.items || data.data || []
} }
const isUnprocessedRecord = (item: AnyItem) => {
const value = item?.recordStatus ?? item?.status ?? item?.dealStatus
return value === false || value === 0 || value === '0' || value === '待处理' || value === 'false'
}
const getItemId = (item: AnyItem) => item?.id ?? item?.islandId ?? item?.devId ?? item?.paramId ?? '' const getItemId = (item: AnyItem) => item?.id ?? item?.islandId ?? item?.devId ?? item?.paramId ?? ''
const getIslandName = (item?: AnyItem | null) => item?.islandName || item?.name || '未命名功能岛' const getIslandName = (item?: AnyItem | null) => item?.islandName || item?.name || '未命名功能岛'
const getIslandDesc = (item?: AnyItem | null) => item?.desc || item?.islandDesc || '暂无描述' const getIslandDesc = (item?: AnyItem | null) => item?.desc || item?.islandDesc || '暂无描述'
@@ -269,15 +334,17 @@ const loadData = async () => {
islandLoading.value = true islandLoading.value = true
detailLoading.value = true detailLoading.value = true
try { try {
const [islandRes, actionRes, paramRes] = await Promise.all([ const [islandRes, actionRes, paramRes, recordRes] = await Promise.all([
islandInfolist({ pageNum: 1, pageSize: 1000 }), islandInfolist({ pageNum: 1, pageSize: 1000 }),
devInfolist({ pageNum: 1, pageSize: 1000, devModelNot: 'PLC' }), devInfolist({ pageNum: 1, pageSize: 1000, devModelNot: 'PLC' }),
devparamselect({ pageNum: 1, pageSize: 5000 }), devparamselect({ pageNum: 1, pageSize: 5000 }),
recordInfolist({ pageNum: 1, pageSize: 5000 }),
]) ])
islands.value = getResponseList(islandRes) islands.value = getResponseList(islandRes)
actions.value = getResponseList(actionRes) actions.value = getResponseList(actionRes)
params.value = getResponseList(paramRes) params.value = getResponseList(paramRes)
abnormalRecords.value = getResponseList(recordRes).filter((item: AnyItem) => isUnprocessedRecord(item))
const firstIsland = islands.value[0] const firstIsland = islands.value[0]
if (firstIsland) { if (firstIsland) {
@@ -322,6 +389,21 @@ const getParamsForAction = (action: AnyItem) => {
}) })
} }
const getAbnormalRecordsByIsland = (item?: AnyItem | null) => {
const islandId = String(getItemId(item ?? {}))
if (!islandId) return []
return abnormalRecords.value.filter(record => String(record.islandId ?? record.functionIslandId ?? record.island_id ?? '') === islandId)
}
const getAbnormalRecordsByAction = (item?: AnyItem | null) => {
const actionId = String(getItemId(item ?? {}))
if (!actionId) return []
return abnormalRecords.value.filter(record => String(record.devId ?? record.actionId ?? record.dev_id ?? '') === actionId)
}
const getIslandRecordCount = (item?: AnyItem | null) => getAbnormalRecordsByIsland(item).length
const getActionRecordCount = (item?: AnyItem | null) => getAbnormalRecordsByAction(item).length
const openIslandDialog = (item: AnyItem) => { const openIslandDialog = (item: AnyItem) => {
dialogIsland.value = item dialogIsland.value = item
activeDialogType.value = 'island' activeDialogType.value = 'island'
@@ -335,11 +417,25 @@ const openActionDialog = (item: AnyItem) => {
actionDialogVisible.value = true actionDialogVisible.value = true
} }
const goToExceptionRecords = () => {
const query: Record<string, string> = {}
if (activeDialogType.value === 'island' && dialogIsland.value) {
query.islandId = String(getItemId(dialogIsland.value))
}
if (activeDialogType.value === 'action' && dialogAction.value) {
query.devId = String(getItemId(dialogAction.value))
}
router.push({ path: '/record-info', query })
}
const actionsToRender = computed(() => getIslandRelatedActions(selectedIslandId.value).map(action => ({ const actionsToRender = computed(() => getIslandRelatedActions(selectedIslandId.value).map(action => ({
action, action,
params: getParamsForAction(action), params: getParamsForAction(action),
}))) })))
const dialogIslandRecords = computed(() => getAbnormalRecordsByIsland(dialogIsland.value))
const dialogActionRecords = computed(() => getAbnormalRecordsByAction(dialogAction.value))
onMounted(loadData) onMounted(loadData)
</script> </script>
@@ -427,6 +523,37 @@ onMounted(loadData)
right: 12px; right: 12px;
} }
.record-dot {
position: absolute;
min-width: 18px;
height: 18px;
padding: 0 5px;
border-radius: 999px;
background: #f56c6c;
color: #fff;
font-size: 12px;
font-weight: 700;
line-height: 18px;
text-align: center;
box-shadow: 0 0 0 2px #fff;
pointer-events: none;
}
.action-record-dot {
top: 10px;
right: 10px;
}
.island-list-record-dot {
bottom: 10px;
right: 10px;
}
.island-detail-record-dot {
top: 10px;
right: 10px;
}
.island-card-main { .island-card-main {
padding-right: 80px; padding-right: 80px;
} }
@@ -622,6 +749,63 @@ onMounted(loadData)
color: var(--el-text-color-secondary); color: var(--el-text-color-secondary);
} }
.record-section {
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid #ebeef5;
}
.record-section-title {
margin-bottom: 10px;
font-size: 14px;
font-weight: 700;
color: var(--el-text-color-primary);
}
.record-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.record-empty-text {
color: var(--el-text-color-secondary);
font-size: 13px;
line-height: 1.6;
}
.record-item {
padding: 12px 14px;
border-radius: 10px;
background: #fafcff;
border: 1px solid #ebeef5;
}
.record-item-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 6px;
}
.record-item-name {
font-weight: 600;
color: var(--el-text-color-primary);
}
.record-item-content {
font-size: 12px;
color: var(--el-text-color-secondary);
line-height: 1.5;
}
.dialog-footer-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
}
@media (min-width: 1201px) { @media (min-width: 1201px) {
.actions-row { .actions-row {
grid-template-columns: repeat(var(--actions-count), minmax(340px, 1fr)); grid-template-columns: repeat(var(--actions-count), minmax(340px, 1fr));