poi-plugin-koushu-rate 2.0.0 → 2.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +605 -14
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -2,7 +2,7 @@ const React = require("react");
2
2
  const fs = require("fs");
3
3
  const path = require("path");
4
4
  const { WindowEnv } = require("views/components/etc/window-env");
5
- const { SlotitemIcon } = require("views/components/etc/icon");
5
+ const { SlotitemIcon, MaterialIcon } = require("views/components/etc/icon");
6
6
 
7
7
  const LEVEL_ONE_ROWS = [
8
8
  { range: "★0→1", p: 1.0 },
@@ -21,6 +21,21 @@ const LEVEL_TWO_ROWS = [
21
21
  { range: "MAX→进化", label: "62%", p: 0.62, source: "upgrade" },
22
22
  ];
23
23
 
24
+ const PLAN_LEVELS_6 = [
25
+ { from: 0, p: 1, source: "phase0" },
26
+ { from: 1, p: 1, source: "phase0" },
27
+ { from: 2, p: 1, source: "phase0" },
28
+ { from: 3, p: 1, source: "phase0" },
29
+ { from: 4, p: 1, source: "phase0" },
30
+ { from: 5, p: 0.95, source: "phase0" },
31
+ ];
32
+ const PLAN_LEVELS_MAX = PLAN_LEVELS_6.concat([
33
+ { from: 6, p: 0.9, source: "phase1" },
34
+ { from: 7, p: 0.82, source: "phase1" },
35
+ { from: 8, p: 0.77, source: "phase1" },
36
+ { from: 9, p: 0.67, source: "phase1" },
37
+ ]);
38
+
24
39
  const WEEKDAY_KEYS = ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"];
25
40
  const WEEKDAY_LABELS = { monday: "周一", tuesday: "周二", wednesday: "周三", thursday: "周四", friday: "周五", saturday: "周六", sunday: "周日" };
26
41
  const ALL_DAYS_KEY = "all";
@@ -140,7 +155,7 @@ const CSS = `
140
155
  font-size: 14px;
141
156
  }
142
157
  .kr2-header {
143
- padding: 12px 14px;
158
+ padding: 12px 14px 20px;
144
159
  border-bottom: 1px solid rgba(255, 255, 255, 0.12);
145
160
  }
146
161
  .kr2-title-row {
@@ -187,7 +202,7 @@ const CSS = `
187
202
  display: flex;
188
203
  gap: 6px;
189
204
  flex-wrap: wrap;
190
- margin-top: 10px;
205
+ margin-top: 0;
191
206
  }
