1
This commit is contained in:
parent
ea6a244942
commit
757de9a43f
@ -1093,18 +1093,22 @@ onBeforeUnmount(() => {
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
title="回到顶部"
|
||||
aria-label="回到顶部"
|
||||
:class="[
|
||||
'fixed bottom-8 right-8 z-40 inline-flex h-11 w-11 cursor-pointer items-center justify-center rounded-full border border-black/15 bg-white text-black shadow-[0_10px_24px_rgba(0,0,0,0.16)] transition-all duration-300 hover:scale-105 hover:border-black/30 hover:bg-black hover:text-white',
|
||||
showScrollTopFab ? 'translate-y-0 opacity-100' : 'pointer-events-none translate-y-3 opacity-0'
|
||||
]"
|
||||
@click="scrollContractsToTop()"
|
||||
>
|
||||
<ArrowUp class="h-5 w-5" />
|
||||
</button>
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger as-child>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="回到顶部"
|
||||
:class="[
|
||||
'fixed bottom-8 right-8 z-40 inline-flex h-11 w-11 cursor-pointer items-center justify-center rounded-full border border-black/15 bg-white text-black shadow-[0_10px_24px_rgba(0,0,0,0.16)] transition-all duration-300 hover:scale-105 hover:border-black/30 hover:bg-black hover:text-white',
|
||||
showScrollTopFab ? 'translate-y-0 opacity-100' : 'pointer-events-none translate-y-3 opacity-0'
|
||||
]"
|
||||
@click="scrollContractsToTop()"
|
||||
>
|
||||
<ArrowUp class="h-5 w-5" />
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left">回到顶部</TooltipContent>
|
||||
</TooltipRoot>
|
||||
|
||||
<div
|
||||
v-if="showCreateModal"
|
||||
|
||||
@ -260,25 +260,6 @@ const processCellFromClipboard = (params: any) => {
|
||||
return params.value
|
||||
}
|
||||
|
||||
const fitColumnsToGrid = () => {
|
||||
if (!gridApi.value) return
|
||||
requestAnimationFrame(() => {
|
||||
gridApi.value?.sizeColumnsToFit({ defaultMinWidth: 68 })
|
||||
})
|
||||
}
|
||||
|
||||
const handleGridReady = (event: GridReadyEvent<FactorRow>) => {
|
||||
gridApi.value = event.api
|
||||
fitColumnsToGrid()
|
||||
}
|
||||
|
||||
const handleGridSizeChanged = (_event: GridSizeChangedEvent<FactorRow>) => {
|
||||
fitColumnsToGrid()
|
||||
}
|
||||
|
||||
const handleFirstDataRendered = (_event: FirstDataRenderedEvent<FactorRow>) => {
|
||||
fitColumnsToGrid()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadFromIndexedDB()
|
||||
@ -321,9 +302,7 @@ onBeforeUnmount(() => {
|
||||
:processCellFromClipboard="processCellFromClipboard"
|
||||
:undoRedoCellEditing="true"
|
||||
:undoRedoCellEditingLimit="20"
|
||||
@grid-ready="handleGridReady"
|
||||
@grid-size-changed="handleGridSizeChanged"
|
||||
@first-data-rendered="handleFirstDataRendered"
|
||||
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -317,25 +317,7 @@ const processCellFromClipboard = (params:any) => {
|
||||
return params.value;
|
||||
};
|
||||
|
||||
const fitColumnsToGrid = () => {
|
||||
if (!gridApi.value) return
|
||||
requestAnimationFrame(() => {
|
||||
gridApi.value?.sizeColumnsToFit({ defaultMinWidth: 80 })
|
||||
})
|
||||
}
|
||||
|
||||
const handleGridReady = (event: GridReadyEvent<DetailRow>) => {
|
||||
gridApi.value = event.api
|
||||
fitColumnsToGrid()
|
||||
}
|
||||
|
||||
const handleGridSizeChanged = (_event: GridSizeChangedEvent<DetailRow>) => {
|
||||
fitColumnsToGrid()
|
||||
}
|
||||
|
||||
const handleFirstDataRendered = (_event: FirstDataRenderedEvent<DetailRow>) => {
|
||||
fitColumnsToGrid()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -370,9 +352,7 @@ const handleFirstDataRendered = (_event: FirstDataRenderedEvent<DetailRow>) => {
|
||||
:processCellFromClipboard="processCellFromClipboard"
|
||||
:undoRedoCellEditing="true"
|
||||
:undoRedoCellEditingLimit="20"
|
||||
@grid-ready="handleGridReady"
|
||||
@grid-size-changed="handleGridSizeChanged"
|
||||
@first-data-rendered="handleFirstDataRendered"
|
||||
|
||||
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -220,8 +220,9 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
editable: params => !params.node?.group && !params.node?.rowPinned,
|
||||
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'ag-right-aligned-cell editable-cell-line' : 'ag-right-aligned-cell'),
|
||||
cellClassRules: {
|
||||
'editable-cell-empty': params =>
|
||||
!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
|
||||
'editable-cell-empty': params =>{
|
||||
// console.log(!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === ''))
|
||||
return !params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')}
|
||||
},
|
||||
aggFunc: decimalAggSum,
|
||||
valueParser: params => {
|
||||
@ -396,25 +397,6 @@ const processCellFromClipboard = (params:any) => {
|
||||
return params.value;
|
||||
};
|
||||
|
||||
const fitColumnsToGrid = () => {
|
||||
if (!gridApi.value) return
|
||||
requestAnimationFrame(() => {
|
||||
gridApi.value?.sizeColumnsToFit({ defaultMinWidth: 80 })
|
||||
})
|
||||
}
|
||||
|
||||
const handleGridReady = (event: GridReadyEvent<DetailRow>) => {
|
||||
gridApi.value = event.api
|
||||
fitColumnsToGrid()
|
||||
}
|
||||
|
||||
const handleGridSizeChanged = (_event: GridSizeChangedEvent<DetailRow>) => {
|
||||
fitColumnsToGrid()
|
||||
}
|
||||
|
||||
const handleFirstDataRendered = (_event: FirstDataRenderedEvent<DetailRow>) => {
|
||||
fitColumnsToGrid()
|
||||
}
|
||||
|
||||
const scrollToGridSection = () => {
|
||||
const target = gridSectionRef.value || agGridRef.value
|
||||
@ -476,9 +458,6 @@ const scrollToGridSection = () => {
|
||||
:processCellFromClipboard="processCellFromClipboard"
|
||||
:undoRedoCellEditing="true"
|
||||
:undoRedoCellEditingLimit="20"
|
||||
@grid-ready="handleGridReady"
|
||||
@grid-size-changed="handleGridSizeChanged"
|
||||
@first-data-rendered="handleFirstDataRendered"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -5,7 +5,7 @@ import draggable from 'vuedraggable'
|
||||
import { useTabStore } from '@/pinia/tab'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
|
||||
import { ChevronDown, RotateCcw, X } from 'lucide-vue-next'
|
||||
import { ChevronDown, CircleHelp, RotateCcw, X } from 'lucide-vue-next'
|
||||
import localforage from 'localforage'
|
||||
import {
|
||||
AlertDialogAction,
|
||||
@ -33,10 +33,92 @@ interface DataPackage {
|
||||
localforageDefault: DataEntry[]
|
||||
}
|
||||
|
||||
interface UserGuideStep {
|
||||
title: string
|
||||
description: string
|
||||
points: string[]
|
||||
}
|
||||
|
||||
type XmInfoLike = {
|
||||
projectName?: unknown
|
||||
}
|
||||
|
||||
const USER_GUIDE_COMPLETED_KEY = 'jgjs-user-guide-completed-v1'
|
||||
const userGuideSteps: UserGuideStep[] = [
|
||||
{
|
||||
title: '欢迎使用',
|
||||
description: '这个引导会覆盖系统主要功能和完整使用路径,建议按顺序走一遍。',
|
||||
points: [
|
||||
'顶部是标签页区,可在项目、合同段、服务计算页之间快速切换。',
|
||||
'页面里的表格与表单会自动保存到本地,无需手动点击保存。',
|
||||
'你可以随时点击右上角“使用引导”重新打开本教程。'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '项目卡片与四个模块',
|
||||
description: '默认标签“项目卡片”是入口,左侧流程线包含四个项目级功能。',
|
||||
points: [
|
||||
'基础信息:填写项目名称与项目规模明细。',
|
||||
'合同段管理:新建、排序、搜索、导入/导出合同段。',
|
||||
'咨询分类系数 / 工程专业系数:维护系数预算取值和备注。'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '基础信息填写',
|
||||
description: '先在“基础信息”中补齐项目级数据,再进入后续合同段计算。',
|
||||
points: [
|
||||
'项目名称会用于导出文件名和页面展示。',
|
||||
'项目明细表支持直接编辑、复制粘贴、撤销重做。',
|
||||
'分组行自动汇总,顶部固定行显示总合计。'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '合同段管理',
|
||||
description: '在“合同段管理”中完成合同段生命周期操作。',
|
||||
points: [
|
||||
'“添加合同段”用于新增,卡片右上角可编辑或删除。',
|
||||
'支持搜索、网格/列表切换,非搜索状态可拖拽排序。',
|
||||
'更多菜单可导入/导出合同段;点击卡片进入该合同段详情。'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '合同段详情',
|
||||
description: '进入合同段后,左侧同样是流程线,包含规模信息和咨询服务两部分。',
|
||||
points: [
|
||||
'规模信息:按工程专业填写当前合同段的规模数据。',
|
||||
'咨询服务:选择服务词典并生成服务费用明细。',
|
||||
'合同段页面会独立缓存,不同合同段互不干扰。'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '咨询服务与计算页',
|
||||
description: '咨询服务页面用于管理服务明细,并进入具体计费方法页面。',
|
||||
points: [
|
||||
'先点击“浏览”选择服务,再确认生成明细行。',
|
||||
'明细表“编辑”可打开服务计算页,“清空”会重置该服务计算结果。',
|
||||
'服务计算页包含投资规模法、用地规模法、工作量法、工时法四种方法。'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '系数维护',
|
||||
description: '项目级系数用于调节预算取值,可在两个系数页分别维护。',
|
||||
points: [
|
||||
'咨询分类系数页:按咨询分类维护预算取值与说明。',
|
||||
'工程专业系数页:按专业树维护预算取值与说明。',
|
||||
'支持批量粘贴、撤销重做,便于一次性维护多行数据。'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '数据管理与恢复',
|
||||
description: '顶部工具栏负责全量数据导入导出与初始化重置。',
|
||||
points: [
|
||||
'“导入/导出”是整项目级别的数据包操作。',
|
||||
'“重置”会清空本地全部数据并恢复默认页面。',
|
||||
'建议在重要调整前先导出备份。'
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const componentMap: Record<string, any> = {
|
||||
XmView: markRaw(defineAsyncComponent(() => import('@/components/views/Xm.vue'))),
|
||||
ContractDetailView: markRaw(defineAsyncComponent(() => import('@/components/views/ContractDetailView.vue'))),
|
||||
@ -56,6 +138,8 @@ const tabContextRef = ref<HTMLElement | null>(null)
|
||||
const dataMenuOpen = ref(false)
|
||||
const dataMenuRef = ref<HTMLElement | null>(null)
|
||||
const importFileRef = ref<HTMLInputElement | null>(null)
|
||||
const userGuideOpen = ref(false)
|
||||
const userGuideStepIndex = ref(0)
|
||||
const tabItemElMap = new Map<string, HTMLElement>()
|
||||
const tabPanelElMap = new Map<string, HTMLElement>()
|
||||
|
||||
@ -69,6 +153,12 @@ const tabsModel = computed({
|
||||
const contextTabIndex = computed(() => tabStore.tabs.findIndex(t => t.id === contextTabId.value))
|
||||
|
||||
const hasClosableTabs = computed(() => tabStore.tabs.some(t => t.id !== 'XmView'))
|
||||
const activeGuideStep = computed(
|
||||
() => userGuideSteps[userGuideStepIndex.value] || userGuideSteps[0]
|
||||
)
|
||||
const isFirstGuideStep = computed(() => userGuideStepIndex.value === 0)
|
||||
const isLastGuideStep = computed(() => userGuideStepIndex.value >= userGuideSteps.length - 1)
|
||||
const guideProgressText = computed(() => `${userGuideStepIndex.value + 1} / ${userGuideSteps.length}`)
|
||||
const canCloseLeft = computed(() => {
|
||||
if (contextTabIndex.value <= 0) return false
|
||||
return tabStore.tabs.slice(0, contextTabIndex.value).some(t => t.id !== 'XmView')
|
||||
@ -86,6 +176,78 @@ const closeMenus = () => {
|
||||
dataMenuOpen.value = false
|
||||
}
|
||||
|
||||
const markGuideCompleted = () => {
|
||||
try {
|
||||
localStorage.setItem(USER_GUIDE_COMPLETED_KEY, '1')
|
||||
} catch (error) {
|
||||
console.error('mark guide completed failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const hasGuideCompleted = () => {
|
||||
try {
|
||||
return localStorage.getItem(USER_GUIDE_COMPLETED_KEY) === '1'
|
||||
} catch (error) {
|
||||
console.error('read guide completion failed:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const hasNonDefaultTabState = () => {
|
||||
try {
|
||||
const raw = localStorage.getItem('tabs')
|
||||
if (!raw) return false
|
||||
const parsed = JSON.parse(raw) as { tabs?: Array<{ id?: string }>; activeTabId?: string }
|
||||
const tabs = Array.isArray(parsed?.tabs) ? parsed.tabs : []
|
||||
const hasCustomTabs = tabs.some(item => item?.id && item.id !== 'XmView')
|
||||
const activeTabId = typeof parsed?.activeTabId === 'string' ? parsed.activeTabId : ''
|
||||
return hasCustomTabs || (activeTabId !== '' && activeTabId !== 'XmView')
|
||||
} catch (error) {
|
||||
console.error('parse tabs cache failed:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const shouldAutoOpenGuide = async () => {
|
||||
if (hasGuideCompleted()) return false
|
||||
if (hasNonDefaultTabState()) return false
|
||||
try {
|
||||
const keys = await localforage.keys()
|
||||
return keys.length === 0
|
||||
} catch (error) {
|
||||
console.error('read localforage keys failed:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const openUserGuide = (startAt = 0) => {
|
||||
closeMenus()
|
||||
userGuideStepIndex.value = Math.min(Math.max(startAt, 0), userGuideSteps.length - 1)
|
||||
userGuideOpen.value = true
|
||||
}
|
||||
|
||||
const closeUserGuide = (completed = false) => {
|
||||
userGuideOpen.value = false
|
||||
if (completed) markGuideCompleted()
|
||||
}
|
||||
|
||||
const prevUserGuideStep = () => {
|
||||
if (isFirstGuideStep.value) return
|
||||
userGuideStepIndex.value -= 1
|
||||
}
|
||||
|
||||
const nextUserGuideStep = () => {
|
||||
if (isLastGuideStep.value) {
|
||||
closeUserGuide(true)
|
||||
return
|
||||
}
|
||||
userGuideStepIndex.value += 1
|
||||
}
|
||||
|
||||
const jumpToGuideStep = (stepIndex: number) => {
|
||||
userGuideStepIndex.value = Math.min(Math.max(stepIndex, 0), userGuideSteps.length - 1)
|
||||
}
|
||||
|
||||
const openTabContextMenu = (event: MouseEvent, tabId: string) => {
|
||||
contextTabId.value = tabId
|
||||
tabContextX.value = event.clientX
|
||||
@ -116,6 +278,24 @@ const handleGlobalMouseDown = (event: MouseEvent) => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleGlobalKeyDown = (event: KeyboardEvent) => {
|
||||
if (!userGuideOpen.value) return
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault()
|
||||
closeUserGuide(false)
|
||||
return
|
||||
}
|
||||
if (event.key === 'ArrowLeft') {
|
||||
event.preventDefault()
|
||||
prevUserGuideStep()
|
||||
return
|
||||
}
|
||||
if (event.key === 'ArrowRight') {
|
||||
event.preventDefault()
|
||||
nextUserGuideStep()
|
||||
}
|
||||
}
|
||||
|
||||
const runTabMenuAction = (action: 'all' | 'left' | 'right' | 'other') => {
|
||||
if (action === 'all') tabStore.closeAllTabs()
|
||||
if (action === 'left') tabStore.closeLeftTabs(contextTabId.value)
|
||||
@ -352,14 +532,21 @@ const handleReset = async () => {
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('mousedown', handleGlobalMouseDown)
|
||||
window.addEventListener('keydown', handleGlobalKeyDown)
|
||||
void nextTick(() => {
|
||||
ensureActiveTabVisible()
|
||||
scheduleRestoreTabInnerScrollTop(tabStore.activeTabId)
|
||||
})
|
||||
void (async () => {
|
||||
if (await shouldAutoOpenGuide()) {
|
||||
openUserGuide(0)
|
||||
}
|
||||
})()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('mousedown', handleGlobalMouseDown)
|
||||
window.removeEventListener('keydown', handleGlobalKeyDown)
|
||||
})
|
||||
|
||||
watch(
|
||||
@ -464,6 +651,11 @@ watch(
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button variant="outline" size="sm" class="mb-2 shrink-0 cursor-pointer" @click="openUserGuide(0)">
|
||||
<CircleHelp class="h-4 w-4 mr-1" />
|
||||
使用引导
|
||||
</Button>
|
||||
|
||||
<AlertDialogRoot>
|
||||
<AlertDialogTrigger as-child>
|
||||
<Button variant="destructive" size="sm" class="mb-2 shrink-0">
|
||||
@ -538,5 +730,50 @@ watch(
|
||||
删除其他
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="userGuideOpen"
|
||||
class="fixed inset-0 z-[120] flex items-center justify-center bg-black/45 p-4"
|
||||
@click.self="closeUserGuide(false)"
|
||||
>
|
||||
<div class="w-full max-w-3xl rounded-xl border bg-background shadow-2xl">
|
||||
<div class="flex items-start justify-between border-b px-6 py-5">
|
||||
<div>
|
||||
<p class="text-xs text-muted-foreground">新用户引导 · {{ guideProgressText }}</p>
|
||||
<h3 class="mt-1 text-lg font-semibold">{{ activeGuideStep.title }}</h3>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" @click="closeUserGuide(false)">
|
||||
<X class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4 px-6 py-5">
|
||||
<p class="text-sm leading-6 text-foreground">{{ activeGuideStep.description }}</p>
|
||||
<ul class="list-disc space-y-2 pl-5 text-sm text-muted-foreground">
|
||||
<li v-for="(point, index) in activeGuideStep.points" :key="`${activeGuideStep.title}-${index}`">
|
||||
{{ point }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 border-t px-6 py-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<button
|
||||
v-for="(_step, index) in userGuideSteps"
|
||||
:key="`guide-dot-${index}`"
|
||||
class="h-2.5 w-2.5 cursor-pointer rounded-full transition-colors"
|
||||
:class="index === userGuideStepIndex ? 'bg-primary' : 'bg-muted'"
|
||||
:aria-label="`跳转到第 ${index + 1} 步`"
|
||||
@click="jumpToGuideStep(index)"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<Button variant="ghost" @click="closeUserGuide(false)">稍后再看</Button>
|
||||
<Button variant="outline" :disabled="isFirstGuideStep" @click="prevUserGuideStep">上一步</Button>
|
||||
<Button @click="nextUserGuideStep">{{ isLastGuideStep ? '完成并不再自动弹出' : '下一步' }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -37,10 +37,10 @@ export const gridOptions: GridOptions<any> = {
|
||||
singleClickEdit: true,
|
||||
suppressClickEdit: false,
|
||||
suppressContextMenu: false,
|
||||
autoSizeStrategy: {
|
||||
type: 'fitGridWidth',
|
||||
defaultMinWidth: 100,
|
||||
},
|
||||
// autoSizeStrategy: {
|
||||
// type: 'fitGridWidth',
|
||||
// defaultMinWidth: 100,
|
||||
// },
|
||||
groupDefaultExpanded: -1,
|
||||
suppressFieldDotNotation: true,
|
||||
getDataPath: data => data.path,
|
||||
|
||||
46
src/main.ts
46
src/main.ts
@ -2,12 +2,50 @@ import { createApp } from 'vue'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import { AllCommunityModule, ModuleRegistry } from 'ag-grid-community';
|
||||
import { TreeDataModule ,LicenseManager,CellSelectionModule,ContextMenuModule,ClipboardModule } from 'ag-grid-enterprise';
|
||||
LicenseManager.setLicenseKey("[v3][RELEASE][0102]_NDg2Njc4MzY3MDgzNw==16d78ca762fb5d2ff740aed081e2af7b");
|
||||
import {
|
||||
ModuleRegistry,
|
||||
ClientSideRowModelModule,
|
||||
ColumnAutoSizeModule,
|
||||
CsvExportModule,
|
||||
LargeTextEditorModule,
|
||||
NumberEditorModule,
|
||||
PinnedRowModule,
|
||||
TextEditorModule,
|
||||
TooltipModule,
|
||||
UndoRedoEditModule,ValidationModule,LocaleModule ,CellStyleModule ,RowAutoHeightModule
|
||||
} from 'ag-grid-community'
|
||||
import {
|
||||
AggregationModule,
|
||||
CellSelectionModule,
|
||||
ClipboardModule,
|
||||
ContextMenuModule,
|
||||
LicenseManager,
|
||||
MenuModule,
|
||||
RowGroupingModule,
|
||||
TreeDataModule
|
||||
} from 'ag-grid-enterprise'
|
||||
LicenseManager.setLicenseKey("[v3][RELEASE][0102]_NDg2Njc4MzY3MDgzNw==16d78ca762fb5d2ff740aed081e2af7b")
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' // 引入
|
||||
const pinia = createPinia()
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
ModuleRegistry.registerModules([AllCommunityModule,TreeDataModule,CellSelectionModule,ContextMenuModule ,ClipboardModule ]);
|
||||
ModuleRegistry.registerModules([
|
||||
ClientSideRowModelModule,
|
||||
ColumnAutoSizeModule,
|
||||
CsvExportModule,
|
||||
TextEditorModule,
|
||||
NumberEditorModule,RowAutoHeightModule,
|
||||
LargeTextEditorModule,
|
||||
UndoRedoEditModule,CellStyleModule ,
|
||||
PinnedRowModule,
|
||||
TooltipModule,
|
||||
TreeDataModule,
|
||||
AggregationModule,
|
||||
RowGroupingModule,
|
||||
MenuModule,
|
||||
CellSelectionModule,
|
||||
ContextMenuModule,
|
||||
ClipboardModule,LocaleModule ,
|
||||
ValidationModule
|
||||
])
|
||||
|
||||
createApp(App).use(pinia).mount('#app')
|
||||
|
||||
@ -226,6 +226,10 @@
|
||||
}
|
||||
|
||||
.xmMx .ag-cell.editable-cell-empty,
|
||||
.xmMx .ag-cell.editable-cell-empty .ag-cell-value {
|
||||
.xmMx .ag-cell.editable-cell-empty .ag-cell-wrapper,
|
||||
.xmMx .ag-cell.editable-cell-empty .ag-cell-value,
|
||||
.xmMx .ag-cell.editable-cell-empty .ag-cell-value * {
|
||||
--ag-data-color: #94a3b8;
|
||||
color: #94a3b8 !important;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@ -37,6 +37,7 @@ export default defineConfig({
|
||||
}
|
||||
}
|
||||
},
|
||||
chunkSizeWarningLimit: 800,
|
||||
// 5. 生产环境是否生成 sourcemap(默认 false,关闭可减小包体积)
|
||||
sourcemap: false,
|
||||
// 6. 清空输出目录(默认 true)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user