本次提交对图纸工作台进行了全面的功能重构和交互体验升级,主要涵盖了数据结构优化、核心逻辑增强、以及全新的可视化交互功能。
### 主要更新内容:
#### 1. **核心应用逻辑 (app.js)**
- **数据结构优化**:
- 重构了初始化逻辑,现在每个图片被视为一个独立的 \drawing\ 对象,包含自身的坐标边界和关联图片,使数据模型更清晰。
- **状态管理**:
- 引入 \currentDrawingId\ 状态,用于精确追踪和控制当前显示的图纸。
- **核心功能增强**:
- **自动图纸切换**: 实现了 \getDrawingForItem\ 映射逻辑,当用户在结构树中选择不同层级的件号时,系统能自动切换到其关联的图纸。
- **件号重命名 (handleItemRename)**: 彻底重写了重命名逻辑,现在支持递归更新所有子孙件号的ID和层级关系,保证了复杂结构下数据的一致性。
- **件号更新 (handleItemUpdate)**: 增强了件号创建和更新的鲁棒性,在添加深层级子件号时,会自动创建不存在的父级装配件,简化了操作流程。
- **图片拖拽**: 新增 \updateImagePosition\ 处理器,允许用户在画布上直接拖动图片并更新其世界坐标。
#### 2. **数据编辑器 (components/DataEditor.js)**
- **UI/UX 改进**:
- 大幅扩展了可编辑字段,增加了中文名、英文名、规格、材料、比例和备注等详细属性。
- 界面现在能够智能区分零件和装配件,并对装配件的重量输入框进行只读处理,因为其重量由子件自动汇总计算。
- 为只读字段添加了明确的提示信息,优化了用户体验。
#### 3. **图纸查看器 (components/DrawingViewer.js)**
- **全新交互功能**:
- **画布平移与缩放**: 实现了完整的画布拖拽平移和鼠标滚轮缩放功能,操作更流畅。
- **表格与图片拖拽**: 用户现在可以直接在画布上拖动明细表 (\GraphicalTable\) 和图纸中的图片,实现自由布局。
- **点击创建件号**: 在画布空白处单击,会弹出输入框,允许用户在指定位置快速创建新的件号,并记录其在图纸上的标记位置 (\markerPosition\)。
- **UI 浮层与控件**:
- 在画布上增加了多个交互浮层,包括左上角的图纸-件号绑定工具、右上角的缩放按钮、以及两侧用于切换图纸的箭头按钮。
- 新增了动态输入框 (\inputBox\),用于响应画布点击事件,提升了操作的直观性。
- **渲染与状态**:
- \GraphicalTable\ 和 \<image>\ 元素现在具备了拖拽所需的所有属性和事件处理。
- 内部状态管理重构,以支持新的视图变换 (\iewTransform\) 和交互状态。
#### 4. **主页面 (index.html)**
- **项目依赖**:
- 将 React 的 CDN 依赖切换为本地 \endor\ 目录下的文件,提高了应用的稳定性和加载速度。
- 移除了 Babel 依赖,因为项目已统一使用 \React.createElement\ API,无需 JSX 编译。
- **样式系统**:
- 全面拥抱 Tailwind CSS 的 \@layer\ 规则,对样式进行了分层(theme, base, components, utilities),使 CSS 结构更清晰、更易于维护。
- 定义了全局 CSS 变量,统一了颜色、尺寸等设计规范。
419 lines
24 KiB
HTML
419 lines
24 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>SVG Drawing Table Generator Workbench</title>
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
body {
|
||
font-family: 'Inter', 'Helvetica Neue', 'Arial', sans-serif;
|
||
background-color: #f1f5f9;
|
||
}
|
||
svg text {
|
||
font-family: 'KaiTi', 'SimSun', sans-serif;
|
||
}
|
||
.form-input {
|
||
@apply w-full p-2 border border-slate-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 text-sm;
|
||
}
|
||
.sidebar-item {
|
||
@apply block w-full text-left px-4 py-2.5 text-sm font-medium text-slate-700 rounded-md hover:bg-slate-200 transition-colors;
|
||
}
|
||
.sidebar-item.active {
|
||
@apply bg-indigo-600 text-white hover:bg-indigo-700;
|
||
}
|
||
.data-table th, .data-table td {
|
||
@apply px-3 py-2 text-left text-sm whitespace-nowrap;
|
||
}
|
||
.data-table thead {
|
||
@apply bg-slate-100;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="h-screen w-screen overflow-hidden flex flex-col">
|
||
<header class="bg-white border-b border-slate-200 shadow-sm z-10">
|
||
<div class="max-w-full mx-auto px-6 py-3">
|
||
<h1 class="text-xl font-bold text-slate-800">图纸物料清单工作台</h1>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="flex flex-grow overflow-hidden">
|
||
<!-- Left Sidebar: Table List -->
|
||
<aside class="w-64 bg-white border-r border-slate-200 p-4 flex flex-col">
|
||
<h2 class="text-sm font-semibold text-slate-500 uppercase tracking-wider mb-3">我的表格</h2>
|
||
<div id="table-list" class="flex-grow space-y-1 overflow-y-auto">
|
||
<!-- Table list items will be injected here -->
|
||
</div>
|
||
<button id="add-table-btn" class="mt-4 w-full flex items-center justify-center px-4 py-2 border border-dashed border-slate-300 text-sm font-medium rounded-md text-slate-700 hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||
+ 新建表格
|
||
</button>
|
||
</aside>
|
||
|
||
<!-- Center: SVG Preview -->
|
||
<main class="flex-grow bg-slate-50 p-6 overflow-auto">
|
||
<div id="svg-container" class="bg-white w-full h-full rounded-lg shadow-inner border border-slate-200 p-4">
|
||
<svg id="drawing-svg" width="100%" height="100%"></svg>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- Right Sidebar: Editor -->
|
||
<aside id="editor-sidebar" class="w-96 bg-white border-l border-slate-200 p-6 flex flex-col overflow-y-auto">
|
||
<!-- Form for adding/editing a row -->
|
||
<div id="editor-form-container">
|
||
<h2 id="editor-title" class="text-lg font-semibold text-slate-800 mb-4">添加新物料行</h2>
|
||
<form id="entry-form" class="grid grid-cols-2 gap-4 text-sm">
|
||
<div>
|
||
<label for="input-jianhao" class="block text-xs font-medium text-slate-600">件号</label>
|
||
<input type="text" id="input-jianhao" class="form-input mt-1">
|
||
</div>
|
||
<div class="col-span-2">
|
||
<label for="input-tuhao" class="block text-xs font-medium text-slate-600">图号或标准号</label>
|
||
<input type="text" id="input-tuhao" class="form-input mt-1">
|
||
</div>
|
||
<div>
|
||
<label for="input-chinese-name" class="block text-xs font-medium text-slate-600">中文名称</label>
|
||
<input type="text" id="input-chinese-name" class="form-input mt-1">
|
||
</div>
|
||
<div>
|
||
<label for="input-english-name" class="block text-xs font-medium text-slate-600">英文名称</label>
|
||
<input type="text" id="input-english-name" class="form-input mt-1">
|
||
</div>
|
||
<div>
|
||
<label for="input-shuliang" class="block text-xs font-medium text-slate-600">数量</label>
|
||
<input type="text" id="input-shuliang" class="form-input mt-1">
|
||
</div>
|
||
<div>
|
||
<label for="input-cailiao" class="block text-xs font-medium text-slate-600">材料</label>
|
||
<input type="text" id="input-cailiao" class="form-input mt-1">
|
||
</div>
|
||
<div>
|
||
<label for="input-dan" class="block text-xs font-medium text-slate-600">单重 (kg)</label>
|
||
<input type="text" id="input-dan" class="form-input mt-1">
|
||
</div>
|
||
<div>
|
||
<label for="input-zong" class="block text-xs font-medium text-slate-600">总重 (kg)</label>
|
||
<input type="text" id="input-zong" class="form-input mt-1">
|
||
</div>
|
||
<div class="col-span-2">
|
||
<label for="input-beizhu" class="block text-xs font-medium text-slate-600">备注</label>
|
||
<input type="text" id="input-beizhu" class="form-input mt-1">
|
||
</div>
|
||
<div class="col-span-2">
|
||
<button type="submit" id="form-submit-btn" class="mt-2 w-full inline-flex justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||
添加行
|
||
</button>
|
||
<button type="button" id="form-cancel-btn" class="mt-2 w-full hidden inline-flex justify-center px-4 py-2 border border-slate-300 text-sm font-medium rounded-md shadow-sm text-slate-700 bg-white hover:bg-slate-50">
|
||
取消编辑
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<!-- Data Management Table -->
|
||
<div class="mt-8 flex-grow flex flex-col overflow-hidden">
|
||
<h2 class="text-lg font-semibold text-slate-800 mb-4">当前数据</h2>
|
||
<div class="overflow-y-auto border border-slate-200 rounded-lg">
|
||
<table class="w-full data-table">
|
||
<thead class="sticky top-0">
|
||
<tr>
|
||
<th>件号</th>
|
||
<th>名称</th>
|
||
<th>数量</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="data-table-body" class="divide-y divide-slate-200">
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
|
||
<script>
|
||
const headerTemplate = { "template_name": "标准物料清单-底部表头", "row_height": 8.0, "header_height": 14.0, "column_boundaries": [0.0, 15.0, 45.0, 100.0, 110.0, 140.0, 150.0, 160.0, 185.0], "header_definition": { "lines": [ {"start": [0.0, 14.0], "end": [185.0, 14.0]}, {"start": [160.0, 6.5], "end": [140.0, 6.5]}, {"start": [110.0, 0.0], "end": [110.0, 14.0]}, {"start": [15.0, 14.0], "end": [15.0, 0.0]}, {"start": [45.0, 0.0], "end": [45.0, 14.0]}, {"start": [100.0, 0.0], "end": [100.0, 14.0]}, {"start": [140.0, 0.0], "end": [140.0, 14.0]}, {"start": [160.0, 0.0], "end": [160.0, 14.0]}, {"start": [150.0, 6.5], "end": [150.0, 14.0]}, {"start": [0.0, 0.0], "end": [185.0, 0.0]}, {"start": [185.0, 0.0], "end": [185.0, 14.0]}], "texts": [ {"content": "总", "relative_pos": [153.62, 10.23], "height": 3.5}, {"content": "单", "relative_pos": [143.79, 10.02], "height": 3.5}, {"content": "TOTAL", "relative_pos": [152.64, 7.45], "height": 2.0}, {"content": "SINGLE", "relative_pos": [141.89, 7.45], "height": 2.0}, {"content": "备 注", "relative_pos": [169.33, 6.8], "height": 3.5, "alignment": "BOTTOM_CENTER"}, {"content": "材 料", "relative_pos": [121.56, 6.56], "height": 3.5}, {"content": "数量", "relative_pos": [102.5, 6.56], "height": 3.5}, {"content": "名 称", "relative_pos": [67.3, 6.56], "height": 3.5}, {"content": "件 号", "relative_pos": [4.43, 6.56], "height": 3.5}, {"content": "图号或标准号", "relative_pos": [22.43, 6.53], "height": 3.5}, {"content": "MAT'L", "relative_pos": [122.94, 2.33], "height": 2.0}, {"content": "QTY.", "relative_pos": [102.89, 2.33], "height": 2.0}, {"content": "REMARKS", "relative_pos": [165.48, 2.3], "height": 2.0}, {"content": "PARTS. NAME.", "relative_pos": [65.06, 1.76], "height": 2.0}, {"content": "DWG NO. OR STD. NO.", "relative_pos": [19.43, 1.76], "height": 2.0}, {"content": "PARTS .NO.", "relative_pos": [7.77, 1.76], "height": 2.0, "alignment": "BOTTOM_CENTER"}, {"content": "MASS(kg)", "relative_pos": [148.32, 1.7], "height": 2.0}, {"content": "质量", "relative_pos": [142.49, 0.87], "height": 3.5}] } };
|
||
const columnsTemplate = { "row_height": 8.0, "column_definitions": [ {"name": "件号", "relative_x_start": 0.0, "text_definitions": [{"data_key": "main", "relative_pos": [5.82, 1.46], "height": 3.5, "alignment": "BOTTOM_LEFT"}]}, {"name": "图号或标准号", "relative_x_start": 15.0, "text_definitions": [{"data_key": "main", "relative_pos": [14.77, 1.15], "height": 3.0, "alignment": "BOTTOM_CENTER"}]}, {"name": "名称", "relative_x_start": 45.0, "text_definitions": [{"data_key": "chinese_name", "relative_pos": [1.76, 3.88], "height": 3.5}, {"data_key": "english_name", "relative_pos": [1.79, 1.0], "height": 2.0}, {"data_key": "specification", "relative_pos": [18.68, 3.9], "height": 3.0}]}, {"name": "数量", "relative_x_start": 100.0, "text_definitions": [{"data_key": "main", "relative_pos": [4.97, 1.37], "height": 3.0, "alignment": "BOTTOM_CENTER"}]}, {"name": "材料", "relative_x_start": 110.0, "text_definitions": [{"data_key": "main", "relative_pos": [15.16, 1.12], "height": 3.5, "alignment": "BOTTOM_CENTER"}]}, {"name": "单", "relative_x_start": 140.0, "text_definitions": [{"data_key": "main", "relative_pos": [5.06, 1.42], "height": 3.0, "alignment": "BOTTOM_CENTER"}]}, {"name": "总", "relative_x_start": 150.0, "text_definitions": [{"data_key": "main", "relative_pos": [5.06, 1.42], "height": 3.0, "alignment": "BOTTOM_CENTER"}]}, {"name": "备注", "relative_x_start": 160.0, "text_definitions": [{"data_key": "main", "relative_pos": [12.06, 1.42], "height": 3.0, "alignment": "BOTTOM_CENTER"}]}] };
|
||
|
||
// --- Application State ---
|
||
let state = {
|
||
tables: [
|
||
{
|
||
id: Date.now(),
|
||
name: "主装配图物料清单",
|
||
data: [
|
||
{"件号": {"main": "1"}, "图号或标准号": {"main": "JB/T 1001"}, "名称": {"chinese_name": "轴承座", "english_name": "Bearing Seat"}, "数量": {"main": "2"}, "材料": {"main": "HT200"}, "单": {"main": "15.5"}, "总": {"main": "31.0"}, "备注": {"main": "外购"}},
|
||
{"件号": {"main": "2"}, "图号或标准号": {"main": "GB/T 5783"}, "名称": {"chinese_name": "六角螺栓", "english_name": "Hex Bolt"}, "数量": {"main": "8"}, "材料": {"main": "Q235"}, "单": {"main": "0.5"}, "总": {"main": "4.0"}, "备注": {"main": "M8x20"}}
|
||
]
|
||
}
|
||
],
|
||
activeTableId: null,
|
||
editingRowIndex: null
|
||
};
|
||
state.activeTableId = state.tables[0].id;
|
||
|
||
// --- DOM Elements ---
|
||
const tableList = document.getElementById('table-list');
|
||
const addTableBtn = document.getElementById('add-table-btn');
|
||
const entryForm = document.getElementById('entry-form');
|
||
const editorSidebar = document.getElementById('editor-sidebar');
|
||
const dataTableBody = document.getElementById('data-table-body');
|
||
const editorTitle = document.getElementById('editor-title');
|
||
const formSubmitBtn = document.getElementById('form-submit-btn');
|
||
const formCancelBtn = document.getElementById('form-cancel-btn');
|
||
|
||
|
||
// --- Main App Logic ---
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
addTableBtn.addEventListener('click', addNewTable);
|
||
entryForm.addEventListener('submit', handleFormSubmit);
|
||
formCancelBtn.addEventListener('click', cancelEdit);
|
||
renderApp();
|
||
});
|
||
|
||
function getActiveTable() {
|
||
return state.tables.find(t => t.id === state.activeTableId);
|
||
}
|
||
|
||
function renderApp() {
|
||
if (!getActiveTable() && state.tables.length > 0) {
|
||
state.activeTableId = state.tables[0].id;
|
||
}
|
||
renderTableList();
|
||
const activeTable = getActiveTable();
|
||
if (activeTable) {
|
||
editorSidebar.classList.remove('hidden');
|
||
renderEditor(activeTable.data);
|
||
renderSVG(activeTable.data);
|
||
} else {
|
||
editorSidebar.classList.add('hidden');
|
||
document.getElementById('drawing-svg').innerHTML = '';
|
||
}
|
||
}
|
||
|
||
// --- Left Sidebar Logic ---
|
||
function renderTableList() {
|
||
tableList.innerHTML = '';
|
||
state.tables.forEach(table => {
|
||
const button = document.createElement('button');
|
||
button.className = `sidebar-item ${table.id === state.activeTableId ? 'active' : ''}`;
|
||
button.textContent = table.name;
|
||
button.dataset.id = table.id;
|
||
button.addEventListener('click', () => {
|
||
state.activeTableId = table.id;
|
||
state.editingRowIndex = null; // Reset editing state when switching tables
|
||
renderApp();
|
||
});
|
||
tableList.appendChild(button);
|
||
});
|
||
}
|
||
|
||
function addNewTable() {
|
||
const newName = prompt("请输入新表格的名称:", `新物料清单 ${state.tables.length + 1}`);
|
||
if (newName) {
|
||
const newTable = {
|
||
id: Date.now(),
|
||
name: newName,
|
||
data: []
|
||
};
|
||
state.tables.push(newTable);
|
||
state.activeTableId = newTable.id;
|
||
renderApp();
|
||
}
|
||
}
|
||
|
||
// --- Right Sidebar Logic ---
|
||
function renderEditor(data) {
|
||
renderManagementTable(data);
|
||
if (state.editingRowIndex !== null) {
|
||
const rowData = data[state.editingRowIndex];
|
||
populateForm(rowData);
|
||
editorTitle.textContent = `编辑行: ${rowData['件号'].main}`;
|
||
formSubmitBtn.textContent = '更新行';
|
||
formCancelBtn.classList.remove('hidden');
|
||
} else {
|
||
entryForm.reset();
|
||
editorTitle.textContent = '添加新物料行';
|
||
formSubmitBtn.textContent = '添加行';
|
||
formCancelBtn.classList.add('hidden');
|
||
}
|
||
}
|
||
|
||
function handleFormSubmit(e) {
|
||
e.preventDefault();
|
||
const activeTable = getActiveTable();
|
||
if (!activeTable) return;
|
||
|
||
const newRow = {
|
||
"件号": { "main": document.getElementById('input-jianhao').value },
|
||
"图号或标准号": { "main": document.getElementById('input-tuhao').value },
|
||
"名称": { "chinese_name": document.getElementById('input-chinese-name').value, "english_name": document.getElementById('input-english-name').value },
|
||
"数量": { "main": document.getElementById('input-shuliang').value },
|
||
"材料": { "main": document.getElementById('input-cailiao').value },
|
||
"单": { "main": document.getElementById('input-dan').value },
|
||
"总": { "main": document.getElementById('input-zong').value },
|
||
"备注": { "main": document.getElementById('input-beizhu').value },
|
||
};
|
||
|
||
if (state.editingRowIndex !== null) {
|
||
activeTable.data[state.editingRowIndex] = newRow;
|
||
state.editingRowIndex = null;
|
||
} else {
|
||
activeTable.data.push(newRow);
|
||
}
|
||
renderApp();
|
||
}
|
||
|
||
function populateForm(rowData) {
|
||
document.getElementById('input-jianhao').value = rowData['件号'].main || '';
|
||
document.getElementById('input-tuhao').value = rowData['图号或标准号'].main || '';
|
||
document.getElementById('input-chinese-name').value = rowData['名称'].chinese_name || '';
|
||
document.getElementById('input-english-name').value = rowData['名称'].english_name || '';
|
||
document.getElementById('input-shuliang').value = rowData['数量'].main || '';
|
||
document.getElementById('input-cailiao').value = rowData['材料'].main || '';
|
||
document.getElementById('input-dan').value = rowData['单'].main || '';
|
||
document.getElementById('input-zong').value = rowData['总'].main || '';
|
||
document.getElementById('input-beizhu').value = rowData['备注'].main || '';
|
||
}
|
||
|
||
function cancelEdit() {
|
||
state.editingRowIndex = null;
|
||
renderApp();
|
||
}
|
||
|
||
function renderManagementTable(data) {
|
||
dataTableBody.innerHTML = '';
|
||
if(!data) return;
|
||
data.forEach((rowData, index) => {
|
||
const tr = document.createElement('tr');
|
||
tr.className = "hover:bg-slate-50";
|
||
tr.innerHTML = `
|
||
<td class="font-medium text-slate-800">${rowData['件号'].main}</td>
|
||
<td>${rowData['名称'].chinese_name || ''}</td>
|
||
<td>${rowData['数量'].main}</td>
|
||
<td class="flex items-center space-x-2">
|
||
<button class="text-indigo-600 hover:text-indigo-800 text-xs" data-index="${index}">编辑</button>
|
||
<button class="text-red-600 hover:text-red-800 text-xs" data-index="${index}">删除</button>
|
||
</td>
|
||
`;
|
||
dataTableBody.appendChild(tr);
|
||
});
|
||
|
||
dataTableBody.querySelectorAll('button').forEach(button => {
|
||
button.addEventListener('click', (e) => {
|
||
const index = parseInt(e.target.dataset.index, 10);
|
||
if (e.target.textContent === '删除') {
|
||
if(confirm(`确定要删除 件号 ${data[index]['件号'].main} 吗?`)) {
|
||
getActiveTable().data.splice(index, 1);
|
||
renderApp();
|
||
}
|
||
} else if (e.target.textContent === '编辑') {
|
||
state.editingRowIndex = index;
|
||
renderApp();
|
||
editorSidebar.scrollTop = 0; // scroll to top
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// --- Center SVG Logic ---
|
||
function renderSVG(drawingData) {
|
||
const svgNS = "http://www.w3.org/2000/svg";
|
||
const svg = document.getElementById('drawing-svg');
|
||
svg.innerHTML = '';
|
||
if (!drawingData) return;
|
||
|
||
const strokeColor = "#334155", strokeWidth = 0.3, textColor = "#0f172a", margin = 10;
|
||
const { header_height, row_height } = headerTemplate;
|
||
const totalRows = drawingData.length;
|
||
const tableWidth = Math.max(...headerTemplate.column_boundaries);
|
||
const tableHeight = header_height + (totalRows * row_height);
|
||
|
||
svg.setAttribute('viewBox', `0 0 ${tableWidth + 2 * margin} ${tableHeight + 2 * margin}`);
|
||
const mainGroup = document.createElementNS(svgNS, 'g');
|
||
mainGroup.setAttribute('transform', `translate(${margin}, ${tableHeight + margin}) scale(1, -1)`);
|
||
svg.appendChild(mainGroup);
|
||
|
||
const bodyGroup = document.createElementNS(svgNS, 'g');
|
||
mainGroup.appendChild(bodyGroup);
|
||
|
||
drawingData.forEach((rowData, rowIndex) => {
|
||
const rowY = header_height + ((totalRows - 1 - rowIndex) * row_height);
|
||
const rowGroup = document.createElementNS(svgNS, 'g');
|
||
rowGroup.setAttribute('transform', `translate(0, ${rowY})`);
|
||
bodyGroup.appendChild(rowGroup);
|
||
|
||
const lineEl = document.createElementNS(svgNS, 'line');
|
||
lineEl.setAttribute('x1', headerTemplate.column_boundaries[0]);
|
||
lineEl.setAttribute('y1', 0);
|
||
lineEl.setAttribute('x2', tableWidth);
|
||
lineEl.setAttribute('y2', 0);
|
||
lineEl.setAttribute('stroke', strokeColor);
|
||
lineEl.setAttribute('stroke-width', strokeWidth);
|
||
rowGroup.appendChild(lineEl);
|
||
|
||
columnsTemplate.column_definitions.forEach(colDef => {
|
||
colDef.text_definitions.forEach(textDef => {
|
||
const dataItem = rowData[colDef.name];
|
||
if (dataItem && dataItem[textDef.data_key] != null) {
|
||
const content = dataItem[textDef.data_key];
|
||
const x = colDef.relative_x_start + textDef.relative_pos[0];
|
||
const y = textDef.relative_pos[1];
|
||
const textEl = createSvgText(content, x, y, textDef.height, textColor, textDef.alignment);
|
||
rowGroup.appendChild(textEl);
|
||
}
|
||
});
|
||
});
|
||
});
|
||
|
||
headerTemplate.column_boundaries.forEach(xPos => {
|
||
if (xPos <= tableWidth) {
|
||
const lineEl = document.createElementNS(svgNS, 'line');
|
||
lineEl.setAttribute('x1', xPos);
|
||
lineEl.setAttribute('y1', header_height);
|
||
lineEl.setAttribute('x2', xPos);
|
||
lineEl.setAttribute('y2', tableHeight);
|
||
lineEl.setAttribute('stroke', strokeColor);
|
||
lineEl.setAttribute('stroke-width', strokeWidth);
|
||
bodyGroup.appendChild(lineEl);
|
||
}
|
||
});
|
||
|
||
const headerGroup = document.createElementNS(svgNS, 'g');
|
||
headerGroup.setAttribute('transform', 'translate(0, 0)');
|
||
mainGroup.appendChild(headerGroup);
|
||
|
||
headerTemplate.header_definition.lines.forEach(line => {
|
||
const lineEl = document.createElementNS(svgNS, 'line');
|
||
lineEl.setAttribute('x1', line.start[0]);
|
||
lineEl.setAttribute('y1', line.start[1]);
|
||
lineEl.setAttribute('x2', line.end[0]);
|
||
lineEl.setAttribute('y2', line.end[1]);
|
||
lineEl.setAttribute('stroke', strokeColor);
|
||
lineEl.setAttribute('stroke-width', strokeWidth);
|
||
headerGroup.appendChild(lineEl);
|
||
});
|
||
|
||
headerTemplate.header_definition.texts.forEach(text => {
|
||
const textEl = createSvgText(text.content, text.relative_pos[0], text.relative_pos[1], text.height, textColor, text.alignment);
|
||
headerGroup.appendChild(textEl);
|
||
});
|
||
}
|
||
|
||
function createSvgText(content, x, y, height, color, alignment = 'BOTTOM_LEFT') {
|
||
const svgNS = "http://www.w3.org/2000/svg";
|
||
const textEl = document.createElementNS(svgNS, 'text');
|
||
textEl.setAttribute('x', x);
|
||
textEl.setAttribute('y', y);
|
||
textEl.setAttribute('font-size', height);
|
||
textEl.setAttribute('fill', color);
|
||
textEl.setAttribute('transform', 'scale(1, -1)');
|
||
textEl.setAttribute('y', -y);
|
||
textEl.setAttribute('text-anchor', (alignment === 'BOTTOM_CENTER') ? 'middle' : (alignment === 'BOTTOM_RIGHT' ? 'end' : 'start'));
|
||
textEl.textContent = content;
|
||
return textEl;
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|
||
|