Skip to content

Commit 84ffa5f

Browse files
authored
Merge pull request #12 from gangbaRuby/feat/export-info
feat(export): 增加排错数据导出功能
2 parents 9a291d7 + 28a9a98 commit 84ffa5f

17 files changed

Lines changed: 268 additions & 5 deletions

.codex/skills/simcompanies-maintenance/SKILL.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ description: Maintain the Auto Max PPHPL SimCompanies Tampermonkey userscript th
2929
- 在适用处使用既有通信方式和共享工具。
3030
- 没有既有约定时,新的 DOM ID/class/data 属性和持久化键使用 `sc-` 前缀。
3131
- 每个 Observer、计时器、Worker 请求和事件监听都必须有明确所有者与清理/重新初始化路径。
32+
- 新增功能若引入 `localStorage`/`sessionStorage` 持久化键或需要排错的持久化状态,必须通过 `src/core/exportInfo.js``registerExportInfo` 注册导出信息;导出中心本身不维护固定键清单。
33+
- 注册时必须标注 `scope``realm`/`global`)并只登记插件自身写入的键;删除或改名存储键时同步更新注册。
3234

3335
修复 Bug 或修改功能时,不得进行架构迁移、大范围清理、命名变更或无关格式化。
3436

.codex/skills/simcompanies-maintenance/references/project-map.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- `src/core/network.js`:带重试的请求辅助方法。
1414
- `src/core/storage.js`:区服识别与作用域存储键辅助方法。
1515
- `src/core/requestHooks.js`:游戏请求拦截与缓存更新。
16+
- `src/core/exportInfo.js`:排错信息导出注册、收集与下载。
1617
- `src/features/dataStorage.js`:常量和区服数据持久化。
1718
- `src/utils/ui.js``src/utils/uiComponents.js`:共享 UI 和开关辅助方法。
1819

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
### 已变更
88

99
- 增加餐馆备货提醒:在餐馆建筑页展示菜品库存、每日消耗与剩余天数,低于 2 天预警;可设置餐馆数量,并支持功能开关。
10+
- 增加排错数据导出:在插件信息区“反馈请说明问题”连续点击 3 次,可导出当前领域及全局的插件 localStorage 原始数据。
1011

1112

1213

