PageSourceSearch

https://poe2lens.com/poe2_base_price.html

html poe2lens.com collected 2026-10-02 01:39:37 UTC 85,884 bytes, 1,836 lines download raw bytes

1<!DOCTYPE html>
2<html lang="zh-CN">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <meta name="description" content="流放之路2基础物品价格查询,包括武器、护甲、饰品等基础装备的实时价格信息">
7    <meta name="keywords" content="POE2,流放之路2,基础物品,装备价格,武器,护甲">
8    <title>基底物品价格 - POE2 Base Items | poe2lens</title>
9    <link rel="icon" type="image/x-icon" href="/favicon.ico">
10    <link rel="canonical" href="https://poe2lens.com/poe2_base_price.html" />
11    <link rel="stylesheet" href="/static/css/page_helper.css">
12    
13    
13<script src="/js/deferred_analytics.js" defer></script>
13
14    
14<script src="/js/deferred_baidu_push.js" defer></script>
14
15    <!-- 结构化数据(Schema.org)- WebSite -->
16    
16<script type="application/ld+json">
17    {
18      "@context": "https://schema.org",
19      "@type": "WebSite",
20      "name": "poe2lens",
21      "description": "流放之路2全能工具箱,提供物价查询、倒货计算、宝石利润分析等功能",
22      "url": "https://poe2lens.com/",
23      "potentialAction": {
24        "@type": "SearchAction",
25        "target": "https://poe2lens.com/poe2_base_price.html?search={search_term_string}",
26        "query-input": "required name=search_term_string"
27      }
28    }
29    </script>
29
30    
31    <!-- 结构化数据(Schema.org)- BreadcrumbList -->
32    
32<script type="application/ld+json">
33    {
34      "@context": "https://schema.org",
35      "@type": "BreadcrumbList",
36      "itemListElement": [
37        {
38          "@type": "ListItem",
39          "position": 1,
40          "name": "首页",
41          "item": "https://poe2lens.com/"
42        },
43        {
44          "@type": "ListItem",
45          "position": 2,
46          "name": "基底物品价格",
47          "item": "https://poe2lens.com/poe2_base_price.html"
48        }
49      ]
50    }
51    </script>
51
52    
53    <link rel="stylesheet" href="/static/vendor/font-awesome/css/all.min.css">
54    <!-- 使用本地axios文件 -->
55    
55<script src="/static/js/axios.min.js" onerror="console.error('axios加载失败');"></script>
55
56    
57    <!-- Chart.js for price history charts -->
58    
58<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
58
59    
60    <style>
61        * { box-sizing: border-box; }
62        body {
63            margin: 0;
64            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
65            background: #111827;
66            color: #e5e7eb;
67        }
68        .page-wrap {
69            max-width: var(--page-max-width, 1440px);
70            margin: 0 auto;
71            padding: var(--page-pad-top, 5.25rem) var(--page-pad-x, 1rem) 2rem;
72        }
73        .page-header h1 { margin: 0 0 0.35rem; font-size: 1.5rem; color: #fff; }
74        .page-header p { margin: 0; color: #9ca3af; font-size: 0.85rem; line-height: 1.45; }
75        .meta-bar {
76            display: flex; flex-wrap: wrap; gap: 0.65rem 1.25rem; align-items: center;
77            margin: 0.85rem 0 0.9rem; color: #9ca3af; font-size: 0.8rem;
78        }
79        .unit-toggle {
80            display: inline-flex; border: 1px solid #374151; border-radius: 0.35rem;
81            overflow: hidden; background: #111827;
82        }
83        .unit-toggle button {
84            background: transparent; border: none; color: #9ca3af;
85            padding: 0.28rem 0.7rem; font-size: 0.78rem; font-weight: 600;
86            cursor: pointer; line-height: 1.2;
87        }
88        .unit-toggle button + button { border-left: 1px solid #374151; }
89        .unit-toggle button:hover { color: #fff; background: #1f2937; }
90        .unit-toggle button.active {
91            background: #1e3a5f; color: #93c5fd;
92        }
93        .rates-hint {
94            color: #9ca3af; font-size: 0.8rem;
95            display: inline-flex; flex-wrap: wrap; gap: 0.4rem; align-items: center;
96        }
97        .rate-pill {
98            display: inline-flex; align-items: baseline; gap: 0.25rem;
99            padding: 0.15rem 0.5rem; border-radius: 4px;
100            background: #1f2937; border: 1px solid #374151;
101            font-variant-numeric: tabular-nums;
102        }
103        .rate-pill strong { color: #fbbf24; font-weight: 600; }
104        .rate-pill .rate-sub { color: #6b7280; font-size: 0.72rem; }
105        .league-badge {
106            display: inline-flex; align-items: center;
107            background: #1f2937; border: 1px solid #4b5563;
108            color: #93c5fd; border-radius: 0.35rem;
109            padding: 0.2rem 0.55rem; font-weight: 600;
110        }
111        .meta-count { color: #6b7280; margin-left: 0.35rem; }
112        .toolbar {
113            display: flex; flex-wrap: wrap; gap: 0.65rem; align-items: center;
114            margin-bottom: 0.85rem;
115        }
116        .toolbar input[type="search"],
117        .toolbar input[type="number"] {
118            background: #1f2937; border: 1px solid #374151; color: #fff;
119            border-radius: 0.375rem; padding: 0.5rem 0.7rem; font-size: 0.88rem;
120        }
121        .toolbar input[type="search"] { flex: 1 1 200px; min-width: 150px; }
122        .toolbar input[type="number"] { width: 5.5rem; }
123        .toolbar input:focus {
124            outline: none; border-color: #60a5fa;
125            box-shadow: 0 0 0 1px #60a5fa;
126        }
127        .toolbar label {
128            color: #9ca3af; font-size: 0.82rem;
129            display: inline-flex; align-items: center; gap: 0.35rem;
130        }
131        .toolbar-btn {
132            background: #1f2937; border: 1px solid #374151; color: #d1d5db;
133            border-radius: 0.375rem; padding: 0.45rem 0.8rem; font-size: 0.84rem;
134            cursor: pointer; line-height: 1.2;
135        }
136        .toolbar-btn:hover { border-color: #4b5563; color: #fff; background: #374151; }
137        .toolbar-btn.primary {
138            background: #1d4ed8; border-color: #2563eb; color: #fff;
139        }
140        .toolbar-btn.primary:hover { filter: brightness(1.08); }
141        .search-wrap { position: relative; flex: 1 1 200px; min-width: 150px; }
142        .search-wrap input { width: 100%; padding-left: 2.2rem; padding-right: 2rem; }
143        .search-wrap .search-ico {
144            position: absolute; left: 0.7rem; top: 50%; transform: translateY(-50%);
145            color: #6b7280; pointer-events: none; font-size: 0.8rem;
146        }
147        .search-wrap .clear-btn {
148            position: absolute; right: 0.45rem; top: 50%; transform: translateY(-50%);
149            background: transparent; border: none; color: #9ca3af; cursor: pointer;
150            padding: 0.2rem 0.35rem; display: none;
151        }
152        .search-wrap .clear-btn:hover { color: #fff; }
153        .search-wrap .clear-btn.show { display: inline-block; }
154        .legend-bar {
155            display: flex; flex-wrap: wrap; gap: 0.5rem 0.85rem; align-items: center;
156            margin-bottom: 0.75rem; color: #9ca3af; font-size: 0.78rem;
157        }
158        .base-layout {
159            display: grid;
160            grid-template-columns: 230px minmax(0, 1fr);
161            gap: 1rem;
162            align-items: stretch;
163            height: calc(100vh - 13.5rem);
164            min-height: 360px;
165            overflow-anchor: none;
166        }
167        .panel {
168            background: #1f2937; border: 1px solid #374151; border-radius: 0.5rem;
169        }
170        .panel-side {
171            position: relative;
172            height: 100%;
173            overflow-y: auto;
174            overflow-x: hidden;
175            padding: 0.55rem 0.45rem;
176            overscroll-behavior: contain;
177        }
178        .panel-title {
179            margin: 0 0 0.45rem; padding: 0 0.4rem;
180            font-size: 0.78rem; font-weight: 600; color: #9ca3af;
181        }
182        .sidebar-category {
183            cursor: pointer;
184            padding: 0.32rem 0.4rem;
185            border-radius: 0.35rem;
186            display: flex;
187            align-items: center;
188            gap: 0.35rem;
189            margin-bottom: 1px;
190            color: #d1d5db;
191            font-size: 0.82rem;
192            border: 1px solid transparent;
193        }
194        .sidebar-category.subcategory {
195            padding-left: 0.45rem;
196            margin-left: 0.55rem;
197            border-left: 2px solid #374151;
198            font-size: 0.78rem;
199            color: #9ca3af;
200        }
201        .sidebar-category:hover {
202            background: #374151;
203            color: #fff;
204        }
205        .sidebar-category.active {
206            background: #1e3a5f;
207            border-color: #2563eb;
208            color: #fff;
209            font-weight: 600;
210        }
211        .sidebar-category.active.subcategory {
212            border-left-color: #2563eb;
213        }
214        .cat-chevron {
215            flex: 0 0 1.1rem; width: 1.1rem; height: 1.1rem;
216            padding: 0; margin: 0; border: none; background: transparent;
217            color: #9ca3af; cursor: pointer; display: inline-flex;
218            align-items: center; justify-content: center; font-size: 0.72rem;
219        }
220        .cat-chevron:hover { color: #fff; }
221        .cat-chevron-spacer { flex: 0 0 1.1rem; width: 1.1rem; }
222        .cat-count {
223            margin-left: auto; color: #6b7280; font-size: 0.72rem; flex-shrink: 0;
224        }
225        .category-name { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
226        .category-icon {
227            display: flex; align-items: center; justify-content: center;
228            width: 18px; height: 18px; flex-shrink: 0;
229        }
230        .category-icon img { width: 18px; height: 18px; object-fit: contain; }
231        .category-divider {
232            height: 1px; margin: 0.45rem 0.35rem;
233            background: #374151;
234        }
235        .table-wrap {
236            background: #1f2937; border: 1px solid #374151; border-radius: 0.5rem;
237            overflow: auto; height: 100%; max-height: none;
238            overscroll-behavior: contain; overflow-anchor: none;
239        }
240        table.base-table {
241            width: 100%; border-collapse: separate; border-spacing: 0;
242            min-width: 980px;
243        }
244        table.base-table th, table.base-table td {
245            padding: 0.55rem 0.6rem; text-align: left; border-bottom: 1px solid #374151;
246            vertical-align: middle; font-size: 0.85rem;
247            background: #1f2937;
248        }
249        table.base-table th {
250            position: sticky; top: 0; background: #111827; color: #9ca3af;
251            font-weight: 600; user-select: none; white-space: nowrap;
252            z-index: 3; box-shadow: 0 1px 0 #374151;
253        }
254        table.base-table th.sortable { cursor: pointer; }
255        table.base-table th.sortable:hover { color: #fff; }
256        table.base-table tbody tr:hover td { background: #374151; }
257        table.base-table .col-type { color: #d1d5db; font-size: 0.8rem; }
258        table.base-table .col-muted { color: #6b7280; }
259        table.base-table .col-premium { color: #6ee7b7; }
260        table.base-table .text-center { text-align: center; }
261        .sort-indicator { margin-left: 0.2rem; font-size: 0.72rem; color: #93c5fd; }
262        .ladder-cell { font-variant-numeric: tabular-nums; font-size: 0.82rem; white-space: nowrap; }
263        .ladder-price { color: #e5e7eb; font-weight: 600; }
264        .ladder-no-listing { color: #fbbf24; font-size: 0.74rem; }
265        .ladder-not-queried { color: #6b7280; font-size: 0.74rem; }
266        .ladder-legend {
267            display: inline-block; padding: 0.12rem 0.4rem; border-radius: 0.25rem;
268            border: 1px solid #374151;
269        }
270        .ladder-legend.ladder-price { background: #1f2937; color: #e5e7eb; font-weight: 600; }
271        .ladder-legend.ladder-no-listing { background: #422006; border-color: #854d0e; color: #fbbf24; }
272        .ladder-legend.ladder-not-queried { background: #111827; color: #9ca3af; }
273        .action-btn {
274            background: transparent; border: 1px solid #374151; color: #93c5fd;
275            border-radius: 0.3rem; padding: 0.2rem 0.45rem; font-size: 0.78rem;
276            cursor: pointer;
277        }
278        .action-btn:hover { background: #1e3a5f; border-color: #2563eb; color: #fff; }
279        .hidden { display: none !important; }
280        .modal-overlay {
281            position: fixed; inset: 0; background: rgba(0,0,0,0.75);
282            z-index: 9999; display: flex; align-items: center; justify-content: center;
283        }
284        .modal-card {
285            background: #1f2937; border: 1px solid #374151; border-radius: 0.5rem;
286            padding: 1rem 1.1rem; max-width: 90%; max-height: 90%;
287            width: 800px; display: flex; flex-direction: column;
288        }
289        .modal-card h3 { margin: 0; color: #fff; font-size: 1.05rem; }
290        .modal-close {
291            background: transparent; border: none; color: #9ca3af;
292            font-size: 1.35rem; cursor: pointer; line-height: 1;
293        }
294        .modal-close:hover { color: #fff; }
295        @media (max-width: 900px) {
296            .base-layout {
297                grid-template-columns: 1fr;
298                height: auto;
299                min-height: 0;
300            }
301            .panel-side { position: static; height: auto; max-height: 40vh; }
302            .table-wrap { height: 65vh; }
303        }
304    </style>
305
306    
307    <!-- AdSense 广告样式 -->
308    <link rel="stylesheet" href="/static/css/adsense.css">
309    <link rel="stylesheet" href="/static/css/styles.css?v=2.3">
310    
311    <!-- AdSense 配置和脚本 -->
312    
312<script src="/js/adsense_config.js"></script>
312
313    
313<script src="/js/adsense.js"></script>
313
314    
315    <!-- 百度统计 -->
316    
316<script>
317    var _hmt = _hmt || [];
318    (function() {
319      var hm = document.createElement("script");
320      hm.src = "https://hm.baidu.com/hm.js?5a085d195477c2675243b896093d6d8c";
321      var s = document.getElementsByTagName("script")[0]; 
322      s.parentNode.insertBefore(hm, s);
323    })();
324    </script>
324
325</head>
326<body data-site-nav data-current-game="poe2" data-nav-page="nav-base-price">
327    <noscript>
328        <div style="padding:20px; background:#1f2937; color:#fff;">
329            <h1>poe2lens - 流放之路2工具箱</h1>
330            <p>本网站需要 JavaScript 支持以获取完整功能。请启用后访问。</p>
331        </div>
332    </noscript>
333
334    <div class="page-wrap">
335        <div class="page-header">
336            <h1>基底物品价格 <span id="itemCount" class="meta-count" style="font-size:1rem;font-weight:400;">0</span></h1>
337            <p>高级底子在不同 ilvl 下的价格阶梯。上方 <b>c/e</b>=1 chaos 兑多少 exalted,<b>d/e</b>=1 divine 兑多少 exalted(与均价换算同一套汇率)。</p>
338        </div>
339
340        <div class="meta-bar" id="baseMetaBar">
341            <span>联赛:<span class="league-badge" id="baseLeagueLabel">—</span></span>
342            <span id="baseUpdatedWrap">数据更新:—</span>
343            <span class="rates-hint" id="baseRatesHint" title="页面换算/均价使用的汇率:X/e = 1 单位 X 等于多少 exalted">
344                <span class="rate-pill" id="baseCeRatePill">c/e:—</span>
345                <span class="rate-pill" id="baseDeRatePill">d/e:—</span>
346            </span>
347            <span class="unit-toggle" id="baseUnitToggle" title="显示货币单位(按上方 c/e、d/e 换算)">
348                <button type="button" data-unit="exalted" class="active">Exalt</button>
349                <button type="button" data-unit="chaos">Chaos</button>
350                <button type="button" data-unit="divine">Divine</button>
351            </span>
352        </div>
353
354        <div class="toolbar">
355            <div class="search-wrap">
356                <i class="fas fa-search search-ico"></i>
357                <input type="search" id="categorySearchInput" placeholder="搜索基底名(中/英)…" autocomplete="off" />
358                <button type="button" id="clearCategorySearchBtn" class="clear-btn" title="清除">×</button>
359            </div>
360            <label><span id="filter-min-price-label">最低价(e)</span>
361                <input type="number" id="filter-min-price" placeholder="0" min="0" step="0.1" />
362            </label>
363            <label>最少卖家
364                <input type="number" id="filter-min-sellers" placeholder="0" min="0" step="1" />
365            </label>
366            <button type="button" id="clear-filters-btn" class="toolbar-btn">清除过滤</button>
367            <button type="button" id="generate-filter-code-btn" class="toolbar-btn primary">生成过滤代码</button>
368        </div>
369
370        <div class="legend-bar">
371            <span>图例:</span>
372            <span id="level-tabs"></span>
373        </div>
374
375        <div class="base-layout">
376            <aside class="panel panel-side" aria-label="类别">
377                <div class="panel-title">类别</div>
378                <div id="base-categories"></div>
379            </aside>
380
381            <div class="table-wrap">
382                <table class="base-table">
383                    <thead>
384                        <tr>
385                            <th class="sortable" data-sort-field="name" style="width:22%;">
386                                高级底子 <span class="sort-indicator" data-sort-indicator="name"></span>
387                            </th>
388                            <th class="sortable" data-sort-field="sub_category" style="width:9%;">
389                                类型 <span class="sort-indicator" data-sort-indicator="sub_category"></span>
390                            </th>
391                            <th class="sortable" data-sort-field="price_82" style="width:8%;text-align:center;">
392                                82 <span class="sort-indicator" data-sort-indicator="price_82"></span>
393                            </th>
394                            <th class="sortable" data-sort-field="price_81" style="width:8%;text-align:center;">
395                                81 <span class="sort-indicator" data-sort-indicator="price_81"></span>
396                            </th>
397                            <th class="sortable" data-sort-field="price_80" style="width:8%;text-align:center;">
398                                80 <span class="sort-indicator" data-sort-indicator="price_80"></span>
399                            </th>
400                            <th class="sortable" data-sort-field="price_79" style="width:8%;text-align:center;">
401                                79 <span class="sort-indicator" data-sort-indicator="price_79"></span>
402                            </th>
403                            <th class="sortable" data-sort-field="min_effective_ilvl" style="width:9%;text-align:center;">
404                                最低有效 <span class="sort-indicator" data-sort-indicator="min_effective_ilvl"></span>
405                            </th>
406                            <th class="sortable" data-sort-field="premium_82" style="width:9%;text-align:center;">
407                                82溢价 <span class="sort-indicator" data-sort-indicator="premium_82"></span>
408                            </th>
409                            <th style="width:7%;text-align:center;">历史</th>
410                            <th style="width:7%;text-align:center;">交易</th>
411                        </tr>
412                    </thead>
413                    <tbody id="base-table-body"></tbody>
414                </table>
415            </div>
416        </div>
417    </div>
418
419    <!-- 价格历史模态框 -->
420    <div id="basePriceHistoryModal" class="modal-overlay hidden">
421        <div class="modal-card">
422            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.75rem;">
423                <h3 id="baseModalItemName">价格历史趋势</h3>
424                <button type="button" class="modal-close" onclick="closeBasePriceHistoryModal()">&times;</button>
425            </div>
426            <div style="flex:1;min-height:400px;position:relative;">
427                <canvas id="basePriceHistoryChart"></canvas>
428            </div>
429        </div>
430    </div>
431    <!-- 页脚 - 完全由 footer.js 统一管理 -->
432    <footer id="site-footer"></footer>
433    
433<script src="/static/js/footer.js"></script>
433
434
435    <!-- 脚本 -->
436    
436<script src="/js/nav_config.js" onerror="console.error('nav_config.js加载失败');"></script>
436
437    
437<script src="/js/nav_bar.js" onerror="console.error('nav_bar.js加载失败');"></script>
437
438    
438<script src="/js/nav_render.js" onerror="console.error('nav_render.js加载失败');"></script>
438
439    
439<script src="/js/nav_filter.js" onerror="console.error('nav_filter.js加载失败');"></script>
439
440    
440<script src="/js/site_navigation.js"></script>
440
441    <!-- 认证脚本由 nav_bar.js 统一加载 -->
442    
442<script src="/js/page_helper.js" onerror="console.error('page_helper.js加载失败');"></script>
442
443    <!-- 统一图标和显示工具 -->
444    
444<script src="/js/iconUtils.js" onerror="console.error('iconUtils.js加载失败');"></script>
444
445    
445<script src="/js/assetNameUtils.js"></script>
445
446    
446<script src="/js/itemDisplayUtils.js" onerror="console.error('itemDisplayUtils.js加载失败');"></script>
446
447    
447<script src="/js/priceUpdateUtils.js"></script>
447
448    
448<script>
449        // 确保页面至少能显示,即使脚本失败
450        console.log('poe2_base_price 页面脚本开始执行');
451        
452        // 全局错误处理
453        window.addEventListener('error', function(e) {
454            console.error('全局错误:', e.error, e.message, e.filename, e.lineno);
455        });
456        
457        window.addEventListener('unhandledrejection', function(e) {
458            console.error('未处理的Promise拒绝:', e.reason);
459        });
460        
461document.addEventListener('DOMContentLoaded', function() {
462            console.log('DOMContentLoaded事件触发');
463            try {
464                // 手机端菜单切换
465                const mobileMenuToggle = document.getElementById('mobile-menu-toggle');
466                const mobileNavMenu = document.getElementById('mobile-nav-menu');
467                if (mobileMenuToggle && mobileNavMenu) {
468                    mobileMenuToggle.addEventListener('click', function() {
469                        mobileNavMenu.classList.toggle('mobile-nav-open');
470                    });
471                    
472                    // 点击导航链接后关闭菜单
473                    mobileNavMenu.addEventListener('click', function(e) {
474                        if (e.target.closest('a')) {
475                            mobileNavMenu.classList.remove('mobile-nav-open');
476                        }
477                    });
478                }
479                
480} catch (error) {
481                console.error('DOMContentLoaded处理失败:', error);
482            }
483        });
484        
485        // Base Price 页面功能脚本
486        // 与 unique 区类似的主类别中文映射(参考价格页面的暗金部分)
487        const BASE_MAIN_CATEGORY_NAME_MAP = {
488            'Off-hand': '副手',
489            'Weapons': '武器',
490            'Armour': '护甲',
491            'Jewellery': '饰品',
492            'Flasks': '药剂',
493            'Jewels': '珠宝',
494            'Misc': '碑牌/遗物',
495            '': '未分类'
496        };
497        window.BASE_MAIN_CATEGORY_NAME_MAP = BASE_MAIN_CATEGORY_NAME_MAP;
498
499        // 子类别名称映射
500        const SUBCATEGORY_NAME_MAP = {
501            'Shields': '盾牌',
502            'Bucklers': '轻盾',
503            'Quivers': '箭袋',
504            'Foci': '法器',
505            'OneHanded': '单手',
506            'TwoHanded': '双手',
507            'Bows': '弓',
508            'Crossbows': '十字弓',
509            'Wands': '魔杖',
510            'Sceptres': '权杖',
511            'Staves': '长杖',
512            'Quarterstaves': '长棍',
513            'Spears': '长矛',
514            'Flails': '连枷',
515            'OneHandMaces': '单手锤',
516            'TwoHandMaces': '双手锤',
517            'Daggers': '匕首',
518            'Claws': '爪',
519            'BodyArmours': '胸甲',
520            'Boots': '靴子',
521            'Gloves': '手套',
522            'Helmets': '头盔',
523            'Rings': '戒指',
524            'Amulets': '项链',
525            'Belts': '腰带',
526            'Life': '生命药剂',
527            'Charms': '护符',
528            'Jewels': '珠宝',
529            'Weapons': '武器'
530        };
531        window.SUBCATEGORY_NAME_MAP = SUBCATEGORY_NAME_MAP;
532        
533        // 主类别图标映射(使用 data/poe2/icons/stackable 下的图标,与子类别不同)
534        const MAIN_CATEGORY_ICON_MAP = {
535            'Armour': 'Adherents_Raiment.png',  // 护甲 - 使用不同的胸甲图标(与BodyArmours子类别不同)
536            'Jewellery': 'Amber_Amulet.png',    // 饰品 - 使用不同的项链图标(与Amulets子类别不同)
537            'Off-hand': 'Ancient_Buckler.png',  // 副手 - 使用不同的轻盾图标(与Bucklers子类别不同)
538            'Weapons': 'Anvil_Maul.png'         // 武器 - 使用不同的武器图标(与TwoHanded子类别不同)
539        };
540        
541        // 子类别图标映射(使用 data/poe2/icons/stackable 下的图标)
542        const SUBCATEGORY_ICON_MAP = {
543            // 护甲子类别
544            'BodyArmours': 'Abyssal_Cuirass.png',  // 胸甲
545            'Boots': 'Bangled_Sandals.png',         // 靴子
546            'Gloves': 'Adorned_Gloves.png',        // 手套
547            'Helmets': 'Archon_Crown.png',         // 头盔
548            // 饰品子类别
549            'Rings': 'Amethyst_Ring.png',          // 戒指
550            'Amulets': 'Gold_Amulet.png',          // 项链
551            'Belts': 'Heavy_Belt.png',             // 腰带
552            // 副手子类别
553            'Shields': 'Aegis_Buckler.png',        // 盾牌
554            'Bucklers': 'Aegis_Buckler.png',       // 轻盾
555            'Quivers': 'Blunt_Quiver.png',         // 箭袋
556            'Foci': 'Crystal_Focus.png',           // 法器
557            // 武器子类别
558            'OneHanded': 'Akoyan_Club.png',        // 单手
559            'TwoHanded': 'Anvil_Maul.png',         // 双手
560            'Bows': 'Adherent_Bow.png',            // 弓
561            'Crossbows': 'Alloy_Crossbow.png',     // 十字弓
562            'Wands': 'Acrid_Wand.png',             // 魔杖
563            'Sceptres': 'Aromatic_Sceptre.png',    // 权杖
564            'Staves': 'Aegis_Quarterstaff.png',    // 长杖
565            'Quarterstaves': 'Aegis_Quarterstaff.png', // 长棍
566            'Spears': 'Akoyan_Spear.png',          // 长矛
567            'OneHandMaces': 'Akoyan_Club.png',     // 单手锤
568            'TwoHandMaces': 'Anvil_Maul.png',      // 双手锤
569            'Daggers': 'Akoyan_Club.png',          // 匕首(暂用替代图标)
570            'Claws': 'Akoyan_Club.png',            // 爪(暂用替代图标)
571            'Flails': 'Anvil_Maul.png'             // 连枷(暂用替代图标)
572        };
573        
574        // 获取类别图标路径
575        function getCategoryIconPath(categoryKey, isSubcategory = false) {
576            if (isSubcategory) {
577                const iconFile = SUBCATEGORY_ICON_MAP[categoryKey];
578                if (iconFile) {
579                    return `/data/poe2/icons/stackable/${iconFile}`;
580                }
581            } else {
582                const iconFile = MAIN_CATEGORY_ICON_MAP[categoryKey];
583                if (iconFile) {
584                    return `/data/poe2/icons/stackable/${iconFile}`;
585                }
586            }
587            // 默认图标
588            return '/data/poe2/icons/stackable/Chaos_Orb.png';
589        }
590
591        // 必须用 var(挂到 window):poe2_base_price.js 的 ladder 渲染读的是 window.*
592        var baseData = null;           // 后端返回的完整数据
593        var selectedLevels = new Set();  // 当前选中的物品等级(多选),空表示全部
594        var selectedMainCategory = null; // 当前选中的主类别(null表示全部)
595        var selectedSubCategory = null;  // 当前选中的子类别(null表示全部)
596        // var expandedCategories = new Set(); // 已展开的主类别 - 已移除展开功能,子类别始终显示
597        var currentSortField = 'avg_price';
598        var currentSortOrder = 'desc';
599        var basePriceHistoryChart = null; // 价格历史图表
600        var divineRate = 0.0; // divine 汇率
601        var categorySearchTerm = ''; // 类别内搜索关键词
602        var minPriceFilter = 10;
602 // 最低价(当前展示单位);按各 ilvl 最高价过滤
603        var minSellersFilter = 0; // 最少卖家
604        var basePriceUnit = 'exalted';
605        var expandedCategories = new Set();
606
607        function mapCategoryToMain(categoryFull) {
608            if (!categoryFull) return '';
609            const parts = categoryFull.split('.');
610            if (!parts.length) return '';
611            return parts[0]; // 直接返回主类别
612        }
613        
614        function mapCategoryToSub(categoryFull) {
615            if (!categoryFull) return '';
616            const parts = categoryFull.split('.');
617            if (parts.length >= 2) {
618                return parts[1]; // 返回子类别
619            }
620            return '';
621        }
622
623        function formatCategoryLabel(mainCategory) {
624            const cn = BASE_MAIN_CATEGORY_NAME_MAP[mainCategory] || mainCategory || '未分类';
625            if (!mainCategory) return cn;
626            return cn + ` (${mainCategory})`;
627        }
628
629        function buildCategories() {
630            const container = document.getElementById('base-categories');
631            container.innerHTML = '';
632            if (!baseData || !baseData.levels) return;
633
634            const allItems = [];
635            Object.values(baseData.levels).forEach(items => {
636                items.forEach(it => allItems.push(it));
637            });
638
639            // 统计主类别和子类别的数量(参考价格页面的暗金物品)
640            const mainCategoryCounts = {};
641            const subCategoryCounts = {}; // key: "main.sub" -> count
642            
643            allItems.forEach(item => {
644                // 优先使用数据库中的main_category和sub_category
645                // 如果没有,从category字段解析(格式:MainCategory.SubCategory)
646                let mainCat = item.main_category || '';
647                let subCat = item.sub_category || '';
648                
649                if (!mainCat && item.category) {
650                    mainCat = mapCategoryToMain(item.category);
651                }
652                if (!subCat && item.category) {
653                    subCat = mapCategoryToSub(item.category);
654                }
655                
656                mainCat = mainCat || '';
657                
658                // 统计主类别
659                if (!mainCategoryCounts[mainCat]) mainCategoryCounts[mainCat] = 0;
660                mainCategoryCounts[mainCat]++;
661                
662                // 统计子类别
663                if (subCat) {
664                    const key = `${mainCat}.${subCat}`;
665                    if (!subCategoryCounts[key]) subCategoryCounts[key] = 0;
666                    subCategoryCounts[key]++;
667                }
668            });
669
670            // "全部"按钮
671            const allDiv = document.createElement('div');
672            allDiv.className = 'sidebar-category';
673            allDiv.setAttribute('data-category', 'ALL');
674            allDiv.onclick = () => {
675                selectedMainCategory = null;
676                selectedSubCategory = null;
677                // 清空搜索词
678                categorySearchTerm = '';
679                const searchInput = document.getElementById('categorySearchInput');
680                const clearBtn = document.getElementById('clearCategorySearchBtn');
681                if (searchInput) searchInput.value = '';
682                if (clearBtn) clearBtn.classList.remove('show');
683                updateCategoryActiveState();
684                renderTableBody();
685            };
686            allDiv.innerHTML = `
687                <div class="category-icon">
688                    <i class="fas fa-folder-open text-gray-400"></i>
689                </div>
690                <span class="category-name">全部类别</span>
691                <span class="text-xs text-gray-400" style="margin-left:auto;">${allItems.length}</span>
692            `;
693            container.appendChild(allDiv);
694
695            // 分隔条
696            const divider = document.createElement('div');
697            divider.className = 'category-divider';
698            container.appendChild(divider);
699
700            // 按主类别分组显示
701            Object.keys(mainCategoryCounts).sort().forEach(mainCat => {
702                const mainCount = mainCategoryCounts[mainCat];
703                const mainKey = mainCat || '';
704                
705                // 主类别按钮
706                const mainDiv = document.createElement('div');
707                mainDiv.className = 'sidebar-category';
708                mainDiv.setAttribute('data-category', `main_${mainKey}`);
709                
710                const mainIconPath = getCategoryIconPath(mainKey, false);
711                mainDiv.innerHTML = `
712                    <div class="category-icon">
713                        <img src="${mainIconPath}" alt="${mainKey}" style="width: 20px; height: 20px; object-fit: contain;" 
714                             onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
715                        <i class="fas fa-folder text-gray-400" style="display: none;"></i>
716                    </div>
717                    <span class="category-name">${formatCategoryLabel(mainKey)}</span>
718                    <span class="text-xs text-gray-400" style="margin-left:auto;">${mainCount}</span>
719                `;
720                container.appendChild(mainDiv);
721                
722                // 单击整个区域选择类别
723                mainDiv.onclick = (e) => {
724                    e.stopPropagation();
725                    // 始终选中该主类别(取消再次点击取消选择的功能)
726                    selectedMainCategory = mainKey;
727                    selectedSubCategory = null; // 选择主类别时,清空子类别选择
728                    // 清空搜索词
729                    categorySearchTerm = '';
730                    const searchInput = document.getElementById('categorySearchInput');
731                    const clearBtn = document.getElementById('clearCategorySearchBtn');
732                    if (searchInput) searchInput.value = '';
733                    if (clearBtn) clearBtn.classList.remove('show');
734                    updateCategoryActiveState();
735                    renderTableBody();
736                };
737                
738                // 始终显示子类别(不再需要展开/收起)
739                // 获取该主类别的所有子类别
740                const subCats = {};
741                Object.keys(subCategoryCounts).forEach(key => {
742                    if (key.startsWith(mainKey + '.')) {
743                        const subCat = key.substring(mainKey.length + 1);
744                        subCats[subCat] = subCategoryCounts[key];
745                    }
746                });
747                
748                Object.keys(subCats).sort().forEach(subCat => {
749                    const subCount = subCats[subCat];
750                    const subDiv = document.createElement('div');
751                    subDiv.className = 'sidebar-category subcategory'; // 添加subcategory类来显示层级关系
752                    subDiv.setAttribute('data-category', `sub_${mainKey}_${subCat}`);
753                    subDiv.onclick = (e) => {
754                        e.stopPropagation();
755                        selectedMainCategory = mainKey;
756                        selectedSubCategory = subCat;
757                        // 清空搜索词
758                        categorySearchTerm = '';
759                        const searchInput = document.getElementById('categorySearchInput');
760                        const clearBtn = document.getElementById('clearCategorySearchBtn');
761                        if (searchInput) searchInput.value = '';
762                        if (clearBtn) clearBtn.classList.remove('show');
763                        updateCategoryActiveState();
764                        renderTableBody();
765                    };
766                    const subIconPath = getCategoryIconPath(subCat, true);
767                    subDiv.innerHTML = `
768                        <div class="category-icon">
769                            <img src="${subIconPath}" alt="${subCat}" style="width: 20px; height: 20px; object-fit: contain;" 
770                                 onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
771                            <i class="fas fa-tag text-gray-400" style="display: none;"></i>
772                        </div>
773                        <span class="category-name">${SUBCATEGORY_NAME_MAP[subCat] || subCat}</span>
774                        <span class="text-xs text-gray-400" style="margin-left:auto;">${subCount}</span>
775                    `;
776                    container.appendChild(subDiv);
777                });
778            });
779
780            updateCategoryActiveState();
781        }
782
783        function updateCategoryActiveState() {
784            document.querySelectorAll('#base-categories .sidebar-category').forEach(el => {
785                el.classList.remove('active');
786                const cat = el.getAttribute('data-category');
787                
788                if (cat === 'ALL' && selectedMainCategory === null && selectedSubCategory === null) {
789                    el.classList.add('active');
790                } else if (cat && cat.startsWith('sub_') && selectedSubCategory) {
791                    const parts = cat.split('_');
792                    if (parts.length >= 3 && parts[1] === selectedMainCategory && parts[2] === selectedSubCategory) {
793                        el.classList.add('active');
794                    }
795                } else if (cat && cat.startsWith('main_') && selectedMainCategory && !selectedSubCategory) {
796                    const mainKey = cat.substring(5);
797                    if (mainKey === selectedMainCategory) {
798                        el.classList.add('active');
799                    }
800                }
801            });
802        }
803
804        function buildLevelTabs() {
805            const container = document.getElementById('level-tabs');
806            container.innerHTML = '';
807            if (!baseData || !baseData.levels) return;
808
809            // "全部"按钮
810            const allBtn = document.createElement('button');
811            allBtn.className = 'level-tab';
812            allBtn.setAttribute('data-level', 'ALL');
813            allBtn.textContent = '全部';
814            allBtn.onclick = () => {
815                selectedLevels.clear(); // 空集合表示全部
816                updateLevelTabActiveState();
817                renderTableBody();
818            };
819            container.appendChild(allBtn);
820
821            const levels = Object.keys(baseData.levels).sort((a, b) => Number(b) - Number(a));
822            levels.forEach(lvl => {
823                const btn = document.createElement('button');
824                btn.className = 'level-tab';
825                btn.setAttribute('data-level', lvl);
826                btn.textContent = `ilvl ${lvl}`;
827                btn.onclick = () => {
828                    // 多选逻辑
829                    if (selectedLevels.has(lvl)) {
830                        selectedLevels.delete(lvl);
831                    } else {
832                        selectedLevels.add(lvl);
833                    }
834                    updateLevelTabActiveState();
835                    renderTableBody();
836                };
837                container.appendChild(btn);
838            });
839
840            updateLevelTabActiveState();
841        }
842
843        function updateLevelTabActiveState() {
844            document.querySelectorAll('#level-tabs .level-tab').forEach(el => {
845                const lvl = el.getAttribute('data-level');
846                if (lvl === 'ALL') {
847                    // "全部"按钮:当 selectedLevels 为空时激活
848                    if (selectedLevels.size === 0) {
849                        el.classList.add('active');
850                    } else {
851                        el.classList.remove('active');
852                    }
853                } else {
854                    // 普通 ilvl 按钮
855                    if (selectedLevels.has(lvl)) {
856                        el.classList.add('active');
857                    } else {
858                        el.classList.remove('active');
859                    }
860                }
861            });
862        }
863
864        function getCurrentItems() {
865            if (!baseData || !baseData.levels) return [];
866            const allItems = [];
867            
868            // 从所有选中的等级中获取物品(如果 selectedLevels 为空,则获取所有等级)
869            if (selectedLevels.size === 0) {
870                // 选择"全部",获取所有等级
871                Object.values(baseData.levels).forEach(items => {
872                    allItems.push(...items);
873                });
874            } else {
875                // 从选中的等级中获取
876                selectedLevels.forEach(level => {
877                    const items = baseData.levels[String(level)] || [];
878                    allItems.push(...items);
879                });
880            }
881            
882            return allItems.filter(item => {
883                // 类别过滤
884                if (selectedMainCategory !== null || selectedSubCategory !== null) {
885                    // 获取主类别和子类别(优先使用数据库字段,否则从category解析)
886                    let mainCat = item.main_category || '';
887                    let subCat = item.sub_category || '';
888                    
889                    if (!mainCat && item.category) {
890                        mainCat = mapCategoryToMain(item.category);
891                    }
892                    if (!subCat && item.category) {
893                        subCat = mapCategoryToSub(item.category);
894                    }
895                    
896                    if (selectedSubCategory) {
897                        // 选择了子类别,必须同时匹配主类别和子类别
898                        if (mainCat !== selectedMainCategory || subCat !== selectedSubCategory) {
899                            return false;
900                        }
901                    } else if (selectedMainCategory) {
902                        // 只选择了主类别
903                        if (mainCat !== selectedMainCategory) {
904                            return false;
905                        }
906                    }
907                }
908                
909                // 最低价过滤(单位:e)
910                if (minPriceFilter > 0) {
911                    const priceValue = item.avg_price || 0;
912                    if (priceValue < minPriceFilter) {
913                        return false;
914                    }
915                }
916                
917                // 最少卖家数过滤
918                if (minSellersFilter > 0) {
919                    const sellerCount = item.total_search_results || 0;
920                    if (sellerCount < minSellersFilter) {
921                        return false;
922                    }
923                }
924                
925                // 类别内搜索过滤
926                if (categorySearchTerm) {
927                    const nameTW = (item.name_tw || '').toLowerCase();
928                    const nameEN = (item.name || '').toLowerCase();
929                    const searchTerm = categorySearchTerm.toLowerCase();
930                    if (!nameTW.includes(searchTerm) && !nameEN.includes(searchTerm)) {
931                        return false;
932                    }
933                }
934                
935                return true;
936            });
937        }
938
939        function renderTableBody() {
940            const tbody = document.getElementById('base-table-body');
941            if (!tbody) {
942                console.error('找不到表格体元素: base-table-body');
943                return;
944            }
945            tbody.innerHTML = '';
946
947            let items = getCurrentItems().slice();
948
949            // 排序
950            if (currentSortField) {
951                items.sort((a, b) => {
952                    let va = a[currentSortField];
953                    let vb = b[currentSortField];
954                    
955                    // 处理 null/undefined 值,排序时放在最后
956                    if (va === null || va === undefined) va = currentSortOrder === 'asc' ? Infinity : -Infinity;
957                    if (vb === null || vb === undefined) vb = currentSortOrder === 'asc' ? Infinity : -Infinity;
958                    
959                    // 对于数字字段,确保按数字比较
960                    if (currentSortField === 'total_search_results' || currentSortField === 'avg_price' || currentSortField === 'ilvl') {
961                        va = Number(va) || 0;
962                        vb = Number(vb) || 0;
963                    }
964                    
965                    if (va === vb) return 0;
966                    if (currentSortOrder === 'asc') {
967                        return va > vb ? 1 : -1;
968                    }
969                    return va < vb ? 1 : -1;
970                });
971            }
972
973            // 移除"当前等级"信息显示
974            const itemCountEl = document.getElementById('itemCount');
975            if (itemCountEl) {
976                itemCountEl.textContent = items.length;
977            }
978
979            items.forEach(item => {
980                const row = document.createElement('tr');
981                row.className = 'border-b border-gray-700 hover:bg-gray-800';
982                
983                // 获取子类型显示名称(参考价格页面的暗金物品)
984                let subCategoryDisplay = '';
985                // 优先使用数据库中的sub_category
986                if (item.sub_category) {
987                    subCategoryDisplay = SUBCATEGORY_NAME_MAP[item.sub_category] || item.sub_category;
988                } 
989                // 如果没有sub_category,尝试从category字段解析
990                else if (item.category) {
991                    const subCat = mapCategoryToSub(item.category);
992                    if (subCat) {
993                        subCategoryDisplay = SUBCATEGORY_NAME_MAP[subCat] || subCat;
994                    } else {
995                        // 如果category中没有子类别,根据主类别显示
996                        const mainCat = item.main_category || mapCategoryToMain(item.category);
997                        if (mainCat === 'Weapons') {
998                            subCategoryDisplay = '武器';
999                        } else if (mainCat) {
1000                            subCategoryDisplay = BASE_MAIN_CATEGORY_NAME_MAP[mainCat] || mainCat;
1001                        }
1002                    }
1003                }
1004                // 最后回退:使用main_category
1005                else if (item.main_category) {
1006                    if (item.main_category === 'Weapons') {
1007                        subCategoryDisplay = '武器';
1008                    } else {
1009                        subCategoryDisplay = BASE_MAIN_CATEGORY_NAME_MAP[item.main_category] || item.main_category;
1010                    }
1011                }
1012                
1013                // 购买按钮(参考暗金物品的实现)
1014                const buyButton = document.createElement('button');
1015                buyButton.className = 'text-blue-400 hover:text-blue-300 text-sm px-2 py-1 rounded hover:bg-gray-700';
1016                buyButton.textContent = 'è´­ä¹°';
1017                buyButton.addEventListener('click', () => {
1018                    // 获取联赛名称
1019                    const league = baseData?.league || 'Forbidden Rites';
1020                    const leagueLower = league.toLowerCase();
1021                    
1022                    // 获取物品名称(base物品使用英文名称)
1023                    const itemName = item.name || '';
1024                    
1025                    // 构建 POE2 交易查询参数(参考抓取脚本中的条件)
1026                    // base物品使用 type 而不是 name,并且需要设置 rarity=normal, ilvl等条件
1027                    const tradeQuery = {
1028                        query: {
1029                            status: { option: 'securable' }, // securable = buyout筛选
1030                            type: itemName.trim(), // base物品使用type字段
1031                            stats: [{ type: 'and', filters: [] }],
1032                            filters: {
1033                                type_filters: {
1034                                    filters: {
1035                                        rarity: {
1036                                            option: 'normal' // 普通品质
1037                                        },
1038                                        ilvl: {
1039                                            min: item.ilvl || 79 // 物品等级最小值
1040                                        }
1041                                    }
1042                                },
1043                                misc_filters: {
1044                                    filters: {
1045                                        corrupted: {
1046                                            option: 'false' // 未腐化
1047                                        }
1048                                    }
1049                                }
1050                            }
1051                        },
1052                        sort: { price: 'asc' }
1053                    };
1054                    
1055                    // POE2 交易链接格式:https://www.pathofexile.com/trade2/search/{league}?q={query}
1056                    const encodedLeague = encodeURIComponent(leagueLower);
1057                    const jsonString = JSON.stringify(tradeQuery);
1058                    const encodedQuery = encodeURIComponent(jsonString);
1059                    const tradeUrl = `https://www.pathofexile.com/trade2/search/${encodedLeague}?q=${encodedQuery}`;
1060                    window.open(tradeUrl, '_blank');
1061                });
1062                
1063                // 历史趋势按钮
1064                const historyButton = document.createElement('button');
1065                historyButton.className = 'text-blue-400 hover:text-blue-300 text-sm px-2 py-1 rounded hover:bg-gray-700';
1066                historyButton.textContent = '查看趋势';
1067                historyButton.onclick = () => showBasePriceHistory(item.name, item.ilvl);
1068                
1069                // 格式化价格:使用图标显示,大于等于1d用divine图标,小于用exalted图标
1070                let priceDisplay = '';
1071                let priceValue = item.avg_price || 0;
1072
1073                if (divineRate > 0 && priceValue >= divineRate) {
1074                    // 大于等于1d,显示为divine图标
1075                    const divineValue = priceValue / divineRate;
1076                    const divineIconHtml = CurrencyUtils.getCurrencyIcon('divine', { size: 20, style: 'margin-left: 4px;' });
1077                    priceDisplay = `<span class="text-yellow-400 font-medium">${divineValue.toFixed(2)}</span>${divineIconHtml}`;
1078                } else {
1079                    // 小于1d,显示为exalted图标
1080                    const exaltedIconHtml = CurrencyUtils.getCurrencyIcon('exalted', { size: 20, style: 'margin-left: 4px;' });
1081                    priceDisplay = `<span class="text-yellow-400 font-medium">${priceValue.toFixed(2)}</span>${exaltedIconHtml}`;
1082                }
1083                
1084                // 获取图标路径(优先使用API返回的icon_path)
1085                let iconPath = item.icon_path || '/data/poe2/icons/stackable/Chaos_Orb.png';
1086                // 确保使用.png扩展名
1087                if (iconPath.endsWith('.webp')) {
1088                    iconPath = iconPath.replace('.webp', '.png');
1089                }
1090                
1091                // 物品名称:中文和英文分上下两行(从CSV第三列Name_TW获取),带图标
1092                const nameTW = item.name_tw || '';
1093                const nameText = nameTW 
1094                    ? `<div style="line-height: 1.4;"><div class="text-white">${nameTW}</div><div style="font-size: 0.75rem; color: #9ca3af;">${item.name}</div></div>`
1095                    : `<div class="text-white">${item.name}</div>`;
1096                
1097                const nameDisplay = `
1098                    <div class="flex items-center">
1099                        <div class="icon-container mr-3" style="width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; flex-shrink: 0;">
1100                            <img src="${iconPath}" alt="${item.name}" 
1101                                 style="max-width: 32px; max-height: 32px; width: auto; height: auto; object-fit: contain; display: block;"
1102                                 onerror="this.src='/data/poe2/icons/stackable/Chaos_Orb.png';">
1103                        </div>
1104                        <div class="flex-1">
1105                            ${nameText}
1106                        </div>
1107                    </div>
1108                `;
1109                
1110                // 卖家数量显示
1111                const sellerCount = item.total_search_results !== null && item.total_search_results !== undefined 
1112                    ? item.total_search_results 
1113                    : '-';
1114                
1115                row.innerHTML = `
1116                    <td class="px-4 py-2">
1117                        ${nameDisplay}
1118                    </td>
1119                    <td class="px-4 py-2 text-center">
1120                        ${item.ilvl || '-'}
1121                    </td>
1122                    <td class="px-4 py-2">
1123                        ${subCategoryDisplay || '-'}
1124                    </td>
1125                    <td class="px-4 py-2 text-right">
1126                        ${priceDisplay}
1127                    </td>
1128                    <td class="px-4 py-2 text-center">
1129                        ${sellerCount}
1130                    </td>
1131                    <td class="px-4 py-2 text-center">
1132                    </td>
1133                    <td class="px-4 py-2 text-center">
1134                    </td>
1135                `;
1136                
1137                // 将按钮添加到对应的单元格
1138                const cells = row.querySelectorAll('td');
1139                const historyCell = cells[cells.length - 2]; // 倒数第二个是历史趋势列
1140                const tradeCell = cells[cells.length - 1]; // 最后一个是交易列
1141                historyCell.appendChild(historyButton);
1142                tradeCell.appendChild(buyButton);
1143                
1144                tbody.appendChild(row);
1145            });
1146
1147            // 更新排序指示
1148            document.querySelectorAll('.sort-indicator').forEach(el => {
1149                const field = el.getAttribute('data-sort-indicator');
1150                if (field === currentSortField) {
1151                    el.textContent = currentSortOrder === 'asc' ? '▲' : '▼';
1152                } else {
1153                    el.textContent = '';
1154                }
1155            });
1156        }
1157
1158        async function loadBaseData() {
1159            const CACHE_KEY = 'poe2_base_items_v7_rates';
1160            const CACHE_TTL_MS = 180000;
1161
1162            function applyBaseData(data) {
1163                baseData = data;
1164                window.baseData = data;
1165                if (baseData && baseData.divine_rate) {
1166                    divineRate = baseData.divine_rate;
1167                    window.divineRate = divineRate;
1168                }
1169                updateBaseMetaBar(baseData);
1170                buildCategories();
1171                if (typeof window.setBasePriceUnit === 'function') {
1172                    window.setBasePriceUnit(window.basePriceUnit || basePriceUnit || 'exalted');
1173                } else {
1174                    buildLevelTabs();
1175                    renderTableBody();
1176                }
1177            }
1178
1179            function updateBaseMetaBar(data) {
1180                const leagueEl = document.getElementById('baseLeagueLabel');
1181                const updatedWrap = document.getElementById('baseUpdatedWrap');
1182                if (leagueEl) {
1183                    leagueEl.textContent = (data && data.league) ? data.league : '—';
1184                }
1185                // c/e、d/e:一律「1 该币 = N exalted」(与 API rates / 均价换算相同)
1186                const rates = (data && data.rates) || {};
1187                const ce = Number(rates.chaos || (data && data.chaos_rate) || 0) || 0;
1188                const de = Number(rates.divine || (data && data.divine_rate) || 0) || 0;
1189                const ceEl = document.getElementById('baseCeRatePill');
1190                const deEl = document.getElementById('baseDeRatePill');
1191                if (ceEl) {
1192                    ceEl.innerHTML = ce > 0
1193                        ? 'c/e:<strong>' + ce.toFixed(2) + '</strong><span class="rate-sub">(1c=' + ce.toFixed(2) + 'e)</span>'
1194                        : 'c/e:—';
1195                }
1196                if (deEl) {
1197                    deEl.innerHTML = de > 0
1198                        ? 'd/e:<strong>' + de.toFixed(1) + '</strong><span class="rate-sub">(1d=' + de.toFixed(1) + 'e)</span>'
1199                        : 'd/e:—';
1200                }
1201                if (typeof window.Poe2BaseLadder !== 'undefined' && window.Poe2BaseLadder.syncRatesHint) {
1202                    window.Poe2BaseLadder.syncRatesHint();
1203                }
1204                if (!updatedWrap) return;
1205                const at = data && (data.updated_at || data.db_mtime);
1206                if (window.PriceUpdate && at != null) {
1207                    updatedWrap.innerHTML = PriceUpdate.renderPageBadge(
1208                        [{ label: 'Base 数据库', at: at }],
1209                        { label: '数据更新' }
1210                    );
1211                } else if (at != null) {
1212                    updatedWrap.textContent = '数据更新:' + String(at);
1213                } else {
1214                    updatedWrap.textContent = '数据更新:—';
1215                }
1216            }
1217
1218            try {
1219                const cachedRaw = sessionStorage.getItem(CACHE_KEY);
1220                if (cachedRaw) {
1221                    try {
1222                        const cached = JSON.parse(cachedRaw);
1223                        if (cached.data && Date.now() - cached.ts < CACHE_TTL_MS) {
1224                            applyBaseData(cached.data);
1225                            return;
1226                        }
1227                    } catch (e) {
1228                        sessionStorage.removeItem(CACHE_KEY);
1229                    }
1230                }
1231
1232                const resp = await axios.get('/api/v1/prices/base-items');
1233                if (typeof resp.data === 'string') {
1234                    try {
1235                        baseData = JSON.parse(resp.data);
1236                    } catch (e) {
1237                        console.error('base_price JSON.parse 失败:', e, resp.data);
1238                        baseData = null;
1239                    }
1240                } else {
1241                    baseData = resp.data;
1242                }
1243
1244                if (baseData) {
1245                    sessionStorage.setItem(CACHE_KEY, JSON.stringify({
1246                        ts: Date.now(),
1247                        data: baseData
1248                    }));
1249                    applyBaseData(baseData);
1250                }
1251            } catch (error) {
1252                console.error('加载 base items 数据失败:', error);
1253            }
1254        }
1255
1256        function setupSortHandlers() {
1257            document.querySelectorAll('.sortable').forEach(el => {
1258                el.addEventListener('click', () => {
1259                    const field = el.getAttribute('data-sort-field');
1260                    if (!field) return;
1261                    console.log('poe2_base_price 点击表头排序:', field);
1262                    if (currentSortField === field) {
1263                        currentSortOrder = currentSortOrder === 'asc' ? 'desc' : 'asc';
1264                    } else {
1265                        currentSortField = field;
1266                        currentSortOrder = field === 'name' ? 'asc' : 'desc';
1267                    }
1268                    renderTableBody();
1269                });
1270            });
1271        }
1272
1273        // 显示基础物品价格历史
1274        async function showBasePriceHistory(itemName, ilvl) {
1275            try {
1276                const params = new URLSearchParams({
1277                    league_name: baseData?.league || 'Forbidden Rites',
1278                    ilvl: ilvl,
1279                    days: 30
1280                });
1281                
1282                const response = await axios.get(`/api/v1/prices/base-items/history/${encodeURIComponent(itemName)}?${params}`);
1283                
1284                console.log('🔍 [base_price] 原始response:', response);
1285                console.log('🔍 [base_price] response.data类型:', typeof response.data);
1286                console.log('🔍 [base_price] response.data:', response.data);
1287                
1288                // 处理响应数据(兼容字符串和对象格式)
1289                let historyData = response.data;
1290                if (typeof historyData === 'string') {
1291                    try {
1292                        historyData = JSON.parse(historyData);
1293                        console.log('🔍 [base_price] 解析后的historyData:', historyData);
1294                    } catch (e) {
1295                        console.error('❌ [base_price] JSON解析失败:', e, historyData);
1296                        alert(`解析历史数据失败: ${e.message}`);
1297                        return;
1298                    }
1299                }
1300                
1301                console.log('🔍 [base_price] 最终historyData:', historyData);
1302                console.log('🔍 [base_price] historyData类型:', typeof historyData);
1303                console.log('🔍 [base_price] historyData的所有键:', Object.keys(historyData || {}));
1304                console.log('🔍 [base_price] history字段:', historyData.history);
1305                console.log('🔍 [base_price] history数组长度:', historyData.history ? historyData.history.length : 0);
1306                console.log('🔍 [base_price] history数组内容:', historyData.history);
1307                
1308                if (!historyData || !historyData.history || historyData.history.length === 0) {
1309                    console.warn('⚠️ [base_price] 历史数据为ç©
1309º:', historyData);
1310                    alert(`物品 "${itemName}" 暂无历史数据`);
1311                    return;
1312                }
1313                
1314                // 显示模态框
1315                const modal = document.getElementById('basePriceHistoryModal');
1316                const modalItemName = document.getElementById('baseModalItemName');
1317                if (!modal) {
1318                    console.error('❌ 找不到模态框元素: basePriceHistoryModal');
1319                    alert('无法显示历史趋势:模态框元素不存在');
1320                    return;
1321                }
1322                
1323                if (modalItemName) {
1324                    modalItemName.textContent = `${itemName} (ilvl ${ilvl}) - 价格历史趋势`;
1325                }
1326                
1327                // 移除 hidden 类并显示模态框
1328                modal.classList.remove('hidden');
1329                modal.style.display = 'flex';
1330                
1331                // 按货币分组数据
1332                const dataByCurrency = {};
1333                historyData.history.forEach(point => {
1334                    console.log('🔍 [base_price] 处理历史数据点:', point);
1335                    const currency = point.currency || 'exalted';
1336                    if (!dataByCurrency[currency]) {
1337                        dataByCurrency[currency] = [];
1338                    }
1339                    dataByCurrency[currency].push(point);
1340                });
1341                console.log('🔍 [base_price] 按货币分组后的数据:', dataByCurrency);
1342                
1343                // 准备图表数据
1344                const labels = [];
1345                const datasets = [];
1346                
1347                // 按时间排序
1348                const sortedHistory = [...historyData.history].sort((a, b) => a.time - b.time);
1349                
1350                // 生成时间标签
1351                sortedHistory.forEach(point => {
1352                    const date = new Date(point.time * 1000);
1353                    const label = date.toLocaleString('zh-CN', { 
1354                        month: 'short', 
1355                        day: 'numeric', 
1356                        hour: '2-digit', 
1357                        minute: '2-digit' 
1358                    });
1359                    if (!labels.includes(label)) {
1360                        labels.push(label);
1361                    }
1362                });
1363                
1364                // 为每种货币创建数据集
1365                const colors = [
1366                    { border: 'rgb(59, 130, 246)', fill: 'rgba(59, 130, 246, 0.1)' }, // blue
1367                    { border: 'rgb(34, 197, 94)', fill: 'rgba(34, 197, 94, 0.1)' }, // green
1368                    { border: 'rgb(251, 146, 60)', fill: 'rgba(251, 146, 60, 0.1)' }, // orange
1369                    { border: 'rgb(168, 85, 247)', fill: 'rgba(168, 85, 247, 0.1)' } // purple
1370                ];
1371                
1372                let colorIndex = 0;
1373                Object.keys(dataByCurrency).forEach(currency => {
1374                    const currencyData = dataByCurrency[currency].sort((a, b) => a.time - b.time);
1375                    const prices = [];
1376                    const currencyLabels = [];
1377                    
1378                    currencyData.forEach(point => {
1379                        console.log('🔍 [base_price] 处理货币数据点:', point, 'price_value:', point.price_value);
1380                        const date = new Date(point.time * 1000);
1381                        const label = date.toLocaleString('zh-CN', { 
1382                            month: 'short', 
1383                            day: 'numeric', 
1384                            hour: '2-digit', 
1385                            minute: '2-digit' 
1386                        });
1387                        currencyLabels.push(label);
1388                        // 确保 price_value 不为 null 或 undefined
1389                        const priceValue = point.price_value !== null && point.price_value !== undefined ? point.price_value : 0;
1390                        prices.push(priceValue);
1391                    });
1392                    
1393                    // 将价格数据映射到主标签数组
1394                    const mappedPrices = labels.map(label => {
1395                        const index = currencyLabels.indexOf(label);
1396                        return index >= 0 ? prices[index] : null;
1397                    });
1398                    
1399                    const color = colors[colorIndex % colors.length];
1400                    datasets.push({
1401                        label: currency === 'exalted' ? 'Exalted' : currency === 'divine' ? 'Divine' : currency === 'chaos' ? 'Chaos' : currency,
1402                        data: mappedPrices,
1403                        borderColor: color.border,
1404                        backgroundColor: color.fill,
1405                        tension: 0.1,
1406                        fill: true,
1407                        spanGaps: true
1408                    });
1409                    colorIndex++;
1410                });
1411                
1412                // 销毁旧图表
1413                if (basePriceHistoryChart) {
1414                    basePriceHistoryChart.destroy();
1415                }
1416                
1417                // 创建新图表
1418                const ctx = document.getElementById('basePriceHistoryChart');
1419                if (!ctx) {
1420                    console.error('❌ 找不到图表canvas元素: basePriceHistoryChart');
1421                    alert('无法创建图表:canvas元素不存在');
1422                    return;
1423                }
1424                
1425                // 检查 Chart 是否已加载
1426                if (typeof Chart === 'undefined') {
1427                    console.error('❌ Chart.js 未加载');
1428                    alert('无法创建图表:Chart.js 库未加载,请刷新页面重试');
1429                    return;
1430                }
1431                
1432                basePriceHistoryChart = new Chart(ctx, {
1433                    type: 'line',
1434                    data: {
1435                        labels: labels,
1436                        datasets: datasets
1437                    },
1438                    options: {
1439                        responsive: true,
1440                        maintainAspectRatio: false,
1441                        plugins: {
1442                            legend: {
1443                                display: true,
1444                                labels: {
1445                                    color: '#d1d5db'
1446                                }
1447                            },
1448                            tooltip: {
1449                                mode: 'index',
1450                                intersect: false,
1451                                callbacks: {
1452                                    label: function(context) {
1453                                        let label = context.dataset.label || '';
1454                                        if (label) {
1455                                            label += ': ';
1456                                        }
1457                                        if (context.parsed.y !== null) {
1458                                            label += context.parsed.y.toFixed(2);
1459                                        } else {
1460                                            label += '无数据';
1461                                        }
1462                                        return label;
1463                                    }
1464                                }
1465                            }
1466                        },
1467                        scales: {
1468                            x: {
1469                                ticks: {
1470                                    color: '#9ca3af',
1471                                    maxRotation: 45,
1472                                    minRotation: 45
1473                                },
1474                                grid: {
1475                                    color: '#374151'
1476                                }
1477                            },
1478                            y: {
1479                                ticks: {
1480                                    color: '#9ca3af'
1481                                },
1482                                grid: {
1483                                    color: '#374151'
1484                                },
1485                                title: {
1486                                    display: true,
1487                                    text: 'ä»·æ ¼',
1488                                    color: '#9ca3af'
1489                                }
1490                            }
1491                        }
1492                    }
1493                });
1494            } catch (error) {
1495                console.error('❌ 显示价格历史失败:', error);
1496                alert('显示价格历史失败: ' + error.message);
1497            }
1498        }
1499
1500        // 关闭价格历史模态框
1501        function closeBasePriceHistoryModal() {
1502            const modal = document.getElementById('basePriceHistoryModal');
1503            if (modal) {
1504                modal.style.display = 'none';
1505                modal.classList.add('hidden');
1506            }
1507            if (basePriceHistoryChart) {
1508                basePriceHistoryChart.destroy();
1509                basePriceHistoryChart = null;
1510            }
1511        }
1512
1513        // 初始化过滤器事件监听
1514        function setupFilters() {
1515            const minPriceInput = document.getElementById('filter-min-price');
1516            const minSellersInput = document.getElementById('filter-min-sellers');
1517            const clearFiltersBtn = document.getElementById('clear-filters-btn');
1518            
1519            // 设置默认值
1520            if (minPriceInput) {
1521                minPriceInput.value = minPriceFilter; // 默认10e
1522                minPriceInput.addEventListener('input', (e) => {
1523                    const value = parseFloat(e.target.value) || 0;
1524                    minPriceFilter = value;
1525                    renderTableBody();
1526                });
1527            }
1528            
1529            if (minSellersInput) {
1530                minSellersInput.value = minSellersFilter;
1531                minSellersInput.addEventListener('input', (e) => {
1532                    const value = parseInt(e.target.value) || 0;
1533                    minSellersFilter = value;
1534                    renderTableBody();
1535                });
1536            }
1537            
1538            if (clearFiltersBtn) {
1539                clearFiltersBtn.addEventListener('click', () => {
1540                    minPriceFilter = 0;
1541                    minSellersFilter = 0;
1542                    if (minPriceInput) minPriceInput.value = '';
1543                    if (minSellersInput) minSellersInput.value = '';
1544                    renderTableBody();
1545                });
1546            }
1547            
1548            // 类别内搜索框
1549            const categorySearchInput = document.getElementById('categorySearchInput');
1550            const clearCategorySearchBtn = document.getElementById('clearCategorySearchBtn');
1551            
1552            if (categorySearchInput) {
1553                categorySearchInput.addEventListener('input', (e) => {
1554                    categorySearchTerm = e.target.value.trim();
1555                    renderTableBody();
1556                    
1557                    // 显示/隐藏清除按钮
1558                    if (clearCategorySearchBtn) {
1559                        if (e.target.value.length > 0) {
1560                            clearCategorySearchBtn.classList.add('show');
1561                        } else {
1562                            clearCategorySearchBtn.classList.remove('show');
1563                        }
1564                    }
1565                });
1566            }
1567            
1568            if (clearCategorySearchBtn) {
1569                clearCategorySearchBtn.addEventListener('click', () => {
1570                    if (categorySearchInput) {
1571                        categorySearchInput.value = '';
1572                        categorySearchTerm = '';
1573                        clearCategorySearchBtn.classList.remove('show');
1574                        renderTableBody();
1575                    }
1576                });
1577            }
1578        }
1579
1580        // 生成过滤代码(基于当前筛选后的结果)
1581        const BASE_FILTER_MARKER_START = '# ====以下是棱镜推荐的基底过滤代码======';
1582        const BASE_FILTER_MARKER_END = '# ====以上是棱镜推荐的基底过滤代码,如更新请删除上述======';
1583
1584        function generateFilterCode() {
1585            const minPriceDisplay = parseFloat(document.getElementById('filter-min-price').value) || 0;
1586            const minSellers = parseInt(document.getElementById('filter-min-sellers').value, 10) || 0;
1587            // 过滤代码阈值一律用 exalted(与游戏 filter / qualification 一致)
1588            const minPrice =
1589                window.Poe2BaseLadder && typeof window.Poe2BaseLadder.displayToExalted === 'function'
1590                    ? window.Poe2BaseLadder.displayToExalted(minPriceDisplay)
1591                    : minPriceDisplay;
1592
1593            // 唯一事实源:API.qualified_sets(S79⊆S80⊆S81);禁止用 levels 再归桶
1594            const contract = (baseData && baseData.prune_contract) || {};
1595            if (contract.ok === false) {
1596                alert('剪枝契约破坏,拒绝生成过滤代码:\n' + (contract.error || ''));
1597                return;
1598            }
1599            let qualified = (baseData && baseData.qualified_sets) || null;
1600            if (!qualified || typeof qualified !== 'object') {
1601                alert('缺少 qualified_sets(请刷新页面);拒绝用 levels 回退重算');
1602                return;
1603            }
1604
1605            // 可选卖家门:保持链包含(从 81 向下收紧)
1606            const ladderByName = {};
1607            ((baseData && baseData.ladder) || []).forEach(function (it) {
1608                if (it && it.name) ladderByName[it.name] = it;
1609            });
1610            function sellersOk(lv, name) {
1611                if (minSellers <= 0) return true;
1612                const it = ladderByName[name];
1613                const t = it && it.total_search_results ? it.total_search_results[String(lv)] : 0;
1614                return Number(t || 0) >= minSellers;
1615            }
1616            function priceOk(lv, name) {
1617                const it = ladderByName[name];
1618                const p = it && it.prices ? it.prices[String(lv)] : null;
1619                if (p == null) return false;
1620                return Number(p) >= minPrice;
1621            }
1622
1623            let s81 = (qualified['81'] || []).filter(function (n) {
1624                return sellersOk(81, n) && priceOk(81, n);
1625            });
1626            const s81set = new Set(s81);
1627            let s80 = (qualified['80'] || []).filter(function (n) {
1628                return s81set.has(n) && sellersOk(80, n) && priceOk(80, n);
1629            });
1630            const s80set = new Set(s80);
1631            let s79 = (qualified['79'] || []).filter(function (n) {
1632                return s80set.has(n) && sellersOk(79, n) && priceOk(79, n);
1633            });
1634            let s82 = (qualified['82'] || []).filter(function (n) {
1635                return sellersOk(82, n) && priceOk(82, n);
1636            });
1637
1638            if (s79.length > s80.length || s80.length > s81.length) {
1639                alert('剪枝契约破坏:|79|≤|80|≤|81| 不成立,拒绝生成过滤代码');
1640                return;
1641            }
1642
1643            const itemsByLevel = {
1644                82: s82,
1645                81: s81,
1646                80: s80,
1647                79: s79,
1648            };
1649
1650            if (!s82.length && !s81.length && !s80.length && !s79.length) {
1651                alert('没有符合条件的物品,无法生成过滤代码');
1652                return;
1653            }
1654
1655            // 生成过滤代码
1656            const filterCodeParts = [];
1657            const generatedAt = new Date().toLocaleString('zh-CN', { hour12: false });
1658            filterCodeParts.push(BASE_FILTER_MARKER_START);
1659            filterCodeParts.push(`# 生成时间: ${generatedAt}  最低价>=${minPrice}e  最少卖家>=${minSellers}`);
1660            filterCodeParts.push('# 契约: 直接消费 qualified_*;S79⊆S80⊆S81;禁止各档独立筛全集');
1661            filterCodeParts.push(`# 数量: 81=${s81.length} 80=${s80.length} 79=${s79.length} 82=${s82.length}`);
1662            filterCodeParts.push('');
1663            const levels = [82, 81, 80, 79];
1664
1665            for (const ilvl of levels) {
1666                const itemNames = itemsByLevel[ilvl];
1667                if (!itemNames || itemNames.length === 0) {
1668                    continue;
1669                }
1670
1671                const uniqueNames = [...new Set(itemNames)].sort();
1672                if (uniqueNames.length === 0) {
1673                    continue;
1674                }
1675
1676                filterCodeParts.push(`# ========== ilvl ${ilvl} (${uniqueNames.length}) ==========`);
1677                filterCodeParts.push(`Show # $type->value material> average_price->${Math.floor(minPrice)}`);
1678                filterCodeParts.push('    Mirrored False');
1679                filterCodeParts.push('    Corrupted False');
1680                filterCodeParts.push('    Rarity Normal');
1681                filterCodeParts.push(`    ItemLevel >= ${ilvl}`);
1682
1683                const formattedNames = uniqueNames.map(name => `"${name}"`).join(' ');
1684                filterCodeParts.push(`    BaseType == ${formattedNames}`);
1685
1686                filterCodeParts.push('    SetFontSize 45');
1687                filterCodeParts.push('    SetTextColor 125 255 89 255');
1688                filterCodeParts.push('    SetBorderColor 125 255 89 255');
1689                filterCodeParts.push('    SetBackgroundColor 0 50 0');
1690                filterCodeParts.push('    PlayAlertSound 1 300');
1691                filterCodeParts.push('    PlayEffect Blue');
1692                filterCodeParts.push('    MinimapIcon 0 Blue Star');
1693                filterCodeParts.push('');
1694            }
1695
1696            filterCodeParts.push(BASE_FILTER_MARKER_END);
1697            const finalFilterCode = filterCodeParts.join('\n');
1698            showFilterCodeModal(finalFilterCode);
1699        }
1700        
1701        // 显示过滤代码模态框
1702        function showFilterCodeModal(code) {
1703            const modal = document.getElementById('filterCodeModal');
1704            const textarea = document.getElementById('generatedFilterCode');
1705            if (modal && textarea) {
1706                textarea.value = code;
1707                modal.classList.remove('hidden');
1708                modal.style.display = 'flex';
1709            }
1710        }
1711        
1712        // 关闭过滤代码模态框
1713        function closeFilterCodeModal() {
1714            const modal = document.getElementById('filterCodeModal');
1715            if (modal) {
1716                modal.classList.add('hidden');
1717                modal.style.display = 'none';
1718            }
1719        }
1720        
1721        // 复制过滤代码
1722        function copyFilterCode() {
1723            const textarea = document.getElementById('generatedFilterCode');
1724            if (textarea) {
1725                textarea.select();
1726                document.execCommand('copy');
1727                showCopySuccessToast();
1728            }
1729        }
1730        
1731        // 显示复制成功提示框(2秒后自动消失)
1732        function showCopySuccessToast() {
1733            const toast = document.getElementById('copySuccessToast');
1734            if (!toast) {
1735                // 如果提示框不存在,回退到alert
1736                alert('已复制到剪贴板');
1737                return;
1738            }
1739            
1740            // 清除之前的定时器(如果存在)
1741            if (toast._autoCloseTimer) {
1742                clearTimeout(toast._autoCloseTimer);
1743            }
1744            
1745            toast.classList.remove('hidden');
1746            toast.style.display = 'flex';
1747            
1748            // 2秒后自动关闭
1749            toast._autoCloseTimer = setTimeout(() => {
1750                toast.classList.add('hidden');
1751                toast.style.display = 'none';
1752                toast._autoCloseTimer = null;
1753            }, 2000);
1754        }
1755
1756        // 初始化
1757        document.addEventListener('DOMContentLoaded', () => {
1758            if (typeof window.Poe2BaseLadderInstall === 'function') {
1759                window.Poe2BaseLadderInstall();
1760            }
1761            setupSortHandlers();
1762            setupFilters();
1763            loadBaseData();
1764            
1765            // 绑定生成过滤代码按钮
1766            const generateBtn = document.getElementById('generate-filter-code-btn');
1767            if (generateBtn) {
1768                generateBtn.addEventListener('click', generateFilterCode);
1769            }
1770            
1771            // 绑定模态框关闭按钮
1772            const closeBtn = document.getElementById('closeFilterCodeModalBtn');
1773            if (closeBtn) {
1774                closeBtn.addEventListener('click', closeFilterCodeModal);
1775            }
1776            
1777            // 绑定复制按钮
1778            const copyBtn = document.getElementById('copyFilterCodeBtn');
1779            if (copyBtn) {
1780                copyBtn.addEventListener('click', copyFilterCode);
1781            }
1782            
1783            // 复制成功提示框的确定按钮
1784            const copySuccessToastBtn = document.getElementById('copySuccessToastBtn');
1785            if (copySuccessToastBtn) {
1786                copySuccessToastBtn.addEventListener('click', () => {
1787                    const toast = document.getElementById('copySuccessToast');
1788                    if (toast) {
1789                        toast.classList.add('hidden');
1790                        // 清除定时器
1791                        if (toast._autoCloseTimer) {
1792                            clearTimeout(toast._autoCloseTimer);
1793                            toast._autoCloseTimer = null;
1794                        }
1795                    }
1796                });
1797            }
1798            
1799            // 点击模态框外部关闭
1800            const modal = document.getElementById('filterCodeModal');
1801            if (modal) {
1802                modal.addEventListener('click', (e) => {
1803                    if (e.target === modal) {
1804                        closeFilterCodeModal();
1805                    }
1806                });
1807            }
1808        });
1809        
1810        console.log('base_price 页面脚本执行完成');
1811    </script>
1811
1812    
1812<script src="/js/poe2_base_price.js?v=20260910e"></script>
1812
1813    
1814    <!-- 过滤代码模态框 -->
1815        <div id="filterCodeModal" class="modal-overlay hidden">
1816        <div class="modal-card" style="width:min(640px,92vw);height:min(60vh,520px);">
1817            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.65rem;">
1818                <h3>过滤代码</h3>
1819                <button type="button" id="closeFilterCodeModalBtn" class="modal-close">&times;</button>
1820            </div>
1821            <textarea id="generatedFilterCode" readonly style="flex:1;width:100%;background:#111827;border:1px solid #374151;border-radius:0.35rem;color:#e5e7eb;padding:0.75rem;font-family:ui-monospace,monospace;font-size:0.8rem;resize:none;"></textarea>
1822            <div style="display:flex;justify-content:flex-end;margin-top:0.75rem;">
1823                <button type="button" id="copyFilterCodeBtn" class="toolbar-btn primary">复制</button>
1824            </div>
1825        </div>
1826    </div>
1827
1828    <div id="copySuccessToast" class="modal-overlay hidden">
1829        <div class="modal-card" style="width:min(320px,90vw);text-align:center;">
1830            <h3 style="margin-bottom:0.5rem;">已复制</h3>
1831            <p style="color:#9ca3af;margin:0 0 0.85rem;">过滤代码已复制到剪贴板</p>
1832            <button type="button" id="copySuccessToastBtn" class="toolbar-btn primary">确定</button>
1833        </div>
1834    </div>
1835</body>
1836</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.