poi-plugin-kai-planner 1.0.0

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 (54) hide show
  1. package/README.md +48 -0
  2. package/index.js +35 -0
  3. package/package.json +25 -0
  4. package/src/app/KaiPlannerApp.js +211 -0
  5. package/src/app/tabs/daily/DailyTab.js +439 -0
  6. package/src/app/tabs/debug/DebugTab.js +554 -0
  7. package/src/app/tabs/wishlist/CreatePlanForm.js +185 -0
  8. package/src/app/tabs/wishlist/WishlistTab.js +704 -0
  9. package/src/app/tabs/wishlist/components/MouseComboBox.js +185 -0
  10. package/src/app/tabs/wishlist/components/WishlistExpandedDetail.js +170 -0
  11. package/src/app/tabs/wishlist/components/WishlistTable.js +253 -0
  12. package/src/core/poi/secretaryName.js +64 -0
  13. package/src/data/indexes/buildArrangementIndex.js +103 -0
  14. package/src/data/loaders/loadStaticData.js +182 -0
  15. package/src/data/static/data_manifest.json +15 -0
  16. package/src/data/static/equip_base_cost.json +5000 -0
  17. package/src/data/static/equipment_upgrade_path.json +3555 -0
  18. package/src/data/static/improvement_arrangement.json +15677 -0
  19. package/src/data/static/improvement_consume_item.json +8933 -0
  20. package/src/data/static/improvement_consume_step.json +9284 -0
  21. package/src/data/static/improvement_upgrade_cost.json +4766 -0
  22. package/src/data/static/improvement_upgrade_target.json +2641 -0
  23. package/src/data/static/material.json +90 -0
  24. package/src/services/common/secretaryDisplay.js +42 -0
  25. package/src/services/daily/buildDailyViewModel.js +402 -0
  26. package/src/services/planner/buildUpgradePath.js +79 -0
  27. package/src/services/planner/calcImproveSteps.js +104 -0
  28. package/src/services/planner/calcRemainingPlan.js +169 -0
  29. package/src/services/planner/calcRoutePlan.js +85 -0
  30. package/src/services/planner/calcUpgradeStep.js +85 -0
  31. package/src/services/planner/detectCurrentPosition.js +57 -0
  32. package/src/services/planner/summarizeShortage.js +76 -0
  33. package/src/services/player/countPlayerEquipByMasterId.js +27 -0
  34. package/src/services/player/getEquipOwnerShip.js +66 -0
  35. package/src/services/player/getPlayerData.js +14 -0
  36. package/src/services/player/getPlayerItemCountByUseitemId.js +45 -0
  37. package/src/services/player/getReduxStateFromEnvWindow.js +12 -0
  38. package/src/services/player/resolveMaterialKeyToUseitemId.js +54 -0
  39. package/src/services/static/indexes/buildConsumeIndexes.js +28 -0
  40. package/src/services/static/indexes/buildPathIndexes.js +29 -0
  41. package/src/services/static/indexes/buildUpgradeIndexes.js +57 -0
  42. package/src/services/static/version/dataSourceConfig.js +25 -0
  43. package/src/services/static/version/dataUpdateManager.js +176 -0
  44. package/src/services/static/version/versionStore.js +205 -0
  45. package/src/services/utils/toInt.js +11 -0
  46. package/src/services/utils/tokyoTime.js +58 -0
  47. package/src/services/wishlist/buildWishlistViewModel.js +56 -0
  48. package/src/services/wishlist/dropdownInteraction.js +30 -0
  49. package/src/services/wishlist/wishlistActions.js +485 -0
  50. package/src/storage/userPlans/fileStore.js +106 -0
  51. package/src/storage/userPlans/localStorageStore.js +50 -0
  52. package/src/storage/userPlans/migrate.js +60 -0
  53. package/src/storage/userPlans/planStore.js +107 -0
  54. package/src/storage/userPlans/storeAdapter.js +77 -0