src/core/exportInfo.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { getRealmIdFromLink } from './storage.js';
2+
3+
const providers = [];
4+
5+
export function registerExportInfo(provider) {
6+
if (!provider || typeof provider !== 'object') return;
7+
providers.push(provider);
8+
}
9+
10+
function collectKeys(provider, realmId) {
11+
const keys = typeof provider.keys === 'function'
12+
? provider.keys(realmId)
13+
: (Array.isArray(provider.keys) ? provider.keys : []);
14+
return Array.isArray(keys) ? keys : [];
15+
}
16+
17+
function keyMatches(provider, realmId, key) {
18+
if (!provider.match) return false;
19+
const pattern = typeof provider.match === 'function' ? provider.match(realmId) : provider.match;
20+
if (pattern instanceof RegExp) return pattern.test(key);
21+
return false;
22+
}
23+
24+
function readRaw(key) {
25+
const raw = localStorage.getItem(key);
26+
if (raw === null) return null;
27+
const entry = { raw };
28+
try {
29+
entry.parsed = JSON.parse(raw);
30+
} catch (e) {
31+
// 非 JSON 值保持原始字符串
32+
}
33+
return entry;
34+
}
35+
36+
export function collectExportData() {
37+
const realmId = typeof getRealmIdFromLink === 'function' ? getRealmIdFromLink() : null;
38+
const realm = {};
39+
const global = {};
40+
const realmSeen = new Set();
41+
const globalSeen = new Set();
42+
43+
const add = (target, seen, key) => {
44+
if (seen.has(key)) return;
45+
const entry = readRaw(key);
46+
if (entry === null) return;
47+
seen.add(key);
48+
target[key] = entry;
49+
};
50+
51+
for (const provider of providers) {
52+
const isRealm = provider.scope === 'realm';
53+
const target = isRealm ? realm : global;
54+
const seen = isRealm ? realmSeen : globalSeen;
55+
for (const key of collectKeys(provider, realmId)) add(target, seen, key);
56+
if (provider.match) {
57+
for (const key of Object.keys(localStorage)) {
58+
if (keyMatches(provider, realmId, key)) add(target, seen, key);
59+
}
60+
}
61+
}
62+
63+
const meta = {
64+
pluginName: typeof GM_info !== 'undefined' ? GM_info.script.name : 'autoMaxPPHPL',
65+
scriptVersion: typeof GM_info !== 'undefined' ? GM_info.script.version : '未知',
66+
exportedAt: new Date().toISOString(),
67+
pageUrl: location.href,
68+
realmId,
69+
registeredProviders: providers.map(p => p.name || '未命名'),
70+
realmKeyCount: Object.keys(realm).length,
71+
globalKeyCount: Object.keys(global).length
72+
};
73+
74+
return { meta, realm, global };
75+
}
76+
77+
export function downloadExportData() {
78+
const data = collectExportData();
79+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
80+
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json;charset=utf-8' });
81+
const url = URL.createObjectURL(blob);
82+
const link = document.createElement('a');
83+
link.href = url;
84+
link.download = `SC_Export_${data.meta.realmId ?? 'unknown'}_${timestamp}.json`;
85+
document.body.appendChild(link);
86+
link.click();
87+
link.remove();
88+
setTimeout(() => URL.revokeObjectURL(url), 1000);
89+
return data;
90+
}
91+
92+
window.SC_ExportInfo = {
93+
registerExportInfo,
94+
collectExportData,
95+
downloadExportData
96+
};

src/features/autoRefresh.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1+
import { registerExportInfo } from '../core/exportInfo.js';
2+
13
(function () {
24
// --- 配置项 ---
35
const CUSTOM_AMOUNTS_STORAGE_KEY = 'SC_AutoAmount_CustomAmounts';
46
const ENABLED_STORAGE_KEY = 'SC_AutoAmount_Enabled'; // 新增:功能开关的存储键
57
const DEFAULT_AMOUNTS_STRING = '10pm';
68
const DEFAULT_BUTTON_CLASS = 'btn btn-secondary';
79

10+
registerExportInfo({
11+
name: '自定义运行时长设置',
12+
scope: 'global',
13+
keys: [ENABLED_STORAGE_KEY, CUSTOM_AMOUNTS_STORAGE_KEY]
14+
});
15+
816
// --- 目标元素选择器 ---
917
const CARD_SELECTOR = '.col-xs-6.css-0.ewayztq2, .col-xs-6.resources.text-center'; //前者生产,后者零售 如果自定义运行时长不显示,则需要检查css是否更改
1018
const PROCESSED_DATA_ATTRIBUTE = 'data-custom-amount-added';
@@ -455,4 +463,4 @@
455463

456464
observeCardsForAutoAmount();
457465

458-
})();
466+
})();

