fix all
This commit is contained in:
1 parent
6ba08da229
commit
f121aa233e
13 files changed
+2559
-128
No files matched your search
@@ -3,7 +3,7 @@ import { ref } from 'vue'
|
||||
import draggable from 'vuedraggable'
|
||||
import { Card, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useTabStore } from '@/stores/tab'
|
||||
import { useTabStore } from '@/pinia/tab'
|
||||
import { Plus, Trash2, Edit3, ExternalLink } from 'lucide-vue-next'
|
||||
|
||||
const tabStore = useTabStore()
|
||||
|
||||
@@ -1,4 +1,22 @@
|
||||
<template>1</template>
|
||||
<script>
|
||||
// const typeLine=markRaw(defineAsyncComponent(() => import('@/layout/typeLine.vue')))
|
||||
</script>
|
||||
<template>
|
||||
<TypeLine
|
||||
scene="xm-tab"
|
||||
title="项目配置"
|
||||
storage-key="project-active-cat"
|
||||
default-category="info"
|
||||
:categories="xmCategories"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { markRaw, defineAsyncComponent } from 'vue'
|
||||
import TypeLine from '@/layout/typeLine.vue'
|
||||
|
||||
const xmView = markRaw(defineAsyncComponent(() => import('@/components/views/xmInfo.vue')))
|
||||
const htView = markRaw(defineAsyncComponent(() => import('@/components/views/Ht.vue')))
|
||||
|
||||
const xmCategories = [
|
||||
{ key: 'info', label: '分类信息', component: xmView },
|
||||
{ key: 'contract', label: '合同段管理', component: htView }
|
||||
]
|
||||
</script>
|
||||
+472
-27
@@ -1,32 +1,477 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { AgGridVue } from 'ag-grid-vue3'
|
||||
import type { ColDef, GridOptions } from 'ag-grid-community'
|
||||
import 'ag-grid-enterprise'
|
||||
import {
|
||||
|
||||
themeQuartz
|
||||
} from "ag-grid-community"
|
||||
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale';
|
||||
// 精简的边框配置(细线条+浅灰色,弱化分割线视觉)
|
||||
const borderConfig = {
|
||||
style: "solid", // 虚线改实线更简洁,也可保留 dotted 但建议用 solid
|
||||
width: 0.5, // 更细的边框,减少视觉干扰
|
||||
color: "#e5e7eb" // 浅灰色边框,清新不刺眼
|
||||
};
|
||||
|
||||
// 简洁清新风格的主题配置
|
||||
const myTheme = themeQuartz.withParams({
|
||||
// 核心:移除外边框,减少视觉包裹感
|
||||
wrapperBorder: false,
|
||||
|
||||
// 表头样式(柔和浅蓝,无加粗,更轻盈)
|
||||
headerBackgroundColor: "#f9fafb", // 极浅的背景色,替代深一点的 #e7f3fc
|
||||
headerTextColor: "#374151", // 深灰色文字,比纯黑更柔和
|
||||
headerFontSize: 15, // 字体稍大一点,更易读
|
||||
headerFontWeight: "normal", // 取消加粗,降低视觉重量
|
||||
|
||||
// 行/列/表头边框(统一浅灰细边框)
|
||||
rowBorder: borderConfig,
|
||||
columnBorder: borderConfig,
|
||||
headerRowBorder: borderConfig,
|
||||
|
||||
|
||||
// 可选:偶数行背景色(轻微区分,更清新)
|
||||
dataBackgroundColor: "#fefefe"
|
||||
});
|
||||
interface DictLeaf {
|
||||
code: string
|
||||
name: string
|
||||
}
|
||||
|
||||
interface DictGroup {
|
||||
code: string
|
||||
name: string
|
||||
children: DictLeaf[]
|
||||
}
|
||||
|
||||
interface DetailRow {
|
||||
id: string
|
||||
groupCode: string
|
||||
groupName: string
|
||||
majorCode: string
|
||||
majorName: string
|
||||
amount: number | null
|
||||
landArea: number | null
|
||||
path: string[]
|
||||
}
|
||||
|
||||
interface XmInfoState {
|
||||
projectName: string
|
||||
detailRows: DetailRow[]
|
||||
}
|
||||
|
||||
const DB_NAME = 'jgjs-pricing-db'
|
||||
const DB_STORE = 'form-state'
|
||||
const DB_KEY = 'xm-info-v3'
|
||||
const DEFAULT_PROJECT_NAME = 'xxx造价咨询服务'
|
||||
|
||||
const projectName = ref(DEFAULT_PROJECT_NAME)
|
||||
const detailRows = ref<DetailRow[]>([])
|
||||
|
||||
const detailDict: DictGroup[] = [
|
||||
{
|
||||
code: 'E1',
|
||||
name: '交通运输工程通用专业',
|
||||
children: [
|
||||
{ code: 'E1-1', name: '征地(用海)补偿' },
|
||||
{ code: 'E1-2', name: '拆迁补偿' },
|
||||
{ code: 'E1-3', name: '迁改工程' },
|
||||
{ code: 'E1-4', name: '工程建设其他费' }
|
||||
]
|
||||
},
|
||||
{
|
||||
code: 'E2',
|
||||
name: '公路工程专业',
|
||||
children: [
|
||||
{ code: 'E2-1', name: '临时工程' },
|
||||
{ code: 'E2-2', name: '路基工程' },
|
||||
{ code: 'E2-3', name: '路面工程' },
|
||||
{ code: 'E2-4', name: '桥涵工程' },
|
||||
{ code: 'E2-5', name: '隧道工程' },
|
||||
{ code: 'E2-6', name: '交叉工程' },
|
||||
{ code: 'E2-7', name: '机电工程' },
|
||||
{ code: 'E2-8', name: '交通安全设施工程' },
|
||||
{ code: 'E2-9', name: '绿化及环境保护工程' },
|
||||
{ code: 'E2-10', name: '房建工程' }
|
||||
]
|
||||
},
|
||||
{
|
||||
code: 'E3',
|
||||
name: '铁路工程专业',
|
||||
children: [
|
||||
{ code: 'E3-1', name: '大型临时设施和过渡工程' },
|
||||
{ code: 'E3-2', name: '路基工程' },
|
||||
{ code: 'E3-3', name: '桥涵工程' },
|
||||
{ code: 'E3-4', name: '隧道及明洞工程' },
|
||||
{ code: 'E3-5', name: '轨道工程' },
|
||||
{ code: 'E3-6', name: '通信、信号、信息及灾害监测工程' },
|
||||
{ code: 'E3-7', name: '电力及电力牵引供电工程' },
|
||||
{ code: 'E3-8', name: '房建工程(房屋建筑及附属工程)' },
|
||||
{ code: 'E3-9', name: '装饰装修工程' }
|
||||
]
|
||||
},
|
||||
{
|
||||
code: 'E4',
|
||||
name: '水运工程专业',
|
||||
children: [
|
||||
{ code: 'E4-1', name: '临时工程' },
|
||||
{ code: 'E4-2', name: '土建工程' },
|
||||
{ code: 'E4-3', name: '机电与金属结构工程' },
|
||||
{ code: 'E4-4', name: '设备工程' },
|
||||
{ code: 'E4-5', name: '附属房建工程(房屋建筑及附属工程)' }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const codeNameMap = new Map<string, string>()
|
||||
for (const group of detailDict) {
|
||||
codeNameMap.set(group.code, group.name)
|
||||
for (const child of group.children) {
|
||||
codeNameMap.set(child.code, child.name)
|
||||
}
|
||||
}
|
||||
|
||||
const buildDefaultRows = (): DetailRow[] => {
|
||||
const rows: DetailRow[] = []
|
||||
for (const group of detailDict) {
|
||||
for (const child of group.children) {
|
||||
rows.push({
|
||||
id: `row-${child.code}`,
|
||||
groupCode: group.code,
|
||||
groupName: group.name,
|
||||
majorCode: child.code,
|
||||
majorName: child.name,
|
||||
amount: null,
|
||||
landArea: null,
|
||||
path: [group.code, child.code]
|
||||
})
|
||||
}
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] => {
|
||||
const dbValueMap = new Map<string, DetailRow>()
|
||||
for (const row of rowsFromDb || []) {
|
||||
dbValueMap.set(row.majorCode, row)
|
||||
}
|
||||
|
||||
return buildDefaultRows().map(row => {
|
||||
const fromDb = dbValueMap.get(row.majorCode)
|
||||
if (!fromDb) return row
|
||||
|
||||
return {
|
||||
...row,
|
||||
amount: typeof fromDb.amount === 'number' ? fromDb.amount : null,
|
||||
landArea: typeof fromDb.landArea === 'number' ? fromDb.landArea : null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const columnDefs: ColDef<DetailRow>[] = [
|
||||
|
||||
{
|
||||
headerName: '造价金额(万元)',
|
||||
field: 'amount',
|
||||
minWidth: 170,
|
||||
flex: 1, // 核心:开启弹性布局,自动占满剩余空间
|
||||
|
||||
editable: params => !params.node?.group && !params.node?.rowPinned,
|
||||
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
|
||||
cellClassRules: {
|
||||
'editable-cell-empty': params =>
|
||||
!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
|
||||
},
|
||||
aggFunc: 'sum',
|
||||
valueParser: params => {
|
||||
if (params.newValue === '' || params.newValue == null) return null
|
||||
const v = Number(params.newValue)
|
||||
return Number.isFinite(v) ? v : null
|
||||
},
|
||||
valueFormatter: params => {
|
||||
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
|
||||
return '点击输入'
|
||||
}
|
||||
if (params.value == null) return ''
|
||||
return Number(params.value).toFixed(2)
|
||||
}
|
||||
},
|
||||
{
|
||||
headerName: '用地面积(亩)',
|
||||
field: 'landArea',
|
||||
minWidth: 170,
|
||||
flex: 1, // 核心:开启弹性布局,自动占满剩余空间
|
||||
|
||||
editable: params => !params.node?.group && !params.node?.rowPinned,
|
||||
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
|
||||
cellClassRules: {
|
||||
'editable-cell-empty': params =>
|
||||
!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
|
||||
},
|
||||
aggFunc: 'sum',
|
||||
valueParser: params => {
|
||||
if (params.newValue === '' || params.newValue == null) return null
|
||||
const v = Number(params.newValue)
|
||||
return Number.isFinite(v) ? v : null
|
||||
},
|
||||
valueFormatter: params => {
|
||||
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
|
||||
return '点击输入'
|
||||
}
|
||||
if (params.value == null) return ''
|
||||
return Number(params.value).toFixed(2)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const autoGroupColumnDef: ColDef = {
|
||||
headerName: '专业编码以及工程专业名称',
|
||||
minWidth: 320,
|
||||
pinned: 'left',
|
||||
flex:2, // 核心:开启弹性布局,自动占满剩余空间
|
||||
|
||||
cellRendererParams: {
|
||||
suppressCount: true
|
||||
},
|
||||
valueFormatter: params => {
|
||||
if (params.node?.rowPinned) {
|
||||
return '总合计'
|
||||
}
|
||||
const code = String(params.value || '')
|
||||
const name = codeNameMap.get(code) || ''
|
||||
return name ? `${code} ${name}` : code
|
||||
}
|
||||
}
|
||||
|
||||
const gridOptions: GridOptions<DetailRow> = {
|
||||
treeData: true,
|
||||
animateRows: true,
|
||||
singleClickEdit: true,
|
||||
suppressClickEdit: false,
|
||||
suppressContextMenu: false,
|
||||
groupDefaultExpanded: -1,
|
||||
suppressFieldDotNotation: true,
|
||||
getDataPath: data => data.path,
|
||||
getContextMenuItems: () => ['copy', 'paste', 'separator', 'export'],
|
||||
defaultColDef: {
|
||||
resizable: true,
|
||||
sortable: false,
|
||||
filter: false
|
||||
}
|
||||
}
|
||||
|
||||
const totalAmount = computed(() =>
|
||||
detailRows.value.reduce((sum, row) => sum + (row.amount || 0), 0)
|
||||
)
|
||||
|
||||
const totalLandArea = computed(() =>
|
||||
detailRows.value.reduce((sum, row) => sum + (row.landArea || 0), 0)
|
||||
)
|
||||
const pinnedTopRowData = computed(() => [
|
||||
{
|
||||
id: 'pinned-total-row',
|
||||
groupCode: '',
|
||||
groupName: '',
|
||||
majorCode: '',
|
||||
majorName: '',
|
||||
amount: totalAmount.value,
|
||||
landArea: totalLandArea.value,
|
||||
path: ['TOTAL']
|
||||
}
|
||||
])
|
||||
|
||||
const openDB = () =>
|
||||
new Promise<IDBDatabase>((resolve, reject) => {
|
||||
const request = window.indexedDB.open(DB_NAME, 1)
|
||||
|
||||
request.onupgradeneeded = () => {
|
||||
const db = request.result
|
||||
if (!db.objectStoreNames.contains(DB_STORE)) {
|
||||
db.createObjectStore(DB_STORE)
|
||||
}
|
||||
}
|
||||
|
||||
request.onsuccess = () => resolve(request.result)
|
||||
request.onerror = () => reject(request.error)
|
||||
})
|
||||
|
||||
const saveToIndexedDB = async () => {
|
||||
try {
|
||||
const db = await openDB()
|
||||
const tx = db.transaction(DB_STORE, 'readwrite')
|
||||
const store = tx.objectStore(DB_STORE)
|
||||
|
||||
const payload: XmInfoState = {
|
||||
projectName: projectName.value,
|
||||
detailRows: detailRows.value
|
||||
}
|
||||
|
||||
store.put(payload, DB_KEY)
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
tx.oncomplete = () => resolve()
|
||||
tx.onerror = () => reject(tx.error)
|
||||
tx.onabort = () => reject(tx.error)
|
||||
})
|
||||
|
||||
db.close()
|
||||
} catch (error) {
|
||||
console.error('saveToIndexedDB failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const loadFromIndexedDB = async () => {
|
||||
try {
|
||||
const db = await openDB()
|
||||
const tx = db.transaction(DB_STORE, 'readonly')
|
||||
const store = tx.objectStore(DB_STORE)
|
||||
const request = store.get(DB_KEY)
|
||||
|
||||
const data = await new Promise<XmInfoState | undefined>((resolve, reject) => {
|
||||
request.onsuccess = () => resolve(request.result as XmInfoState | undefined)
|
||||
request.onerror = () => reject(request.error)
|
||||
})
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
tx.oncomplete = () => resolve()
|
||||
tx.onerror = () => reject(tx.error)
|
||||
tx.onabort = () => reject(tx.error)
|
||||
})
|
||||
|
||||
db.close()
|
||||
|
||||
if (data) {
|
||||
projectName.value = data.projectName || DEFAULT_PROJECT_NAME
|
||||
detailRows.value = mergeWithDictRows(data.detailRows)
|
||||
return
|
||||
}
|
||||
|
||||
detailRows.value = buildDefaultRows()
|
||||
} catch (error) {
|
||||
console.error('loadFromIndexedDB failed:', error)
|
||||
detailRows.value = buildDefaultRows()
|
||||
}
|
||||
}
|
||||
|
||||
let persistTimer: ReturnType<typeof setTimeout> | null = null
|
||||
const schedulePersist = () => {
|
||||
if (persistTimer) clearTimeout(persistTimer)
|
||||
persistTimer = setTimeout(() => {
|
||||
void saveToIndexedDB()
|
||||
}, 250)
|
||||
}
|
||||
|
||||
const handleBeforeUnload = () => {
|
||||
void saveToIndexedDB()
|
||||
}
|
||||
|
||||
const handleCellValueChanged = () => {
|
||||
schedulePersist()
|
||||
}
|
||||
|
||||
watch(projectName, schedulePersist)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadFromIndexedDB()
|
||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
if (persistTimer) clearTimeout(persistTimer)
|
||||
void saveToIndexedDB()
|
||||
})
|
||||
const processCellForClipboard = (params:any) => {
|
||||
if (Array.isArray(params.value)) {
|
||||
return JSON.stringify(params.value); // 数组转字符串复制
|
||||
}
|
||||
return params.value;
|
||||
};
|
||||
|
||||
const processCellFromClipboard = (params:any) => {
|
||||
try {
|
||||
const parsed = JSON.parse(params.value);
|
||||
if (Array.isArray(parsed)) return parsed;
|
||||
} catch (e) {
|
||||
// 解析失败时返回原始值,无需额外处理
|
||||
}
|
||||
return params.value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<div class="p-4 border rounded-lg bg-card shadow-sm">
|
||||
<label class="text-sm font-medium mb-2 block">项目名称</label>
|
||||
<Input placeholder="请输入项目名称" class="max-w-md" />
|
||||
<div class="rounded-lg border bg-card p-4 shadow-sm">
|
||||
<label class="mb-2 block text-sm font-medium text-foreground">项目名称</label>
|
||||
<input
|
||||
v-model="projectName"
|
||||
type="text"
|
||||
placeholder="请输入项目名称"
|
||||
class="h-10 w-full max-w-xl rounded-md border bg-background px-3 text-sm outline-none ring-offset-background transition focus-visible:ring-2 focus-visible:ring-ring"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Table class="border rounded-md">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>专业编码</TableHead>
|
||||
<TableHead>工程专业名称</TableHead>
|
||||
<TableHead>造价金额(万元)</TableHead>
|
||||
<TableHead>用地面积(亩)</TableHead>
|
||||
<TableHead class="text-right">操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="i in 3" :key="i">
|
||||
<TableCell>010{{i}}</TableCell>
|
||||
<TableCell>土木工程建设</TableCell>
|
||||
<TableCell><Input type="number" class="h-8 w-24" value="1200" /></TableCell>
|
||||
<TableCell><Input type="number" class="h-8 w-24" value="50" /></TableCell>
|
||||
<TableCell class="text-right">
|
||||
<Button variant="ghost" size="sm" class="text-destructive">删除</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Button variant="outline" class="w-full border-dashed"><Plus class="mr-2 h-4 w-4" /> 新增专业行</Button>
|
||||
<div class="rounded-lg border bg-card xmMx">
|
||||
<div class="flex items-center justify-between border-b px-4 py-3">
|
||||
<h3 class="text-sm font-semibold text-foreground">项目明细</h3>
|
||||
<div class="text-xs text-muted-foreground">导入导出</div>
|
||||
</div>
|
||||
|
||||
<div class="ag-theme-quartz h-[580px] w-full">
|
||||
<AgGridVue
|
||||
:style="{ height: '100%' }"
|
||||
:rowData="detailRows"
|
||||
:pinnedTopRowData="pinnedTopRowData"
|
||||
:columnDefs="columnDefs"
|
||||
:autoGroupColumnDef="autoGroupColumnDef"
|
||||
:gridOptions="gridOptions"
|
||||
:theme="myTheme"
|
||||
@cell-value-changed="handleCellValueChanged"
|
||||
:suppressColumnVirtualisation="true"
|
||||
:suppressRowVirtualisation="true"
|
||||
:cellSelection="{ handle: { mode: 'range' } }"
|
||||
:enableClipboard="true"
|
||||
:localeText="AG_GRID_LOCALE_CN"
|
||||
:tooltipShowDelay="500"
|
||||
:headerHeight="50"
|
||||
:processCellForClipboard="processCellForClipboard"
|
||||
:processCellFromClipboard="processCellFromClipboard"
|
||||
:undoRedoCellEditing="true"
|
||||
:undoRedoCellEditingLimit="20"
|
||||
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style >
|
||||
.ag-floating-top{
|
||||
overflow-y:auto !important
|
||||
}
|
||||
|
||||
.xmMx .editable-cell-line .ag-cell-value {
|
||||
display: inline-block;
|
||||
min-width: 84%;
|
||||
padding: 2px 4px;
|
||||
border-bottom: 1px solid #cbd5e1;
|
||||
}
|
||||
|
||||
.xmMx .editable-cell-line.ag-cell-focus .ag-cell-value,
|
||||
.xmMx .editable-cell-line:hover .ag-cell-value {
|
||||
border-bottom-color: #2563eb;
|
||||
}
|
||||
|
||||
.xmMx .editable-cell-empty .ag-cell-value {
|
||||
color: #94a3b8 !important;
|
||||
font-style: italic;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.xmMx .ag-cell.editable-cell-empty,
|
||||
.xmMx .ag-cell.editable-cell-empty .ag-cell-value {
|
||||
color: #94a3b8 !important;
|
||||
}
|
||||
</style>
|
||||
+83
-28
@@ -1,50 +1,105 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import xmView from '@/components/views/xmInfo.vue'
|
||||
import { computed, ref, watch, type Component } from 'vue'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
|
||||
// 使用 sessionStorage 管理左侧分类切换状态
|
||||
const activeCategory = ref(sessionStorage.getItem('project-active-cat') || 'info')
|
||||
interface TypeLineCategory {
|
||||
key: string
|
||||
label: string
|
||||
component: Component
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
scene?: string
|
||||
title?: string
|
||||
categories: TypeLineCategory[]
|
||||
storageKey?: string
|
||||
defaultCategory?: string
|
||||
}>(),
|
||||
{
|
||||
scene: 'default',
|
||||
title: '配置',
|
||||
storageKey: '',
|
||||
defaultCategory: ''
|
||||
}
|
||||
)
|
||||
|
||||
const cacheKey = computed(() => props.storageKey || `type-line-active-cat-${props.scene}`)
|
||||
|
||||
const resolveInitialCategory = () => {
|
||||
const defaultKey = props.defaultCategory || props.categories[0]?.key || ''
|
||||
const savedKey = sessionStorage.getItem(cacheKey.value)
|
||||
const validSavedKey = props.categories.some(item => item.key === savedKey)
|
||||
return validSavedKey ? (savedKey as string) : defaultKey
|
||||
}
|
||||
|
||||
const activeCategory = ref(resolveInitialCategory())
|
||||
|
||||
watch(
|
||||
() => [props.categories, props.defaultCategory, cacheKey.value],
|
||||
() => {
|
||||
activeCategory.value = resolveInitialCategory()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const switchCategory = (cat: string) => {
|
||||
activeCategory.value = cat
|
||||
sessionStorage.setItem('project-active-cat', cat)
|
||||
sessionStorage.setItem(cacheKey.value, cat)
|
||||
}
|
||||
|
||||
const activeComponent = computed(() => {
|
||||
const selected = props.categories.find(item => item.key === activeCategory.value)
|
||||
return selected?.component || props.categories[0]?.component || null
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-full w-full bg-background">
|
||||
<div class="w-1/5 border-r p-6 flex flex-col gap-8 relative">
|
||||
<div class="font-bold text-lg mb-4 text-primary">项目配置</div>
|
||||
|
||||
<div class="font-bold text-lg mb-4 text-primary">{{ props.title }}</div>
|
||||
|
||||
<div class="flex flex-col gap-10 relative">
|
||||
<div class="absolute left-[11px] top-2 bottom-2 w-[2px] bg-muted"></div>
|
||||
|
||||
<div class="relative flex items-center gap-4 cursor-pointer group" @click="switchCategory('info')">
|
||||
<div :class="['z-10 w-6 h-6 rounded-full border-2 flex items-center justify-center transition-all',
|
||||
activeCategory === 'info' ? 'bg-primary border-primary scale-110' : 'bg-background border-muted-foreground']">
|
||||
<div v-if="activeCategory === 'info'" class="w-2 h-2 bg-background rounded-full"></div>
|
||||
<div
|
||||
v-for="item in props.categories"
|
||||
:key="item.key"
|
||||
class="relative flex items-center gap-4 cursor-pointer group"
|
||||
@click="switchCategory(item.key)"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'z-10 w-6 h-6 rounded-full border-2 flex items-center justify-center transition-all',
|
||||
activeCategory === item.key
|
||||
? 'bg-primary border-primary scale-110'
|
||||
: 'bg-background border-muted-foreground'
|
||||
]"
|
||||
>
|
||||
<div v-if="activeCategory === item.key" class="w-2 h-2 bg-background rounded-full"></div>
|
||||
</div>
|
||||
<span :class="['text-sm transition-colors', activeCategory === 'info' ? 'font-bold text-primary' : 'text-muted-foreground group-hover:text-foreground']">
|
||||
分类信息
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="relative flex items-center gap-4 cursor-pointer group" @click="switchCategory('contract')">
|
||||
<div :class="['z-10 w-6 h-6 rounded-full border-2 flex items-center justify-center transition-all',
|
||||
activeCategory === 'contract' ? 'bg-primary border-primary scale-110' : 'bg-background border-muted-foreground']">
|
||||
<div v-if="activeCategory === 'contract'" class="w-2 h-2 bg-background rounded-full"></div>
|
||||
</div>
|
||||
<span :class="['text-sm transition-colors', activeCategory === 'contract' ? 'font-bold text-primary' : 'text-muted-foreground group-hover:text-foreground']">
|
||||
合同段管理
|
||||
<span
|
||||
:class="[
|
||||
'text-sm transition-colors',
|
||||
activeCategory === item.key
|
||||
? 'font-bold text-primary'
|
||||
: 'text-muted-foreground group-hover:text-foreground'
|
||||
]"
|
||||
>
|
||||
{{ item.label }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-4/5 p-8 overflow-auto">
|
||||
<keep-alive>
|
||||
<component :is="activeCategory === 'info' ? xmView : xmView" />
|
||||
</keep-alive>
|
||||
<div class="w-4/5 min-h-0">
|
||||
<ScrollArea class="h-full w-full">
|
||||
<div class="p-3">
|
||||
<keep-alive>
|
||||
<component :is="activeComponent" />
|
||||
</keep-alive>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
@@ -2,7 +2,12 @@ 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 piniaPluginPersistedstate from 'pinia-plugin-persistedstate' // 引入
|
||||
const pinia = createPinia()
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
ModuleRegistry.registerModules([AllCommunityModule,TreeDataModule,CellSelectionModule,ContextMenuModule ,ClipboardModule ]);
|
||||
|
||||
createApp(App).use(pinia).mount('#app')
|
||||
Reference in new issue
Block a user