工料机、定额基价、定额费率、定额取费

This commit is contained in:
2026-01-03 14:59:45 +08:00
parent e974bf361d
commit 618bb6699e
65 changed files with 13251 additions and 2624 deletions

View File

@@ -6,16 +6,28 @@ import { HotTable } from '@handsontable/vue3'
import { registerLanguageDictionary, zhCN } from 'handsontable/i18n'
import { registerAllModules } from 'handsontable/registry'
import 'handsontable/styles/handsontable.css'
import 'handsontable/styles/ht-theme-main.css'
// import 'handsontable/styles/ht-theme-main.css'
import 'handsontable/styles/ht-theme-classic.css';
registerAllModules()
registerLanguageDictionary(zhCN)
import { handlerDropdownRenderer } from './dropdown'
import { handlerTableRenderer } from './table'
import { handlerDuplicateCodeRenderer } from './text'
import { computeCodeColWidth,codeRenderer } from './tree'
import ContextMenu from './contextmenu.vue'
import { handleRowOperation } from '#/components/db-hst/tree'
// import { sourceDataObject } from './mockData'
// const language = ref('zh-CN')
defineOptions({ name: 'DbHst' });
const componentProps = defineProps<{ settings?: any }>()
const componentProps = defineProps<{
settings?: any
contextMenuItems?: Array<{
key: string
name: string
callback?: (hotInstance: any) => void
separator?: boolean
}>
}>()
// 导入和注册插件和单元格类型
// import { registerCellType, NumericCellType } from 'handsontable/cellTypes';
// import { registerPlugin, UndoRedo } from 'handsontable/plugins';
@@ -24,13 +36,15 @@ const componentProps = defineProps<{ settings?: any }>()
// const tableHeight = computed(() => componentProps.height ?? 0)
// const defaultData = ref<any[][]>(Array.from({ length: 30 }, () => Array(componentProps.columns?.length ?? 0).fill('')))
const hotTableComponent = ref<any>(null)
const selectedRow = ref<number | null>(null) // 记录当前选中的行
const codeColWidth = ref<number>(120)
// const colHeaders = ref<string[]>([])
let defaultSettings = {
themeName: 'ht-theme-main',
// themeName: 'ht-theme-main',
themeName: 'ht-theme-classic',
language: 'zh-CN',
// data: sourceDataObject,
// colWidths: [100, 120, 100, 100, 100, 100],
@@ -40,7 +54,7 @@ let defaultSettings = {
// return (index + 1) * 40;
// },
// colWidths: undefined,
rowHeights: '23px', // 固定行高
rowHeights: 23, // 固定行高
wordWrap: false,// 禁止单元格内容自动换行
//manualColumnMove: true,
@@ -90,25 +104,69 @@ let defaultSettings = {
// return cellProperties;
// },
modifyColWidth: (width: number, col: number) => {
const hot = hotInstance.value
if (!hot) return width
const codeCol = hot.propToCol('code')
// console.log('modifyColWidth',codeCol,width)
return col === codeCol ? (codeColWidth.value ?? width) : width
},
afterChange: (changes: any, source: string) => {
if (!changes || !hotInstance.value) return
if (source !== 'edit' && source !== 'Autofill' && source !== 'UndoRedo') return
const hot = hotInstance.value
const codeCol = hot.propToCol('code')
const hasCodeEdit = changes.some((c: any) => c && (c[1] === 'code' || c[1] === codeCol))
// console.log('afterChange',changes,hasCodeEdit, codeCol)
if (!hasCodeEdit) return
codeColWidth.value = computeCodeColWidth(hot)
// console.log('afterChange',codeColWidth.value)
hot.render()
// console.log('afterChange',codeColWidth.value)
// afterSelection(row1: number, _col1: number, _row2: number, _col2: number) {
// const hot = this as any
// if (selectedRow.value !== null && selectedRow.value !== row1) {
// const colCount = hot.countCols()
// for (let c = 0; c < colCount; c++) {
// const meta = hot.getCellMeta(selectedRow.value, c)
// const classes = (meta.className || '').split(' ').filter(Boolean)
// const idx = classes.indexOf('row-highlight')
// if (idx !== -1) classes.splice(idx, 1)
// hot.setCellMeta(selectedRow.value, c, 'className', classes.join(' '))
// }
// }
// selectedRow.value = row1
// const colCount = hot.countCols()
// for (let c = 0; c < colCount; c++) {
// const meta = hot.getCellMeta(row1, c)
// const classes = (meta.className || '').split(' ').filter(Boolean)
// if (!classes.includes('row-highlight')) classes.push('row-highlight')
// hot.setCellMeta(row1, c, 'className', classes.join(' '))
// }
// hot.render()
// },
//afterDeselect() {
// const hot = this as any
// if (selectedRow.value === null) return
// const colCount = hot.countCols()
// for (let c = 0; c < colCount; c++) {
// const meta = hot.getCellMeta(selectedRow.value, c)
// const classes = (meta.className || '').split(' ').filter(Boolean)
// const idx = classes.indexOf('row-highlight')
// if (idx !== -1) classes.splice(idx, 1)
// hot.setCellMeta(selectedRow.value, c, 'className', classes.join(' '))
// }
// selectedRow.value = null
// hot.render()
//},
// modifyColWidth(width: number, col: number) {
// const hot = hotInstance.value
// if (!hot) return width
// const codeCol = hot.propToCol('code')
// // console.log('modifyColWidth',codeCol,width)
// return col === codeCol ? (codeColWidth.value ?? width) : width
// },
// afterChange(changes: any, source: string) {
// if (!changes || !hotInstance.value) return
// if (source !== 'edit' && source !== 'Autofill' && source !== 'UndoRedo') return
// const hot = hotInstance.value
// const codeCol = hot.propToCol('code')
// const hasCodeEdit = changes.some((c: any) => c && (c[1] === 'code' || c[1] === codeCol))
// // console.log('afterChange',changes,hasCodeEdit, codeCol)
// if (!hasCodeEdit) return
// codeColWidth.value = computeCodeColWidth(hot)
// // console.log('afterChange',codeColWidth.value)
// hot.render()
// // console.log('afterChange',codeColWidth.value)
// },
afterOnCellContextMenu: () => {
// Handsontable 内置右键菜单打开后,关闭自定义菜单
contextMenuRef.value?.hideContextMenu?.()
},
}
// 合并外部 settings 和默认配置
@@ -116,6 +174,12 @@ let hotSettings = {}
// 保留必要的回调函数
const hotInstance = ref<any>(null)
const contextMenuRef = ref<any>(null)
// 处理右键菜单事件
const handleContextMenu = (event: MouseEvent) => {
contextMenuRef.value?.handleContextMenu(event)
}
onMounted(() => {
hotInstance.value = hotTableComponent.value?.hotInstance
@@ -127,15 +191,17 @@ watch(
() => componentProps.settings,
(newSettings) => {
if (!newSettings) return
const merged = {
...defaultSettings,
...newSettings,
}
Object.assign(hotSettings, merged)
hotSettings = merged
// console.log(merged)
},
{ immediate: true }
{ immediate: true,deep:true }
)
const loadData = (rows: any[][]) => {
@@ -143,12 +209,51 @@ const loadData = (rows: any[][]) => {
if (!hotInstance.value) return
// hotInstance.value.loadData(rows.length === 0?defaultData.value:rows)
hotInstance.value.loadData(rows)
console.log('Source Data:', hotInstance.value.getSourceData());
//console.log('Source Data:', hotInstance.value.getSourceData());
}
const updateCodeColWidth = () => {
if (!hotInstance.value) return
codeColWidth.value = computeCodeColWidth(hotInstance.value)
const newWidth = computeCodeColWidth(hotInstance.value)
// 如果宽度没有变化,不需要更新
if (newWidth === codeColWidth.value) return
codeColWidth.value = newWidth
// 查找配置了 code: true 的列
const currentSettings = hotInstance.value.getSettings()
const columns = currentSettings.columns || []
const codeColIndex = columns.findIndex((col: any) => col.code === true)
if (codeColIndex !== null && codeColIndex >= 0) {
// 获取当前列数
const colCount = hotInstance.value.countCols()
const currentColWidths = currentSettings.colWidths
// 构建新的列宽数组
const newColWidths: number[] = []
for (let i = 0; i < colCount; i++) {
if (i === codeColIndex) {
newColWidths[i] = codeColWidth.value
} else {
// 获取其他列的当前宽度
if (Array.isArray(currentColWidths)) {
newColWidths[i] = currentColWidths[i] || 100
} else if (typeof currentColWidths === 'function') {
newColWidths[i] = 100
} else {
newColWidths[i] = currentColWidths || 100
}
}
}
console.log(newColWidths)
// 更新列宽
hotInstance.value.updateSettings({
colWidths: newColWidths
})
}
hotInstance.value.render()
}
defineExpose({ loadData, hotTableComponent, hotInstance, updateCodeColWidth, codeColWidth })
@@ -156,11 +261,15 @@ defineExpose({ loadData, hotTableComponent, hotInstance, updateCodeColWidth, cod
Handsontable.renderers.registerRenderer("db-table", handlerTableRenderer);
Handsontable.renderers.registerRenderer("db-dropdown", handlerDropdownRenderer);
Handsontable.renderers.registerRenderer("db-duplicate", handlerDuplicateCodeRenderer);
</script>
<template>
<hot-table ref="hotTableComponent" :settings="hotSettings"></hot-table>
<div @contextmenu="handleContextMenu">
<hot-table ref="hotTableComponent" :settings="hotSettings"></hot-table>
<ContextMenu ref="contextMenuRef" :hotTableComponent="hotTableComponent" :menuItems="componentProps.contextMenuItems" />
</div>
<!-- <div id="hot-dialog-container" style="display:none">
<div class="ht-dialog-content">
<h3>执行操作</h3>
@@ -214,14 +323,14 @@ Handsontable.renderers.registerRenderer("db-dropdown", handlerDropdownRenderer);
.ht_master .wtHolder::-webkit-scrollbar-thumb:hover {
background-color: #555;
} */
/* 滚动条width */
.ht_master .wtHolder{
/* overflow: hidden !important; */
scrollbar-width: thin;
scrollbar-color: #a6a8ac #ecf0f1;
}
/* dropdown */
.ht-cell-dropdown { display: flex; align-items: center; justify-content: space-between; width: 100%; position: relative; box-sizing: border-box; height: 100%; }
.ht-cell-dropdown { display: flex; align-items: center; justify-content: center; width: 100%; position: relative; box-sizing: border-box; height: 100%; }
.ht-cell-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ht-cell-value:empty::after { content: "\200b"; }
.ht-cell-caret { position: absolute; right: 0; top: 50%; transform: translateY(-50%); width: 0; height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 5px solid #979797; }
@@ -283,66 +392,86 @@ Handsontable.renderers.registerRenderer("db-dropdown", handlerDropdownRenderer);
.hot-dropdown-search-input { width: 100%; padding: 6px 10px; border: 1px solid #d1d5db; border-radius: 4px; font-size: 12px; outline: none; transition: border-color 0.2s; }
.hot-dropdown-search-input:focus { border-color: #3b82f6; }
.hot-dropdown-table-wrapper { overflow: auto; flex: 1; }
.hot-dropdown-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.hot-dropdown-table thead th { position: sticky; top: 0; background: #f9fafb; font-weight: 600; color: #374151; padding: 8px; border-bottom: 1px solid #e5e7eb; text-align: left; }
.hot-dropdown-table { width: 100%; border-collapse: collapse; font-size: 12px; text-align: center; }
.hot-dropdown-table thead th { position: sticky; top: 0; background: #f9fafb; font-weight: 600; color: #374151; padding: 8px; border-bottom: 1px solid #e5e7eb; text-align: center; }
.hot-dropdown-table tbody td { padding: 8px; border-bottom: 1px solid #f3f4f6; color: #374151; }
.hot-dropdown-row { cursor: pointer; }
.hot-dropdown-row:hover { background: #f3f4f6; }
/** 指引线line */
tbody[role="rowgroup"] tr[role="row"] td .ht_nestingLevel_empty{
position: relative;
display: inline-block;
width: 5px;
height: 1px;
order: -2;
/* 整行高亮样式 */
.row-highlight {
background-color: #e9ecfc !important; /* 浅蓝色背景 */
}
tbody[role="rowgroup"] tr[role="row"] td .ht_nestingLevel_empty:last-child {
padding-left: calc(var(--ht-icon-size) + 5px);
/* 确保 Handsontable 右键菜单在 ElDialog 之上 - 必须是全局样式 */
.handsontable.htDropdownMenu:not(.htGhostTable),
.handsontable.htContextMenu:not(.htGhostTable),
.handsontable.htFiltersConditionsMenu:not(.htGhostTable) {
z-index: 9999 !important;
}
tbody[role="rowgroup"] tr[role="row"] td .ht_nestingLevel_empty::before{
content: '';
position: absolute;
top: -13px;
height: 26px;
width: 1px;
border-left: 1px solid #ababab;
.ht-id-cell {
position: relative !important;
z-index: 3 !important;
overflow: visible !important;
}
tbody[role="rowgroup"] tr[role="row"] td .ht_nestingLevel_empty::after{
content: '';
.ht-id-icon {
position: absolute;
top: 0px;
width: 16px;
height: 1px;
border-top: 1px solid #ababab;
}
tbody[role="rowgroup"] tr[role="row"] td .ht_nestingLevel_empty{
padding-left: 7px;
right: -14px;
width: 14px;
height: 14px;
display: none;
cursor: pointer;
z-index: 4;
}
/* 最后一个 ht_nestingLevel_emptyrowHeader 前面的那个) */
tbody[role="rowgroup"] tr[role="row"] td .ht_nestingLevel_empty + .rowHeader {
/* 通过相邻选择器反向选择 */
padding-left: 10px !important
}
/* 选择后面还有 ht_nestingLevel_empty 的元素(不是最后一个) */
tbody[role="rowgroup"] tr[role="row"] td .ht_nestingLevel_empty:has(+ .ht_nestingLevel_empty)::before {
/* 你的样式 */
/* height: 0px; */
}
/* 选择后面还有 ht_nestingLevel_empty 的元素(不是最后一个) */
tbody[role="rowgroup"] tr[role="row"] td .ht_nestingLevel_empty:has(+ .ht_nestingLevel_empty)::after {
/* 你的样式 */
width: 0px !important;
.ht-id-cell.current .ht-id-icon,
.ht-id-cell.area .ht-id-icon {
display: inline-flex;
}
/* 或者用这个:选择后面不是 ht_nestingLevel_empty 的那个 */
tbody[role="rowgroup"] tr[role="row"] td .ht_nestingLevel_empty:not(:has(+ .ht_nestingLevel_empty))::before {
/* height: 13px; */
.handsontable {
--ht-tree-line-color: #7c7c7c;
--ht-tree-line-style: solid;
--ht-tree-line-width: 1px;
--ht-tree-indent: 14px;
}
/** 新树形连接线 */
.handsontable .ht_treeCell {
display: flex;
align-items: stretch;
height: 100%;
}
tbody[role="rowgroup"] tr[role="row"] td .ht_nestingLevel_empty:not(:has(+ .ht_nestingLevel_empty))::after {
/* 你的特殊样式 */
.handsontable .ht_treeIndentLayer {
flex: 0 0 auto;
height: 100%;
}
.handsontable .ht_treeContent {
display: flex;
align-items: center;
gap: 6px;
}
.handsontable .ht_treeToggleSpacer {
display: inline-block;
width: var(--ht-icon-size);
height: var(--ht-icon-size);
flex: 0 0 auto;
}
.handsontable .ht_treeContent {
min-width: 0;
}
/* 整行高亮样式 */
.handsontable td.ht_rowHighlight:not(.current):not(.area) {
background-color: #e9ecfc;
}
</style>