src/features/dataStorage.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { registerExportInfo } from '../core/exportInfo.js';
2+
13
export const Storage = (() => {
24
const KEYS = {
35
region: realmId => `SimcompaniesRetailCalculation_${realmId}`,
@@ -10,6 +12,18 @@ export const Storage = (() => {
1012
return `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, '0')}-${d.getDate().toString().padStart(2, '0')} ${d.getHours().toString().padStart(2, '0')}:${d.getMinutes().toString().padStart(2, '0')}`;
1113
};
1214

15+
registerExportInfo({
16+
name: '基础数据',
17+
scope: 'global',
18+
keys: [KEYS.constants]
19+
});
20+
21+
registerExportInfo({
22+
name: '领域数据',
23+
scope: 'realm',
24+
keys: realmId => [KEYS.region(realmId)]
25+
});
26+
1327
return {
1428
save: (type, data) => {
1529
const key = type === 'region' ? KEYS.region(data.realmId) : KEYS.constants;

src/features/executiveBoardroom.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { DM, showToast, theme } from '../utils/ui.js';
33
import { Storage } from './dataStorage.js';
44
import { Network } from '../core/network.js';
5+
import { registerExportInfo } from '../core/exportInfo.js';
56

67
export const executiveCustomButton = (function () {
78
let boardroomState = {
@@ -890,3 +891,11 @@ export const executiveCustomButton = (function () {
890891

891892
return { forceInject: injectCustomButton };
892893
})();
894+
895+
registerExportInfo({
896+
name: '自定义高管数据',
897+
scope: 'realm',
898+
keys: realmId => realmId === null
899+
? ['SC-Saved-Boardroom', 'SC-Saved-Bonuses']
900+
: [`R${realmId}-SC-Saved-Boardroom`, `R${realmId}-SC-Saved-Bonuses`]
901+
});

src/features/executiveTrainingModule.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getRealmIdFromLink } from '../core/storage.js';
22
import { DM } from '../utils/ui.js';
3+
import { registerExportInfo } from '../core/exportInfo.js';
34

45
const ExecutiveTrainingModule = (function () {
56
const OFFERS_URL = "/api/v2/companies/executives/my-offers/";
@@ -251,5 +252,13 @@ import { DM } from '../utils/ui.js';
251252
};
252253
})();
253254

255+
registerExportInfo({
256+
name: '高管培训记录',
257+
scope: 'realm',
258+
keys: realmId => realmId === null
259+
? ['SC-my-offers', 'SC-AGENCY_FOUND_EXECUTIVE']
260+
: [`R${realmId}-SC-my-offers`, `R${realmId}-SC-AGENCY_FOUND_EXECUTIVE`]
261+
});
262+
254263
window.SC_Modules = window.SC_Modules || {};
255-
window.SC_Modules.ExecutiveTrainingModule = ExecutiveTrainingModule;
264+
window.SC_Modules.ExecutiveTrainingModule = ExecutiveTrainingModule;

src/features/formerExecutivesModule.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getRealmIdFromLink } from '../core/storage.js';
22
import { DM } from '../utils/ui.js';
3+
import { registerExportInfo } from '../core/exportInfo.js';
34

45
const FormerExecutivesModule = (function () {
56
const FORMER_EXEC_API_REGEX = /\/api\/v2\/companies\/(\d+)\/former-executives\//;
@@ -403,5 +404,13 @@ import { DM } from '../utils/ui.js';
403404
return { forceInject: injectMoreInfoButtons };
404405
})();
405406

407+
registerExportInfo({
408+
name: '前任高管记录',
409+
scope: 'realm',
410+
keys: realmId => realmId === null
411+
? ['SC-former-executives']
412+
: [`R${realmId}-SC-former-executives`]
413+
});
414+
406415
window.SC_Modules = window.SC_Modules || {};
407-
window.SC_Modules.FormerExecutivesModule = FormerExecutivesModule;
416+
window.SC_Modules.FormerExecutivesModule = FormerExecutivesModule;

src/features/incomingContractsHandler.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ import { Network } from '../core/network.js';
88
import { constantsData } from './constantsData.js';
99
import { executiveCustomButton } from './executiveBoardroom.js';
1010
import { resourceIdNameMap } from '../constants/resourceMap.js';
11+
import { registerExportInfo } from '../core/exportInfo.js';
12+
13+
registerExportInfo({
14+
name: '合同高价提醒设置',
15+
scope: 'global',
16+
keys: ['SC_Contract_HighPrice_Settings']
17+
});
1118

1219
const { SCXXCS, PROFIT_PER_BUILDING_LEVEL, RETAIL_ADJUSTMENT } = state;
1320

0 commit comments

Comments
 (0)