chanjs 2.0.17 → 2.0.19
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/config/code.js +6 -2
- package/core/controller.js +5 -1
- package/core/service.js +161 -118
- package/extend/art-template.js +1 -1
- package/global/import.js +5 -6
- package/helper/data-parse.js +63 -46
- package/helper/db.js +16 -13
- package/helper/file.js +33 -33
- package/helper/html.js +29 -21
- package/helper/index.js +4 -8
- package/helper/ip.js +18 -17
- package/helper/jwt.js +15 -16
- package/helper/loader.js +46 -6
- package/helper/time.js +49 -3
- package/index.js +7 -5
- package/middleware/cookie.js +2 -2
- package/middleware/cors.js +3 -3
- package/middleware/header.js +1 -1
- package/middleware/index.js +21 -21
- package/middleware/setBody.js +2 -3
- package/middleware/template.js +4 -4
- package/middleware/validator.js +2 -2
- package/middleware/waf.js +133 -25
- package/package.json +1 -1
- package/utils/response.js +23 -5
package/config/code.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @description 状态码定义
|
|
3
|
+
*/
|
|
2
4
|
export const CODE = {
|
|
3
5
|
200: "操作成功", // 成功
|
|
4
6
|
201: "操作失败", // 通用失败
|
|
@@ -36,7 +38,9 @@ export const CODE = {
|
|
|
36
38
|
6009: "数据库连接已关闭,请重试", // 连接已关闭
|
|
37
39
|
};
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
/**
|
|
42
|
+
* @description 数据库错误码定义
|
|
43
|
+
*/
|
|
40
44
|
export const DB_ERROR = {
|
|
41
45
|
ECONNREFUSED: 6001,
|
|
42
46
|
ER_ACCESS_DENIED_ERROR: 6002,
|
package/core/controller.js
CHANGED
package/core/service.js
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
import { success, fail, error } from "../utils/response.js";
|
|
2
2
|
import { CODE } from "../config/code.js";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 数据库服务类
|
|
6
|
+
*/
|
|
7
|
+
class Service {
|
|
8
|
+
/**
|
|
9
|
+
* 构造函数
|
|
10
|
+
* @param {Object} knex - Knex实例
|
|
11
|
+
* @param {string} tableName - 表名
|
|
12
|
+
*/
|
|
13
|
+
constructor(knex, tableName) {
|
|
14
|
+
if (!knex || !tableName) {
|
|
15
|
+
throw new Error("Service: knex instance and tableName are required");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.db = knex;
|
|
19
|
+
this.tableName = tableName;
|
|
20
|
+
this.pageSize = 100;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
5
25
|
/**
|
|
6
26
|
* 查询表所有记录(慎用)
|
|
7
27
|
* @param {Object} query - 查询条件
|
|
@@ -9,112 +29,130 @@ const databaseMethods = {
|
|
|
9
29
|
*/
|
|
10
30
|
async all(query = {}) {
|
|
11
31
|
try {
|
|
12
|
-
let
|
|
13
|
-
if (Object.keys(query).length
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
const res = await dbQuery.select();
|
|
32
|
+
let _query = this.db(this.tableName);
|
|
33
|
+
if (Object.keys(query).length) _query = _query.where(query);
|
|
34
|
+
const res = await _query.select();
|
|
17
35
|
return success(res);
|
|
18
36
|
} catch (err) {
|
|
19
37
|
return error(err);
|
|
20
38
|
}
|
|
21
|
-
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 查询所有文档(仅支持对象模式排序)
|
|
43
|
+
* @param {Object} query - 查询条件,可包含 sort: { field: 'asc|desc' }
|
|
44
|
+
* @returns {Promise} 查询结果
|
|
45
|
+
*/
|
|
46
|
+
async find(query = {}) {
|
|
47
|
+
try {
|
|
48
|
+
let _query = this.db(this.tableName);
|
|
49
|
+
|
|
50
|
+
// 解构出排序参数,剩余作为 where 条件
|
|
51
|
+
const { sort, ...whereConditions } = query;
|
|
52
|
+
|
|
53
|
+
// 应用 where 条件
|
|
54
|
+
if (Object.keys(whereConditions).length > 0) {
|
|
55
|
+
_query = _query.where(whereConditions);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 应用排序(仅支持对象模式)
|
|
59
|
+
if (sort && typeof sort === 'object') {
|
|
60
|
+
for (const [field, dir] of Object.entries(sort)) {
|
|
61
|
+
const direction = ['asc', 'desc'].includes(dir.toLowerCase())
|
|
62
|
+
? dir.toLowerCase()
|
|
63
|
+
: 'asc'; // 默认升序
|
|
64
|
+
_query = _query.orderBy(field, direction);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const res = await _query.select();
|
|
69
|
+
return success(res);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
return error(err);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
22
74
|
|
|
23
75
|
/**
|
|
24
76
|
* 获取单个记录
|
|
25
77
|
* @param {Object} query - 查询条件
|
|
26
78
|
* @returns {Promise} 查询结果
|
|
27
79
|
*/
|
|
28
|
-
async
|
|
80
|
+
async findOne(query = {}) {
|
|
29
81
|
try {
|
|
30
|
-
let dbQuery = this.
|
|
31
|
-
if (Object.keys(query).length
|
|
32
|
-
dbQuery = dbQuery.where(query);
|
|
33
|
-
}
|
|
82
|
+
let dbQuery = this.db(this.tableName);
|
|
83
|
+
if (Object.keys(query).length) dbQuery = dbQuery.where(query);
|
|
34
84
|
const res = await dbQuery.first();
|
|
35
85
|
return success(res);
|
|
36
86
|
} catch (err) {
|
|
37
87
|
return error(err);
|
|
38
88
|
}
|
|
39
|
-
}
|
|
89
|
+
}
|
|
40
90
|
|
|
41
91
|
/**
|
|
42
92
|
* 根据ID查询记录
|
|
43
93
|
* @param {Object} options - 查询选项
|
|
44
94
|
* @param {Object} options.query - 查询条件
|
|
45
|
-
* @param {Array}
|
|
46
|
-
* @param {number} options.len - 返回数量
|
|
95
|
+
* @param {Array} options.field - 返回字段
|
|
47
96
|
*/
|
|
48
|
-
async findById({ query, field = []
|
|
97
|
+
async findById({ query, field = [] }) {
|
|
49
98
|
try {
|
|
50
|
-
let
|
|
51
|
-
if (field.length
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const res = await dataQuery;
|
|
56
|
-
return success(res || (len === 1 ? {} : []));
|
|
99
|
+
let _query = this.db(this.tableName).where(query);
|
|
100
|
+
if (field.length) _query = _query.select(field);
|
|
101
|
+
const res = await _query.first();
|
|
102
|
+
return success(res);
|
|
57
103
|
} catch (err) {
|
|
58
104
|
return error(err);
|
|
59
105
|
}
|
|
60
|
-
}
|
|
106
|
+
}
|
|
61
107
|
|
|
108
|
+
|
|
62
109
|
/**
|
|
63
110
|
* 创建新记录
|
|
64
|
-
* @param {Object} data -
|
|
111
|
+
* @param {Object} data - 要插入的数据
|
|
65
112
|
* @returns {Promise} 操作结果
|
|
66
113
|
*/
|
|
67
114
|
async insert(data = {}) {
|
|
68
115
|
try {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
console.log('this.model--->',this.model)
|
|
75
|
-
const result = await this.knex(this.model).insert(data);
|
|
76
|
-
return success(result?.length > 0 || !!result);
|
|
116
|
+
if (!Object.keys(data).length) return fail(CODE[2002], { code: 2002 });
|
|
117
|
+
const result = await this.db(this.tableName).insert(data);
|
|
118
|
+
return success(result);
|
|
77
119
|
} catch (err) {
|
|
78
120
|
return error(err);
|
|
79
121
|
}
|
|
80
|
-
}
|
|
122
|
+
}
|
|
81
123
|
|
|
82
124
|
/**
|
|
83
125
|
* 插入多条记录
|
|
84
|
-
* @param {Array} records -
|
|
126
|
+
* @param {Array} records - 数据数组
|
|
85
127
|
* @returns {Promise} 操作结果
|
|
86
128
|
*/
|
|
87
129
|
async insertMany(records = []) {
|
|
88
130
|
try {
|
|
89
|
-
if (records.length
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
const result = await this.knex(this.model).insert(records);
|
|
131
|
+
if (!records.length) return fail(CODE[2002], { code: 2002 });
|
|
132
|
+
const result = await this.db(this.tableName).insert(records);
|
|
93
133
|
return success(result);
|
|
94
134
|
} catch (err) {
|
|
95
135
|
return error(err);
|
|
96
136
|
}
|
|
97
|
-
}
|
|
137
|
+
}
|
|
98
138
|
|
|
99
139
|
/**
|
|
100
|
-
*
|
|
101
|
-
* @param {Object} query -
|
|
140
|
+
* 根据条件删除记录
|
|
141
|
+
* @param {Object} query - 查询条件
|
|
102
142
|
* @returns {Promise} 操作结果
|
|
103
143
|
*/
|
|
104
144
|
async delete(query = {}) {
|
|
105
145
|
try {
|
|
106
|
-
if (Object.keys(query).length
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
const affectedRows = await this.knex(this.model).where(query).del();
|
|
146
|
+
if (!Object.keys(query).length) return fail(CODE[2002], { code: 2002 });
|
|
147
|
+
const affectedRows = await this.db(this.tableName).where(query).del();
|
|
110
148
|
return success(affectedRows > 0);
|
|
111
149
|
} catch (err) {
|
|
112
150
|
return error(err);
|
|
113
151
|
}
|
|
114
|
-
}
|
|
152
|
+
}
|
|
115
153
|
|
|
116
154
|
/**
|
|
117
|
-
*
|
|
155
|
+
* 根据条件更新记录
|
|
118
156
|
* @param {Object} options - 更新选项
|
|
119
157
|
* @param {Object} options.query - 查询条件
|
|
120
158
|
* @param {Object} options.params - 更新数据
|
|
@@ -122,37 +160,38 @@ const databaseMethods = {
|
|
|
122
160
|
*/
|
|
123
161
|
async update({ query, params } = {}) {
|
|
124
162
|
try {
|
|
125
|
-
if (!query || !params || Object.keys(query).length
|
|
163
|
+
if (!query || !params || !Object.keys(query).length) {
|
|
126
164
|
return fail(CODE[2001], { code: 2001 });
|
|
127
165
|
}
|
|
128
|
-
|
|
129
|
-
console.log('this.model--->',this.model)
|
|
130
|
-
|
|
131
|
-
console.log('params---->',params)
|
|
132
|
-
const result = await this.knex(this.model).where(query).update(params);
|
|
133
|
-
console.log('result--->',result)
|
|
166
|
+
const result = await this.db(this.tableName).where(query).update(params);
|
|
134
167
|
return success(!!result);
|
|
135
168
|
} catch (err) {
|
|
136
169
|
return error(err);
|
|
137
170
|
}
|
|
138
|
-
}
|
|
171
|
+
}
|
|
139
172
|
|
|
140
173
|
/**
|
|
141
|
-
*
|
|
142
|
-
* @param {Array} updates -
|
|
174
|
+
* 批量更新多条记录(事务)
|
|
175
|
+
* @param {Array} updates - [{ query, params }, ...]
|
|
143
176
|
* @returns {Promise} 操作结果
|
|
144
177
|
*/
|
|
145
178
|
async updateMany(updates = []) {
|
|
146
|
-
if (!Array.isArray(updates) || updates.length
|
|
179
|
+
if (!Array.isArray(updates) || !updates.length) {
|
|
147
180
|
return fail(CODE[2002], { code: 2002 });
|
|
148
181
|
}
|
|
149
|
-
|
|
150
|
-
const trx = await this.
|
|
182
|
+
|
|
183
|
+
const trx = await this.db.transaction();
|
|
151
184
|
try {
|
|
152
185
|
for (const { query, params } of updates) {
|
|
153
|
-
|
|
186
|
+
// FIX-4 防止空条件全表更新
|
|
187
|
+
if (!query || !Object.keys(query).length) {
|
|
188
|
+
await trx.rollback();
|
|
189
|
+
return fail('批量更新不允许空条件');
|
|
190
|
+
}
|
|
191
|
+
const result = await trx(this.tableName).where(query).update(params);
|
|
154
192
|
if (result === 0) {
|
|
155
193
|
await trx.rollback();
|
|
194
|
+
console.warn('[updateMany] 未匹配到行, query=%j', query); // FIX-3 日志
|
|
156
195
|
return fail(`更新失败: ${JSON.stringify(query)}`);
|
|
157
196
|
}
|
|
158
197
|
}
|
|
@@ -162,91 +201,95 @@ const databaseMethods = {
|
|
|
162
201
|
await trx.rollback();
|
|
163
202
|
return error(err);
|
|
164
203
|
}
|
|
165
|
-
}
|
|
204
|
+
}
|
|
205
|
+
|
|
166
206
|
|
|
167
207
|
/**
|
|
168
208
|
* 分页查询
|
|
169
209
|
* @param {Object} options - 查询选项
|
|
170
|
-
* @param {number} options.current
|
|
171
|
-
* @param {number} options.pageSize -
|
|
172
|
-
* @param {Object} options.query
|
|
173
|
-
* @param {Array}
|
|
210
|
+
* @param {number} options.current - 当前页
|
|
211
|
+
* @param {number} options.pageSize - 每页条数
|
|
212
|
+
* @param {Object} options.query - 查询条件
|
|
213
|
+
* @param {Array} options.field - 返回字段
|
|
174
214
|
* @returns {Promise} 查询结果
|
|
175
215
|
*/
|
|
176
216
|
async query({ current = 1, pageSize = 10, query = {}, field = [] }) {
|
|
177
217
|
try {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
218
|
+
const size = pageSize; // 如需要可再做非空校验
|
|
219
|
+
const offset = (current - 1) * size;
|
|
220
|
+
|
|
221
|
+
let countQuery = this.db(this.tableName).count("* as total");
|
|
222
|
+
let dataQuery = this.db(this.tableName);
|
|
223
|
+
|
|
224
|
+
if (Object.keys(query).length) {
|
|
225
|
+
Object.entries(query).forEach(([k, v]) => {
|
|
226
|
+
countQuery = countQuery.where(k, v);
|
|
227
|
+
dataQuery = dataQuery.where(k, v);
|
|
186
228
|
});
|
|
187
229
|
}
|
|
188
230
|
|
|
189
|
-
if (field.length
|
|
231
|
+
if (field && field.length) dataQuery = dataQuery.select(field);
|
|
190
232
|
|
|
191
233
|
const [totalResult, list] = await Promise.all([
|
|
192
234
|
countQuery.first(),
|
|
193
|
-
dataQuery.offset(offset).limit(
|
|
235
|
+
dataQuery.offset(offset).limit(size),
|
|
194
236
|
]);
|
|
195
237
|
|
|
196
|
-
|
|
197
|
-
|
|
238
|
+
// FIX-1 显式转数字,防止字符串 "0"
|
|
239
|
+
const total = Number(totalResult?.total ?? 0);
|
|
240
|
+
return success({ list, total, current, pageSize: size });
|
|
198
241
|
} catch (err) {
|
|
199
242
|
return error(err);
|
|
200
243
|
}
|
|
201
|
-
}
|
|
244
|
+
}
|
|
202
245
|
|
|
203
246
|
/**
|
|
204
247
|
* 计数查询
|
|
205
|
-
* @param {
|
|
248
|
+
* @param {Object} query - 查询条件
|
|
206
249
|
* @returns {Promise} 查询结果
|
|
207
250
|
*/
|
|
208
|
-
async count(query =
|
|
251
|
+
async count(query = {}) {
|
|
209
252
|
try {
|
|
210
|
-
let dataQuery = this.
|
|
211
|
-
if (query.length
|
|
212
|
-
query.forEach(condition => dataQuery = dataQuery.where(condition));
|
|
213
|
-
}
|
|
253
|
+
let dataQuery = this.db(this.tableName);
|
|
254
|
+
if (Object.keys(query).length) dataQuery = dataQuery.where(query);
|
|
214
255
|
const result = await dataQuery.count("* as total").first();
|
|
215
|
-
return success(Number(result?.total
|
|
256
|
+
return success(Number(result?.total ?? 0));
|
|
216
257
|
} catch (err) {
|
|
217
258
|
return error(err);
|
|
218
259
|
}
|
|
219
260
|
}
|
|
220
|
-
};
|
|
221
261
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
262
|
+
/**
|
|
263
|
+
* 多表关联查询
|
|
264
|
+
* @param {Object} query - 关联配置
|
|
265
|
+
* @returns {Promise} 查询结果
|
|
266
|
+
*/
|
|
267
|
+
async join(query) {
|
|
268
|
+
try {
|
|
269
|
+
const {
|
|
270
|
+
joinTable,
|
|
271
|
+
localField,
|
|
272
|
+
foreignField,
|
|
273
|
+
select = "*",
|
|
274
|
+
where = {},
|
|
275
|
+
} = query;
|
|
276
|
+
|
|
277
|
+
let _query = this.db(this.tableName)
|
|
278
|
+
.join(
|
|
279
|
+
joinTable,
|
|
280
|
+
`${this.tableName}.${localField}`,
|
|
281
|
+
"=",
|
|
282
|
+
`${joinTable}.${foreignField}`
|
|
283
|
+
)
|
|
284
|
+
.select(select);
|
|
285
|
+
|
|
286
|
+
if (Object.keys(where).length) _query = _query.where(where);
|
|
287
|
+
const res = await _query;
|
|
288
|
+
return success(res);
|
|
289
|
+
} catch (err) {
|
|
290
|
+
return error(err);
|
|
248
291
|
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return service;
|
|
292
|
+
}
|
|
252
293
|
}
|
|
294
|
+
|
|
295
|
+
export default Service;
|
package/extend/art-template.js
CHANGED
package/global/import.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import fs from "fs/promises";
|
|
3
2
|
import { pathToFileURL } from "url";
|
|
4
3
|
import { createRequire } from "module";
|
|
@@ -9,8 +8,8 @@ import { createRequire } from "module";
|
|
|
9
8
|
* @returns {Promise<object|null>} 模块对象或null
|
|
10
9
|
*/
|
|
11
10
|
const importFile = async (filepath) => {
|
|
12
|
-
if (!filepath || typeof filepath !==
|
|
13
|
-
console.error(
|
|
11
|
+
if (!filepath || typeof filepath !== "string") {
|
|
12
|
+
console.error("错误: 文件路径必须是有效的字符串");
|
|
14
13
|
return null;
|
|
15
14
|
}
|
|
16
15
|
|
|
@@ -20,9 +19,9 @@ const importFile = async (filepath) => {
|
|
|
20
19
|
const module = await import(fileUrl);
|
|
21
20
|
return module.default || module;
|
|
22
21
|
} catch (error) {
|
|
23
|
-
if (error.code ===
|
|
22
|
+
if (error.code === "ENOENT") {
|
|
24
23
|
console.error(`文件不存在: ${filepath}`);
|
|
25
|
-
} else if (error.code ===
|
|
24
|
+
} else if (error.code === "EACCES") {
|
|
26
25
|
console.error(`没有权限访问文件: ${filepath}`);
|
|
27
26
|
} else {
|
|
28
27
|
console.error(`导入文件时出错 [${filepath}]:`, error.message);
|
|
@@ -37,4 +36,4 @@ const importFile = async (filepath) => {
|
|
|
37
36
|
export const importjs = createRequire(import.meta.url);
|
|
38
37
|
|
|
39
38
|
global.requirejs = importjs;
|
|
40
|
-
global.importFile = importFile;
|
|
39
|
+
global.importFile = importFile;
|
package/helper/data-parse.js
CHANGED
|
@@ -5,53 +5,58 @@
|
|
|
5
5
|
* @param {string} valueField - 作为值的字段名
|
|
6
6
|
* @returns {Object} 转换后的对象
|
|
7
7
|
*/
|
|
8
|
-
export const arrToObj = (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const value = item[valueField];
|
|
18
|
-
if (key !== undefined && value !== undefined) {
|
|
19
|
-
result[key] = value;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return result;
|
|
23
|
-
}, {});
|
|
24
|
-
};
|
|
25
|
-
|
|
8
|
+
export const arrToObj = (
|
|
9
|
+
arr,
|
|
10
|
+
keyField = "config_key",
|
|
11
|
+
valueField = "config_value"
|
|
12
|
+
) => {
|
|
13
|
+
// 输入验证
|
|
14
|
+
if (!Array.isArray(arr)) {
|
|
15
|
+
throw new Error("arrToObj 期望接收数组作为第一个参数");
|
|
16
|
+
}
|
|
26
17
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export function parseJsonFields(obj) {
|
|
33
|
-
const result = {};
|
|
34
|
-
for (const key in obj) {
|
|
35
|
-
if (!obj.hasOwnProperty(key)) continue;
|
|
36
|
-
const value = obj[key];
|
|
37
|
-
// 如果是字符串,并且看起来像 JSON(以 { 或 [ 开头)
|
|
38
|
-
if (typeof value === 'string' && (value.startsWith('{') || value.startsWith('['))) {
|
|
39
|
-
try {
|
|
40
|
-
result[key] = JSON.parse(value);
|
|
41
|
-
} catch (e) {
|
|
42
|
-
console.warn(`JSON parse failed for field: ${key}`, e);
|
|
43
|
-
result[key] = value; // 保留原始值
|
|
44
|
-
}
|
|
45
|
-
} else {
|
|
18
|
+
return arr.reduce((result, item) => {
|
|
19
|
+
if (item && typeof item === "object") {
|
|
20
|
+
const key = item[keyField];
|
|
21
|
+
const value = item[valueField];
|
|
22
|
+
if (key !== undefined && value !== undefined) {
|
|
46
23
|
result[key] = value;
|
|
47
24
|
}
|
|
48
25
|
}
|
|
49
|
-
|
|
50
26
|
return result;
|
|
27
|
+
}, {});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @description 将字符串中的 JSON 字符串转换为对象
|
|
32
|
+
* @param {*} obj - 包含 JSON 字符串的对象
|
|
33
|
+
* @returns
|
|
34
|
+
*/
|
|
35
|
+
export function parseJsonFields(obj) {
|
|
36
|
+
const result = {};
|
|
37
|
+
for (const key in obj) {
|
|
38
|
+
if (!obj.hasOwnProperty(key)) continue;
|
|
39
|
+
const value = obj[key];
|
|
40
|
+
// 如果是字符串,并且看起来像 JSON(以 { 或 [ 开头)
|
|
41
|
+
if (
|
|
42
|
+
typeof value === "string" &&
|
|
43
|
+
(value.startsWith("{") || value.startsWith("["))
|
|
44
|
+
) {
|
|
45
|
+
try {
|
|
46
|
+
result[key] = JSON.parse(value);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.warn(`JSON parse failed for field: ${key}`, e);
|
|
49
|
+
result[key] = value; // 保留原始值
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
result[key] = value;
|
|
53
|
+
}
|
|
51
54
|
}
|
|
52
55
|
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
53
58
|
|
|
54
|
-
|
|
59
|
+
/**
|
|
55
60
|
* @param {Array} arr - 原始数据数组
|
|
56
61
|
* @param {number|string} [pid=0] - 根节点父ID
|
|
57
62
|
* @param {string} [idKey='id'] - ID字段名
|
|
@@ -59,27 +64,39 @@ export const arrToObj = (arr, keyField = 'config_key', valueField = 'config_valu
|
|
|
59
64
|
* @param {string} [childrenKey='children'] - 子节点字段名
|
|
60
65
|
* @returns {Array} 树形结构数组
|
|
61
66
|
*/
|
|
62
|
-
export function buildTree(
|
|
67
|
+
export function buildTree(
|
|
68
|
+
arr,
|
|
69
|
+
pid = 0,
|
|
70
|
+
idKey = "id",
|
|
71
|
+
pidKey = "pid",
|
|
72
|
+
childrenKey = "children"
|
|
73
|
+
) {
|
|
63
74
|
// 基础参数校验
|
|
64
75
|
if (!Array.isArray(arr)) return [];
|
|
65
|
-
|
|
76
|
+
|
|
66
77
|
const tree = [];
|
|
67
|
-
|
|
78
|
+
|
|
68
79
|
for (let i = 0; i < arr.length; i++) {
|
|
69
80
|
const item = arr[i];
|
|
70
81
|
// 找到当前层级的节点
|
|
71
82
|
if (item[pidKey] === pid) {
|
|
72
83
|
// 递归查找子节点(通过slice创建子数组,避免重复遍历已处理项)
|
|
73
|
-
const children = buildTree(
|
|
74
|
-
|
|
84
|
+
const children = buildTree(
|
|
85
|
+
arr.slice(i + 1),
|
|
86
|
+
item[idKey],
|
|
87
|
+
idKey,
|
|
88
|
+
pidKey,
|
|
89
|
+
childrenKey
|
|
90
|
+
);
|
|
91
|
+
|
|
75
92
|
// 有子节点则添加,避免空数组
|
|
76
93
|
if (children.length) {
|
|
77
94
|
item[childrenKey] = children;
|
|
78
95
|
}
|
|
79
|
-
|
|
96
|
+
|
|
80
97
|
tree.push(item);
|
|
81
98
|
}
|
|
82
99
|
}
|
|
83
|
-
|
|
100
|
+
|
|
84
101
|
return tree;
|
|
85
102
|
}
|