全局自适应样式调整,换行自适应行高

This commit is contained in:
wintsa 2026-03-19 17:00:50 +08:00
parent 75a6209010
commit 241a6c4003
11 changed files with 329 additions and 77 deletions

View File

@ -93,15 +93,15 @@ let data1 = {
services: [
{
id: 0,
fee: 100000,
finalFee: 100000,
fee: 250000,//小计
finalFee: 250000,//确认金额
process: 0,// 工作环节0为编制1为审核
method1: { // 投资规模法
cost: 100000,
basicFee: 200,
basicFee_basic: 200,
basicFee_optional: 0,
fee: 250000,
fee: 250000, //小计
proAmount: 3,
det: [
{
@ -195,7 +195,9 @@ let data1 = {
},
],
},
tasks: [{ serviceid: 0, process: 0, text: ['abc', 'efg'] }, { serviceid: 1, process: 0, text: ['abc', 'efg'] }],// 工作内容
tasks: [{ serviceid: 0, text: ['abc', 'efg'] },
{ serviceid: 2,text: ['abc', 'efg'] } //tasks不分组的时候传单对象[{text: ['abc', 'efg']}],分组的时候传分组的serviceid
],// 工作内容
},
],
addtional: {// 附加工作费
@ -252,6 +254,7 @@ let data1 = {
},
],
},
tasks:[]
},
{
id: 1,
@ -306,6 +309,7 @@ let data1 = {
},
],
},
tasks:[]
},
]
},
@ -313,6 +317,7 @@ let data1 = {
ref: { richText: [{ font: { charset: 134, color: { theme: 1 }, italic: true, name: '宋体', size: 10 }, text: 'Y' }, { font: { charset: 134, color: { theme: 1 }, italic: true, name: 'Calibri', size: 10, vertAlign: 'subscript' }, text: 'B' }] },
name: '预备费',
fee: 10000,
tasks:[],
m0: {
coe: 0.03,
fee: 10000,

View File

@ -1469,7 +1469,7 @@ watch(budgetRefreshSignature, (next, prev) => {
<CardHeader
:class="[
'flex flex-row items-center justify-between gap-0 space-y-0',
isListLayout ? 'px-3 py-2' : 'pb-6'
isListLayout ? 'px-3 py-2' : 'pb-4'
]"
>
<CardTitle
@ -1542,7 +1542,7 @@ watch(budgetRefreshSignature, (next, prev) => {
v-if="!isListLayout"
:class="[
'px-6 text-xs text-muted-foreground',
'space-y-1 '
'space-y-1 pb-1'
]"
>
<div class="break-all">ID{{ element.id }}</div>

View File

@ -211,7 +211,7 @@ onBeforeUnmount(() => {
<label class="space-y-1.5">
<div class="text-xs text-muted-foreground">费率%</div>
<input v-model="rateInput" type="text" inputmode="decimal" placeholder="请输入费率建议1 ~ 5"
class="h-9 w-full rounded-md border bg-background px-3 text-sm text-foreground outline-none focus:ring-2 focus:ring-primary/30"
class="rate-input h-9 w-full rounded-md border bg-background px-3 text-sm text-foreground outline-none focus:ring-2 focus:ring-primary/30"
@blur="applyRateInput" />
</label>
@ -231,3 +231,8 @@ onBeforeUnmount(() => {
</div>
</template>
<style scoped>
:deep(.rate-input) {
text-align: left !important;
}
</style>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from 'vue'
import { computed, nextTick, onActivated, onBeforeUnmount, onDeactivated, onMounted, ref, watch } from 'vue'
import { AgGridVue } from 'ag-grid-vue3'
import type { ColDef, ColGroupDef, GridApi, GridReadyEvent } from 'ag-grid-community'
import { expertList } from '@/sql'
@ -293,7 +293,7 @@ const editableNumberCol = <K extends keyof DetailRow>(
): ColDef<DetailRow> => ({
headerName,
field,
minWidth: 150,
minWidth: 120,
flex: 1,
editable: params => !params.node?.group && !params.node?.rowPinned,
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
@ -314,7 +314,7 @@ const editableMoneyCol = <K extends keyof DetailRow>(
headerName,
field,
headerClass: 'ag-right-aligned-header',
minWidth: 150,
minWidth: 120,
flex: 1,
editable: params => !params.node?.group && !params.node?.rowPinned,
cellClass: params =>
@ -343,7 +343,7 @@ const readonlyTextCol = <K extends keyof DetailRow>(
): ColDef<DetailRow> => ({
headerName,
field,
minWidth: 170,
minWidth: 120,
flex: 1,
editable: false,
valueFormatter: params => params.value || '',
@ -354,8 +354,8 @@ const columnDefs: (ColDef<DetailRow> | ColGroupDef<DetailRow>)[] = [
{
headerName: '编码',
field: 'expertCode',
minWidth: 120,
width: 140,
minWidth: 90,
width: 100,
pinned: 'left',
colSpan: params => (params.node?.rowPinned ? 2 : 1),
valueFormatter: params => (params.node?.rowPinned ? '总合计' : params.value || '')
@ -363,10 +363,13 @@ const columnDefs: (ColDef<DetailRow> | ColGroupDef<DetailRow>)[] = [
{
headerName: '人员名称',
field: 'expertName',
minWidth: 200,
width: 220,
minWidth: 210,
width: 230,
pinned: 'left',
tooltipField: 'expertName',
wrapText: true,
autoHeight: true,
cellStyle: { whiteSpace: 'normal', lineHeight: '1.2' },
valueFormatter: params => (params.node?.rowPinned ? '' : params.value || '')
},
{
@ -391,7 +394,7 @@ const columnDefs: (ColDef<DetailRow> | ColGroupDef<DetailRow>)[] = [
headerName: '服务预算(元)',
field: 'serviceBudget',
headerClass: 'ag-right-aligned-header',
minWidth: 150,
minWidth: 120,
flex: 1,
cellClass: 'ag-right-aligned-cell',
editable: false,
@ -405,8 +408,8 @@ const columnDefs: (ColDef<DetailRow> | ColGroupDef<DetailRow>)[] = [
{
headerName: '说明',
field: 'remark',
minWidth: 180,
flex: 1.2,
minWidth: 120,
flex: 1,
cellEditor: 'agLargeTextCellEditor',
wrapText: true,
autoHeight: true,
@ -522,11 +525,40 @@ const loadFromIndexedDB = async () => {
const handleCellValueChanged = () => {
syncServiceBudgetToRows()
gridApi.value?.refreshCells({ force: true })
scheduleAutoRowHeights()
void saveToIndexedDB()
}
const handleGridReady = (event: GridReadyEvent<DetailRow>) => {
gridApi.value = event.api
scheduleAutoRowHeights()
}
let autoHeightSyncTimer: ReturnType<typeof setTimeout> | null = null
const syncAutoRowHeights = async () => {
await nextTick()
const api = gridApi.value
if (!api) return
api.resetRowHeights()
api.onRowHeightChanged()
api.refreshCells({ force: true })
api.redrawRows()
}
const scheduleAutoRowHeights = () => {
if (autoHeightSyncTimer) clearTimeout(autoHeightSyncTimer)
autoHeightSyncTimer = setTimeout(() => {
autoHeightSyncTimer = null
void syncAutoRowHeights()
}, 0)
}
const onGridSizeChanged = () => {
scheduleAutoRowHeights()
}
const onColumnResized = () => {
scheduleAutoRowHeights()
}
const processCellForClipboard = (params: any) => {
@ -546,16 +578,26 @@ const processCellFromClipboard = (params: any) => {
onMounted(async () => {
await loadFromIndexedDB()
scheduleAutoRowHeights()
})
onActivated(async () => {
await loadFromIndexedDB()
scheduleAutoRowHeights()
})
watch(
() => props.storageKey,
() => {
void loadFromIndexedDB()
scheduleAutoRowHeights()
}
)
watch(
() => detailRows.value.length,
() => {
scheduleAutoRowHeights()
}
)
@ -567,6 +609,10 @@ onDeactivated(() => {
onBeforeUnmount(() => {
gridApi.value?.stopEditing()
gridApi.value = null
if (autoHeightSyncTimer) {
clearTimeout(autoHeightSyncTimer)
autoHeightSyncTimer = null
}
void saveToIndexedDB()
})
</script>
@ -602,6 +648,8 @@ onBeforeUnmount(() => {
:undoRedoCellEditing="true"
:undoRedoCellEditingLimit="20"
@grid-ready="handleGridReady"
@grid-size-changed="onGridSizeChanged"
@column-resized="onColumnResized"
/>
</div>
</div>

View File

@ -219,7 +219,7 @@ const hydrateRowsFromMethodStores = async (rows: FeeMethodRow[]): Promise<FeeMet
const rateValue = toFiniteUnknown(rateData?.rate)
const rateFee =
contractBase != null && rateValue != null
? round3(contractBase * rateValue)
? round3(contractBase * rateValue / 100)
: storedRateFee != null
? round3(storedRateFee)
: null

View File

@ -250,7 +250,15 @@ onMounted(async () => {
</label>
</div>
</div>
<div class="md:col-span-2 xl:col-span-4">
<label class="block text-sm font-medium text-foreground">项目概况</label>
<textarea
v-model="overview"
rows="3"
placeholder="请输入项目概况"
class="mt-2 w-full rounded-lg border bg-background px-4 py-2 text-sm outline-none ring-offset-background shadow-sm transition placeholder:text-muted-foreground/70 focus-visible:border-primary/60 focus-visible:ring-2 focus-visible:ring-ring resize-none"
/>
</div>
<div>
<label class="block text-sm font-medium text-foreground">编制人</label>
<input
@ -391,18 +399,10 @@ onMounted(async () => {
</DatePickerContent>
</DatePickerRoot>
</div>
<div class="md:col-span-2 xl:col-span-4">
<label class="block text-sm font-medium text-foreground">项目概况</label>
<textarea
v-model="overview"
rows="3"
placeholder="请输入项目概况"
class="mt-2 w-full rounded-lg border bg-background px-4 py-2 text-sm outline-none ring-offset-background shadow-sm transition placeholder:text-muted-foreground/70 focus-visible:border-primary/60 focus-visible:ring-2 focus-visible:ring-ring resize-none"
/>
</div>
<div class="md:col-span-2 xl:col-span-4">
<label class="block text-sm font-medium text-foreground">备注</label>
<label class="block text-sm font-medium text-foreground">其他说明</label>
<textarea
v-model="desc"
rows="4"

View File

@ -8,7 +8,7 @@ import { useKvStore } from '@/pinia/kv'
import { Button } from '@/components/ui/button'
import { ScrollArea } from '@/components/ui/scroll-area'
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/components/ui/tooltip'
import { ChevronDown, CircleHelp, RotateCcw, X } from 'lucide-vue-next'
import { ChevronDown, CircleHelp, Loader2, RotateCcw, X } from 'lucide-vue-next'
import localforage from 'localforage'
import {
AlertDialogAction,
@ -361,6 +361,7 @@ const MAJOR_FACTOR_DB_KEY = 'xm-major-factor-v1'
const PINIA_PERSIST_DB_NAME = 'DB'
const PINIA_PERSIST_BASE_STORE_NAME = 'pinia'
const PINIA_PERSIST_STORE_IDS = ['tabs', 'zxFwPricing', 'kv'] as const
const RESET_MIN_LOADING_MS = 1000
const userGuideSteps: UserGuideStep[] = [
{
title: '欢迎使用',
@ -457,6 +458,8 @@ const tabContextRef = ref<HTMLElement | null>(null)
const dataMenuOpen = ref(false)
const dataMenuRef = ref<HTMLElement | null>(null)
const resetConfirmOpen = ref(false)
const isResetting = ref(false)
const importFileRef = ref<HTMLInputElement | null>(null)
const importConfirmOpen = ref(false)
const pendingImportPayload = shallowRef<DataPackage | null>(null)
@ -1731,6 +1734,9 @@ const confirmImportOverride = async () => {
}
const handleReset = async () => {
if (isResetting.value) return
const wait = (ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms))
const resetStartedAt = Date.now()
const deleteIndexedDBByName = (dbName: string) =>
new Promise<void>((resolve) => {
try {
@ -1758,53 +1764,54 @@ const handleReset = async () => {
}
try {
isResetting.value = true
dataMenuOpen.value = false
// 1)
tabStore.resetTabs()
zxFwPricingStore.$patch({
contracts: {},
contractLoaded: {},
servicePricingStates: {},
htFeeMainStates: {},
htFeeMethodStates: {},
keyedStates: {}
} as any)
await kvStore.clear()
// 2) localforage
// 1)
localStorage.clear()
sessionStorage.clear()
await localforage.clear()
// 3) pinia
// 2) pinia
await Promise.all(
getPiniaPersistStores().map(async ({ store }) => {
await store.clear()
})
)
// 4) store key
// 3) store key
const clearTasks: Promise<void>[] = []
if (tabStore.$clearPersisted) clearTasks.push(tabStore.$clearPersisted())
if (zxFwPricingStore.$clearPersisted) clearTasks.push(zxFwPricingStore.$clearPersisted())
if (kvStore.$clearPersisted) clearTasks.push(kvStore.$clearPersisted())
await Promise.all(clearTasks)
// 5) IndexedDB
// 4) IndexedDB
await purgeKnownIndexedDB()
// 6)
// 5)
writeWorkspaceMode('project')
localStorage.setItem(USER_GUIDE_COMPLETED_KEY, '1')
// 6) 2s
const elapsed = Date.now() - resetStartedAt
if (elapsed < RESET_MIN_LOADING_MS) {
await wait(RESET_MIN_LOADING_MS - elapsed)
}
// 7)
window.location.reload()
} catch (error) {
console.error('reset failed:', error)
isResetting.value = false
}
}
const handleResetConfirmOpenChange = (open: boolean) => {
if (isResetting.value) return
resetConfirmOpen.value = open
}
onMounted(() => {
window.addEventListener('mousedown', handleGlobalMouseDown)
window.addEventListener('keydown', handleGlobalKeyDown)
@ -1931,22 +1938,26 @@ watch(
<div class="flex shrink-0 self-center items-center gap-1">
<div ref="dataMenuRef" class="relative shrink-0">
<Button variant="outline" size="sm"
class="h-9 min-h-9 shrink-0 px-3 py-0 text-sm leading-none cursor-pointer"
class="app-toolbar-btn shrink-0 cursor-pointer"
:disabled="isResetting"
@click="dataMenuOpen = !dataMenuOpen">
<ChevronDown class="h-4 w-4 mr-1" />
导入/导出
</Button>
<div v-if="dataMenuOpen"
class="absolute right-0 top-full mt-1 z-50 rounded-md border bg-background p-1 shadow-md">
<button class="w-full cursor-pointer rounded px-3 py-1.5 text-left text-sm hover:bg-muted"
<button class="w-full cursor-pointer rounded px-3 py-1.5 text-left text-sm hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50"
:disabled="isResetting"
@click="triggerImport">
导入
</button>
<button class="w-full cursor-pointer rounded px-3 py-1.5 text-left text-sm hover:bg-muted"
<button class="w-full cursor-pointer rounded px-3 py-1.5 text-left text-sm hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50"
:disabled="isResetting"
@click="exportData">
导出
</button>
<button class="w-full cursor-pointer rounded px-3 py-1.5 text-left text-sm hover:bg-muted"
<button class="w-full cursor-pointer rounded px-3 py-1.5 text-left text-sm hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50"
:disabled="isResetting"
v-if="readWorkspaceMode() !== 'quick'"
@click="exportReport">
导出报表
@ -1955,16 +1966,18 @@ watch(
<input ref="importFileRef" type="file" accept=".zw" class="hidden" @change="importData" />
</div>
<Button variant="outline" size="sm" class="h-9 min-h-9 shrink-0 px-3 py-0 text-sm leading-none cursor-pointer"
<Button variant="outline" size="sm" class="app-toolbar-btn shrink-0 cursor-pointer"
:disabled="isResetting"
@click="openUserGuide(0)">
<CircleHelp class="h-4 w-4 mr-1" />
使用引导
</Button>
<AlertDialogRoot>
<AlertDialogRoot :open="resetConfirmOpen" @update:open="handleResetConfirmOpenChange">
<AlertDialogTrigger as-child>
<Button variant="destructive" size="sm"
class="h-9 min-h-9 shrink-0 px-3 py-0 text-sm leading-none cursor-pointer">
class="app-toolbar-btn shrink-0 cursor-pointer"
:disabled="isResetting">
<RotateCcw class="h-4 w-4 mr-1" />
重置
</Button>
@ -1978,12 +1991,11 @@ watch(
将清空所有项目数据并恢复默认页面确认继续吗
</AlertDialogDescription>
<div class="mt-4 flex items-center justify-end gap-2">
<AlertDialogCancel as-child>
<Button variant="outline">取消</Button>
</AlertDialogCancel>
<AlertDialogAction as-child>
<Button variant="destructive" @click="handleReset">确认重置</Button>
</AlertDialogAction>
<Button variant="outline" :disabled="isResetting" @click="resetConfirmOpen = false">取消</Button>
<Button variant="destructive" :disabled="isResetting" @click="handleReset">
<Loader2 v-if="isResetting" class="mr-1 h-4 w-4 animate-spin" />
{{ isResetting ? '重置中...' : '确认重置' }}
</Button>
</div>
</AlertDialogContent>
</AlertDialogPortal>
@ -2012,6 +2024,7 @@ watch(
</div>
</div>
<div v-if="isResetting" class="fixed inset-0 z-40 cursor-wait bg-transparent" />
<div class="flex-1 overflow-auto relative">
<div v-for="tab in tabStore.tabs" :key="tab.id" :ref="el => setTabPanelRef(tab.id, el)"

View File

@ -73,6 +73,13 @@ const activeComponent = computed(() => {
return selected?.component || props.categories[0]?.component || null
})
const sideWidthStyle = computed(() => ({ width: 'var(--app-typeline-side-w)' }))
const itemGapStyle = computed(() => ({ gap: 'var(--app-typeline-gap)' }))
const dotStyle = computed(() => ({ width: 'var(--app-typeline-dot)', height: 'var(--app-typeline-dot)' }))
const dotInnerStyle = computed(() => ({ width: 'var(--app-typeline-dot-inner)', height: 'var(--app-typeline-dot-inner)' }))
const labelStyle = computed(() => ({ fontSize: 'var(--app-typeline-label-font)', lineHeight: 'var(--app-typeline-label-line)' }))
const lineStyle = computed(() => ({ left: 'var(--app-typeline-line-left)' }))
const copyBtnText = ref('复制')
const sheetOpen = ref(false)
@ -199,7 +206,7 @@ useMotionValueEvent(
<template>
<TooltipProvider>
<div class="flex h-full w-full bg-background p-2">
<div class="w-[200px] shrink-0 border-r px-4 py-3 flex flex-col gap-6 relative ">
<div :style="sideWidthStyle" class="shrink-0 border-r px-4 py-3 flex flex-col gap-6 relative">
<div v-if="props.title || props.subtitle || props.metaText" class="space-y-1">
<TooltipRoot>
<TooltipTrigger as-child>
@ -228,25 +235,25 @@ useMotionValueEvent(
</div>
</div>
<div :class="['flex flex-col gap-6 relative', (props.title || props.subtitle || props.metaText) ? 'mt-4' : '']">
<div class="absolute left-[9px] top-3 bottom-3 w-[1.5px] bg-border/60"></div>
<div :class="['flex flex-col gap-6 relative ', (props.title || props.subtitle || props.metaText) ? 'mt-3' : 'mt-6']">
<div :style="lineStyle" class="absolute top-3 bottom-3 w-[1.5px] bg-border/60"></div>
<div v-for="item in props.categories" :key="item.key"
class="relative flex items-center gap-3 cursor-pointer group" @click="switchCategory(item.key)">
:style="itemGapStyle" class="relative flex items-center cursor-pointer group" @click="switchCategory(item.key)">
<div :class="[
'z-10 w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all duration-200',
'z-10 rounded-full border-2 flex items-center justify-center transition-all duration-200',
activeCategory === item.key
? 'bg-primary border-primary shadow-[0_0_0_3px_rgba(var(--primary),0.15)]'
: 'bg-background border-muted-foreground/40 group-hover:border-muted-foreground/70'
]">
<div v-if="activeCategory === item.key" class="w-1.5 h-1.5 bg-background rounded-full"></div>
]" :style="dotStyle">
<div v-if="activeCategory === item.key" class="bg-background rounded-full" :style="dotInnerStyle"></div>
</div>
<span :class="[
'text-[12px] leading-4 transition-colors duration-200',
'transition-colors duration-200',
activeCategory === item.key
? 'font-semibold text-primary'
: 'text-muted-foreground group-hover:text-foreground'
]">
]" :style="labelStyle">
{{ item.label }}
</span>
</div>

View File

@ -62,7 +62,11 @@ export const gridOptions: GridOptions = {
sortable: false,
filter: false,
wrapHeaderText: true,
autoHeaderHeight: true
autoHeaderHeight: true,
// 默认把数值型单元格右对齐,减少每个列重复配置。
cellClassRules: {
'ag-right-aligned-cell': params => typeof params.value === 'number' && Number.isFinite(params.value)
}
},
defaultColGroupDef: {
wrapHeaderText: true,

View File

@ -14,7 +14,7 @@ import {
} from 'ag-grid-community'
import {
AggregationModule,
AggregationModule,ServerSideRowModelApiModule ,
CellSelectionModule,
ClipboardModule,
LicenseManager,
@ -47,7 +47,7 @@ const AG_GRID_MODULES = [
RowGroupingModule,
CellSelectionModule,
ClipboardModule,
LocaleModule,ValidationModule ,CellSpanModule ,RowStyleModule ,RowSelectionModule
LocaleModule,ValidationModule ,CellSpanModule ,RowStyleModule ,RowSelectionModule ,ServerSideRowModelApiModule
]
const pinia = createPinia()

View File

@ -50,6 +50,19 @@ html {
}
:root {
--app-toolbar-btn-h: 2.25rem;
--app-toolbar-btn-px: 0.75rem;
--app-toolbar-btn-font: 0.875rem;
--app-grid-header-h: 3rem;
--app-grid-row-h: 2.25rem;
--app-grid-font-size: 0.875rem;
--app-typeline-side-w: 12.5rem;
--app-typeline-gap: 0.75rem;
--app-typeline-label-font: 0.8125rem;
--app-typeline-label-line: 1rem;
--app-typeline-dot: 1.25rem;
--app-typeline-dot-inner: 0.375rem;
--app-typeline-line-left: 0.5625rem;
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
@ -143,6 +156,43 @@ html {
overflow-y: scroll !important;
}
/* Numeric-like inputs: align right in forms. */
input[type='number'],
input[inputmode='decimal'],
input[inputmode='numeric'] {
text-align: right;
}
/* Keep AG Grid numeric cells right aligned even with custom flex cell layout. */
.ag-theme-quartz .ag-cell.ag-right-aligned-cell {
justify-content: flex-end;
text-align: right;
}
.ag-theme-quartz .ag-cell.ag-right-aligned-cell .ag-cell-wrapper {
width: 100%;
justify-content: flex-end;
}
.ag-theme-quartz .ag-cell.ag-right-aligned-cell .ag-cell-value {
width: 100%;
text-align: right;
}
.app-toolbar-btn {
height: var(--app-toolbar-btn-h) !important;
min-height: var(--app-toolbar-btn-h) !important;
padding-left: var(--app-toolbar-btn-px) !important;
padding-right: var(--app-toolbar-btn-px) !important;
font-size: var(--app-toolbar-btn-font) !important;
line-height: 1 !important;
}
.ag-theme-quartz {
--ag-font-size: var(--app-grid-font-size);
--ag-row-height: var(--app-grid-row-h);
}
/* When one column uses auto-height rows, keep other columns vertically centered. */
/* .xmMx .ag-row .ag-cell-wrapper {
@ -308,9 +358,129 @@ html {
font-style: italic;
}
/* Laptop screens often need a slightly denser desktop layout. */
@media (max-width: 1536px) {
/* Web adaptive typography baseline: tablet / laptop / 1080p / 2K / 4K */
@media (max-width: 1024px) {
html {
font-size: 14.4px;
font-size: 14px;
}
:root {
--app-toolbar-btn-h: 2.125rem;
--app-toolbar-btn-px: 0.625rem;
--app-toolbar-btn-font: 0.8125rem;
--app-grid-header-h: 2.5rem;
--app-grid-row-h: 2.125rem;
--app-grid-font-size: 0.8125rem;
--app-typeline-side-w: 11.5rem;
--app-typeline-gap: 0.625rem;
--app-typeline-label-font: 0.75rem;
--app-typeline-label-line: 0.95rem;
--app-typeline-dot: 1.125rem;
--app-typeline-dot-inner: 0.3125rem;
--app-typeline-line-left: 0.5rem;
}
}
@media (min-width: 1025px) and (max-width: 1440px) {
html {
font-size: 15px;
}
:root {
--app-toolbar-btn-h: 2.25rem;
--app-toolbar-btn-px: 0.75rem;
--app-toolbar-btn-font: 0.875rem;
--app-grid-header-h: 2.75rem;
--app-grid-row-h: 2.25rem;
--app-grid-font-size: 0.875rem;
--app-typeline-side-w: 12rem;
--app-typeline-gap: 0.6875rem;
--app-typeline-label-font: 0.8125rem;
--app-typeline-label-line: 1rem;
--app-typeline-dot: 1.1875rem;
--app-typeline-dot-inner: 0.375rem;
--app-typeline-line-left: 0.5625rem;
}
}
@media (min-width: 1441px) and (max-width: 1919px) {
html {
font-size: 16px;
}
:root {
--app-toolbar-btn-h: 2.25rem;
--app-toolbar-btn-px: 0.75rem;
--app-toolbar-btn-font: 0.875rem;
--app-grid-header-h: 3rem;
--app-grid-row-h: 2.25rem;
--app-grid-font-size: 0.875rem;
--app-typeline-side-w: 12.5rem;
--app-typeline-gap: 0.75rem;
--app-typeline-label-font: 0.875rem;
--app-typeline-label-line: 1.1rem;
--app-typeline-dot: 1.25rem;
--app-typeline-dot-inner: 0.4375rem;
--app-typeline-line-left: 0.625rem;
}
}
@media (min-width: 1920px) and (max-width: 2559px) {
html {
font-size: 17px;
}
:root {
--app-toolbar-btn-h: 2.5rem;
--app-toolbar-btn-px: 0.875rem;
--app-toolbar-btn-font: 0.9375rem;
--app-grid-header-h: 3.125rem;
--app-grid-row-h: 2.5rem;
--app-grid-font-size: 0.9375rem;
--app-typeline-side-w: 13rem;
--app-typeline-gap: 0.8125rem;
--app-typeline-label-font: 0.9375rem;
--app-typeline-label-line: 1.2rem;
--app-typeline-dot: 1.375rem;
--app-typeline-dot-inner: 0.5rem;
--app-typeline-line-left: 0.6875rem;
}
}
@media (min-width: 2560px) and (max-width: 3839px) {
html {
font-size: 18px;
}
:root {
--app-toolbar-btn-h: 2.625rem;
--app-toolbar-btn-px: 1rem;
--app-toolbar-btn-font: 1rem;
--app-grid-header-h: 3.25rem;
--app-grid-row-h: 2.625rem;
--app-grid-font-size: 1rem;
--app-typeline-side-w: 13.5rem;
--app-typeline-gap: 0.875rem;
--app-typeline-label-font: 1rem;
--app-typeline-label-line: 1.25rem;
--app-typeline-dot: 1.5rem;
--app-typeline-dot-inner: 0.5625rem;
--app-typeline-line-left: 0.75rem;
}
}
@media (min-width: 3840px) {
html {
font-size: 20px;
}
:root {
--app-toolbar-btn-h: 2.875rem;
--app-toolbar-btn-px: 1.125rem;
--app-toolbar-btn-font: 1.0625rem;
--app-grid-header-h: 3.5rem;
--app-grid-row-h: 2.875rem;
--app-grid-font-size: 1.0625rem;
--app-typeline-side-w: 14.5rem;
--app-typeline-gap: 1rem;
--app-typeline-label-font: 1.0625rem;
--app-typeline-label-line: 1.35rem;
--app-typeline-dot: 1.625rem;
--app-typeline-dot-inner: 0.625rem;
--app-typeline-line-left: 0.8125rem;
}
}