优化小车卡片界面和车队编队的功能,优化首页菜单的快捷入口功能
This commit is contained in:
@@ -44,22 +44,44 @@
|
||||
<span>智能调度 · 一站式平台</span>
|
||||
</div>
|
||||
<h1 class="hero-title">迷毂智能调度平台</h1>
|
||||
<p class="hero-desc">以下是系统快捷入口,也可以通过点击「添加」功能进行调整</p>
|
||||
<p class="hero-desc">长按图标拖动可交换位置,或点击「添加」新增入口</p>
|
||||
|
||||
<div class="quick-grid">
|
||||
<button
|
||||
v-for="(item, idx) in quickEntries"
|
||||
:key="item.key"
|
||||
class="quick-item"
|
||||
:class="{ primary: item.primary }"
|
||||
:style="{ animationDelay: `${idx * 60}ms` }"
|
||||
:title="item.hint ?? item.label"
|
||||
@click="onQuickClick(item)">
|
||||
<span class="quick-icon">
|
||||
<el-icon :size="22"><component :is="item.icon" /></el-icon>
|
||||
</span>
|
||||
<span class="quick-label">{{ item.label }}</span>
|
||||
</button>
|
||||
<div class="quick-grid" :class="{ 'is-dragging': dragKey }">
|
||||
<div
|
||||
v-for="(row, rowIdx) in quickEntryRowList"
|
||||
:key="rowIdx"
|
||||
class="quick-row"
|
||||
>
|
||||
<button
|
||||
v-for="(item, idx) in row"
|
||||
:key="item.key"
|
||||
type="button"
|
||||
class="quick-item"
|
||||
:class="{
|
||||
primary: item.primary,
|
||||
'is-add': item.key === 'add',
|
||||
'is-dragging-source': dragKey === item.key,
|
||||
'is-drag-hover-target': hoverTargetKey === item.key
|
||||
}"
|
||||
:data-quick-key="item.key"
|
||||
:style="{ animationDelay: `${(rowIdx === 0 ? 0 : quickEntryRows.top.length) + idx * 60}ms` }"
|
||||
:title="quickItemTitle(item)"
|
||||
@pointerdown="onQuickPointerDown(item, $event)"
|
||||
@click="onQuickClick(item)"
|
||||
>
|
||||
<span
|
||||
v-if="item.key !== 'add'"
|
||||
class="quick-item-remove"
|
||||
title="移除"
|
||||
@pointerdown.stop
|
||||
@click.stop="onRemoveQuick(item.key)"
|
||||
>×</span>
|
||||
<span class="quick-icon">
|
||||
<el-icon :size="22"><component :is="item.icon" /></el-icon>
|
||||
</span>
|
||||
<span class="quick-label">{{ item.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -334,18 +356,41 @@
|
||||
</el-card>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<QuickEntryPickerDialog
|
||||
v-model="pickerOpen"
|
||||
:items="pickerCatalog"
|
||||
:pinned-keys="pinnedKeys"
|
||||
:can-add="canAddMore"
|
||||
@pick="onAddQuick" />
|
||||
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="dragKey && dragGhostItem"
|
||||
class="quick-drag-ghost"
|
||||
:style="{ left: `${ghostPos.x}px`, top: `${ghostPos.y}px` }"
|
||||
>
|
||||
<span class="quick-icon">
|
||||
<el-icon :size="22"><component :is="dragGhostItem.icon" /></el-icon>
|
||||
</span>
|
||||
<span class="quick-label">{{ dragGhostItem.label }}</span>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import * as echarts from 'echarts'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
Avatar, Bell, Box, Coordinate, Cpu, Lightning,
|
||||
List, Operation, PieChart, Plus, Setting,
|
||||
Tools, TrendCharts, Van, Warning
|
||||
Bell, Box, Coordinate, Cpu, Lightning,
|
||||
Operation, PieChart, Plus,
|
||||
TrendCharts, Van
|
||||
} from '@element-plus/icons-vue'
|
||||
import QuickEntryPickerDialog from '@/components/dashboard/QuickEntryPickerDialog.vue'
|
||||
import { useDashboardQuickEntries } from '@/composables/useDashboardQuickEntries'
|
||||
import { useQuickEntryDragSwap } from '@/composables/useQuickEntryDragSwap'
|
||||
import { listCars, listMissions, listSites, listTracks } from '@/api/projection'
|
||||
import type { Site, Track } from '@/types/map'
|
||||
import type { Car } from '@/types/car'
|
||||
@@ -353,6 +398,112 @@ import type { Mission } from '@/types/mission'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const {
|
||||
resolvedEntries,
|
||||
pickerCatalog,
|
||||
pinnedKeys,
|
||||
pickerOpen,
|
||||
canAddMore,
|
||||
openPicker,
|
||||
addKey,
|
||||
removeKey,
|
||||
swapKeys
|
||||
} = useDashboardQuickEntries()
|
||||
|
||||
interface QuickTile {
|
||||
key: string
|
||||
label: string
|
||||
icon: unknown
|
||||
hint?: string
|
||||
primary?: boolean
|
||||
path?: string
|
||||
}
|
||||
|
||||
const displayQuickEntries = computed<QuickTile[]>(() => {
|
||||
const items: QuickTile[] = resolvedEntries.value.map((e) => ({
|
||||
key: e.key,
|
||||
label: e.label,
|
||||
icon: e.icon,
|
||||
hint: e.hint,
|
||||
primary: e.primary,
|
||||
path: e.path
|
||||
}))
|
||||
if (canAddMore.value) {
|
||||
items.push({
|
||||
key: 'add',
|
||||
label: '添加',
|
||||
icon: Plus,
|
||||
hint: '从菜单添加快捷入口'
|
||||
})
|
||||
}
|
||||
return items
|
||||
})
|
||||
|
||||
/** 上下两行均衡分布:5 个 → 上 3 / 下 2,6 个 → 上 3 / 下 3 */
|
||||
const quickEntryRows = computed(() => {
|
||||
const all = displayQuickEntries.value
|
||||
const topCount = Math.ceil(all.length / 2)
|
||||
return {
|
||||
top: all.slice(0, topCount),
|
||||
bottom: all.slice(topCount)
|
||||
}
|
||||
})
|
||||
|
||||
const quickEntryRowList = computed(() => {
|
||||
const { top, bottom } = quickEntryRows.value
|
||||
return bottom.length ? [top, bottom] : [top]
|
||||
})
|
||||
|
||||
const {
|
||||
dragKey,
|
||||
hoverTargetKey,
|
||||
ghostPos,
|
||||
onPointerDown: onQuickPointerDown,
|
||||
shouldSuppressClick
|
||||
} = useQuickEntryDragSwap(async (from, to) => {
|
||||
await swapKeys(from, to)
|
||||
ElMessage.success('已交换位置')
|
||||
})
|
||||
|
||||
const dragGhostItem = computed(() => {
|
||||
if (!dragKey.value) return null
|
||||
return displayQuickEntries.value.find((i) => i.key === dragKey.value) ?? null
|
||||
})
|
||||
|
||||
function quickItemTitle(item: QuickTile): string {
|
||||
if (item.key === 'add') return item.hint ?? item.label
|
||||
if (dragKey.value === item.key) return '拖动到目标图标上松开以交换位置'
|
||||
return item.hint ?? item.label
|
||||
}
|
||||
|
||||
async function onQuickClick(item: QuickTile) {
|
||||
if (shouldSuppressClick()) return
|
||||
|
||||
if (item.key === 'add') {
|
||||
openPicker()
|
||||
return
|
||||
}
|
||||
|
||||
if (item.path) router.push(item.path)
|
||||
}
|
||||
|
||||
async function onAddQuick(key: string) {
|
||||
await addKey(key)
|
||||
}
|
||||
|
||||
async function onRemoveQuick(key: string) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定从快捷入口移除此项?', '移除快捷入口', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '移除',
|
||||
cancelButtonText: '取消'
|
||||
})
|
||||
await removeKey(key)
|
||||
} catch {
|
||||
/* cancel */
|
||||
}
|
||||
}
|
||||
|
||||
const sites = ref<Site[]>([])
|
||||
const tracks = ref<Track[]>([])
|
||||
const cars = ref<Car[]>([])
|
||||
@@ -388,23 +539,6 @@ const activeMissionCount = computed(() =>
|
||||
missions.value.filter((m) => m.status === 'running' || m.status === 'assigned').length
|
||||
)
|
||||
|
||||
// ─── 快捷入口 ───
|
||||
interface QuickEntry { key: string; label: string; icon: unknown; hint?: string; primary?: boolean; to?: string; action?: () => void }
|
||||
const quickEntries = computed<QuickEntry[]>(() => [
|
||||
{ key: 'platform-config', label: '平台配置', icon: Setting, primary: true, hint: '进入地图编辑 / 平台搭建(map-editor)', to: '/admin/map-editor' },
|
||||
{ key: 'mission', label: '任务编排', icon: Operation, to: '/admin/task-templates' },
|
||||
{ key: 'cars', label: 'AGV 配置', icon: Van, to: '/admin/cars' },
|
||||
{ key: 'auth', label: '权限配置', icon: Avatar, to: '/admin/config/system-center?tab=auth' },
|
||||
{ key: 'system', label: '系统配置', icon: Tools, to: '/admin/config/system-center?tab=system' },
|
||||
{ key: 'ops', label: '异常处理', icon: Warning, to: '/admin/config/ops-center?tab=ops' },
|
||||
{ key: 'tasks', label: '任务管理', icon: List, to: '/admin/config/strategy?tab=task' },
|
||||
{ key: 'add', label: '添加', icon: Plus, hint: '自定义快捷入口(待实现)', action: () => { ElMessage.info('自定义快捷入口 — 即将在 v1.8 中开放') } }
|
||||
])
|
||||
function onQuickClick(item: QuickEntry) {
|
||||
if (item.action) item.action()
|
||||
else if (item.to) router.push(item.to)
|
||||
}
|
||||
|
||||
// ─── KPI(每张卡片含 trend + sparkline) ───
|
||||
type KpiTone = 'info' | 'success' | 'warning' | 'danger' | 'idle'
|
||||
interface KpiTrend { dir: 'up' | 'down' | 'flat'; arrow: '↑' | '↓' | '→'; text: string }
|
||||
@@ -830,12 +964,19 @@ onUnmounted(() => {
|
||||
letter-spacing: 0.8px;
|
||||
}
|
||||
|
||||
/* ───── 快捷入口栅格(按截图 8 项,两行 4 列) ───── */
|
||||
/* ───── 快捷入口:上下两行均衡左对齐(最多 16 项 + 添加) ───── */
|
||||
.quick-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 14px;
|
||||
max-width: 1080px;
|
||||
}
|
||||
.quick-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
gap: 14px 18px;
|
||||
max-width: 540px;
|
||||
}
|
||||
.quick-item {
|
||||
appearance: none;
|
||||
@@ -843,6 +984,8 @@ onUnmounted(() => {
|
||||
border: 1px solid rgba(var(--mg-accent-rgb), 0.35);
|
||||
border-radius: 14px;
|
||||
padding: 14px 10px 12px;
|
||||
width: 118px;
|
||||
flex: 0 0 auto;
|
||||
display: flex; flex-direction: column; align-items: center; gap: 8px;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
@@ -854,6 +997,84 @@ onUnmounted(() => {
|
||||
0 4px 12px rgba(0, 0, 0, 0.25),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.06) inset;
|
||||
}
|
||||
.quick-item-remove {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 8px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity .2s, background .2s;
|
||||
z-index: 2;
|
||||
}
|
||||
.quick-item:hover .quick-item-remove { opacity: 1; }
|
||||
.quick-item-remove:hover {
|
||||
background: rgba(var(--mg-status-danger-rgb), 0.75);
|
||||
color: #fff;
|
||||
}
|
||||
.quick-item.is-add .quick-item-remove { display: none; }
|
||||
.quick-grid.is-dragging {
|
||||
user-select: none;
|
||||
}
|
||||
.quick-grid.is-dragging .quick-item:not(.is-add) {
|
||||
cursor: grabbing;
|
||||
}
|
||||
.quick-item.is-dragging-source {
|
||||
opacity: 0.35;
|
||||
transform: scale(0.96);
|
||||
}
|
||||
.quick-item.is-drag-hover-target {
|
||||
border-color: rgba(var(--mg-accent-rgb), 0.95);
|
||||
background: rgba(var(--mg-primary-rgb), 0.35);
|
||||
transform: translateY(-2px) scale(1.05);
|
||||
box-shadow:
|
||||
0 0 0 2px rgba(var(--mg-accent-rgb), 0.65),
|
||||
0 12px 28px rgba(var(--mg-primary-rgb), 0.45);
|
||||
}
|
||||
.quick-drag-ghost {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 118px;
|
||||
padding: 14px 10px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(var(--mg-accent-rgb), 0.85);
|
||||
background: rgba(var(--mg-primary-rgb), 0.92);
|
||||
color: #fff;
|
||||
box-shadow:
|
||||
0 16px 40px rgba(0, 0, 0, 0.45),
|
||||
0 0 0 2px rgba(var(--mg-accent-rgb), 0.5);
|
||||
}
|
||||
.quick-drag-ghost .quick-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 12px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
.quick-drag-ghost .quick-label {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
}
|
||||
@keyframes quick-in {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
@@ -898,11 +1119,19 @@ onUnmounted(() => {
|
||||
0 0 0 1px rgba(255, 255, 255, 0.35) inset;
|
||||
}
|
||||
.quick-label {
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 1.35;
|
||||
text-align: center;
|
||||
letter-spacing: 1.2px;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.quick-item.primary {
|
||||
border-color: rgba(var(--mg-accent-rgb), 0.75);
|
||||
@@ -1551,8 +1780,12 @@ onUnmounted(() => {
|
||||
.bento-agv { grid-column: span 12; }
|
||||
.bento-ratio { grid-column: span 12; }
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.quick-item { width: 104px; }
|
||||
.quick-row { gap: 10px 12px; flex-wrap: wrap; max-width: 100%; }
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.quick-grid { grid-template-columns: repeat(2, 1fr); }
|
||||
.quick-item { width: 92px; }
|
||||
.bento-kpi { grid-column: span 12; }
|
||||
.agv-row { flex-direction: column; align-items: stretch; }
|
||||
.agv-chart { width: 100%; height: 220px; }
|
||||
|
||||
@@ -58,20 +58,58 @@
|
||||
<el-button size="small" :icon="Refresh" :loading="loading || healthLoading" @click="refreshAll">
|
||||
刷新
|
||||
</el-button>
|
||||
<el-radio-group
|
||||
v-model="viewMode"
|
||||
size="small"
|
||||
class="view-toggle"
|
||||
@change="viewModeTouched = true"
|
||||
>
|
||||
<el-radio-button value="grid">卡片</el-radio-button>
|
||||
<el-radio-button value="list">列表</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-loading="loading && !cardModels.length" class="card-grid">
|
||||
<VehicleHealthCard
|
||||
v-for="v in filteredCards"
|
||||
:key="v.id"
|
||||
:vehicle="v"
|
||||
:selected="selectedId === v.id"
|
||||
:can-write="canWrite"
|
||||
@select="selectedId = $event"
|
||||
@maintenance-changed="refreshAll"
|
||||
/>
|
||||
<el-empty v-if="!filteredCards.length && !loading" description="无匹配车辆" />
|
||||
<div
|
||||
v-loading="loading && !cardModels.length"
|
||||
class="vehicle-scroll"
|
||||
:class="viewMode === 'list' ? 'is-list' : 'is-grid'"
|
||||
>
|
||||
<div v-if="viewMode === 'list' && sortedCards.length" class="list-head">
|
||||
<span>车辆</span>
|
||||
<span>状态</span>
|
||||
<span>电量</span>
|
||||
<span>IP</span>
|
||||
<span>延迟</span>
|
||||
<span>故障率</span>
|
||||
<span>群组</span>
|
||||
<span class="head-actions">操作</span>
|
||||
</div>
|
||||
|
||||
<template v-if="viewMode === 'grid'">
|
||||
<VehicleHealthCard
|
||||
v-for="v in sortedCards"
|
||||
:key="v.id"
|
||||
:vehicle="v"
|
||||
:selected="selectedId === v.id"
|
||||
:can-write="canWrite"
|
||||
@select="selectedId = $event"
|
||||
@maintenance-changed="refreshAll"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<VehicleHealthRow
|
||||
v-for="v in sortedCards"
|
||||
:key="v.id"
|
||||
:vehicle="v"
|
||||
:selected="selectedId === v.id"
|
||||
:can-write="canWrite"
|
||||
@select="selectedId = $event"
|
||||
@maintenance-changed="refreshAll"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<el-empty v-if="!sortedCards.length && !loading" description="无匹配车辆" />
|
||||
</div>
|
||||
|
||||
<FleetAllocationPanel :cars="cardModels" :can-write="canWrite" @saved="onFleetSaved" />
|
||||
@@ -101,13 +139,14 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Search, Refresh, ArrowDown } from '@element-plus/icons-vue'
|
||||
import VehicleHealthCard from '@/components/fleet/VehicleHealthCard.vue'
|
||||
import VehicleHealthRow from '@/components/fleet/VehicleHealthRow.vue'
|
||||
import FleetAllocationPanel from '@/components/fleet/FleetAllocationPanel.vue'
|
||||
import VehicleMaintenanceView from '@/views/admin/config/VehicleMaintenanceView.vue'
|
||||
import FleetLifecycleView from '@/views/admin/config/FleetLifecycleView.vue'
|
||||
import { useVehicleHub } from '@/composables/useVehicleHub'
|
||||
import { useFleetGroups } from '@/composables/useFleetGroups'
|
||||
import { setVehicleMaintenance, type VehicleMaintenanceMode } from '@/api/vehicleOps'
|
||||
import type { CarState } from '@/types/car'
|
||||
import type { CarState, VehicleCardModel } from '@/types/car'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
const auth = useAuthStore()
|
||||
@@ -147,6 +186,10 @@ const {
|
||||
const { groups: fleetGroups, reload: reloadFleetGroups, fleetNameForCarId, regionForCarId } = useFleetGroups()
|
||||
onMounted(() => void reloadFleetGroups())
|
||||
|
||||
const DENSE_THRESHOLD = 12
|
||||
const viewMode = ref<'grid' | 'list'>('grid')
|
||||
const viewModeTouched = ref(false)
|
||||
|
||||
const search = ref('')
|
||||
const filterState = ref<CarState | ''>('')
|
||||
const filterFleet = ref('')
|
||||
@@ -191,11 +234,37 @@ const filteredCards = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
function vehicleSortPriority(v: VehicleCardModel): number {
|
||||
if (v.isAlarmActive) return 0
|
||||
if (v.reachable === false) return 1
|
||||
if (v.state === 'fault') return 2
|
||||
if (v.maintenanceMode && v.maintenanceMode !== 'online') return 3
|
||||
if (v.state === 'running') return 4
|
||||
if (v.state === 'charging') return 5
|
||||
return 6
|
||||
}
|
||||
|
||||
const sortedCards = computed(() =>
|
||||
[...filteredCards.value].sort((a, b) => {
|
||||
const d = vehicleSortPriority(a) - vehicleSortPriority(b)
|
||||
return d !== 0 ? d : a.name.localeCompare(b.name, 'zh-CN')
|
||||
})
|
||||
)
|
||||
|
||||
watch(
|
||||
() => sortedCards.value.length,
|
||||
(n) => {
|
||||
if (viewModeTouched.value) return
|
||||
viewMode.value = n > DENSE_THRESHOLD ? 'list' : 'grid'
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const selectedIds = computed(() => (selectedId.value ? [selectedId.value] : []))
|
||||
|
||||
async function onBatchCommand(cmd: string) {
|
||||
const mode = cmd as VehicleMaintenanceMode
|
||||
const targets = filteredCards.value.filter((c) => selectedId.value ? c.id === selectedId.value : true)
|
||||
const targets = sortedCards.value.filter((c) => selectedId.value ? c.id === selectedId.value : true)
|
||||
if (!targets.length) return
|
||||
try {
|
||||
await ElMessageBox.confirm(`对 ${targets.length} 辆车执行「${cmd}」?`, '批量维护', { type: 'warning' })
|
||||
@@ -317,16 +386,61 @@ async function onBatchCommand(cmd: string) {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
.view-toggle {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.vehicle-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 12px;
|
||||
align-content: start;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.vehicle-scroll.is-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
||||
grid-auto-rows: max-content;
|
||||
gap: 14px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.vehicle-scroll.is-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.list-head {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(140px, 1.4fr) 72px 80px 100px 64px 64px 56px 88px;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 4px 12px 4px 14px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: var(--mg-text-muted, rgba(255, 255, 255, 0.45));
|
||||
letter-spacing: 0.3px;
|
||||
flex-shrink: 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background: rgba(var(--mg-bg-card-darker-rgb, 20, 12, 48), 0.92);
|
||||
backdrop-filter: blur(8px);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.list-head .head-actions {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.list-head { display: none; }
|
||||
.vehicle-scroll.is-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.footnote {
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
|
||||
Reference in New Issue
Block a user