@yorha2b-lab/autodev 7.5.10 → 7.6.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.
- package/package.json +1 -1
- package/src/dialogues/EN-US.js +1 -1
- package/src/dialogues/FR-FR.js +1 -1
- package/src/dialogues/JA-JP.js +1 -1
- package/src/dialogues/RU-RU.js +1 -1
- package/src/dialogues/ZH-CN.js +1 -1
- package/src/labs/council.js +145 -103
- package/src/labs/tower.js +11 -4
- package/src/logistics/analyzer.js +31 -5
- package/src/workshop/react/blueprint/molds/handleBlock.hbs +3 -0
- package/src/workshop/react/blueprint/molds/stateBlock.hbs +1 -1
- package/src/workshop/react/standardParts/MyModalForm.js +3 -2
- package/src/workshop/react/standardParts/MyTable.js +11 -10
package/package.json
CHANGED
package/src/dialogues/EN-US.js
CHANGED
|
@@ -23,8 +23,8 @@ module.exports = {
|
|
|
23
23
|
},
|
|
24
24
|
pod153: {
|
|
25
25
|
signalLinkFault: virus => `Signal Link Fault: ${virus}`,
|
|
26
|
-
signalSynchronized: count => `Signal Synchronized: ${count} nodes energized`,
|
|
27
26
|
alignmentFailed: virus => `Alignment failed. Encountered powerful API firewall: ${virus}`,
|
|
27
|
+
signalSynchronized: (count, module) => `[${module}] Signal Synchronized: ${count} nodes energized`,
|
|
28
28
|
autonomousAddressing: `Initiating 'Full-Channel Autonomous Addressing Protocol [Semantic Scan]'...`,
|
|
29
29
|
inactiveModule: fileName => `Inactive module: [${fileName}]. Requesting 9S for precise alignment...`,
|
|
30
30
|
reconEncryptedData: fileName => `Encrypted data source [${fileName}] detected. Initiating semantic alignment...`,
|
package/src/dialogues/FR-FR.js
CHANGED
|
@@ -23,7 +23,7 @@ module.exports = {
|
|
|
23
23
|
},
|
|
24
24
|
pod153: {
|
|
25
25
|
signalLinkFault: virus => `Erreur de liaison du signal : ${virus}`,
|
|
26
|
-
signalSynchronized: count =>
|
|
26
|
+
signalSynchronized: (count, module) => `[${module}] ${count} nœuds synchronisés. Alimentation confirmée`,
|
|
27
27
|
inactiveModule: fileName => `Module non activé détecté [${fileName}]. Analyse haute précision demandée à 9S`,
|
|
28
28
|
autonomousAddressing: 'Activation du « protocole d’adressage automatique multi-canaux [version analyse sémantique] »...',
|
|
29
29
|
alignmentFailed: virus => `Échec de synchronisation du signal. Les données API sont bloquées par un pare-feu puissant : ${virus}`,
|
package/src/dialogues/JA-JP.js
CHANGED
|
@@ -23,8 +23,8 @@ module.exports = {
|
|
|
23
23
|
},
|
|
24
24
|
pod153: {
|
|
25
25
|
signalLinkFault: virus => `シグナルリンク障害: ${virus}`,
|
|
26
|
-
signalSynchronized: count => `${count}個のノード同期完了。電力供給を確認`,
|
|
27
26
|
autonomousAddressing: '「全チャンネル自動アドレス指定プロトコル [セマンティック解析版]」を起動...',
|
|
27
|
+
signalSynchronized: (count, module) => `[${module}] ${count}個のノード同期完了。電力供給を確認`,
|
|
28
28
|
inactiveModule: fileName => `未通電モジュール [${fileName}] を検出。9Sによる高精度照合を要求します`,
|
|
29
29
|
capturedRuntimeSignal: fileName => `ランタイムシグナル [${fileName}] を捕捉。自動同期プロトコルを開始`,
|
|
30
30
|
alignmentFailed: virus => `シグナル同期失敗。APIデータが強力なファイアウォールに阻害されています:${virus}`,
|
package/src/dialogues/RU-RU.js
CHANGED
|
@@ -23,10 +23,10 @@ module.exports = {
|
|
|
23
23
|
},
|
|
24
24
|
pod153: {
|
|
25
25
|
signalLinkFault: virus => `Ошибка сигнального соединения: ${virus}`,
|
|
26
|
-
signalSynchronized: count => `Синхронизация ${count} узлов завершена. Питание подтверждено`,
|
|
27
26
|
inactiveModule: fileName => `Обнаружен неактивный модуль [${fileName}]. Запрошен высокоточный анализ 9S`,
|
|
28
27
|
autonomousAddressing: 'Запуск «протокола автоматической адресации всех каналов [семантическая версия]»...',
|
|
29
28
|
alignmentFailed: virus => `Ошибка выравнивания сигнала. Данные API заблокированы мощным брандмауэром: ${virus}`,
|
|
29
|
+
signalSynchronized: (count, module) => `[${module}] Синхронизация ${count} узлов завершена. Питание подтверждено`,
|
|
30
30
|
unknownModule: fileName => `Ресурс модуля [${fileName}] не найден в базе Bunker. Сначала выполните визуальную сборку`,
|
|
31
31
|
bridgeProtocolComplete: (count, time) => `Семантический мост завершён. Исправлено различий полей: ${count}. Время: ${time}s`,
|
|
32
32
|
reconEncryptedData: fileName => `Обнаружен зашифрованный источник данных [${fileName}]. Попытка семантического согласования...`,
|
package/src/dialogues/ZH-CN.js
CHANGED
|
@@ -24,10 +24,10 @@ module.exports = {
|
|
|
24
24
|
pod153: {
|
|
25
25
|
signalLinkFault: virus => `信号链路故障: ${virus}`,
|
|
26
26
|
autonomousAddressing: `启动‘全频道自动寻址协议 [语义扫描版]’...`,
|
|
27
|
-
signalSynchronized: count => `信号同步完成,${count} 个节点已通电`,
|
|
28
27
|
alignmentFailed: virus => `信号对齐失败。接口数据遭遇强力防火墙:${virus}`,
|
|
29
28
|
inactiveModule: fileName => `发现未通电模块 [${fileName}],请求 9S 执行高精度对账...`,
|
|
30
29
|
capturedRuntimeSignal: fileName => `截获运行时信号 [${fileName}]。执行自动对齐协议...`,
|
|
30
|
+
signalSynchronized: (count, module) => `[${module}] 信号同步完成,${count} 个节点已通电`,
|
|
31
31
|
reconEncryptedData: fileName => `发现加密数据源 [${fileName}]。正在尝试执行语义对齐协议...`,
|
|
32
32
|
unknownModule: fileName => `地堡数据库中未检索到模块 [${fileName}] 的资源文件。请先执行视觉构筑`,
|
|
33
33
|
bridgeProtocolComplete: (count, time) => `语义桥接协议执行完毕。已物理修正 ${count} 处字段偏差。耗时: ${time}s`,
|
package/src/labs/council.js
CHANGED
|
@@ -7,124 +7,166 @@ module.exports = async ({ llm, yorha, dialog, logistics }) => {
|
|
|
7
7
|
const { pod153, commander } = yorha
|
|
8
8
|
const { contextStringify } = logistics.formatter
|
|
9
9
|
const { apiDoc, pagesDir } = logistics.supporter.getConfig()
|
|
10
|
-
const { getLocalScore, getSemanticKeywords } = logistics.analyzer
|
|
10
|
+
const { getLocalScore, unwrapSchemaRef, getSemanticKeywords } = logistics.analyzer
|
|
11
11
|
|
|
12
12
|
if (!apiDoc) return
|
|
13
13
|
|
|
14
14
|
pod153.report(dialog.pod153.autonomousAddressing, 'magenta')
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
16
|
+
const apiData = await fetch(apiDoc).then(res => res.json())
|
|
17
|
+
const refinedApis = Object.entries(apiData.paths).flatMap(([apiUrl, methods]) => {
|
|
18
|
+
return Object.entries(methods).map(([method, info]) => ({
|
|
19
|
+
path: apiUrl,
|
|
20
|
+
method: method?.toUpperCase() || '',
|
|
21
|
+
desc: info.summary || info.description || 'N/A',
|
|
22
|
+
}))
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const falseTruth = (apiUrl, method = 'post') => {
|
|
26
|
+
const cleanUrl = apiUrl.split('?')[0]
|
|
27
|
+
const matchedPath = Object.keys(apiData.paths || {}).find(p => cleanUrl.endsWith(p) || p.endsWith(cleanUrl))
|
|
28
|
+
if (!matchedPath) return null
|
|
29
|
+
const definitions = apiData.definitions || apiData.components?.schemas || {}
|
|
30
|
+
// 1. 找到该接口 200 响应对应的根 Schema
|
|
31
|
+
const methodObj = apiData.paths?.[apiUrl]?.[method.toLowerCase()]
|
|
32
|
+
const rootSchema = methodObj?.responses?.['200']?.schema || methodObj?.responses?.['200']?.content?.['application/json']?.schema
|
|
33
|
+
if (!rootSchema) return null
|
|
34
|
+
// 辅助函数:根据 $ref 名字解析出实体
|
|
35
|
+
const resolveRef = (refStr) => {
|
|
36
|
+
if (!refStr) return null
|
|
37
|
+
const refName = refStr.split('/').at(-1)
|
|
38
|
+
return definitions[refName]
|
|
39
|
+
}
|
|
40
|
+
// 2. 解析外层包装类 (如 TableResult)
|
|
41
|
+
let wrapperDef = resolveRef(rootSchema.$ref)
|
|
42
|
+
// 3. 在外层属性里寻找我们熟悉的列表字段 (datas, records, list, rows 等)
|
|
43
|
+
const rowRef = unwrapSchemaRef(wrapperDef)
|
|
44
|
+
// 4. 顺藤摸瓜拿到终极行数据定义 (Row DTO)
|
|
45
|
+
const rowDef = resolveRef(rowRef)
|
|
46
|
+
if (!rowDef?.properties) return null
|
|
47
|
+
// 5. 组装成高纯度的伪样本数据!
|
|
48
|
+
// 格式形如:{ contractNum: "合同编号" }
|
|
49
|
+
return Object.fromEntries(
|
|
50
|
+
Object.entries(rowDef.properties).map(([fieldName, propMeta]) => [
|
|
51
|
+
fieldName,
|
|
52
|
+
propMeta.description || propMeta.title || fieldName // 优先拿后端写的中文注释!
|
|
53
|
+
])
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const resurrection = async () => {
|
|
58
|
+
try {
|
|
59
|
+
const pages = path.join(process.cwd(), pagesDir)
|
|
60
|
+
const files = fs.readdirSync(pages)
|
|
61
|
+
const enumParamsMap = {}
|
|
62
|
+
|
|
63
|
+
for (const fileName of files) {
|
|
64
|
+
const pageDir = path.join(pages, fileName)
|
|
65
|
+
if (!fs.existsSync(pageDir) || !fs.statSync(pageDir).isDirectory()) continue
|
|
66
|
+
|
|
67
|
+
const subFiles = fs.readdirSync(pageDir).filter(f => f.endsWith('.js') || f.endsWith('.jsx'))
|
|
68
|
+
const fileContents = {}
|
|
69
|
+
const allSemantics = []
|
|
70
|
+
const allAnchors = new Set()
|
|
71
|
+
|
|
72
|
+
for (const file of subFiles) {
|
|
73
|
+
const filePath = path.join(pageDir, file)
|
|
74
|
+
const code = fs.readFileSync(filePath, 'utf-8')
|
|
75
|
+
fileContents[filePath] = code
|
|
76
|
+
|
|
77
|
+
const anchors = code.match(/BUNKER_API_ANCHOR_\w+/g) || []
|
|
78
|
+
anchors.forEach(a => allAnchors.add(a))
|
|
79
|
+
|
|
80
|
+
const textMatches = code.match(/(?:['"`]([^'"`\n]{2,})['"`]|>([^<>{}\n]{2,})<)/g) || []
|
|
81
|
+
const uiSemantics = textMatches
|
|
82
|
+
.map(str => str.replace(/['"`><]/g, '').trim())
|
|
83
|
+
// 过滤掉纯代码路径(/api/xxx)、锚点占位符、纯数字和空字符
|
|
84
|
+
.filter(text => (
|
|
85
|
+
text.length >= 2 &&
|
|
86
|
+
!text.startsWith('/') &&
|
|
87
|
+
!text.startsWith('http') &&
|
|
88
|
+
!text.includes('BUNKER_API_ANCHOR') &&
|
|
89
|
+
!/^[0-9]+$/.test(text)
|
|
90
|
+
))
|
|
91
|
+
|
|
92
|
+
allSemantics.push(...uiSemantics)
|
|
93
|
+
}
|
|
61
94
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
if (allAnchors.size > 0) {
|
|
96
|
+
const bunkerAnchors = Array.from(allAnchors).join('\n')
|
|
97
|
+
const pageKeywords = getSemanticKeywords(allSemantics)
|
|
98
|
+
|
|
99
|
+
const candidates = refinedApis.map(api => ({ ...api, score: getLocalScore(api, pageKeywords, fileName) }))
|
|
100
|
+
.filter(item => item.score > 0)
|
|
101
|
+
.sort((a, b) => b.score - a.score)
|
|
102
|
+
.slice(0, 20)
|
|
103
|
+
|
|
104
|
+
const finalCandidates = candidates.map(item => `${item.method} ${item.path} ${item.desc}`).join('\n')
|
|
105
|
+
const spinner = pod153.start(dialog.pod153.inactiveModule(fileName), 'yellow')
|
|
106
|
+
const result = await apiParser({ bunkerAnchors, realApis: finalCandidates })
|
|
107
|
+
|
|
108
|
+
if (result) {
|
|
109
|
+
for (const [filePath, rawCode] of Object.entries(fileContents)) {
|
|
110
|
+
let code = rawCode
|
|
111
|
+
let isModified = false
|
|
112
|
+
let hasQsImport = code.includes(`import qs from 'qs'`)
|
|
113
|
+
|
|
114
|
+
Object.entries(result).forEach(([anchor, { uri, method }]) => {
|
|
115
|
+
if (code.includes(anchor)) {
|
|
116
|
+
isModified = true
|
|
117
|
+
const cleanPath = uri.startsWith('/') ? uri : `/${uri}`
|
|
118
|
+
let finalUri = cleanPath.startsWith('/api') ? cleanPath : `/api${cleanPath}`
|
|
119
|
+
finalUri = finalUri.replace(/\{(\w+)\}/g, (_, key) => '${params.' + key + '}')
|
|
120
|
+
const quotationMark = finalUri.includes('${params.') ? '`' : "'"
|
|
121
|
+
if (method?.toUpperCase() === 'GET') {
|
|
122
|
+
code = code.replaceAll(anchor, `request(\`${finalUri}?\${qs.stringify(params)}\`)`)
|
|
123
|
+
if (!hasQsImport) {
|
|
124
|
+
code = `import qs from 'qs'\n${code}`
|
|
125
|
+
hasQsImport = true
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
code = code.replaceAll(anchor, `request(${quotationMark}${finalUri}${quotationMark}, { method: '${method?.toUpperCase()}', body: params })`)
|
|
93
129
|
}
|
|
94
|
-
} else {
|
|
95
|
-
code = code.replaceAll(anchor, `request(${quotationMark}${finalUri}${quotationMark}, { method: '${method?.toUpperCase()}', body: params })`)
|
|
96
130
|
}
|
|
131
|
+
})
|
|
132
|
+
if (isModified) {
|
|
133
|
+
fs.writeFileSync(filePath, code)
|
|
97
134
|
}
|
|
98
|
-
})
|
|
99
|
-
if (isModified) {
|
|
100
|
-
fs.writeFileSync(filePath, code)
|
|
101
135
|
}
|
|
102
|
-
}
|
|
103
136
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
137
|
+
const pagesConfig = result?.['BUNKER_API_ANCHOR_pages']
|
|
138
|
+
if (pagesConfig) {
|
|
139
|
+
const { uri: pagesApi, method: pagesMethod } = pagesConfig
|
|
140
|
+
const schemas = apiData.definitions || apiData.components?.schemas || {}
|
|
141
|
+
const methodObj = apiData.paths[pagesApi]?.[pagesMethod] ?? apiData.paths[pagesApi]?.[pagesMethod?.toLowerCase()]
|
|
142
|
+
let schemaName = methodObj?.parameters?.flatMap(item => Object.values(item.schema ?? {})?.flatMap(def => def?.split('/')?.at(-1)))?.[0]
|
|
143
|
+
if (!schemaName && methodObj?.requestBody) {
|
|
144
|
+
const bodySchema = methodObj.requestBody.content?.['application/json']?.schema
|
|
145
|
+
schemaName = (bodySchema?.$ref || bodySchema?.items?.$ref)?.split('/')?.at(-1)
|
|
146
|
+
}
|
|
147
|
+
const parameters = schemas[schemaName]?.properties ?? {}
|
|
148
|
+
const enumParams = Object.entries(parameters)?.filter(([_, value]) => value.hasOwnProperty('enum')) || []
|
|
149
|
+
enumParamsMap[fileName] = contextStringify({
|
|
150
|
+
maxLength: 100,
|
|
151
|
+
context: Object.fromEntries(enumParams.map(([key, value]) => [`${key}Options`, value.enum?.map(opt => ({ label: opt, value: opt }))])),
|
|
152
|
+
})
|
|
113
153
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
enumParamsMap[fileName] = contextStringify({
|
|
117
|
-
maxLength: 100,
|
|
118
|
-
context: Object.fromEntries(enumParams.map(([key, value]) => [`${key}Options`, value.enum?.map(opt => ({ label: opt, value: opt }))])),
|
|
119
|
-
})
|
|
154
|
+
pod153.success(spinner, dialog.pod153.signalSynchronized(Object.keys(result).length, fileName))
|
|
155
|
+
commander.report(dialog.bunker.disclaimer, 'yellow')
|
|
120
156
|
}
|
|
121
|
-
pod153.success(spinner, dialog.pod153.signalSynchronized(Object.keys(result).length))
|
|
122
|
-
commander.report(dialog.bunker.disclaimer, 'yellow')
|
|
123
157
|
}
|
|
124
158
|
}
|
|
159
|
+
return enumParamsMap
|
|
160
|
+
} catch (e) {
|
|
161
|
+
pod153.report(dialog.pod153.signalLinkFault(e.message), 'red')
|
|
125
162
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const enumParamsMap = await resurrection()
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
falseTruth,
|
|
169
|
+
resurrection,
|
|
170
|
+
...enumParamsMap,
|
|
129
171
|
}
|
|
130
172
|
}
|
package/src/labs/tower.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
module.exports = ({
|
|
1
|
+
module.exports = ({ yorha, dialog, logistics, headquarters }) => {
|
|
2
2
|
|
|
3
3
|
const http = require('http')
|
|
4
4
|
const zlib = require('zlib')
|
|
@@ -27,10 +27,17 @@ module.exports = ({ units, yorha, dialog, logistics, headquarters }) => {
|
|
|
27
27
|
const rawBody = encoding === 'gzip' ? zlib.gunzipSync(buffer) : buffer
|
|
28
28
|
const json = JSON.parse(rawBody.toString())
|
|
29
29
|
const coreData = unwrapSignal(json)
|
|
30
|
-
if (!isQuerySignal(req, json, coreData)
|
|
30
|
+
if (!isQuerySignal(req, json, coreData)) {
|
|
31
31
|
return
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
let sampleData = coreData?.[0]
|
|
34
|
+
if (!sampleData) {
|
|
35
|
+
const { labs } = require('../awakening').get()
|
|
36
|
+
const council = await labs?.council
|
|
37
|
+
sampleData = council?.falseTruth?.(req.url, req.method)
|
|
38
|
+
}
|
|
39
|
+
if (!sampleData) return
|
|
40
|
+
const referer = req.headers.referer || 'http://localhost'
|
|
34
41
|
const urlPath = new URL(referer).pathname
|
|
35
42
|
const fileName = routeMap?.[urlPath] ?? urlPath.split('/').filter(Boolean).at(-1)
|
|
36
43
|
const fingerprint = getJsonFingerprint(coreData)
|
|
@@ -39,7 +46,7 @@ module.exports = ({ units, yorha, dialog, logistics, headquarters }) => {
|
|
|
39
46
|
return
|
|
40
47
|
}
|
|
41
48
|
pod153.report(dialog.pod153.capturedRuntimeSignal(fileName))
|
|
42
|
-
await headquarters.reconciler({ fileName, data: [
|
|
49
|
+
await headquarters.reconciler({ fileName, data: [sampleData] })
|
|
43
50
|
hackedRegistry.set(fileName, fingerprint)
|
|
44
51
|
} catch (e) {
|
|
45
52
|
// 非 JSON 信号,保持静默
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
const { Segment, useDefault } = require('segmentit')
|
|
2
2
|
const segmentit = useDefault(new Segment())
|
|
3
3
|
|
|
4
|
+
const dataKeys = ['data', 'list', 'items', 'datas', 'rows', 'result', 'payload', 'results', 'dataList']
|
|
5
|
+
|
|
4
6
|
const unwrapSignal = json => {
|
|
5
7
|
if (!json || typeof json !== 'object') return null
|
|
6
8
|
if (Array.isArray(json)) return json
|
|
7
|
-
const dataKeys = ['data', 'list', 'items', 'datas', 'rows', 'result', 'payload', 'results', 'dataList']
|
|
8
9
|
for (const key of dataKeys) {
|
|
9
10
|
if (json[key] && Array.isArray(json[key])) return json[key]
|
|
10
11
|
}
|
|
@@ -17,6 +18,23 @@ const unwrapSignal = json => {
|
|
|
17
18
|
return null
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
const unwrapSchemaRef = schemaObj => {
|
|
22
|
+
if (!schemaObj) return null
|
|
23
|
+
// 1. 如果传进来直接是个数组描述
|
|
24
|
+
if (schemaObj.type === 'array') {
|
|
25
|
+
return schemaObj.items?.$ref || schemaObj.items
|
|
26
|
+
}
|
|
27
|
+
const props = schemaObj.properties || schemaObj
|
|
28
|
+
// 2. 共享同一套 DATA_KEYS 关键词清单!
|
|
29
|
+
for (const key of dataKeys) {
|
|
30
|
+
const target = props[key]
|
|
31
|
+
if (target && (target.type === 'array' || target.items?.$ref)) {
|
|
32
|
+
return target.items?.$ref || null
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
20
38
|
const getSemanticKeywords = text => {
|
|
21
39
|
const content = Array.isArray(text) ? text.join(' ') : (text || '')
|
|
22
40
|
const result = segmentit.doSegment(content, {
|
|
@@ -26,6 +44,16 @@ const getSemanticKeywords = text => {
|
|
|
26
44
|
return Array.from(new Set(result.filter(word => word.length >= 2 && !/^[0-9]+$/.test(word))))
|
|
27
45
|
}
|
|
28
46
|
|
|
47
|
+
const hasDeepKey = (obj, depth = 0) => {
|
|
48
|
+
if (!obj || typeof obj !== 'object' || depth > 3) return false
|
|
49
|
+
const targetKeys = ['total', 'records', 'page', 'size', 'count', 'current']
|
|
50
|
+
const currentKeys = Object.keys(obj)
|
|
51
|
+
if (currentKeys.some(rawKey => targetKeys.some(k => rawKey.toLowerCase().includes(k)))) {
|
|
52
|
+
return true
|
|
53
|
+
}
|
|
54
|
+
return currentKeys.some(key => typeof obj[key] === 'object' && !Array.isArray(obj[key]) && hasDeepKey(obj[key], depth + 1))
|
|
55
|
+
}
|
|
56
|
+
|
|
29
57
|
const isQuerySignal = (req, json, coreData) => {
|
|
30
58
|
const pathname = req.url.split('?')[0].toLowerCase()
|
|
31
59
|
const actionKeywords = ['add', 'delete', 'update', 'save', 'remove', 'edit', 'insert', 'create', 'export', 'upload']
|
|
@@ -33,10 +61,7 @@ const isQuerySignal = (req, json, coreData) => {
|
|
|
33
61
|
return false
|
|
34
62
|
}
|
|
35
63
|
const hasListData = Array.isArray(coreData) && coreData.length > 0
|
|
36
|
-
const hasPaginationFingerprint =
|
|
37
|
-
const k = key.toLowerCase()
|
|
38
|
-
return Object.keys(json).some(rawKey => rawKey.toLowerCase().includes(k))
|
|
39
|
-
})
|
|
64
|
+
const hasPaginationFingerprint = hasDeepKey(json)
|
|
40
65
|
return hasListData || hasPaginationFingerprint
|
|
41
66
|
}
|
|
42
67
|
|
|
@@ -59,5 +84,6 @@ module.exports = {
|
|
|
59
84
|
unwrapSignal,
|
|
60
85
|
isQuerySignal,
|
|
61
86
|
getLocalScore,
|
|
87
|
+
unwrapSchemaRef,
|
|
62
88
|
getSemanticKeywords,
|
|
63
89
|
}
|
|
@@ -70,12 +70,15 @@ const showImg = (record, dataIndex) => {
|
|
|
70
70
|
|
|
71
71
|
{{/if}}
|
|
72
72
|
|
|
73
|
+
let fetching = false
|
|
73
74
|
const modalSubmit = async params => {
|
|
74
75
|
const requestMap={
|
|
75
76
|
{{#each operations}}
|
|
76
77
|
{{this.action}}:()=> {{this.request}},
|
|
77
78
|
{{/each}}
|
|
78
79
|
}
|
|
80
|
+
if (fetching) return
|
|
81
|
+
fetching = true
|
|
79
82
|
const response = await requestMap[modal.type]?.()
|
|
80
83
|
if({{{responseSuccess}}}){
|
|
81
84
|
setModal({ visible: false })
|
|
@@ -15,4 +15,4 @@ const [activeKey, setActiveKey] = useState(tabs[0].key)
|
|
|
15
15
|
{{#if hasExpandable}}
|
|
16
16
|
const [expandedRowKeys, setExpandedRowKeys] = useState([])
|
|
17
17
|
{{/if}}
|
|
18
|
-
const [modal, setModal] = useState({ visible:false, title:'',
|
|
18
|
+
const [modal, setModal] = useState({ visible:false, title:'', modalItems })
|
|
@@ -114,7 +114,7 @@ const flattenFormItems = items => {
|
|
|
114
114
|
* @param {Object} [props.wrapperCol] - 控件布局校准
|
|
115
115
|
* @param {string|number} [props.width] - 舱体物理宽度
|
|
116
116
|
* @param {boolean} props.visible - 激活信号:控制弹窗的物理显示状态
|
|
117
|
-
* @param {Array} props.
|
|
117
|
+
* @param {Array} props.modalItems - 零部件清单:定义表单内部的输入单元
|
|
118
118
|
* @param {Object} [props.record] - 初始物资包:用于编辑模式的数据回显
|
|
119
119
|
* @param {Function} props.submit - 数据发射协议:提交表单后的核心回调函数
|
|
120
120
|
* @param {Function} props.setModal - 指令中心:用于更新弹窗状态(开启/关闭)
|
|
@@ -128,9 +128,10 @@ const flattenFormItems = items => {
|
|
|
128
128
|
* formItems={[{ label: '截止日期', name: 'deadline', type: 'date' }]}
|
|
129
129
|
* />
|
|
130
130
|
*/
|
|
131
|
-
export const MyModalForm = ({ extra, width, title, layout, okText, footer, submit, record, visible, setModal, labelCol,
|
|
131
|
+
export const MyModalForm = ({ extra, width, title, layout, okText, options, footer, submit, record, visible, setModal, labelCol, modalItems, wrapperCol, cancelText, tableConfig, okButtonProps, onValuesChange, handleModalTableOk }) => {
|
|
132
132
|
|
|
133
133
|
const rowKey = tableConfig?.rowKey || 'id'
|
|
134
|
+
const formItems = useMemo(() => modalItems.map(item => ({ ...item, ...(['select'].includes(item.type) ? { options: item.options ?? options?.[item.name] ?? [] } : {}) })), [modalItems, options])
|
|
134
135
|
|
|
135
136
|
const [form] = Form.useForm()
|
|
136
137
|
const [pending, setPending] = useState(false)
|
|
@@ -58,6 +58,8 @@ export const MyTable = ({ size, query, total, search, autoScroll, onChange, setS
|
|
|
58
58
|
|
|
59
59
|
const tableRef = useRef(null)
|
|
60
60
|
const hasScrolledRef = useRef(false)
|
|
61
|
+
const dataSourceRef = useRef(dataSource)
|
|
62
|
+
dataSourceRef.current = dataSource
|
|
61
63
|
|
|
62
64
|
/**
|
|
63
65
|
* @description [权限侦察] 自动扫描列配置。若存在 editType 型号,则激活“骇入模式(编辑模式)”。
|
|
@@ -67,19 +69,18 @@ export const MyTable = ({ size, query, total, search, autoScroll, onChange, setS
|
|
|
67
69
|
/**
|
|
68
70
|
* @function handleSave
|
|
69
71
|
* @description [原子级物理封存] 执行数据更新协议。
|
|
70
|
-
* 采用函数式状态更新(prev => ...)以彻底消除闭航陷阱(Stale Closure),确保数据高频修改时的绝对准确。
|
|
71
|
-
*
|
|
72
72
|
* @param {Object} row - 修改后的行片段
|
|
73
73
|
*/
|
|
74
74
|
const handleSave = useCallback((row) => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
const prev = dataSourceRef.current
|
|
76
|
+
const index = prev.findIndex(item => row[rowKey] === item[rowKey])
|
|
77
|
+
if (index === -1) return
|
|
78
|
+
const item = prev[index]
|
|
79
|
+
const updatedRow = { ...item, ...row }
|
|
80
|
+
const newData = [...prev]
|
|
81
|
+
newData.splice(index, 1, updatedRow)
|
|
82
|
+
setDataSource(newData)
|
|
83
|
+
customSave?.(updatedRow, newData)
|
|
83
84
|
}, [rowKey, customSave, setDataSource])
|
|
84
85
|
|
|
85
86
|
/**
|