eext-leye/iframe/leye.html

497 lines
19 KiB
HTML
Raw Permalink Normal View History

2026-02-09 11:40:12 +08:00
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>放置 LEYE 库存器件</title>
<link href="/iframe/css/index.css" rel="stylesheet" />
<style>
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
#fixed-window {
width: 1280px;
height: 680px;
border: 1px solid #e5e7eb;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.table-container {
height: calc(680px - 48px - 52px - 64px);
overflow-y: auto;
}
.sticky-header {
position: sticky;
top: 0;
z-index: 10;
}
/* 排序图标样式 */
.sort-icon {
display: inline-block;
margin-left: 4px;
font-size: 10px;
color: #9ca3af;
}
.sort-active {
color: #2563eb !important;
}
</style>
</head>
<body class="bg-gray-100 font-sans text-sm">
<div id="fixed-window" class="bg-gray-50 flex flex-col overflow-hidden">
<header class="flex-shrink-0 bg-white border-b border-gray-200 shadow-sm p-2 text-sm">
<div class="max-w-full mx-auto flex items-center space-x-3">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
<input
id="global-search-input"
type="text"
placeholder="搜索元器件"
class="w-[92%] px-2 py-1 text-sm border-0 focus:ring-0 focus:outline-none placeholder-gray-400"
/>
<button id="search-btn" class="w-[5%] px-3 py-1 bg-blue-600 text-white rounded-md hover:bg-blue-700 text-xs">搜索</button>
</div>
</header>
<div id="main-content" class="flex-grow flex p-3 space-x-3 overflow-hidden">
<aside class="w-56 flex-shrink-0 bg-white border border-gray-200 rounded-lg shadow-md p-3 flex flex-col overflow-hidden">
<h3 class="text-base font-semibold text-gray-800 border-b pb-1 mb-2">筛选类别</h3>
<div id="category-tree" class="flex-grow overflow-y-auto scrollbar-hide space-y-0.5 text-xs">
<div class="cursor-pointer hover:bg-blue-50 rounded p-1" data-value="0">
<input type="radio" name="category" value="0" id="cat-all" class="mr-1 checked:bg-blue-600" />
<label for="cat-all" class="font-bold text-gray-900">全部</label>
</div>
<div class="text-gray-500 p-1" id="category-loading">加载中...</div>
</div>
</aside>
<main class="flex-grow flex flex-col bg-white border border-gray-200 rounded-lg shadow-md overflow-hidden">
<div class="table-container overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-100 sticky-header text-xs uppercase tracking-wider text-gray-600">
<tr>
<th scope="col" class="w-12 px-2 py-2 text-center">ID</th>
<th scope="col" class="w-48 px-3 py-2 text-left">型号</th>
<th scope="col" id="sort-type" class="w-24 px-3 py-2 text-left cursor-pointer hover:bg-gray-200">
类型 <span class="sort-icon"></span>
</th>
<th scope="col" id="sort-value" class="w-20 px-3 py-2 text-left cursor-pointer hover:bg-gray-200">
<span class="sort-icon"></span>
</th>
<th scope="col" class="w-24 px-3 py-2 text-left">封装</th>
<th scope="col" class="w-32 px-3 py-2 text-left">品牌</th>
<th scope="col" id="sort-quantity" class="w-24 px-3 py-2 text-right cursor-pointer hover:bg-gray-200">
余量 <span class="sort-icon"></span>
</th>
<th scope="col" class="w-32 px-3 py-2 text-left">CID</th>
</tr>
</thead>
<tbody id="data-table-body" class="bg-white divide-y divide-gray-200 text-xs">
<tr>
<td colspan="8" class="text-center py-6 text-gray-500" id="table-status">正在加载元器件列表...</td>
</tr>
</tbody>
</table>
</div>
<div class="flex-shrink-0 border-t border-gray-200 bg-gray-50 p-2 flex justify-between items-center text-xs">
<div class="text-gray-600">
<span id="selected-rows-info">未选择行</span>
</div>
<div class="flex items-center space-x-3">
<div class="text-gray-600">总计 <span id="total-items">0</span> 条 | <span id="total-pages">0</span></div>
<div class="flex items-center space-x-1" hidden>
<button
class="px-2 py-0.5 border border-gray-300 rounded-md text-gray-600 hover:bg-gray-200 disabled:opacity-50"
disabled
>
&lt;
</button>
<span class="px-2 py-0.5 bg-blue-600 text-white rounded-md" id="current-page">1</span>
<button class="px-2 py-0.5 border border-gray-300 rounded-md text-gray-600 hover:bg-gray-200" disabled>&gt;</button>
</div>
</div>
</div>
</main>
</div>
<div class="flex-shrink-0 p-3 bg-white border-t border-gray-200 flex justify-end space-x-3 shadow-lg">
<button id="cancel-btn" class="px-4 py-1.5 border border-gray-300 text-gray-700 rounded-md hover:bg-gray-100 text-sm">取消</button>
<button
id="place-btn"
class="px-4 py-1.5 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-4 focus:ring-blue-300 text-sm"
>
放置
</button>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const categoryTree = document.getElementById('category-tree');
const tableBody = document.getElementById('data-table-body');
const placeButton = document.getElementById('place-btn');
const searchButton = document.getElementById('search-btn');
const searchInput = document.getElementById('global-search-input');
const tableStatus = document.getElementById('table-status');
const selectedRowsInfo = document.getElementById('selected-rows-info');
const totalItemsSpan = document.getElementById('total-items');
const totalPagesSpan = document.getElementById('total-pages');
const SERVER = eda.sys_Storage.getExtensionUserConfig('server-host') || 'http://localhost:21816';
let selectedRowData = null;
let mappings = {
footprint: {},
brand: {},
category: {},
};
const unitMap = new Map([
['M', 1e6],
['k', 1e3],
['m', 1e-3],
['u', 1e-6],
['n', 1e-9],
['p', 1e-12],
]);
let sortRules = [];
let currentRawData = [];
function mapIdToName(type, id) {
const key = String(id);
return mappings[type][key] || `ID:${id}`;
}
function parseValue(val) {
if (val === null || val === undefined || val === '') return -Infinity;
const str = String(val).trim();
if (/^\d/.test(str)) {
const numPart = parseFloat(str);
const match = str.match(/[\d.]+\s*([a-zA-Z])/);
if (match && match[1]) {
const unit = match[1];
if (unitMap.has(unit)) {
return numPart * unitMap.get(unit);
}
}
return numPart;
}
return str;
}
async function fetchMappings() {
try {
const [footprintRes, brandRes] = await Promise.all([
eda.sys_ClientUrl.request(SERVER + '/getLeyeFootprint'),
eda.sys_ClientUrl.request(SERVER + '/getLeyeBrand'),
]);
const footprintData = await footprintRes.json();
const brandData = await brandRes.json();
if (footprintData.data) {
footprintData.data.forEach((item) => {
mappings.footprint[String(item.id)] = item.name;
});
}
if (brandData.data) {
brandData.data.forEach((item) => {
mappings.brand[String(item.id)] = item.name;
});
}
} catch (error) {
console.error('获取映射数据失败:', error);
}
}
function renderCategory(item, isChild = false) {
mappings.category[String(item.value)] = item.title;
const div = document.createElement('div');
div.className = `cursor-pointer hover:bg-blue-50 rounded p-1 ${isChild ? 'ml-4' : ''}`;
div.dataset.value = item.value;
div.innerHTML = `
<input type="radio" name="category" value="${item.value}" id="cat-${item.value}" class="mr-1 checked:bg-blue-600">
<label for="cat-${item.value}" class="${isChild ? 'text-gray-600' : 'text-gray-700 font-medium'}">${item.title}</label>
`;
categoryTree.appendChild(div);
if (item.children && item.children.length > 0) {
item.children.forEach((child) => {
mappings.category[String(child.value)] = child.title;
const childDiv = document.createElement('div');
childDiv.className = 'cursor-pointer hover:bg-blue-50 rounded p-1 ml-8';
childDiv.dataset.value = child.value;
childDiv.innerHTML = `
<input type="radio" name="category" value="${child.value}" id="cat-${child.value}" class="mr-1 checked:bg-blue-600">
<label for="cat-${child.value}" class="text-gray-600">${child.title}</label>
`;
categoryTree.appendChild(childDiv);
});
}
}
async function fetchCategories() {
try {
const loadingElement = document.getElementById('category-loading');
if (loadingElement) loadingElement.textContent = '加载中...';
const response = await eda.sys_ClientUrl.request(SERVER + '/getLeyeType');
const result = await response.json();
if (loadingElement) loadingElement.remove();
if (result.success && result.data) {
result.data.forEach((item) => renderCategory(item));
}
} catch (error) {
const loadingElement = document.getElementById('category-loading');
if (loadingElement) loadingElement.textContent = '加载失败';
console.error('获取分类数据失败:', error);
}
}
async function fetchList(type = '', keyword = '') {
selectedRowData = null;
selectedRowsInfo.textContent = '未选择行';
tableBody.innerHTML = `<tr><td colspan="8" class="text-center py-6 text-gray-500">正在搜索元器件...</td></tr>`;
let url = SERVER + '/getLeyeList?pageSize=20&current=1';
if (type && type !== '0') url += `&type=${type}`;
if (keyword) url += `&name=${encodeURIComponent(keyword)}`;
try {
const response = await eda.sys_ClientUrl.request(url);
const result = await response.json();
if (result.success && result.data) {
currentRawData = result.data;
applySortAndRender(result.total, result.pageSize, result.current);
} else {
tableBody.innerHTML = `<tr><td colspan="8" class="text-center py-6 text-gray-500">未找到数据</td></tr>`;
totalItemsSpan.textContent = 0;
totalPagesSpan.textContent = 0;
}
} catch (error) {
tableBody.innerHTML = `<tr><td colspan="8" class="text-center py-6 text-red-500">数据加载失败</td></tr>`;
console.error('获取元器件列表失败:', error);
}
}
function applySortAndRender(total, pageSize, current) {
let displayData = [...currentRawData];
if (sortRules.length > 0) {
displayData.sort((a, b) => {
for (const rule of sortRules) {
let valA, valB;
if (rule.key === 'type') {
valA = mapIdToName('category', a.type);
valB = mapIdToName('category', b.type);
} else if (rule.key === 'value') {
valA = parseValue(a.value);
valB = parseValue(b.value);
if (typeof valA === typeof valB) {
if (typeof valA === 'number') {
if (valA === valB) continue;
return rule.order === 'asc' ? valA - valB : valB - valA;
} else {
const res = valA.localeCompare(valB);
if (res === 0) continue;
return rule.order === 'asc' ? res : -res;
}
}
const mixedRes = (typeof valA === 'number') ? -1 : 1;
return rule.order === 'asc' ? mixedRes : -mixedRes;
} else if (rule.key === 'quantity') {
valA = Number(a.quantity) || 0;
valB = Number(b.quantity) || 0;
}
if (valA < valB) return rule.order === 'asc' ? -1 : 1;
if (valA > valB) return rule.order === 'asc' ? 1 : -1;
}
return 0;
});
}
renderTable(displayData, total, pageSize, current);
updateSortIcons();
}
function renderTable(data, total, pageSize, current) {
let html = '';
if (data.length === 0) {
tableBody.innerHTML = `<tr><td colspan="8" class="text-center py-6 text-gray-500">无数据</td></tr>`;
return;
}
data.forEach((item) => {
const footprintName = mapIdToName('footprint', item.footprint);
const brandName = mapIdToName('brand', item.brand);
const typeName = mapIdToName('category', item.type);
const rowData = JSON.stringify(item);
html += `
<tr class="hover:bg-blue-50 cursor-pointer" data-id="${item.id}" data-row='${rowData}'>
<td class="px-2 py-1.5 text-center text-gray-500">${item.id}</td> <td class="px-3 py-1.5 font-medium text-blue-600">${item.name || '-'}</td>
<td class="px-3 py-1.5 text-left text-gray-700">${typeName || item.type}</td>
<td class="px-3 py-1.5 text-left text-gray-700">${item.value || '-'}</td>
<td class="px-3 py-1.5 text-left text-gray-700">${footprintName}</td>
<td class="px-3 py-1.5 text-left text-gray-700">${brandName}</td>
<td class="px-3 py-1.5 text-right ${item.quantity > 0 ? 'text-green-600' : 'text-red-500'}">${item.quantity}</td>
<td class="px-3 py-1.5 text-left text-gray-500">${item.lcscId || '-'}</td>
</tr>
`;
});
tableBody.innerHTML = html;
totalItemsSpan.textContent = total;
totalPagesSpan.textContent = Math.ceil(total / pageSize);
document.getElementById('current-page').textContent = current;
const firstRow = tableBody.querySelector('tr');
if (firstRow) {
firstRow.classList.add('bg-blue-100');
selectedRowData = JSON.parse(firstRow.dataset.row);
selectedRowsInfo.textContent = '已选择 1 行';
}
}
function updateSortIcons() {
['type', 'value', 'quantity'].forEach((key) => {
const th = document.getElementById(`sort-${key}`);
const icon = th.querySelector('.sort-icon');
const ruleIndex = sortRules.findIndex((r) => r.key === key);
const rule = sortRules[ruleIndex];
if (rule) {
const arrow = rule.order === 'asc' ? '↑' : '↓';
const priority = sortRules.length > 1 ? `(${ruleIndex + 1})` : '';
icon.textContent = arrow + priority;
icon.classList.add('sort-active');
th.classList.add('bg-blue-50');
} else {
icon.textContent = '⇅';
icon.classList.remove('sort-active');
th.classList.remove('bg-blue-50');
}
});
}
const handleSortClick = (key) => {
const existingIndex = sortRules.findIndex((r) => r.key === key);
if (existingIndex > -1) {
if (sortRules[existingIndex].order === 'asc') {
sortRules[existingIndex].order = 'desc';
} else {
sortRules.splice(existingIndex, 1); // 第二次点击 desc 后取消该项排序
}
} else {
sortRules.push({ key, order: 'asc' }); // 添加新排序规则
}
applySortAndRender(totalItemsSpan.textContent, 20, document.getElementById('current-page').textContent);
};
document.getElementById('sort-type').onclick = () => handleSortClick('type');
document.getElementById('sort-value').onclick = () => handleSortClick('value');
document.getElementById('sort-quantity').onclick = () => handleSortClick('quantity');
tableBody.addEventListener('click', function (event) {
let row = event.target.closest('tr');
if (!row || !row.dataset.row) return;
tableBody.querySelectorAll('tr').forEach((r) => r.classList.remove('bg-blue-100'));
row.classList.add('bg-blue-100');
selectedRowData = JSON.parse(row.dataset.row);
selectedRowsInfo.textContent = '已选择 1 行';
});
searchButton.addEventListener('click', function () {
const searchValue = searchInput.value.trim();
const selectedCategoryRadio = document.querySelector('input[name="category"]:checked');
const categoryValue = selectedCategoryRadio ? selectedCategoryRadio.value : '0';
fetchList(categoryValue, searchValue);
});
categoryTree.addEventListener('click', function (event) {
const item = event.target.closest('div[data-value]');
if (item) {
const radio = item.querySelector('input[type="radio"]');
if (radio) radio.checked = true;
fetchList(item.dataset.value, searchInput.value.trim());
}
});
placeButton.addEventListener('click', async function () {
if (selectedRowData) {
if (!selectedRowData.lcscId) {
eda.sys_Message.showToastMessage('无立创商城 CID无法放置', ESYS_ToastMessageType.ERROR);
return;
}
const devices = await eda.lib_Device.getByLcscIds([selectedRowData.lcscId]);
await eda.sys_IFrame.hideIFrame('leye-main');
eda.sys_Message.showToastMessage('请在原理图中点击放置位置', ESYS_ToastMessageType.INFO);
try {
await eda.sch_PrimitiveComponent.placeComponentWithMouse({
uuid: devices[0].uuid,
libraryUuid: '0819f05c4eef4c71ace90d822a990e87',
path: '0819f05c4eef4c71ace90d822a990e87',
});
return;
} catch (e) {
eda.sys_Log.add('call placeComponentWithMouse api fail');
console.log(e);
}
if (eda.sch_Event.isEventListenerAlreadyExist('place_device')) {
eda.sch_Event.removeEventListener('place_device');
eda.sys_Log.add('place_device event listener is already exist');
}
eda.sch_Event.addMouseEventListener('place_device', ESCH_MouseEventType.SELECTED, async () => {
const position = await eda.sch_SelectControl.getCurrentMousePosition();
await eda.sch_PrimitiveComponent.create(
{
uuid: devices[0].uuid,
libraryUuid: '0819f05c4eef4c71ace90d822a990e87',
},
position.x,
position.y,
);
await eda.sys_IFrame.showIFrame('leye-main');
eda.sch_Event.removeEventListener('place_device');
}, true);
}
});
document.getElementById('cancel-btn').addEventListener('click', function () {
eda.sys_IFrame.closeIFrame('leye-main');
});
async function initialize() {
await Promise.all([fetchMappings(), fetchCategories()]);
await fetchList();
}
initialize();
});
</script>
</body>
</html>