@@ -0,0 +1,704 @@
1
+ /* src/app/tabs/wishlist/WishlistTab.js */
2
+
3
+ const React = require("react");
4
+
5
+ const { loadStaticData } = require("../../../data/loaders/loadStaticData");
6
+ const { buildArrangementIndex } = require("../../../data/indexes/buildArrangementIndex");
7
+ const { getReduxStateFromEnvWindow } = require("../../../services/player/getReduxStateFromEnvWindow");
8
+ const { getPlayerEquipByApiId } = require("../../../services/player/getPlayerData");
9
+ const {
10
+ buildEquipOwnerShipIndex,
11
+ getEquipOwnerShipByApiId,
12
+ buildPlanOwnerShipNameMap,
13
+ } = require("../../../services/player/getEquipOwnerShip");
14
+ const { getPlayerItemCountByUseitemId } = require("../../../services/player/getPlayerItemCountByUseitemId");
15
+ const { countPlayerEquipByMasterId } = require("../../../services/player/countPlayerEquipByMasterId");
16
+ const { resolveMaterialKeyToUseitemId } = require("../../../services/player/resolveMaterialKeyToUseitemId");
17
+ const { buildConsumeIndexes } = require("../../../services/static/indexes/buildConsumeIndexes");
18
+ const { buildUpgradeIndexes } = require("../../../services/static/indexes/buildUpgradeIndexes");
19
+ const { buildPathIndexes } = require("../../../services/static/indexes/buildPathIndexes");
20
+ const { calcRemainingPlan } = require("../../../services/planner/calcRemainingPlan");
21
+ const { detectCurrentPosition } = require("../../../services/planner/detectCurrentPosition");
22
+ const { toInt } = require("../../../services/utils/toInt");
23
+ const { formatTokyoDateTimeWithWeekday, getTokyoWeekdayKey } = require("../../../services/utils/tokyoTime");
24
+ const {
25
+ loadPlans,
26
+ createPlan,
27
+ deletePlan,
28
+ replacePlanStartApiId,
29
+ updatePlanPriority,
30
+ updatePlanTargetLevel,
31
+ upsertLastResult,
32
+ } = require("../../../storage/userPlans/planStore");
33
+ const { CreatePlanForm } = require("./CreatePlanForm");
34
+ const { WishlistTable } = require("./components/WishlistTable");
35
+ const { WishlistExpandedDetail } = require("./components/WishlistExpandedDetail");
36
+ const { buildWishlistViewModel } = require("../../../services/wishlist/buildWishlistViewModel");
37
+ const {
38
+ buildStartOptionsAction,
39
+ refreshPlansAction,
40
+ refreshPlanByIdAction,
41
+ submitCreateAction,
42
+ updatePlanPriorityAction,
43
+ updatePlanTargetLevelAction,
44
+ } = require("../../../services/wishlist/wishlistActions");
45
+
46
+ const PRIORITIES = ["P0", "P1", "P2", "P3", "P4", "P5"];
47
+ const START_OPTIONS_DEFAULT_LIMIT = 120;
48
+
49
+ function formatTokyoText() {
50
+ return `当前时间(GMT+9):${formatTokyoDateTimeWithWeekday()}`;
51
+ }
52
+
53
+ function getEquipName(masterEquipsById, id) {
54
+ const row = masterEquipsById && masterEquipsById[String(id)];
55
+ if (row && row.api_name) return String(row.api_name);
56
+ return `UnknownEquip(${id})`;
57
+ }
58
+
59
+ function getEquipSortno(masterEquipsById, id) {
60
+ const row = masterEquipsById && masterEquipsById[String(id)];
61
+ return row && row.api_sortno != null ? toInt(row.api_sortno, 0) : 0;
62
+ }
63
+
64
+ function getPathDistance(pathByPair, fromEquipId, toEquipId) {
65
+ const from = toInt(fromEquipId, 0);
66
+ const to = toInt(toEquipId, 0);
67
+ if (!from || !to) return Number.POSITIVE_INFINITY;
68
+ if (from === to) return 0;
69
+ const k = `${from}|${to}`;
70
+ const path = pathByPair && Array.isArray(pathByPair[k]) ? pathByPair[k] : null;
71
+ if (!path || path.length < 2) return Number.POSITIVE_INFINITY;
72
+ return Math.max(0, path.length - 1);
73
+ }
74
+
75
+ function getPlanStartSnapshotText(plan) {
76
+ const snap = plan && plan.startSnapshot ? plan.startSnapshot : null;
77
+ if (!snap) return "-";
78
+ const name = snap.name ? String(snap.name) : `UnknownEquip(${String(snap.equipId || "")})`;
79
+ const level = toInt(snap.level, 0);
80
+ return `${name}(+${level})`;
81
+ }
82
+
83
+ function stockStatusMark(need, owned) {
84
+ const ok = toInt(owned, 0) >= toInt(need, 0);
85
+ return ok
86
+ ? React.createElement("span", { style: { color: "#22c55e", marginLeft: 6 } }, "✔")
87
+ : React.createElement("span", { style: { color: "#ef4444", marginLeft: 6 } }, "⚠");
88
+ }
89
+
90
+ class WishlistTab extends React.Component {
91
+ constructor(props) {
92
+ super(props);
93
+
94
+ this.staticData = loadStaticData();
95
+ this.consumeIndexes = buildConsumeIndexes(this.staticData);
96
+ this.upgradeIndexes = buildUpgradeIndexes(this.staticData);
97
+ this.pathByPair = buildPathIndexes(this.staticData).pathByPair;
98
+ this.arrangementIndex = buildArrangementIndex(this.staticData.improvementArrangement).index;
99
+ this._ownerShipIndexCache = { shipsRef: null, map: {} };
100
+ this._startOptionsCache = { targetEquipId: null, equipsRef: null, plansKey: "", ownerMapRef: null, options: [] };
101
+
102
+ this.state = {
103
+ nowText: formatTokyoText(),
104
+ plans: [],
105
+ planResults: {},
106
+ expandedById: {},
107
+ rebindInputById: {},
108
+ editById: {},
109
+ filterTargetName: "",
110
+ showCreateForm: false,
111
+ create: {
112
+ targetEquipId: "",
113
+ targetLevel: "0",
114
+ priority: "P0",
115
+ startApiId: "",
116
+ note: "",
117
+ targetQuery: "",
118
+ startQuery: "",
119
+ },
120
+ createError: null,
121
+ error: null,
122
+ };
123
+
124
+ this.refreshPlans = this.refreshPlans.bind(this);
125
+ this.refreshPlanById = this.refreshPlanById.bind(this);
126
+ }
127
+
128
+ componentDidMount() {
129
+ this._timer = setInterval(() => this.setState({ nowText: formatTokyoText() }), 1000);
130
+ this.refreshPlans();
131
+ }
132
+
133
+ componentWillUnmount() {
134
+ if (this._timer) clearInterval(this._timer);
135
+ }
136
+
137
+ getReduxState() {
138
+ return getReduxStateFromEnvWindow(this.props.envWindow);
139
+ }
140
+
141
+ runPlanForInput({ state, apiId, targetEquipId, targetLevel }) {
142
+ const playerEquip = getPlayerEquipByApiId(state, apiId);
143
+ if (!playerEquip) return { ok: false, error: `player equip api_id not found: ${apiId}` };
144
+
145
+ return calcRemainingPlan({
146
+ staticData: this.staticData,
147
+ state,
148
+ apiId,
149
+ targetEquipId,
150
+ targetLevel,
151
+ consumeIndexes: this.consumeIndexes,
152
+ upgradeIndexes: this.upgradeIndexes,
153
+ pathByPair: this.pathByPair,
154
+ masterEquipsById: state.const.$equips || {},
155
+ masterUseitemsById: state.const.$useitems || {},
156
+ playerEquip,
157
+ playerHelpers: {
158
+ countPlayerEquipByMasterId,
159
+ resolveMaterialKeyToUseitemId,
160
+ getPlayerItemCountByUseitemId,
161
+ },
162
+ });
163
+ }
164
+
165
+ runPlanForSnapshotInput({ state, apiId, startEquipId, startLevel, targetEquipId, targetLevel }) {
166
+ const equipId = toInt(startEquipId, 0);
167
+ if (!equipId) return { ok: false, error: `invalid start equip id: ${startEquipId}` };
168
+
169
+ const pseudoPlayerEquip = {
170
+ api_id: toInt(apiId, 0) || 0,
171
+ api_slotitem_id: equipId,
172
+ api_level: Math.max(0, toInt(startLevel, 0)),
173
+ };
174
+
175
+ return calcRemainingPlan({
176
+ staticData: this.staticData,
177
+ state,
178
+ apiId,
179
+ targetEquipId,
180
+ targetLevel,
181
+ consumeIndexes: this.consumeIndexes,
182
+ upgradeIndexes: this.upgradeIndexes,
183
+ pathByPair: this.pathByPair,
184
+ masterEquipsById: state.const.$equips || {},
185
+ masterUseitemsById: state.const.$useitems || {},
186
+ playerEquip: pseudoPlayerEquip,
187
+ playerHelpers: {
188
+ countPlayerEquipByMasterId,
189
+ resolveMaterialKeyToUseitemId,
190
+ getPlayerItemCountByUseitemId,
191
+ },
192
+ });
193
+ }
194
+
195
+ isTargetImprovable(targetEquipId) {
196
+ const id = toInt(targetEquipId, 0);
197
+ if (!id) return false;
198
+ const steps = this.consumeIndexes.stepById || {};
199
+ return !!(steps[`${id}_0`] || steps[`${id}_1`]);
200
+ }
201
+
202
+ getTargetCandidates(masterEquipsById) {
203
+ const improvable = new Set();
204
+ for (const k of Object.keys(this.consumeIndexes.stepById || {})) {
205
+ const equipId = String(k).split("_")[0];
206
+ if (equipId) improvable.add(String(toInt(equipId, 0)));
207
+ }
208
+
209
+ const pathTerminals = new Set();
210
+ for (const row of this.staticData.equipmentUpgradePath || []) {
211
+ if (!row || row.to_equipment_id == null || row.is_deleted === true) continue;
212
+ pathTerminals.add(String(toInt(row.to_equipment_id, 0)));
213
+ }
214
+
215
+ const all = new Set([...improvable, ...pathTerminals]);
216
+ return Array.from(all)
217
+ .map((idStr) => toInt(idStr, 0))
218
+ .filter((x) => x > 0 && masterEquipsById[String(x)])
219
+ .sort((a, b) => {
220
+ const sa = getEquipSortno(masterEquipsById, a);
221
+ const sb = getEquipSortno(masterEquipsById, b);
222
+ if (sa !== sb) return sa - sb;
223
+ return a - b;
224
+ });
225
+ }
226
+
227
+ getEquipOwnerShipIndex(state) {
228
+ const shipsRef = state && state.info ? state.info.ships : null;
229
+ if (this._ownerShipIndexCache.shipsRef === shipsRef) {
230
+ return this._ownerShipIndexCache.map || {};
231
+ }
232
+ const map = buildEquipOwnerShipIndex(state);
233
+ this._ownerShipIndexCache = { shipsRef, map };
234
+ return map;
235
+ }
236
+
237
+ buildStartOptions(state, plans, targetEquipId, equipOwnerShipIndex) {
238
+ const res = buildStartOptionsAction({
239
+ state,
240
+ plans,
241
+ targetEquipId,
242
+ equipOwnerShipIndex,
243
+ pathByPair: this.pathByPair,
244
+ getEquipSortno,
245
+ getEquipName,
246
+ getPathDistance,
247
+ cache: this._startOptionsCache,
248
+ getEquipOwnerShipByApiId,
249
+ });
250
+ this._startOptionsCache = res.cache || this._startOptionsCache;
251
+ return res.options || [];
252
+ }
253
+
254
+ refreshPlans() {
255
+ try {
256
+ const state = this.getReduxState();
257
+ const plans = loadPlans();
258
+ const equipOwnerShipIndex = this.getEquipOwnerShipIndex(state);
259
+ const actionRes = refreshPlansAction({
260
+ state,
261
+ plans,
262
+ detectCurrentPosition,
263
+ runPlanForInput: ({ state: s, apiId, targetEquipId, targetLevel }) =>
264
+ this.runPlanForInput({ state: s, apiId, targetEquipId, targetLevel }),
265
+ upsertLastResult,
266
+ arrangementIndex: this.arrangementIndex,
267
+ weekdayKey: getTokyoWeekdayKey(),
268
+ masterShipsRaw: state && state.const ? state.const.$ships || {} : {},
269
+ planOwnerShipNameByPlanId: buildPlanOwnerShipNameMap(plans, equipOwnerShipIndex),
270
+ });
271
+
272
+ this.setState({
273
+ plans: actionRes.plans || plans,
274
+ planResults: actionRes.planResults || {},
275
+ error: actionRes.error || null,
276
+ });
277
+ } catch (e) {
278
+ this.setState({ error: String(e && (e.stack || e)) });
279
+ }
280
+ }
281
+
282
+ startEditPlan(plan) {
283
+ if (!plan || !plan.id) return;
284
+ this.setState((prev) => ({
285
+ editById: {
286
+ ...(prev.editById || {}),
287
+ [plan.id]: {
288
+ isEditing: true,
289
+ saving: false,
290
+ error: null,
291
+ priority: String(plan.priority || "P0"),
292
+ targetLevel: String(plan.targetLevel == null ? "0" : plan.targetLevel),
293
+ },
294
+ },
295
+ }));
296
+ }
297
+
298
+ cancelEditPlan(planId) {
299
+ this.setState((prev) => {
300
+ const next = { ...(prev.editById || {}) };
301
+ delete next[planId];
302
+ return { editById: next };
303
+ });
304
+ }
305
+
306
+ patchEditPlan(planId, patch) {
307
+ this.setState((prev) => ({
308
+ editById: {
309
+ ...(prev.editById || {}),
310
+ [planId]: { ...((prev.editById || {})[planId] || {}), ...(patch || {}) },
311
+ },
312
+ }));
313
+ }
314
+
315
+ saveEditPlan(planId) {
316
+ const draft = (this.state.editById || {})[planId];
317
+ if (!draft || !draft.isEditing || draft.saving) return;
318
+
319
+ const plans = this.state.plans || [];
320
+ const plan = plans.find((p) => String(p.id) === String(planId));
321
+ if (!plan) return;
322
+
323
+ this.patchEditPlan(planId, { saving: true, error: null });
324
+ try {
325
+ const nextPriority = String(draft.priority || "P0");
326
+ const nextTargetLevel = String(draft.targetLevel == null ? "0" : draft.targetLevel);
327
+
328
+ if (nextPriority !== String(plan.priority || "P0")) {
329
+ const pRes = updatePlanPriorityAction({
330
+ planId,
331
+ priority: nextPriority,
332
+ priorities: PRIORITIES,
333
+ loadPlans,
334
+ updatePlanPriority,
335
+ });
336
+ if (!pRes || !pRes.ok) {
337
+ this.patchEditPlan(planId, {
338
+ saving: false,
339
+ error: pRes && pRes.error ? pRes.error : "更新优先级失败。",
340
+ });
341
+ return;
342
+ }
343
+ }
344
+
345
+ if (nextTargetLevel !== String(plan.targetLevel == null ? "0" : plan.targetLevel)) {
346
+ const tRes = updatePlanTargetLevelAction({
347
+ state: this.getReduxState(),
348
+ planId,
349
+ targetLevel: nextTargetLevel,
350
+ isTargetImprovable: (targetEquipId) => this.isTargetImprovable(targetEquipId),
351
+ loadPlans,
352
+ runPlanForInput: ({ state, apiId, targetEquipId, targetLevel }) =>
353
+ this.runPlanForInput({ state, apiId, targetEquipId, targetLevel }),
354
+ runPlanForSnapshotInput: ({ state, apiId, startEquipId, startLevel, targetEquipId, targetLevel }) =>
355
+ this.runPlanForSnapshotInput({ state, apiId, startEquipId, startLevel, targetEquipId, targetLevel }),
356
+ updatePlanTargetLevel,
357
+ getPlayerEquipByApiId,
358
+ getEquipSortno,
359
+ });
360
+ if (!tRes || !tRes.ok) {
361
+ this.patchEditPlan(planId, {
362
+ saving: false,
363
+ error: tRes && tRes.error ? tRes.error : "更新目标星数失败。",
364
+ });
365
+ return;
366
+ }
367
+ }
368
+
369
+ this.cancelEditPlan(planId);
370
+ this.refreshPlans();
371
+ } catch (e) {
372
+ this.patchEditPlan(planId, {
373
+ saving: false,
374
+ error: String(e && (e.stack || e)),
375
+ });
376
+ }
377
+ }
378
+
379
+ refreshPlanById(planId) {
380
+ try {
381
+ const state = this.getReduxState();
382
+ if (!state || !state.const || !state.info) return;
383
+ const target = loadPlans().find((p) => String(p.id) === String(planId));
384
+ if (!target) return;
385
+ const equipOwnerShipIndex = this.getEquipOwnerShipIndex(state);
386
+ const wrap = refreshPlanByIdAction({
387
+ state,
388
+ targetPlan: target,
389
+ detectCurrentPosition,
390
+ runPlanForInput: ({ state: s, apiId, targetEquipId, targetLevel }) =>
391
+ this.runPlanForInput({ state: s, apiId, targetEquipId, targetLevel }),
392
+ upsertLastResult,
393
+ arrangementIndex: this.arrangementIndex,
394
+ weekdayKey: getTokyoWeekdayKey(),
395
+ masterShipsRaw: state.const.$ships || {},
396
+ planOwnerShipNameByPlanId: buildPlanOwnerShipNameMap([target], equipOwnerShipIndex),
397
+ });
398
+ if (!wrap) return;
399
+
400
+ this.setState((prev) => ({
401
+ plans: loadPlans(),
402
+ planResults: { ...(prev.planResults || {}), [target.id]: wrap },
403
+ }));
404
+ } catch {}
405
+ }
406
+
407
+ submitCreate() {
408
+ try {
409
+ const actionRes = submitCreateAction({
410
+ state: this.getReduxState(),
411
+ createState: this.state.create,
412
+ priorities: PRIORITIES,
413
+ isTargetImprovable: (targetEquipId) => this.isTargetImprovable(targetEquipId),
414
+ pathByPair: this.pathByPair,
415
+ getPlayerEquipByApiId,
416
+ loadPlans,
417
+ runPlanForInput: ({ state, apiId, targetEquipId, targetLevel }) =>
418
+ this.runPlanForInput({ state, apiId, targetEquipId, targetLevel }),
419
+ createPlan,
420
+ getEquipName,
421
+ });
422
+ if (!actionRes || !actionRes.ok) {
423
+ this.setState({ createError: actionRes && actionRes.error ? actionRes.error : "创建失败。" });
424
+ return;
425
+ }
426
+
427
+ this.setState({
428
+ showCreateForm: false,
429
+ createError: null,
430
+ create: { targetEquipId: "", targetLevel: "0", priority: "P0", startApiId: "", note: "", targetQuery: "", startQuery: "" },
431
+ });
432
+ this.refreshPlans();
433
+ } catch (e) {
434
+ this.setState({ createError: String(e && (e.stack || e)) });
435
+ }
436
+ }
437
+
438
+ renderCreateForm(state, plans) {
439
+ if (!this.state.showCreateForm) return null;
440
+ const masterEquipsById = state.const.$equips || {};
441
+ const targetCandidatesAll = this.getTargetCandidates(masterEquipsById);
442
+ const c = this.state.create;
443
+ const improvable = this.isTargetImprovable(c.targetEquipId);
444
+ const targetQuery = String(c.targetQuery || "").trim().toLowerCase();
445
+ const targetCandidates = targetCandidatesAll.filter((id) => {
446
+ if (!targetQuery) return true;
447
+ const name = getEquipName(masterEquipsById, id).toLowerCase();
448
+ const idText = String(id);
449
+ return name.includes(targetQuery) || idText.includes(targetQuery);
450
+ });
451
+ const equipOwnerShipIndex = this.getEquipOwnerShipIndex(state);
452
+ const startOptionsAll = c.targetEquipId ? this.buildStartOptions(state, plans, c.targetEquipId, equipOwnerShipIndex) : [];
453
+ const startQuery = String(c.startQuery || "").trim().toLowerCase();
454
+ const startFiltered = startOptionsAll.filter((o) => {
455
+ if (!startQuery) return true;
456
+ return `${o.name} ${o.apiId} ${o.ownerShipName || ""}`.toLowerCase().includes(startQuery);
457
+ });
458
+ const isStartUnsearched = !startQuery;
459
+ const startRenderList = isStartUnsearched ? startFiltered.slice(0, START_OPTIONS_DEFAULT_LIMIT) : startFiltered;
460
+ const startOptionsTip =
461
+ isStartUnsearched && startFiltered.length > START_OPTIONS_DEFAULT_LIMIT
462
+ ? `仅显示前 ${START_OPTIONS_DEFAULT_LIMIT} 条起点装备,请输入更准确关键词继续筛选。`
463
+ : "";
464
+
465
+ const targetOptions = targetCandidates.map((id) => ({
466
+ value: String(id),
467
+ label: `${getEquipName(masterEquipsById, id)} (equipId:${id})`,
468
+ inputText: `${getEquipName(masterEquipsById, id)} (equipId:${id})`,
469
+ disabled: false,
470
+ }));
471
+ const startOptions = startRenderList.map((o) => ({
472
+ value: o.apiId,
473
+ label: `[api:${o.apiId}] ${o.name} +${o.level}${o.ownerShipName ? `(${o.ownerShipName})` : ""}${
474
+ o.isLocked ? " [锁定]" : " [未锁定]"
475
+ } (${o.disabled ? o.disabledReason : "可选"})`,
476
+ inputText: `[api:${o.apiId}] ${o.name} +${o.level}${o.ownerShipName ? `(${o.ownerShipName})` : ""}${
477
+ o.isLocked ? " [锁定]" : " [未锁定]"
478
+ }`,
479
+ disabled: o.disabled,
480
+ }));
481
+ const targetInputValue = c.targetQuery || "";
482
+ const startInputValue = c.startQuery || "";
483
+ const selectedTargetText = c.targetEquipId
484
+ ? `${getEquipName(masterEquipsById, c.targetEquipId)} (equipId:${c.targetEquipId})`
485
+ : "";
486
+ const selectedStart = startOptionsAll.find((o) => String(o.apiId) === String(c.startApiId));
487
+ const selectedStartText = selectedStart
488
+ ? `[api:${selectedStart.apiId}] ${selectedStart.name} +${selectedStart.level}${
489
+ selectedStart.ownerShipName ? `(${selectedStart.ownerShipName})` : ""
490
+ }${selectedStart.isLocked ? " [锁定]" : " [未锁定]"}`
491
+ : "";
492
+
493
+ return React.createElement(CreatePlanForm, {
494
+ visible: this.state.showCreateForm,
495
+ create: c,
496
+ createError: this.state.createError,
497
+ improvable,
498
+ priorities: PRIORITIES,
499
+ targetOptions,
500
+ startOptions,
501
+ startOptionsTip,
502
+ targetInputValue,
503
+ startInputValue,
504
+ selectedTargetText,
505
+ selectedStartText,
506
+ onPatchCreate: (patch) =>
507
+ this.setState((prev) => ({
508
+ create: { ...(prev.create || {}), ...(patch || {}) },
509
+ })),
510
+ onSelectTarget: (targetEquipId, option) => {
511
+ const canImprove = this.isTargetImprovable(targetEquipId);
512
+ this.setState((prev) => ({
513
+ create: {
514
+ ...(prev.create || {}),
515
+ targetEquipId,
516
+ targetQuery: option && option.inputText ? option.inputText : "",
517
+ targetLevel: canImprove ? (prev.create && prev.create.targetLevel ? prev.create.targetLevel : "0") : "0",
518
+ startApiId: "",
519
+ startQuery: "",
520
+ },
521
+ }));
522
+ },
523
+ onSelectStart: (startApiId, option) =>
524
+ this.setState((prev) => ({
525
+ create: {
526
+ ...(prev.create || {}),
527
+ startApiId: String(startApiId || ""),
528
+ startQuery: option && option.inputText ? option.inputText : "",
529
+ },
530
+ })),
531
+ onClearTarget: () =>
532
+ this.setState((prev) => ({
533
+ create: {
534
+ ...(prev.create || {}),
535
+ targetEquipId: "",
536
+ targetQuery: "",
537
+ targetLevel: "0",
538
+ startApiId: "",
539
+ startQuery: "",
540
+ },
541
+ })),
542
+ onClearStart: () =>
543
+ this.setState((prev) => ({
544
+ create: {
545
+ ...(prev.create || {}),
546
+ startApiId: "",
547
+ startQuery: "",
548
+ },
549
+ })),
550
+ onSubmit: () => this.submitCreate(),
551
+ onCancel: () => this.setState({ showCreateForm: false, createError: null }),
552
+ });
553
+ }
554
+
555
+ renderExpandedRow(plan, planWrap) {
556
+ const masterEquipsById = ((this.getReduxState() || {}).const || {}).$equips || {};
557
+ return React.createElement(WishlistExpandedDetail, {
558
+ plan,
559
+ planWrap,
560
+ masterEquipsById,
561
+ getEquipName,
562
+ toInt,
563
+ stockStatusMark,
564
+ });
565
+ }
566
+
567
+ render() {
568
+ const state = this.getReduxState();
569
+ const plans = this.state.plans || [];
570
+
571
+ const wrapper = {
572
+ height: "calc(100vh - 140px)",
573
+ display: "flex",
574
+ flexDirection: "column",
575
+ color: "#e5e7eb",
576
+ fontFamily: "system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial",
577
+ fontSize: 12,
578
+ };
579
+ const top = {
580
+ padding: 12,
581
+ border: "1px solid rgba(255,255,255,0.12)",
582
+ borderRadius: 10,
583
+ background: "rgba(16,24,40,0.92)",
584
+ flexShrink: 0,
585
+ };
586
+ const bottom = {
587
+ marginTop: 10,
588
+ flex: 1,
589
+ overflowY: "auto",
590
+ border: "1px solid rgba(255,255,255,0.12)",
591
+ borderRadius: 10,
592
+ background: "rgba(0,0,0,0.08)",
593
+ };
594
+ const row = { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" };
595
+ const input = {
596
+ padding: "6px 10px",
597
+ borderRadius: 8,
598
+ border: "1px solid rgba(255,255,255,0.12)",
599
+ background: "rgba(0,0,0,0.15)",
600
+ color: "#e5e7eb",
601
+ outline: "none",
602
+ width: 240,
603
+ };
604
+ const th = { textAlign: "left", padding: "10px 10px", borderBottom: "1px solid rgba(255,255,255,0.12)" };
605
+ const td = { padding: "10px 10px", borderBottom: "1px solid rgba(255,255,255,0.08)", verticalAlign: "top" };
606
+
607
+ if (!state || !state.const || !state.info) {
608
+ return React.createElement("pre", null, this.state.error || "Redux state unavailable.");
609
+ }
610
+
611
+ const masterEquipsById = state.const.$equips || {};
612
+ const vm = buildWishlistViewModel({
613
+ plans,
614
+ planResults: this.state.planResults,
615
+ filterTargetName: this.state.filterTargetName,
616
+ getEquipName,
617
+ getEquipSortno,
618
+ masterEquipsById,
619
+ });
620
+
621
+ return React.createElement(
622
+ "div",
623
+ { style: wrapper },
624
+ React.createElement(
625
+ "div",
626
+ { style: top },
627
+ React.createElement("div", { style: { fontSize: 16, fontWeight: 900 } }, "改修心愿清单"),
628
+ React.createElement(
629
+ "div",
630
+ { style: { marginTop: 4, opacity: 0.8 } },
631
+ "创建与管理改修计划,可查看计划进度、剩余消耗缺口等信息"
632
+ ),
633
+ React.createElement("div", { style: { marginTop: 4, opacity: 0.75 } }, this.state.nowText),
634
+ React.createElement(
635
+ "div",
636
+ { style: { ...row, marginTop: 10 } },
637
+ React.createElement("input", {
638
+ style: input,
639
+ placeholder: "按目标装备名称筛选",
640
+ value: this.state.filterTargetName,
641
+ onChange: (e) => this.setState({ filterTargetName: e.target.value }),
642
+ }),
643
+ React.createElement("button", { onClick: () => this.setState({ showCreateForm: true }) }, "新增改修计划"),
644
+ React.createElement("button", { onClick: this.refreshPlans }, "刷新")
645
+ ),
646
+ this.renderCreateForm(state, plans),
647
+ Object.values(this.state.editById || {}).some((x) => x && x.error)
648
+ ? React.createElement(
649
+ "div",
650
+ { style: { marginTop: 8, color: "#fca5a5" } },
651
+ Object.values(this.state.editById || {})
652
+ .map((x) => (x && x.error ? x.error : ""))
653
+ .filter(Boolean)[0]
654
+ )
655
+ : null,
656
+ this.state.error ? React.createElement("div", { style: { marginTop: 8, color: "#fca5a5" } }, this.state.error) : null
657
+ ),
658
+
659
+ React.createElement(
660
+ "div",
661
+ { style: bottom },
662
+ React.createElement(WishlistTable, {
663
+ rows: vm.rows,
664
+ expandedById: this.state.expandedById,
665
+ rebindInputById: this.state.rebindInputById,
666
+ editById: this.state.editById,
667
+ priorities: PRIORITIES,
668
+ styles: { row, input, th, td },
669
+ getPlanStartSnapshotText,
670
+ isTargetImprovable: (targetEquipId) => this.isTargetImprovable(targetEquipId),
671
+ onToggleExpand: (planId, isExp) =>
672
+ this.setState((prev) => ({
673
+ expandedById: { ...(prev.expandedById || {}), [planId]: !isExp },
674
+ })),
675
+ onRefreshPlan: (planId) => this.refreshPlanById(planId),
676
+ onDeletePlan: (planId) => {
677
+ deletePlan(planId);
678
+ this.refreshPlans();
679
+ },
680
+ onRebindInputChange: (planId, value) =>
681
+ this.setState((prev) => ({
682
+ rebindInputById: { ...(prev.rebindInputById || {}), [planId]: value },
683
+ })),
684
+ onRebindConfirm: (planId, rawValue) => {
685
+ const newApiId = String(rawValue || "").trim();
686
+ if (!newApiId) return;
687
+ replacePlanStartApiId(planId, newApiId);
688
+ this.setState((prev) => ({
689
+ rebindInputById: { ...(prev.rebindInputById || {}), [planId]: "" },
690
+ }));
691
+ this.refreshPlans();
692
+ },
693
+ onStartEdit: (plan) => this.startEditPlan(plan),
694
+ onCancelEdit: (planId) => this.cancelEditPlan(planId),
695
+ onPatchEdit: (planId, patch) => this.patchEditPlan(planId, patch),
696
+ onSaveEdit: (planId) => this.saveEditPlan(planId),
697
+ renderExpandedRow: (plan, wrap) => this.renderExpandedRow(plan, wrap),
698
+ })
699
+ )
700
+ );
701
+ }
702
+ }
703
+
704
+ module.exports = WishlistTab;