init
This commit is contained in:
252
apps/web-ele/src/views/database/interface/project.vue
Normal file
252
apps/web-ele/src/views/database/interface/project.vue
Normal file
@@ -0,0 +1,252 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, reactive, ref, nextTick, createVNode, render, watch,computed, readonly } from 'vue'
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { ElSplitter,ElSplitterPanel,ElCard } from 'element-plus';
|
||||
import { useElementSize } from '@vueuse/core'
|
||||
import { DbTree } from '#/components/db-tree';
|
||||
import { DbHst } from '#/components/db-hst';
|
||||
|
||||
import { handleRowOperation, codeRenderer } from '#/components/db-hst/tree'
|
||||
// import { sourceDataObject } from '#/components/db-hst/mockData'
|
||||
const containerRef = ref<HTMLElement | null>(null)
|
||||
const { height: containerHeight } = useElementSize(containerRef)
|
||||
const topContainerRef = ref<HTMLElement | null>(null)
|
||||
const { height: topContainerHeight } = useElementSize(topContainerRef)
|
||||
const bottomContainerRef = ref<HTMLElement | null>(null)
|
||||
const { height: bottomContainerHeight } = useElementSize(bottomContainerRef)
|
||||
|
||||
type Tree = { id: string; label: string; children?: Tree[] }
|
||||
const categoryTreeData = ref<Tree[]>([
|
||||
{
|
||||
id: '1',
|
||||
label: '行业总类',
|
||||
children: [
|
||||
{
|
||||
id: '2',
|
||||
label: '广东',
|
||||
children: [
|
||||
{ id: '3', label: '行业1' },
|
||||
{ id: '4', label: '行业2' },
|
||||
{ id: '5', label: '行业3' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '11',
|
||||
label: '行业2',
|
||||
children: [
|
||||
{
|
||||
id: '12',
|
||||
label: '广西',
|
||||
children: [
|
||||
{ id: '13', label: '行业5' },
|
||||
{ id: '14', label: '行业6' },
|
||||
{ id: '15', label: '行业7' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
const tagRenderer = (instance: any, td: HTMLElement, row: number, col: number, prop: string, value: any, cellProperties: any) => {
|
||||
// 清空单元格内容
|
||||
td.innerHTML = ''
|
||||
td.style.padding = '4px'
|
||||
td.style.overflow = 'visible'
|
||||
|
||||
// 获取当前单元格的标签数据
|
||||
const getCurrentTags = (): string[] => {
|
||||
const currentValue = instance.getDataAtCell(row, col)
|
||||
// console.log(currentValue)
|
||||
if (typeof currentValue === 'string') {
|
||||
return currentValue ? currentValue.split(',').map(t => t.trim()).filter(t => t) : []
|
||||
} else if (Array.isArray(currentValue)) {
|
||||
return [...currentValue]
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
// 创建容器
|
||||
const container = document.createElement('div')
|
||||
container.style.cssText = 'display: flex; flex-wrap: wrap; gap: 4px; align-items: center; min-height: 24px;'
|
||||
|
||||
// 渲染标签
|
||||
const renderTags = () => {
|
||||
const tags = getCurrentTags()
|
||||
container.innerHTML = ''
|
||||
|
||||
tags.forEach((tag, index) => {
|
||||
const tagEl = document.createElement('span')
|
||||
tagEl.style.cssText = `
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
background: #f0f0f0;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
gap: 4px;
|
||||
`
|
||||
tagEl.textContent = tag
|
||||
|
||||
// 删除按钮
|
||||
const closeBtn = document.createElement('span')
|
||||
closeBtn.innerHTML = '×'
|
||||
closeBtn.style.cssText = `
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
margin-left: 2px;
|
||||
`
|
||||
closeBtn.onmouseover = () => closeBtn.style.color = '#333'
|
||||
closeBtn.onmouseout = () => closeBtn.style.color = '#999'
|
||||
closeBtn.onclick = (e) => {
|
||||
e.stopPropagation()
|
||||
const currentTags = getCurrentTags()
|
||||
currentTags.splice(index, 1)
|
||||
instance.setDataAtCell(row, col, currentTags.join(','))
|
||||
renderTags()
|
||||
}
|
||||
|
||||
tagEl.appendChild(closeBtn)
|
||||
container.appendChild(tagEl)
|
||||
})
|
||||
|
||||
// 添加输入框
|
||||
const inputWrapper = document.createElement('span')
|
||||
inputWrapper.style.cssText = 'display: inline-flex; align-items: center;'
|
||||
|
||||
const input = document.createElement('input')
|
||||
input.type = 'text'
|
||||
input.placeholder = '按Enter回车键添加'
|
||||
//border: 1px solid #d9d9d9;
|
||||
input.style.cssText = `
|
||||
|
||||
border-radius: 4px;
|
||||
padding: 2px 8px;
|
||||
font-size: 12px;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
input.onfocus = () => {
|
||||
input.style.borderColor = '#409eff'
|
||||
}
|
||||
|
||||
input.onblur = () => {
|
||||
input.style.borderColor = '#d9d9d9'
|
||||
|
||||
// 失去焦点时添加标签
|
||||
if (input.value.trim()) {
|
||||
const newTag = input.value.trim()
|
||||
const currentTags = getCurrentTags()
|
||||
console.log('添加前的标签:', currentTags)
|
||||
if (!currentTags.includes(newTag)) {
|
||||
currentTags.push(newTag)
|
||||
console.log('添加后的标签:', currentTags)
|
||||
instance.setDataAtCell(row, col, currentTags.join(','))
|
||||
input.value = ''
|
||||
renderTags()
|
||||
} else {
|
||||
input.value = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input.onkeydown = (e) => {
|
||||
// 保留 Enter 键功能,按 Enter 也可以添加
|
||||
if (e.key === 'Enter' && input.value.trim()) {
|
||||
e.preventDefault()
|
||||
input.blur() // 触发失去焦点事件
|
||||
}
|
||||
}
|
||||
|
||||
inputWrapper.appendChild(input)
|
||||
container.appendChild(inputWrapper)
|
||||
}
|
||||
|
||||
renderTags()
|
||||
td.appendChild(container)
|
||||
|
||||
return td
|
||||
}
|
||||
|
||||
const bottomColumns = ref<any[]>([
|
||||
{type:'text',data:'code',title:'序号'},
|
||||
{type:'text',data:'name',title:'名称'},
|
||||
{type:'text',data:'content',title:'内容',width: 300, renderer: tagRenderer, readOnly:true},
|
||||
{type:'text',data:'spec',title:'代码'},
|
||||
|
||||
])
|
||||
// const colHeaders = ref<string[]>(topColHeaders)
|
||||
|
||||
const bottomHstRef = ref<any>(null)
|
||||
|
||||
const bootomMock = ()=>{
|
||||
// 生成模拟数据
|
||||
const mockData = Array.from({ length: 30 }, (_, index) => ({
|
||||
code: `DTL${String(index + 1).padStart(6, '0')}`,
|
||||
name: `明细项目${index + 1}`,
|
||||
content: ``,
|
||||
spec: `规格${index + 1}`,
|
||||
}))
|
||||
return mockData;
|
||||
}
|
||||
let bottomDbSettings = {
|
||||
columns: bottomColumns.value,
|
||||
}
|
||||
const categoryHandleSelect = (node: Tree) => {
|
||||
console.log('categoryhandleSelect',node)
|
||||
}
|
||||
|
||||
const detailHandleSelect = (node: Tree) => {
|
||||
// if (topHstRef.value && typeof topHstRef.value.loadData === 'function') {
|
||||
// // console.log('hstData.value',hstData.value)
|
||||
// // topHstRef.value.loadData(topHstData.value)
|
||||
// }
|
||||
}
|
||||
function onBottomHeight(height: number){
|
||||
if (bottomHstRef.value?.hotInstance) {
|
||||
bottomHstRef.value.hotInstance.updateSettings({
|
||||
height: height-15
|
||||
})
|
||||
|
||||
bottomHstRef.value.loadData(bootomMock())
|
||||
bottomHstRef.value.hotInstance.render()
|
||||
console.log('onResizeEnd-bottomHstRef',height);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
onBottomHeight(bottomContainerHeight.value)
|
||||
}, 200);
|
||||
})
|
||||
onUnmounted(() => {
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<ElSplitter >
|
||||
<ElSplitterPanel collapsible size="15%" :min="200">
|
||||
<ElCard class="w-full h-full border-radius-0" body-class="!p-0 h-full" ref="containerRef">
|
||||
<DbTree :height="containerHeight" :data="categoryTreeData" @select="categoryHandleSelect" :defaultExpandedKeys="2" :search="false" />
|
||||
</ElCard>
|
||||
</ElSplitterPanel>
|
||||
<ElSplitterPanel collapsible :min="200">
|
||||
<ElCard class="w-full h-full border-radius-0" body-class="!p-0 h-full">
|
||||
<DbHst ref="bottomHstRef" :settings="bottomDbSettings"></DbHst>
|
||||
</ElCard>
|
||||
</ElSplitterPanel>
|
||||
</ElSplitter>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<style lang="css">
|
||||
|
||||
</style>
|
||||
129
apps/web-ele/src/views/database/interface/unit.vue
Normal file
129
apps/web-ele/src/views/database/interface/unit.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, reactive, ref, nextTick, createVNode, render, watch,computed, readonly } from 'vue'
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { ElSplitter,ElSplitterPanel,ElCard,ElTabs,ElTabPane } from 'element-plus';
|
||||
import { useElementSize } from '@vueuse/core'
|
||||
import { DbTree } from '#/components/db-tree';
|
||||
import { DbHst } from '#/components/db-hst';
|
||||
|
||||
import { handleRowOperation, codeRenderer } from '#/components/db-hst/tree'
|
||||
// import { sourceDataObject } from '#/components/db-hst/mockData'
|
||||
|
||||
// 导入子组件
|
||||
import FieldName from './unit/FieldName.vue'
|
||||
import SubItem from './unit/SubItem.vue'
|
||||
import MeasureItem from './unit/MeasureItem.vue'
|
||||
import OtherItem from './unit/OtherItem.vue'
|
||||
import UnitSummary from './unit/UnitSummary.vue'
|
||||
import VariableSettings from './unit/VariableSettings.vue'
|
||||
import MaterialField from './unit/MaterialField.vue'
|
||||
const containerRef = ref<HTMLElement | null>(null)
|
||||
const { height: containerHeight } = useElementSize(containerRef)
|
||||
// const topContainerRef = ref<HTMLElement | null>(null)
|
||||
// const { height: topContainerHeight } = useElementSize(topContainerRef)
|
||||
// const bottomContainerRef = ref<HTMLElement | null>(null)
|
||||
// const { height: bottomContainerHeight } = useElementSize(bottomContainerRef)
|
||||
// const bottomPanelHeight = ref<number>(0)
|
||||
type Tree = { id: string; label: string; children?: Tree[] }
|
||||
const categoryTreeData = ref<Tree[]>([
|
||||
{
|
||||
id: '1',
|
||||
label: '行业总类',
|
||||
children: [
|
||||
{
|
||||
id: '2',
|
||||
label: '广东',
|
||||
children: [
|
||||
{ id: '3', label: '行业1' },
|
||||
{ id: '4', label: '行业2' },
|
||||
{ id: '5', label: '行业3' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '11',
|
||||
label: '行业2',
|
||||
children: [
|
||||
{
|
||||
id: '12',
|
||||
label: '广西',
|
||||
children: [
|
||||
{ id: '13', label: '行业5' },
|
||||
{ id: '14', label: '行业6' },
|
||||
{ id: '15', label: '行业7' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
// const colHeaders = ref<string[]>(topColHeaders)
|
||||
const activeTab = ref('fieldName')
|
||||
const categoryHandleSelect = (node: Tree) => {
|
||||
console.log('categoryhandleSelect',node)
|
||||
}
|
||||
|
||||
const detailHandleSelect = (node: Tree) => {
|
||||
// if (topHstRef.value && typeof topHstRef.value.loadData === 'function') {
|
||||
// // console.log('hstData.value',hstData.value)
|
||||
// // topHstRef.value.loadData(topHstData.value)
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
})
|
||||
onUnmounted(() => {
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<!-- <DbHst ref="bottomHstRef" :settings="bottomDbSettings"></DbHst> -->
|
||||
<ElSplitter >
|
||||
<ElSplitterPanel collapsible size="15%" :min="200">
|
||||
<ElCard class="w-full h-full border-radius-0" body-class="!p-0 h-full" ref="containerRef">
|
||||
<DbTree :height="containerHeight" :data="categoryTreeData" @select="categoryHandleSelect" :defaultExpandedKeys="2" :search="false" />
|
||||
</ElCard>
|
||||
</ElSplitterPanel>
|
||||
<ElSplitterPanel collapsible :min="200">
|
||||
<ElCard class="w-full h-full border-radius-0" body-class="!p-0 h-full">
|
||||
<ElTabs v-model="activeTab" type="border-card" class="h-full">
|
||||
<ElTabPane label="字段名称" name="fieldName" lazy>
|
||||
<FieldName :height="containerHeight"/>
|
||||
</ElTabPane>
|
||||
<ElTabPane label="分部分项" name="subItem" lazy>
|
||||
<!-- <SubItem /> -->
|
||||
<FieldName :height="containerHeight"/>
|
||||
</ElTabPane>
|
||||
<ElTabPane label="措施项目" name="measureItem" lazy>
|
||||
<!-- <MeasureItem /> -->
|
||||
<FieldName :height="containerHeight"/>
|
||||
</ElTabPane>
|
||||
<ElTabPane label="其他项目" name="otherItem" lazy>
|
||||
<!-- <OtherItem /> -->
|
||||
<FieldName :height="containerHeight"/>
|
||||
</ElTabPane>
|
||||
<ElTabPane label="单位汇总" name="unitSummary" lazy>
|
||||
<!-- <UnitSummary /> -->
|
||||
<FieldName :height="containerHeight"/>
|
||||
</ElTabPane>
|
||||
<ElTabPane label="变量设置" name="variableSettings" lazy>
|
||||
<VariableSettings />
|
||||
</ElTabPane>
|
||||
<ElTabPane label="工料机字段" name="materialField" lazy>
|
||||
<MaterialField />
|
||||
</ElTabPane>
|
||||
</ElTabs>
|
||||
</ElCard>
|
||||
</ElSplitterPanel>
|
||||
</ElSplitter>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<style lang="css">
|
||||
.el-tabs--border-card>.el-tabs__content {
|
||||
padding: 5px 0;
|
||||
}
|
||||
</style>
|
||||
150
apps/web-ele/src/views/database/interface/unit/FieldName.vue
Normal file
150
apps/web-ele/src/views/database/interface/unit/FieldName.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { DbHst } from '#/components/db-hst'
|
||||
import { handleRowOperation, codeRenderer } from '#/components/db-hst/tree'
|
||||
const props = defineProps<{
|
||||
height?: number
|
||||
}>()
|
||||
const hstRef = ref<any>(null)
|
||||
const columns = ref<any[]>([
|
||||
{ type: 'text', data: 'seq', title: '序', width: 40 },
|
||||
{ type: 'text', data: 'code', title: '编码', renderer: codeRenderer },
|
||||
{ type: 'text', data: 'category', title: '类别' },
|
||||
{ type: 'text', data: 'name', title: '名称' },
|
||||
{ type: 'text', data: 'feature', title: '项目特征' },
|
||||
{ type: 'text', data: 'locked', title: '锁定' },
|
||||
{ type: 'text', data: 'unitPrice', title: '综合单价' },
|
||||
{ type: 'text', data: 'unit', title: '单位' },
|
||||
{ type: 'text', data: 'quantity', title: '工程量' },
|
||||
{ type: 'text', data: 'comprehensivePrice', title: '综合单价' },
|
||||
{ type: 'text', data: 'totalPrice', title: '综合合价' },
|
||||
{ type: 'text', data: 'remark', title: '备注' },
|
||||
])
|
||||
let rowSchema: any = {}
|
||||
// 根据 columns 的 data 字段生成对象结构
|
||||
columns.value.forEach((col: any) => {
|
||||
if (col.data ) {
|
||||
rowSchema[col.data] = null
|
||||
}
|
||||
})
|
||||
|
||||
const mockData = (() => {
|
||||
const data: any[] = []
|
||||
|
||||
// 生成5个父级数据
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const parent = {
|
||||
seq: `${i}`,
|
||||
code: `CODE${String(i).padStart(6, '0')}`,
|
||||
category: i % 3 === 0 ? '分部分项' : i % 3 === 1 ? '措施项目' : '其他项目',
|
||||
name: `项目名称${i}`,
|
||||
feature: `特征描述${i}`,
|
||||
locked: i % 2 === 0 ? '是' : '否',
|
||||
unitPrice: `${i * 100}`,
|
||||
unit: 'm²',
|
||||
quantity: `${i * 10}`,
|
||||
comprehensivePrice: `${i * 100}`,
|
||||
totalPrice: `${i * 1000}`,
|
||||
remark: `备注${i}`,
|
||||
level: String(i - 1),
|
||||
__children: [] as any[]
|
||||
}
|
||||
|
||||
// 为每个父级生成3-5个子级
|
||||
const childCount = Math.floor(Math.random() * 3) + 3
|
||||
for (let j = 1; j <= childCount; j++) {
|
||||
const child = {
|
||||
seq: `${i}.${j}`,
|
||||
code: `CODE${String(i).padStart(6, '0')}-${String(j).padStart(3, '0')}`,
|
||||
category: '子项',
|
||||
name: `子项目${i}-${j}`,
|
||||
feature: `子特征${j}`,
|
||||
locked: '否',
|
||||
unitPrice: `${j * 50}`,
|
||||
unit: 'm²',
|
||||
quantity: `${j * 5}`,
|
||||
comprehensivePrice: `${j * 50}`,
|
||||
totalPrice: `${j * 250}`,
|
||||
remark: `子备注${j}`,
|
||||
level: `${i - 1}.${j - 1}`,
|
||||
__children: []
|
||||
}
|
||||
parent.__children.push(child)
|
||||
}
|
||||
|
||||
data.push(parent)
|
||||
}
|
||||
|
||||
return data
|
||||
})()
|
||||
|
||||
const settings = {
|
||||
data: mockData,
|
||||
dataSchema: rowSchema,
|
||||
colWidths: 120,
|
||||
columns: columns.value,
|
||||
rowHeaders: false,
|
||||
nestedRows: true,
|
||||
bindRowsWithHeaders: true,
|
||||
contextMenu: {
|
||||
items: {
|
||||
custom_row_above: {
|
||||
name: '在上方插入行',
|
||||
callback: function() {
|
||||
handleRowOperation(this, 'above')
|
||||
}
|
||||
},
|
||||
custom_row_below: {
|
||||
name: '在下方插入行',
|
||||
callback: function() {
|
||||
handleRowOperation(this, 'below')
|
||||
}
|
||||
},
|
||||
separator1: '---------',
|
||||
custom_add_child: {
|
||||
name: '添加子行',
|
||||
callback: function() {
|
||||
handleRowOperation(this, 'child')
|
||||
}
|
||||
},
|
||||
separator2: '---------',
|
||||
remove_row: {
|
||||
name: '删除行',
|
||||
callback: function() {
|
||||
handleRowOperation(this, 'delete')
|
||||
}
|
||||
},
|
||||
// separator3: '---------',
|
||||
// undo: {},
|
||||
// redo: {}
|
||||
}
|
||||
},
|
||||
}
|
||||
watch(
|
||||
() => props.height,
|
||||
(newHeight) => {
|
||||
console.log('MarketMaterials newHeight', newHeight)
|
||||
if (newHeight && hstRef.value?.hotInstance) {
|
||||
hstRef.value.hotInstance.updateSettings({
|
||||
height: newHeight - 50 - 15// 减去 tabs 头部和 padding 的高度,滚动条
|
||||
})
|
||||
hstRef.value.hotInstance.render()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
// if (hstRef.value) {
|
||||
// hstRef.value.loadData(mockData)
|
||||
// }
|
||||
}, 100)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<DbHst ref="hstRef" :settings="settings" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { DbHst } from '#/components/db-hst'
|
||||
const hstRef = ref<any>(null)
|
||||
|
||||
const columns = ref<any[]>([
|
||||
{ type: 'text', data: 'code', title: '序号' },
|
||||
{ type: 'text', data: 'name', title: '字段名称' },
|
||||
{ type: 'checkbox', data: 'unit', title: '分部分项隐藏' },
|
||||
{ type: 'checkbox', data: 'price', title: '措施项目隐藏' },
|
||||
])
|
||||
|
||||
const settings = {
|
||||
columns: columns.value,
|
||||
}
|
||||
|
||||
const mockData = Array.from({ length: 20 }, (_, index) => ({
|
||||
code: `MAT${String(index + 1).padStart(6, '0')}`,
|
||||
name: `工料机${index + 1}`,
|
||||
unit: false,
|
||||
price: false,
|
||||
}))
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
if (hstRef.value) {
|
||||
hstRef.value.loadData(mockData)
|
||||
}
|
||||
}, 100)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<DbHst ref="hstRef" :settings="settings" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { DbHst } from '#/components/db-hst'
|
||||
|
||||
const hstRef = ref<any>(null)
|
||||
|
||||
const columns = ref<any[]>([
|
||||
{ type: 'text', data: 'code', title: '编码' },
|
||||
{ type: 'text', data: 'name', title: '措施项目名称' },
|
||||
{ type: 'text', data: 'unit', title: '单位' },
|
||||
{ type: 'text', data: 'amount', title: '金额' },
|
||||
])
|
||||
|
||||
const settings = {
|
||||
columns: columns.value,
|
||||
}
|
||||
|
||||
const mockData = Array.from({ length: 20 }, (_, index) => ({
|
||||
code: `MSR${String(index + 1).padStart(6, '0')}`,
|
||||
name: `措施项目${index + 1}`,
|
||||
unit: '项',
|
||||
amount: `${(index + 1) * 1000}`,
|
||||
}))
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
if (hstRef.value) {
|
||||
hstRef.value.loadData(mockData)
|
||||
}
|
||||
}, 100)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<DbHst ref="hstRef" :settings="settings" />
|
||||
</div>
|
||||
</template>
|
||||
38
apps/web-ele/src/views/database/interface/unit/OtherItem.vue
Normal file
38
apps/web-ele/src/views/database/interface/unit/OtherItem.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { DbHst } from '#/components/db-hst'
|
||||
|
||||
const hstRef = ref<any>(null)
|
||||
|
||||
const columns = ref<any[]>([
|
||||
{ type: 'text', data: 'code', title: '编码' },
|
||||
{ type: 'text', data: 'name', title: '其他项目名称' },
|
||||
{ type: 'text', data: 'type', title: '类型' },
|
||||
{ type: 'text', data: 'amount', title: '金额' },
|
||||
])
|
||||
|
||||
const settings = {
|
||||
columns: columns.value,
|
||||
}
|
||||
|
||||
const mockData = Array.from({ length: 20 }, (_, index) => ({
|
||||
code: `OTH${String(index + 1).padStart(6, '0')}`,
|
||||
name: `其他项目${index + 1}`,
|
||||
type: '其他',
|
||||
amount: `${(index + 1) * 500}`,
|
||||
}))
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
if (hstRef.value) {
|
||||
hstRef.value.loadData(mockData)
|
||||
}
|
||||
}, 100)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<DbHst ref="hstRef" :settings="settings" />
|
||||
</div>
|
||||
</template>
|
||||
38
apps/web-ele/src/views/database/interface/unit/SubItem.vue
Normal file
38
apps/web-ele/src/views/database/interface/unit/SubItem.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { DbHst } from '#/components/db-hst'
|
||||
|
||||
const hstRef = ref<any>(null)
|
||||
|
||||
const columns = ref<any[]>([
|
||||
{ type: 'text', data: 'code', title: '编码' },
|
||||
{ type: 'text', data: 'name', title: '项目名称' },
|
||||
{ type: 'text', data: 'unit', title: '单位' },
|
||||
{ type: 'text', data: 'quantity', title: '工程量' },
|
||||
])
|
||||
|
||||
const settings = {
|
||||
columns: columns.value,
|
||||
}
|
||||
|
||||
const mockData = Array.from({ length: 20 }, (_, index) => ({
|
||||
code: `SUB${String(index + 1).padStart(6, '0')}`,
|
||||
name: `分部分项${index + 1}`,
|
||||
unit: 'm²',
|
||||
quantity: `${(index + 1) * 10}`,
|
||||
}))
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
if (hstRef.value) {
|
||||
hstRef.value.loadData(mockData)
|
||||
}
|
||||
}, 100)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<DbHst ref="hstRef" :settings="settings" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { DbHst } from '#/components/db-hst'
|
||||
|
||||
const hstRef = ref<any>(null)
|
||||
|
||||
const columns = ref<any[]>([
|
||||
{ type: 'text', data: 'category', title: '类别' },
|
||||
{ type: 'text', data: 'count', title: '数量' },
|
||||
{ type: 'text', data: 'totalAmount', title: '合计金额' },
|
||||
{ type: 'text', data: 'remark', title: '备注' },
|
||||
])
|
||||
|
||||
const settings = {
|
||||
columns: columns.value,
|
||||
}
|
||||
|
||||
const mockData = [
|
||||
{ category: '分部分项', count: '50', totalAmount: '500000', remark: '' },
|
||||
{ category: '措施项目', count: '20', totalAmount: '100000', remark: '' },
|
||||
{ category: '其他项目', count: '10', totalAmount: '50000', remark: '' },
|
||||
{ category: '合计', count: '80', totalAmount: '650000', remark: '' },
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
if (hstRef.value) {
|
||||
hstRef.value.loadData(mockData)
|
||||
}
|
||||
}, 100)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full">
|
||||
<DbHst ref="hstRef" :settings="settings" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,267 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { ElDialog, ElTable, ElTableColumn, ElButton, ElTabs, ElTabPane, ElInput, ElSegmented } from 'element-plus'
|
||||
import { DbHst } from '#/components/db-hst'
|
||||
import { handleRowOperation, codeRenderer } from '#/components/db-hst/tree'
|
||||
|
||||
const props = defineProps<{
|
||||
height?: number
|
||||
}>()
|
||||
|
||||
const hstRef = ref<any>(null)
|
||||
const dialogVisible = ref(false)
|
||||
const activeTab = ref('global')
|
||||
const currentRowData = ref<any>(null)
|
||||
const formulaInput = ref('')
|
||||
|
||||
// 各个 Tab 的表格数据
|
||||
const tabTableData = ref({
|
||||
global: [
|
||||
{ seq: '1', code: 'GF001', name: '全局费用1', value: '100' },
|
||||
{ seq: '2', code: 'GF002', name: '全局费用2', value: '200' },
|
||||
],
|
||||
division: [
|
||||
{ seq: '1', code: 'FB001', name: '分部分项1', value: '150' },
|
||||
{ seq: '2', code: 'FB002', name: '分部分项2', value: '250' },
|
||||
],
|
||||
measure: [
|
||||
{ seq: '1', code: 'CS001', name: '措施项目1', value: '180' },
|
||||
{ seq: '2', code: 'CS002', name: '措施项目2', value: '280' },
|
||||
],
|
||||
other: [
|
||||
{ seq: '1', code: 'QT001', name: '其他项目1', value: '120' },
|
||||
{ seq: '2', code: 'QT002', name: '其他项目2', value: '220' },
|
||||
],
|
||||
supplement: [
|
||||
{ seq: '1', code: 'BC001', name: '补充费用1', value: '160' },
|
||||
{ seq: '2', code: 'BC002', name: '补充费用2', value: '260' },
|
||||
],
|
||||
})
|
||||
|
||||
const dialogRenderer = (instance: any, td: HTMLElement, row: number, col: number, prop: string, value: any, cellProperties: any) => {
|
||||
td.innerHTML = ''
|
||||
td.style.position = 'relative'
|
||||
td.style.padding = '0 8px'
|
||||
|
||||
// 创建文本容器
|
||||
const textSpan = document.createElement('span')
|
||||
textSpan.textContent = value || ''
|
||||
textSpan.style.display = 'inline-block'
|
||||
textSpan.style.verticalAlign = 'middle'
|
||||
textSpan.style.maxWidth = 'calc(100% - 24px)'
|
||||
textSpan.style.overflow = 'hidden'
|
||||
textSpan.style.textOverflow = 'ellipsis'
|
||||
textSpan.style.whiteSpace = 'nowrap'
|
||||
|
||||
// 创建图标按钮
|
||||
const iconBtn = document.createElement('span')
|
||||
iconBtn.innerHTML = '⚙️'
|
||||
iconBtn.style.cursor = 'pointer'
|
||||
iconBtn.style.marginLeft = '4px'
|
||||
iconBtn.style.fontSize = '14px'
|
||||
iconBtn.style.verticalAlign = 'middle'
|
||||
iconBtn.style.float = 'right'
|
||||
|
||||
iconBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation()
|
||||
const rowData = instance.getSourceDataAtRow(instance.toPhysicalRow(row))
|
||||
currentRowData.value = rowData
|
||||
dialogVisible.value = true
|
||||
})
|
||||
|
||||
td.appendChild(textSpan)
|
||||
td.appendChild(iconBtn)
|
||||
|
||||
return td
|
||||
}
|
||||
|
||||
const columns = ref<any[]>([
|
||||
{ type: 'text', data: 'seq', title: '序', width: 40 },
|
||||
{ type: 'text', data: 'name', title: '费用名称', renderer: codeRenderer },
|
||||
{ type: 'text', data: 'spec', title: '费用代码', width: 300,renderer: dialogRenderer },
|
||||
{ type: 'text', data: 'cardinal', title: '计算基数(用户端不显示)', width: 200 },
|
||||
])
|
||||
let rowSchema: any = {}
|
||||
// 根据 columns 的 data 字段生成对象结构
|
||||
columns.value.forEach((col: any) => {
|
||||
if (col.data ) {
|
||||
rowSchema[col.data] = null
|
||||
}
|
||||
})
|
||||
|
||||
const mockData = (() => {
|
||||
const data: any[] = []
|
||||
|
||||
// 生成5个父级数据
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const parent: any = {
|
||||
seq: `${i}`,
|
||||
name: `费用名称${i}`,
|
||||
spec: `SPEC${String(i).padStart(3, '0')}`,
|
||||
cardinal: `基数${i}`,
|
||||
__children: [] as any[]
|
||||
}
|
||||
|
||||
// 为每个父级生成3-5个子级
|
||||
const childCount = Math.floor(Math.random() * 3) + 3
|
||||
for (let j = 1; j <= childCount; j++) {
|
||||
const child: any = {
|
||||
seq: `${i}.${j}`,
|
||||
name: `子费用${i}-${j}`,
|
||||
spec: `SPEC${String(i).padStart(3, '0')}-${String(j).padStart(2, '0')}`,
|
||||
cardinal: `子基数${i}-${j}`,
|
||||
__children: []
|
||||
}
|
||||
parent.__children.push(child)
|
||||
}
|
||||
|
||||
data.push(parent)
|
||||
}
|
||||
|
||||
return data
|
||||
})()
|
||||
|
||||
const settings = {
|
||||
data: mockData,
|
||||
dataSchema: rowSchema,
|
||||
colWidths: 120,
|
||||
columns: columns.value,
|
||||
rowHeaders: false,
|
||||
nestedRows: true,
|
||||
bindRowsWithHeaders: true,
|
||||
contextMenu: {
|
||||
items: {
|
||||
custom_row_above: {
|
||||
name: '在上方插入行',
|
||||
callback: function() {
|
||||
handleRowOperation(this, 'above')
|
||||
}
|
||||
},
|
||||
custom_row_below: {
|
||||
name: '在下方插入行',
|
||||
callback: function() {
|
||||
handleRowOperation(this, 'below')
|
||||
}
|
||||
},
|
||||
separator1: '---------',
|
||||
custom_add_child: {
|
||||
name: '添加子行',
|
||||
callback: function() {
|
||||
handleRowOperation(this, 'child')
|
||||
}
|
||||
},
|
||||
separator2: '---------',
|
||||
remove_row: {
|
||||
name: '删除行',
|
||||
callback: function() {
|
||||
handleRowOperation(this, 'delete')
|
||||
}
|
||||
},
|
||||
// separator3: '---------',
|
||||
// undo: {},
|
||||
// redo: {}
|
||||
}
|
||||
},
|
||||
}
|
||||
watch(
|
||||
() => props.height,
|
||||
(newHeight) => {
|
||||
console.log('MarketMaterials newHeight', newHeight)
|
||||
if (newHeight && hstRef.value?.hotInstance) {
|
||||
hstRef.value.hotInstance.updateSettings({
|
||||
height: newHeight - 50 - 15// 减去 tabs 头部和 padding 的高度,滚动条
|
||||
})
|
||||
hstRef.value.hotInstance.render()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const value = ref('分部分项???')
|
||||
|
||||
const options = ['分部分项???', '措施项目???', '其他项目???', '单位汇总???']
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
// if (hstRef.value) {
|
||||
// hstRef.value.loadData(mockData)
|
||||
// }
|
||||
}, 100)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full h-full">
|
||||
|
||||
<div class="flex flex-col items-start gap-4">
|
||||
<ElSegmented v-model="value" :options="options" size="large" />
|
||||
</div>
|
||||
|
||||
<DbHst ref="hstRef" :settings="settings" />
|
||||
|
||||
<!-- 弹窗 -->
|
||||
<ElDialog v-model="dialogVisible" title="费用代码设置" width="60%" :close-on-click-modal="false">
|
||||
<div style="margin-bottom: 16px;">
|
||||
<div style="color: #909399; font-size: 13px; margin-bottom: 8px;">
|
||||
提示: 现在书写的公式只能调用在当前项目以前的取费和系统参数
|
||||
</div>
|
||||
<ElInput
|
||||
v-model="formulaInput"
|
||||
placeholder="请输入公式"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ElTabs v-model="activeTab">
|
||||
<ElTabPane label="全局费用" name="global">
|
||||
<ElTable :data="tabTableData.global" border style="width: 100%" max-height="400">
|
||||
<ElTableColumn prop="seq" label="序号" width="80" />
|
||||
<ElTableColumn prop="code" label="费用代号" width="150" />
|
||||
<ElTableColumn prop="name" label="费用名称" min-width="200" />
|
||||
<ElTableColumn prop="value" label="值" width="120" />
|
||||
</ElTable>
|
||||
</ElTabPane>
|
||||
|
||||
<ElTabPane label="分部分项" name="division">
|
||||
<ElTable :data="tabTableData.division" border style="width: 100%" max-height="400">
|
||||
<ElTableColumn prop="seq" label="序号" width="80" />
|
||||
<ElTableColumn prop="code" label="费用代号" width="150" />
|
||||
<ElTableColumn prop="name" label="费用名称" min-width="200" />
|
||||
<ElTableColumn prop="value" label="值" width="120" />
|
||||
</ElTable>
|
||||
</ElTabPane>
|
||||
|
||||
<ElTabPane label="措施项目" name="measure">
|
||||
<ElTable :data="tabTableData.measure" border style="width: 100%" max-height="400">
|
||||
<ElTableColumn prop="seq" label="序号" width="80" />
|
||||
<ElTableColumn prop="code" label="费用代号" width="150" />
|
||||
<ElTableColumn prop="name" label="费用名称" min-width="200" />
|
||||
<ElTableColumn prop="value" label="值" width="120" />
|
||||
</ElTable>
|
||||
</ElTabPane>
|
||||
|
||||
<ElTabPane label="其他项目" name="other">
|
||||
<ElTable :data="tabTableData.other" border style="width: 100%" max-height="400">
|
||||
<ElTableColumn prop="seq" label="序号" width="80" />
|
||||
<ElTableColumn prop="code" label="费用代号" width="150" />
|
||||
<ElTableColumn prop="name" label="费用名称" min-width="200" />
|
||||
<ElTableColumn prop="value" label="值" width="120" />
|
||||
</ElTable>
|
||||
</ElTabPane>
|
||||
|
||||
<ElTabPane label="补充费用" name="supplement">
|
||||
<ElTable :data="tabTableData.supplement" border style="width: 100%" max-height="400">
|
||||
<ElTableColumn prop="seq" label="序号" width="80" />
|
||||
<ElTableColumn prop="code" label="费用代号" width="150" />
|
||||
<ElTableColumn prop="name" label="费用名称" min-width="200" />
|
||||
<ElTableColumn prop="value" label="值" width="120" />
|
||||
</ElTable>
|
||||
</ElTabPane>
|
||||
</ElTabs>
|
||||
|
||||
<template #footer>
|
||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="dialogVisible = false">确定</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user