192
207
  .kr2-day {
193
208
  padding: 5px 12px;
@@ -206,6 +221,219 @@ const CSS = `
206
221
  color: #fff;
207
222
  border-color: #2d72d2;
208
223
  }
224
+ .kr2-nav {
225
+ display: flex;
226
+ gap: 6px;
227
+ margin-top: 12px;
228
+ margin-bottom: 20px;
229
+ }
230
+ .kr2-nav-btn {
231
+ padding: 7px 18px;
232
+ border: 1px solid #5f6b7c;
233
+ border-radius: 6px;
234
+ background: #2f343c;
235
+ color: #f6f7f9;
236
+ cursor: pointer;
237
+ font-size: 13px;
238
+ }
239
+ .kr2-nav-btn:hover {
240
+ background: #383e47;
241
+ }
242
+ .kr2-nav-active {
243
+ background: #2d72d2;
244
+ border-color: #2d72d2;
245
+ color: #fff;
246
+ }
247
+ .kr2-plan {
248
+ margin-top: 12px;
249
+ flex: 1;
250
+ min-height: 0;
251
+ overflow-x: hidden;
252
+ overflow-y: scroll;
253
+ }
254
+ .kr2-plan-bar {
255
+ display: flex;
256
+ align-items: center;
257
+ justify-content: space-between;
258
+ gap: 10px;
259
+ flex-wrap: wrap;
260
+ margin-bottom: 10px;
261
+ }
262
+ .kr2-plan-chips {
263
+ display: flex;
264
+ gap: 8px;
265
+ }
266
+ .kr2-plan-chip {
267
+ padding: 4px 12px;
268
+ border-radius: 12px;
269
+ font-size: 12px;
270
+ background: #232a33;
271
+ border: 1px solid #3a4450;
272
+ color: #c8d0da;
273
+ cursor: pointer;
274
+ }
275
+ .kr2-plan-chip:hover {
276
+ background: #1d3048;
277
+ border-color: #4f9dcc;
278
+ }
279
+ .kr2-plan-summary {
280
+ display: grid;
281
+ grid-template-columns: repeat(6, 1fr);
282
+ border: 1px solid #343c46;
283
+ border-radius: 8px;
284
+ overflow: hidden;
285
+ background: #1c2127;
286
+ margin-bottom: 12px;
287
+ }
288
+ .kr2-plan-sum-cell {
289
+ display: flex;
290
+ align-items: center;
291
+ justify-content: center;
292
+ gap: 8px;
293
+ padding: 8px 10px;
294
+ border-right: 1px solid #2a313a;
295
+ font-size: 13px;
296
+ min-width: 0;
297
+ }
298
+ .kr2-plan-sum-cell:last-child {
299
+ border-right: 0;
300
+ }
301
+ .kr2-plan-sum-icon {
302
+ font-size: 15px;
303
+ line-height: 1;
304
+ }
305
+ .kr2-plan-mat-icon {
306
+ width: 20px;
307
+ height: 20px;
308
+ object-fit: contain;
309
+ }
310
+ .kr2-plan-sum-num {
311
+ font-weight: 700;
312
+ color: #f6f7f9;
313
+ white-space: nowrap;
314
+ }
315
+ .kr2-plan-table {
316
+ border-collapse: separate;
317
+ border-spacing: 0;
318
+ }
319
+ .kr2-plan-table td {
320
+ border-top: 0;
321
+ }
322
+ .kr2-plan-table thead th {
323
+ text-align: center;
324
+ }
325
+ .kr2-plan-table thead th.kr2-plan-mat-th {
326
+ text-align: left;
327
+ }
328
+ .kr2-plan-table th,
329
+ .kr2-plan-table td {
330
+ text-align: center;
331
+ border-bottom: 1px solid rgba(255, 255, 255, 0.12);
332
+ }
333
+ .kr2-plan-table tbody tr:last-child td {
334
+ border-bottom: 0;
335
+ }
336
+ .kr2-plan-current {
337
+ font-size: 12px;
338
+ }
339
+ .kr2-plan-current-stock {
340
+ font-size: 11px;
341
+ }
342
+ .kr2-plan-bar-left {
343
+ display: flex;
344
+ align-items: center;
345
+ gap: 10px;
346
+ }
347
+ .kr2-plan-total-label {
348
+ font-size: 12px;
349
+ color: #abb3bf;
350
+ }
351
+ .kr2-plan-table td.kr2-plan-mat-col,
352
+ .kr2-plan-table td.kr2-plan-consumed {
353
+ text-align: left;
354
+ }
355
+ .kr2-plan-table td.kr2-plan-name {
356
+ text-align: left;
357
+ vertical-align: middle;
358
+ }
359
+ .kr2-plan-table .kr2-plan-name-inner {
360
+ display: flex;
361
+ align-items: center;
362
+ gap: 8px;
363
+ }
364
+ .kr2-plan-table .kr2-icon {
365
+ width: 24px;
366
+ height: 24px;
367
+ object-fit: contain;
368
+ flex: 0 0 24px;
369
+ }
370
+ .kr2-plan-qty {
371
+ display: inline-flex;
372
+ align-items: center;
373
+ gap: 4px;
374
+ }
375
+ .kr2-plan-qty-btn {
376
+ width: 22px;
377
+ height: 22px;
378
+ border: 1px solid #46515e;
379
+ background: #2f343c;
380
+ color: #e8ebef;
381
+ border-radius: 4px;
382
+ cursor: pointer;
383
+ font-size: 14px;
384
+ line-height: 1;
385
+ display: inline-flex;
386
+ align-items: center;
387
+ justify-content: center;
388
+ }
389
+ .kr2-plan-qty-btn:hover {
390
+ background: #1d3048;
391
+ border-color: #4f9dcc;
392
+ }
393
+ .kr2-plan-qty-input {
394
+ width: 42px;
395
+ height: 24px;
396
+ border: 1px solid #46515e;
397
+ border-radius: 4px;
398
+ background: #1c2127;
399
+ color: #f6f7f9;
400
+ text-align: center;
401
+ font-size: 13px;
402
+ }
403
+ .kr2-plan-mat-btn {
404
+ display: inline-block;
405
+ padding: 3px 10px;
406
+ border: 1px solid #5f6b7c;
407
+ border-radius: 4px;
408
+ background: #2f343c;
409
+ color: #f6f7f9;
410
+ font-size: 12px;
411
+ cursor: pointer;
412
+ }
413
+ .kr2-plan-mat-btn:hover {
414
+ background: #1d3048;
415
+ border-color: #4f9dcc;
416
+ color: #d6e4f5;
417
+ }
418
+ .kr2-plan-stock-default {
419
+ color: #f6f7f9;
420
+ }
421
+ .kr2-plan-stock-low {
422
+ color: #ff6b6b;
423
+ font-weight: 700;
424
+ }
425
+ .kr2-plan-stock-enough {
426
+ color: #009900;
427
+ font-weight: 700;
428
+ }
429
+ .kr2-plan-sec {
430
+ margin-top: 16px;
431
+ }
432
+ .kr2-plan-sec h3 {
433
+ font-size: 14px;
434
+ color: #d6dce3;
435
+ margin-bottom: 8px;
436
+ }
209
437
  .kr2-search {
210
438
  width: 240px;
211
439
  padding: 6px 10px;
@@ -268,8 +496,28 @@ const CSS = `
268
496
  .kr2-list {
269
497
  flex: 1;
270
498
  min-height: 0;
271
- overflow-y: auto;
499
+ overflow-x: hidden;
500
+ overflow-y: scroll;
501
+ }
502
+ .kr2-plan::-webkit-scrollbar,
503
+ .kr2-list::-webkit-scrollbar {
504
+ width: 10px;
505
+ height: 10px;
272
506
  }
507
+ .kr2-plan::-webkit-scrollbar-track,
508
+ .kr2-list::-webkit-scrollbar-track {
509
+ background: #1c2127;
510
+ }
511
+ .kr2-plan::-webkit-scrollbar-thumb,
512
+ .kr2-list::-webkit-scrollbar-thumb {
513
+ background: #5f6b7c;
514
+ border-radius: 5px;
515
+ }
516
+ .kr2-plan::-webkit-scrollbar-thumb:hover,
517
+ .kr2-list::-webkit-scrollbar-thumb:hover {
518
+ background: #8b95a1;
519
+ }
520
+
273
521
  .kr2-dropdown {
274
522
  position: relative;
275
523
  }
@@ -1542,17 +1790,18 @@ function getInventoryEquips(envWindow) {
1542
1790
  try {
1543
1791
  const state = getReduxState(envWindow)
1544
1792
  const info = state && state.info
1545
- if (info && info.equips) return info.equips
1793
+ const infoEquips = (info && (info.equips || info.slotitems)) || (state && (state.equips || state.slotitems))
1794
+ if (infoEquips) return infoEquips
1546
1795
  const candidates = []
1547
1796
  if (envWindow) candidates.push(envWindow)
1548
1797
  if (typeof window !== "undefined" && window) candidates.push(window)
1549
1798
  for (const w of candidates) {
1550
1799
  if (w && typeof w.getStore === "function") {
1551
- const direct = w.getStore("info.equips")
1800
+ const direct = w.getStore("info.equips") || w.getStore("info.slotitems")
1552
1801
  if (direct) return direct
1553
1802
  }
1554
1803
  }
1555
- return (info && info.equips) || {}
1804
+ return infoEquips || {}
1556
1805
  } catch (_) {
1557
1806
  return {}
1558
1807
  }
@@ -1562,14 +1811,16 @@ function getUseItemCounts(envWindow) {
1562
1811
  try {
1563
1812
  const state = getReduxState(envWindow)
1564
1813
  const info = state && state.info
1565
- const useItems = (info && info.useitems) || {}
1566
- const $useItems = (state && state.const && state.const.$useitems) || {}
1814
+ const constData = (state && state.const) || {}
1815
+ const useItems = (info && (info.useitems || info.items)) || {}
1816
+ const $useItems = (constData.$useitems || constData.useitems) || {}
1567
1817
  const byName = {}
1568
1818
  for (const id of Object.keys(useItems)) {
1569
1819
  const item = useItems[id] || {}
1570
1820
  const master = $useItems[id] || (item.api_id != null ? $useItems[String(item.api_id)] : null) || {}
1571
1821
  const name = master.api_name || item.api_name
1572
- if (name && item.api_count != null) byName[String(name)] = item.api_count
1822
+ const count = item.api_count != null ? item.api_count : item.count
1823
+ if (name && count != null) byName[String(name)] = count
1573
1824
  }
1574
1825
  return byName
1575
1826
  } catch (_) {
@@ -1829,6 +2080,285 @@ function InventoryModal({ equipId, name, inventory, onClose }) {
1829
2080
  )
1830
2081
  }
1831
2082
 
2083
+ function planCurrentLevel(row, inventoryByEquip) {
2084
+ const inv = (inventoryByEquip || {})[row.id]
2085
+ if (inv && inv.levels) {
2086
+ const keys = Object.keys(inv.levels).map(Number)
2087
+ if (keys.length) return Math.max(0, Math.min(10, Math.max.apply(null, keys)))
2088
+ }
2089
+ return 0
2090
+ }
2091
+
2092
+ function planInventoryLevels(row, inventoryByEquip) {
2093
+ const inv = (inventoryByEquip || {})[row.id]
2094
+ const out = []
2095
+ if (inv && inv.levels) {
2096
+ for (const level of Object.keys(inv.levels)) {
2097
+ const lv = Number(level)
2098
+ const count = Math.max(0, Math.round(Number(inv.levels[level]) || 0))
2099
+ for (let i = 0; i < count; i += 1) out.push(lv)
2100
+ }
2101
+ }
2102
+ out.sort((a, b) => b - a)
2103
+ return out
2104
+ }
2105
+
2106
+ function planCopyCosts(row, target, current) {
2107
+ const levels = target === "max" ? PLAN_LEVELS_MAX : PLAN_LEVELS_6
2108
+ const out = { screws: 0, dev: 0, fuel: 0, ammo: 0, steel: 0, bauxite: 0, materials: [] }
2109
+ for (const lv of levels) {
2110
+ if (lv.from < current) continue
2111
+ const step = lv.source === "phase0" ? row.phase0 : row.phase1
2112
+ if (!step) continue
2113
+ const rec = buildRecommendation(step.consume_improvement_min, step.consume_improvement_max, lv.p)
2114
+ const ensure = rec.tone === "ensure"
2115
+ const expected = Number.isFinite(rec.expected) ? rec.expected : step.consume_improvement_min / lv.p
2116
+ const screws = ensure ? step.consume_improvement_max : expected
2117
+ const attempts = ensure ? 1 : expected
2118
+ const dev = ensure ? step.consume_development_max : step.consume_development_min * attempts
2119
+ out.screws += screws
2120
+ out.dev += dev
2121
+ const base = row.base || {}
2122
+ out.fuel += (base.consume_fuel || 0) * attempts
2123
+ out.ammo += (base.consume_ammo || 0) * attempts
2124
+ out.steel += (base.consume_steel || 0) * attempts
2125
+ out.bauxite += (base.consume_bauxite || 0) * attempts
2126
+ for (const m of step.materials || []) {
2127
+ out.materials.push({ item: m, count: m.count || 1 })
2128
+ }
2129
+ }
2130
+ return out
2131
+ }
2132
+
2133
+ function planRowCosts(row, target, qty, inventoryByEquip) {
2134
+ const count = Math.max(1, Math.min(99, Math.round(Number(qty) || 1)))
2135
+ let starts = []
2136
+ if (count === 1) {
2137
+ starts = [planCurrentLevel(row, inventoryByEquip)]
2138
+ } else {
2139
+ const levels = planInventoryLevels(row, inventoryByEquip)
2140
+ for (let i = 0; i < count; i += 1) starts.push(i < levels.length ? levels[i] : 0)
2141
+ }
2142
+ const out = { screws: 0, dev: 0, fuel: 0, ammo: 0, steel: 0, bauxite: 0, materials: [] }
2143
+ for (const current of starts) {
2144
+ const c = planCopyCosts(row, target, current)
2145
+ out.screws += c.screws
2146
+ out.dev += c.dev
2147
+ out.fuel += c.fuel
2148
+ out.ammo += c.ammo
2149
+ out.steel += c.steel
2150
+ out.bauxite += c.bauxite
2151
+ for (const m of c.materials) out.materials.push(m)
2152
+ }
2153
+ out.materials = planAggregateMaterials(out.materials)
2154
+ return out
2155
+ }
2156
+
2157
+ function planCurrentDisplay(row, sel, inventoryByEquip) {
2158
+ if (Math.round(Number(sel.qty || 1)) === 1) return "★ " + String(planCurrentLevel(row, inventoryByEquip))
2159
+ const inv = (inventoryByEquip || {})[row.id]
2160
+ return "库存 " + String(inv && Number.isFinite(inv.total) ? inv.total : 0)
2161
+ }
2162
+
2163
+ function planMaterialKey(m) {
2164
+ return m.item_equipment_id != null ? "equip:" + String(m.item_equipment_id) : "material:" + String(m.item_material_key || m.item_name || "")
2165
+ }
2166
+
2167
+ function planAggregateMaterials(materials) {
2168
+ const byKey = {}
2169
+ const out = []
2170
+ for (const m of materials || []) {
2171
+ const key = planMaterialKey(m.item)
2172
+ const target = byKey[key] || (byKey[key] = { item: m.item, count: 0 })
2173
+ target.count += m.count || 1
2174
+ }
2175
+ return Object.keys(byKey).map((k) => byKey[k])
2176
+ }
2177
+
2178
+ function planStockValue(m, inventoryByEquip, useItemCounts) {
2179
+ if (m.item_equipment_id != null) {
2180
+ const inv = (inventoryByEquip || {})[String(m.item_equipment_id)]
2181
+ if (inv) return Number.isFinite(inv.stock) ? inv.stock : 0
2182
+ const inventoryLoaded = inventoryByEquip && Object.keys(inventoryByEquip).length > 0
2183
+ return inventoryLoaded ? 0 : null
2184
+ }
2185
+ const name = String(m.item_name || m.item_material_key || "")
2186
+ const v = (useItemCounts || {})[name]
2187
+ return v == null ? null : Number(v)
2188
+ }
2189
+
2190
+ function planFormatNumber(n) {
2191
+ const v = Number(n) || 0
2192
+ return String(Math.max(0, Math.ceil(v - 1e-9)).toLocaleString())
2193
+ }
2194
+
2195
+ function planFormatScrews(n) {
2196
+ const v = Number(n) || 0
2197
+ if (Math.abs(v - Math.round(v)) < 0.05) return String(Math.round(v).toLocaleString())
2198
+ return v.toFixed(1)
2199
+ }
2200
+
2201
+ function PlanningPage({ rows, inventoryByEquip, useItemCounts, selection, onToggle, onTarget, onQty, onSelectAll, onMaterialClick }) {
2202
+ const planRows = rows.map((row) => {
2203
+ const sel = (selection || {})[row.id] || { selected: true, target: "max", qty: 1 }
2204
+ return { row, sel, costs: planRowCosts(row, sel.target, sel.qty, inventoryByEquip), display: planCurrentDisplay(row, sel, inventoryByEquip) }
2205
+ })
2206
+ const selected = planRows.filter((p) => p.sel.selected)
2207
+ const totals = { screws: 0, dev: 0, fuel: 0, ammo: 0, steel: 0, bauxite: 0 }
2208
+ const materialMap = {}
2209
+ for (const p of selected) {
2210
+ totals.screws += p.costs.screws
2211
+ totals.dev += p.costs.dev
2212
+ totals.fuel += p.costs.fuel
2213
+ totals.ammo += p.costs.ammo
2214
+ totals.steel += p.costs.steel
2215
+ totals.bauxite += p.costs.bauxite
2216
+ for (const m of p.costs.materials) {
2217
+ const key = planMaterialKey(m.item)
2218
+ const entry = materialMap[key] || (materialMap[key] = { name: String(m.item.item_name || m.item.item_material_key || ("装备 " + m.item.item_equipment_id)), item: m.item, required: 0, stock: null, equips: [] })
2219
+ entry.required += m.count
2220
+ if (entry.stock == null) entry.stock = planStockValue(m.item, inventoryByEquip, useItemCounts)
2221
+ if (entry.equips.indexOf(p.row.name) < 0) entry.equips.push(p.row.name)
2222
+ }
2223
+ }
2224
+ const materialList = Object.keys(materialMap).map((k) => materialMap[k]).sort((a, b) => a.name.localeCompare(b.name, "zh-Hans-CN"))
2225
+ const stockClass = (stock, required) => stock == null ? "kr2-plan-stock-default" : stock < required ? "kr2-plan-stock-low" : "kr2-plan-stock-enough"
2226
+ const summaryItems = [
2227
+ { materialId: 8, title: "螺丝", value: totals.screws },
2228
+ { materialId: 7, title: "资材", value: totals.dev },
2229
+ { materialId: 1, title: "油", value: totals.fuel },
2230
+ { materialId: 2, title: "弹", value: totals.ammo },
2231
+ { materialId: 3, title: "钢", value: totals.steel },
2232
+ { materialId: 4, title: "铝", value: totals.bauxite },
2233
+ ]
2234
+ return React.createElement(
2235
+ "div",
2236
+ { className: "kr2-plan" },
2237
+ React.createElement(
2238
+ "div",
2239
+ { className: "kr2-plan-bar" },
2240
+ React.createElement(
2241
+ "div",
2242
+ { className: "kr2-plan-bar-left" },
2243
+ React.createElement("span", { className: "kr2-plan-total-label" }, "总资源消耗"),
2244
+ React.createElement("span", { className: "kr2-status" }, "已选 " + selected.length + " / " + planRows.length + " 件收藏装备")
2245
+ ),
2246
+ React.createElement(
2247
+ "div",
2248
+ { className: "kr2-plan-chips" },
2249
+ React.createElement("button", { className: "kr2-plan-chip", onClick: () => onSelectAll(true) }, "全选"),
2250
+ React.createElement("button", { className: "kr2-plan-chip", onClick: () => onSelectAll(false) }, "全不选")
2251
+ )
2252
+ ),
2253
+ React.createElement(
2254
+ "div",
2255
+ { className: "kr2-plan-summary" },
2256
+ summaryItems.map((item) =>
2257
+ React.createElement(
2258
+ "div",
2259
+ { key: item.title, className: "kr2-plan-sum-cell", title: item.title },
2260
+ React.createElement(MaterialIcon, { materialId: item.materialId, className: "kr2-plan-mat-icon", alt: item.title }),
2261
+ React.createElement("span", { className: "kr2-plan-sum-num" }, item.materialId === 8 ? planFormatScrews(item.value) : planFormatNumber(item.value))
2262
+ )
2263
+ )
2264
+ ),
2265
+ React.createElement(
2266
+ "table",
2267
+ { className: "kr2-table kr2-plan-table" },
2268
+ React.createElement(
2269
+ "thead",
2270
+ null,
2271
+ React.createElement(
2272
+ "tr",
2273
+ null,
2274
+ React.createElement("th", { style: { width: 36 } }, "选用"),
2275
+ React.createElement("th", null, "装备"),
2276
+ React.createElement("th", null, "当前"),
2277
+ React.createElement("th", null, "目标"),
2278
+ React.createElement("th", null, "目标数量"),
2279
+ React.createElement("th", null, "资材"),
2280
+ React.createElement("th", null, "螺丝"),
2281
+ React.createElement("th", null, "素材")
2282
+ )
2283
+ ),
2284
+ React.createElement(
2285
+ "tbody",
2286
+ null,
2287
+ planRows.map((p) => {
2288
+ const matText = p.costs.materials.map((m) => String(m.item.item_name || m.item.item_material_key || ("装备 " + m.item.item_equipment_id)) + " ×" + String(m.count)).join(" / ") || "无"
2289
+ return React.createElement(
2290
+ "tr",
2291
+ { key: p.row.id },
2292
+ React.createElement("td", null, React.createElement("input", { type: "checkbox", checked: !!p.sel.selected, onChange: () => onToggle(p.row.id) })),
2293
+ React.createElement("td", { className: "kr2-plan-name" }, React.createElement("span", { className: "kr2-plan-name-inner" }, React.createElement(SlotitemIcon, { slotitemId: p.row.icon, className: "kr2-icon" }), " ", p.row.name)),
2294
+ React.createElement("td", { className: Math.round(Number(p.sel.qty || 1)) === 1 ? "kr2-plan-current" : "kr2-plan-current-stock" }, p.display),
2295
+ React.createElement(
2296
+ "td",
2297
+ null,
2298
+ React.createElement(
2299
+ "label",
2300
+ { style: { marginRight: 8 } },
2301
+ React.createElement("input", { type: "radio", name: "plan-" + p.row.id, checked: p.sel.target !== "max", onChange: () => onTarget(p.row.id, "6") }),
2302
+ "+6"
2303
+ ),
2304
+ React.createElement(
2305
+ "label",
2306
+ null,
2307
+ React.createElement("input", { type: "radio", name: "plan-" + p.row.id, checked: p.sel.target === "max", onChange: () => onTarget(p.row.id, "max") }),
2308
+ "MAX"
2309
+ )
2310
+ ),
2311
+ React.createElement(
2312
+ "td",
2313
+ null,
2314
+ React.createElement(
2315
+ "span",
2316
+ { className: "kr2-plan-qty" },
2317
+ React.createElement("button", { type: "button", className: "kr2-plan-qty-btn", onClick: () => onQty(p.row.id, Number(p.sel.qty || 1) - 1) }, "−"),
2318
+ React.createElement("input", { className: "kr2-plan-qty-input", value: String(p.sel.qty || 1), onChange: (e) => onQty(p.row.id, e.target.value) }),
2319
+ React.createElement("button", { type: "button", className: "kr2-plan-qty-btn", onClick: () => onQty(p.row.id, Number(p.sel.qty || 1) + 1) }, "+")
2320
+ )
2321
+ ),
2322
+ React.createElement("td", null, planFormatNumber(p.costs.dev)),
2323
+ React.createElement("td", null, planFormatScrews(p.costs.screws)),
2324
+ React.createElement("td", { className: "kr2-plan-mat-col" }, matText)
2325
+ )
2326
+ })
2327
+ )
2328
+ ),
2329
+ React.createElement(
2330
+ "div",
2331
+ { className: "kr2-plan-sec" },
2332
+ React.createElement("h3", null, "素材需求汇总(按素材分别统计)"),
2333
+ React.createElement(
2334
+ "table",
2335
+ { className: "kr2-table kr2-plan-table" },
2336
+ React.createElement(
2337
+ "thead",
2338
+ null,
2339
+ React.createElement("tr", null, React.createElement("th", { className: "kr2-plan-mat-th" }, "素材名称"), React.createElement("th", null, "需求"), React.createElement("th", null, "库存"), React.createElement("th", { className: "kr2-plan-mat-th" }, "消耗于装备"))
2340
+ ),
2341
+ React.createElement(
2342
+ "tbody",
2343
+ null,
2344
+ materialList.length === 0
2345
+ ? React.createElement("tr", null, React.createElement("td", { colSpan: 4 }, "暂无素材需求"))
2346
+ : materialList.map((m) =>
2347
+ React.createElement(
2348
+ "tr",
2349
+ { key: m.name },
2350
+ React.createElement("td", { className: "kr2-plan-name" }, React.createElement("button", { className: "kr2-plan-mat-btn", onClick: () => onMaterialClick && onMaterialClick(m.item) }, m.name)),
2351
+ React.createElement("td", null, String(m.required)),
2352
+ React.createElement("td", { className: stockClass(m.stock, m.required) }, m.stock == null ? "--" : String(m.stock)),
2353
+ React.createElement("td", { className: "kr2-plan-consumed" }, m.equips.join("、"))
2354
+ )
2355
+ )
2356
+ )
2357
+ )
2358
+ )
2359
+ )
2360
+ }
2361
+
1832
2362
  function RateCell({ range, open, onToggle, tone }) {
1833
2363
  return React.createElement(
1834
2364
  "span",
@@ -2119,6 +2649,8 @@ class KoushuRateApp extends React.Component {
2119
2649
  favorites: readFavorites(),
2120
2650
  favoritesOnly: false,
2121
2651
  starSort: null,
2652
+ activeTab: "list",
2653
+ planSelection: {},
2122
2654
  }
2123
2655
 
2124
2656
  componentDidMount() {
@@ -2276,6 +2808,46 @@ class KoushuRateApp extends React.Component {
2276
2808
  this.setState({ inventoryPopup: null })
2277
2809
  }
2278
2810
 
2811
+ setActiveTab = (tab) => {
2812
+ this.setState({ activeTab: tab })
2813
+ }
2814
+
2815
+ planDefaultSel = (id) => ({ selected: true, target: "max", qty: 1 })
2816
+
2817
+ togglePlanRow = (id) => {
2818
+ this.setState((prev) => {
2819
+ const cur = prev.planSelection[id] || this.planDefaultSel(id)
2820
+ return { planSelection: Object.assign({}, prev.planSelection, { [id]: Object.assign({}, cur, { selected: !cur.selected }) }) }
2821
+ })
2822
+ }
2823
+
2824
+ setPlanTarget = (id, target) => {
2825
+ this.setState((prev) => {
2826
+ const cur = prev.planSelection[id] || this.planDefaultSel(id)
2827
+ return { planSelection: Object.assign({}, prev.planSelection, { [id]: Object.assign({}, cur, { target }) }) }
2828
+ })
2829
+ }
2830
+
2831
+ setPlanQty = (id, qty) => {
2832
+ const n = Math.max(1, Math.min(99, Math.round(Number(qty) || 1)))
2833
+ this.setState((prev) => {
2834
+ const cur = prev.planSelection[id] || this.planDefaultSel(id)
2835
+ return { planSelection: Object.assign({}, prev.planSelection, { [id]: Object.assign({}, cur, { qty: n }) }) }
2836
+ })
2837
+ }
2838
+
2839
+ selectAllPlan = (selected) => {
2840
+ this.setState((prev) => {
2841
+ const planSelection = Object.assign({}, prev.planSelection)
2842
+ for (const row of prev.rows) {
2843
+ if (!prev.favorites[row.id]) continue
2844
+ const cur = planSelection[row.id] || this.planDefaultSel(row.id)
2845
+ planSelection[row.id] = Object.assign({}, cur, { selected })
2846
+ }
2847
+ return { planSelection }
2848
+ })
2849
+ }
2850
+
2279
2851
  setKcDevSort = (key) => {
2280
2852
  this.setState((prev) => {
2281
2853
  const popup = prev.kcDevPopup
@@ -2390,7 +2962,7 @@ class KoushuRateApp extends React.Component {
2390
2962
  ),
2391
2963
  React.createElement(
2392
2964
  "div",
2393
- { className: "kr2-toolbar" },
2965
+ { className: "kr2-toolbar", style: this.state.activeTab === "plan" ? { display: "none" } : null },
2394
2966
  React.createElement("input", {
2395
2967
  className: "kr2-search",
2396
2968
  value: this.state.query,
@@ -2434,7 +3006,13 @@ class KoushuRateApp extends React.Component {
2434
3006
  ),
2435
3007
  React.createElement(
2436
3008
  "div",
2437
- { className: "kr2-days" },
3009
+ { className: "kr2-nav" },
3010
+ React.createElement("button", { className: "kr2-nav-btn" + (this.state.activeTab === "list" ? " kr2-nav-active" : ""), onClick: () => this.setActiveTab("list") }, "改修列表"),
3011
+ React.createElement("button", { className: "kr2-nav-btn" + (this.state.activeTab === "plan" ? " kr2-nav-active" : ""), onClick: () => this.setActiveTab("plan") }, "素材计算")
3012
+ ),
3013
+ React.createElement(
3014
+ "div",
3015
+ { className: "kr2-days", style: this.state.activeTab === "plan" ? { display: "none" } : null },
2438
3016
  React.createElement(
2439
3017
  "button",
2440
3018
  { className: "kr2-day" + (day === ALL_DAYS_KEY ? " kr2-day-active" : ""), onClick: () => this.setState({ day: ALL_DAYS_KEY }) },
@@ -2459,7 +3037,7 @@ class KoushuRateApp extends React.Component {
2459
3037
  : null,
2460
3038
  React.createElement(
2461
3039
  "div",
2462
- { className: "kr2-list-head" },
3040
+ { className: "kr2-list-head", style: this.state.activeTab === "plan" ? { display: "none" } : null },
2463
3041
  React.createElement("span", { className: "kr2-head-name" }, "装备"),
2464
3042
  React.createElement("span", { className: "kr2-head-secretary" }, "秘书舰"),
2465
3043
  React.createElement("span", { className: "kr2-head-meta" }, "可改修日期"),
@@ -2476,7 +3054,7 @@ class KoushuRateApp extends React.Component {
2476
3054
  ),
2477
3055
  React.createElement(
2478
3056
  "div",
2479
- { className: "kr2-list" },
3057
+ { className: "kr2-list", style: this.state.activeTab === "plan" ? { display: "none" } : null },
2480
3058
  rows.length === 0
2481
3059
  ? React.createElement("div", { className: "kr2-empty" }, favoritesOnly ? "还没有收藏装备" : "没有匹配的装备")
2482
3060
  : rows.map((row) =>
@@ -2495,6 +3073,19 @@ class KoushuRateApp extends React.Component {
2495
3073
  })
2496
3074
  )
2497
3075
  ),
3076
+ this.state.activeTab === "plan"
3077
+ ? React.createElement(PlanningPage, {
3078
+ rows: this.state.rows.filter((row) => !!this.state.favorites[row.id]),
3079
+ inventoryByEquip,
3080
+ useItemCounts,
3081
+ selection: this.state.planSelection || {},
3082
+ onToggle: this.togglePlanRow,
3083
+ onTarget: this.setPlanTarget,
3084
+ onQty: this.setPlanQty,
3085
+ onSelectAll: this.selectAllPlan,
3086
+ onMaterialClick: this.openKcDevPopup,
3087
+ })
3088
+ : null,
2498
3089
  kcDevPopup
2499
3090
  ? React.createElement(
2500
3091
  "div",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poi-plugin-koushu-rate",
3
- "version": "2.0.0",
3
+ "version": "2.0.8",
4
4
  "description": "全装备改修资材计算与推荐",
5
5
  "main": "index.js",
6
6
  "files": [