eext-leye/iframe/leye.html
2026-02-09 11:40:12 +08:00

497 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>