migrate from github

This commit is contained in:
2026-07-15 22:51:56 +08:00
commit a1cccc8068
215 changed files with 38318 additions and 0 deletions
@@ -0,0 +1,260 @@
<template>
<div class="tree-table-scroll">
<div
class="tree-table"
:style="{
'--tree-toggle-width': `${resolvedToggleWidth}px`,
'--tree-data-template': resizableDataTemplate
}"
>
<!-- Header -->
<div class="tree-head">
<div class="tree-toggle-head list-cell list-cell--head" />
<div class="tree-data-head">
<div
v-for="column in effectiveDataColumns"
:key="`tree-head-${column.key}`"
class="list-cell list-cell--head"
:class="[column.className, alignClass(column.headerAlign || column.align)]"
>
<slot :name="`header-${column.key}`" :column="column">
{{ column.label }}
</slot>
<span
class="col-resize-handle"
@mousedown="startResize($event, column)"
/>
</div>
</div>
</div>
<!-- Body -->
<div class="tree-body" v-loading="treeLoading">
<div
v-for="(row, rowIndex) in treeFlatRows"
:key="resolveRowKey(row.raw, rowIndex)"
class="tree-row"
>
<!-- Sticky indent/toggle cell -->
<div class="tree-toggle-cell">
<div
class="tree-toggle-inner-content"
:style="{ paddingLeft: `${row.level * treeIndent + treeBaseIndent}px` }"
>
<button
v-if="row.hasChildren"
class="tree-toggle"
type="button"
@click.stop="toggleTreeNode(row)"
>
<el-icon :size="14">
<Minus v-if="row.expanded" />
<Plus v-else />
</el-icon>
</button>
<span v-else class="tree-leaf-indicator" />
</div>
</div>
<!-- Data cells -->
<div class="tree-data-row">
<div
v-for="(column, columnIndex) in effectiveDataColumns"
:key="`${resolveRowKey(row.raw, rowIndex)}-${column.key}`"
class="list-cell list-cell--tree"
:class="[column.className, alignClass(column.align)]"
>
<template v-if="isTreeColumn(column, columnIndex)">
<div class="tree-cell">
<slot name="tree-cell" :row="row.raw" :level="row.level">
<span class="tree-node-label">{{ row.raw?.[treeColumnResolvedKey] ?? '-' }}</span>
</slot>
</div>
</template>
<template v-else-if="column.isIndexColumn">
<slot
:name="`cell-${column.key}`"
:row="row.raw"
:row-index="rowIndex"
:column="column"
:value="row.raw?.[column.key]"
>
{{ resolveSerialNumber(rowIndex, currentPage, pageSize) }}
</slot>
</template>
<template v-else>
<slot
:name="`cell-${column.key}`"
:row="row.raw"
:row-index="rowIndex"
:column="column"
:value="row.raw?.[column.key]"
>
{{ row.raw?.[column.key] ?? '-' }}
</slot>
</template>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { Plus, Minus } from '@element-plus/icons-vue';
import { useColumnResize } from '@/composables/list/useColumnResize.js';
const props = defineProps({
treeToggleWidth: { type: Number, default: 81 },
treeDataColumns: { type: Array, default: () => [] },
treeDataGridTemplate: { type: String, default: '1fr' },
currentPage: { type: Number, default: 1 },
pageSize: { type: Number, default: 10 },
treeLoading: { type: Boolean, default: false },
treeFlatRows: { type: Array, default: () => [] },
maxTreeIndentWidth: { type: Number, default: 0 },
toggleScrollLeft: { type: Number, default: 0 },
treeIndent: { type: Number, default: 16 },
treeBaseIndent: { type: Number, default: 6 },
treeColumnResolvedKey: { type: String, default: 'label' },
resolveRowKey: { type: Function, required: true },
alignClass: { type: Function, required: true },
toggleTreeNode: { type: Function, required: true },
isTreeColumn: { type: Function, required: true },
buildGridTemplate: { type: Function, required: true },
resolveSerialNumber: { type: Function, required: true },
});
defineEmits(['toggle-scroll']);
const resolvedToggleWidth = computed(() =>
Math.max(props.treeToggleWidth, props.maxTreeIndentWidth)
);
const treeDataColumnsRef = computed(() => props.treeDataColumns);
const { effectiveColumns: effectiveDataColumns, gridTemplateColumns: resizableDataTemplate, startResize } =
useColumnResize(treeDataColumnsRef, (cols) => props.buildGridTemplate(cols));
</script>
<style scoped>
.tree-table-scroll {
overflow-x: auto;
}
.tree-table {
display: flex;
flex-direction: column;
min-width: max-content;
width: 100%;
}
.tree-head {
display: grid;
grid-template-columns: var(--tree-toggle-width) 1fr;
}
.tree-toggle-head {
justify-content: center;
position: sticky;
left: 0;
z-index: 2;
}
.tree-data-head {
display: grid;
grid-template-columns: var(--tree-data-template);
}
.tree-body {
display: flex;
flex-direction: column;
}
.tree-row {
display: grid;
grid-template-columns: var(--tree-toggle-width) 1fr;
}
.tree-toggle-cell {
position: sticky;
left: 0;
z-index: 1;
background: var(--list-cell-bg, var(--bg-white));
border-right: 1px solid var(--list-cell-border, var(--border-color));
border-bottom: 1px solid var(--list-cell-border, #d6dbe3);
display: flex;
align-items: center;
}
.tree-toggle-inner-content {
display: flex;
align-items: center;
height: 100%;
padding: 12px 14px;
box-sizing: border-box;
white-space: nowrap;
}
.tree-data-row {
display: grid;
grid-template-columns: var(--tree-data-template);
}
.list-cell--tree {
border-bottom: 1px solid var(--list-cell-border, #d6dbe3);
}
.tree-row:last-child .tree-toggle-cell,
.tree-row:last-child .list-cell--tree {
border-bottom: none;
}
.tree-cell {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
box-sizing: border-box;
}
.tree-toggle {
width: 22px;
height: 22px;
flex-shrink: 0;
border: 1px solid var(--border-color);
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--list-cell-text, var(--text-dark));
background: var(--list-cell-bg, var(--bg-white));
cursor: pointer;
}
.tree-toggle:hover {
color: var(--primary-color);
border-color: var(--primary-color);
}
.tree-leaf-indicator {
width: 6px;
height: 6px;
border-radius: 999px;
background: #9aa4b2;
display: inline-block;
flex-shrink: 0;
}
.tree-node-label {
font-size: 14px;
color: var(--text-medium);
}
</style>
<style>
@import '@/styles/list-cell.css';
@import '@/styles/column-resize.css';
</style>