neo-cmp-cli 1.5.0-beta.1 → 1.5.0-beta.3
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/cmpUtils/publishCmp.js +39 -31
- package/src/config/default.config.js +14 -2
- package/src/neo/NeoUMDContent.js +29 -0
- package/src/neo/neoService.js +151 -50
- package/src/neo/wrapperContent.js +2 -1
- package/src/plugins/AddNeoRequirePlugin.js +3 -2
- package/src/template/neo-custom-cmp-template/neo.config.js +2 -1
- package/src/template/neo-custom-cmp-template/package.json +1 -1
- package/src/template/neo-custom-cmp-template/src/components/entity-detail/index.tsx +2 -0
package/package.json
CHANGED
|
@@ -83,9 +83,9 @@ function getFramework(_framework) {
|
|
|
83
83
|
* 构建组件数据映射
|
|
84
84
|
* @param {string} assetsRoot 构建产物的目录
|
|
85
85
|
* @param {object} cmpInfo 自定义组件信息
|
|
86
|
-
* @returns {object} 自定义组件数据(含自定义组件模型信息)
|
|
86
|
+
* @returns {Promise<object|null>} 自定义组件数据(含自定义组件模型信息)
|
|
87
87
|
*/
|
|
88
|
-
const buildComponentData = (assetsRoot, cmpInfo) => {
|
|
88
|
+
const buildComponentData = async (assetsRoot, cmpInfo) => {
|
|
89
89
|
if (!cmpInfo || !cmpInfo.cmpType) {
|
|
90
90
|
console.error('自定义组件信息或组件名称不能为空');
|
|
91
91
|
return null;
|
|
@@ -94,51 +94,52 @@ const buildComponentData = (assetsRoot, cmpInfo) => {
|
|
|
94
94
|
const { cmpType } = cmpInfo;
|
|
95
95
|
|
|
96
96
|
if (!assetsRoot || !fs.existsSync(assetsRoot)) {
|
|
97
|
-
console.error(
|
|
97
|
+
console.error(`未找到自定义组件目录: ${assetsRoot}`);
|
|
98
98
|
return null;
|
|
99
99
|
}
|
|
100
|
-
const widgetName = _.
|
|
100
|
+
const widgetName = _.camelCase(cmpType);
|
|
101
101
|
const modelFile = path.join(assetsRoot, `${widgetName}Model.js`);
|
|
102
102
|
|
|
103
|
-
let
|
|
103
|
+
let CatchCustomCmpModelClass = null;
|
|
104
|
+
|
|
105
|
+
// 为 Node.js 环境设置全局 window 对象(模型文件可能需要)
|
|
106
|
+
// 使用 globalThis 以确保在 Node.js 和浏览器环境中都能工作
|
|
107
|
+
const originalWindow = globalThis.window;
|
|
108
|
+
if (!globalThis.window) {
|
|
109
|
+
globalThis.window = {
|
|
110
|
+
console: console,
|
|
111
|
+
neoRequire: () => {},
|
|
112
|
+
// 可以添加其他常用的 window 属性
|
|
113
|
+
};
|
|
114
|
+
}
|
|
104
115
|
|
|
105
116
|
try {
|
|
106
117
|
// 加载自定义组件模型资源文件
|
|
107
118
|
if (fs.existsSync(modelFile)) {
|
|
108
|
-
|
|
109
|
-
const resolvedPath = require.resolve(modelFile);
|
|
110
|
-
if (require.cache[resolvedPath]) {
|
|
111
|
-
delete require.cache[resolvedPath];
|
|
112
|
-
}
|
|
113
|
-
const modelModule = require(modelFile);
|
|
119
|
+
let modelModule = require(modelFile);
|
|
114
120
|
// 获取导出的模型类(可能是 default 导出或命名导出)
|
|
115
|
-
|
|
116
|
-
// 如果是命名导出,尝试查找类名(例如 EntityCardListModel)
|
|
117
|
-
if (typeof ModelClass !== 'function' && typeof modelModule === 'object') {
|
|
118
|
-
// 查找所有导出的类
|
|
119
|
-
const exportedClasses = Object.values(modelModule).filter(
|
|
120
|
-
(item) => typeof item === 'function'
|
|
121
|
-
);
|
|
122
|
-
if (exportedClasses.length > 0) {
|
|
123
|
-
ModelClass = exportedClasses[0];
|
|
124
|
-
}
|
|
125
|
-
}
|
|
121
|
+
CatchCustomCmpModelClass = modelModule.default || modelModule;
|
|
126
122
|
}
|
|
127
|
-
// 如果资源文件不存在,报错
|
|
128
123
|
else {
|
|
129
|
-
console.error(
|
|
124
|
+
console.error(`未找到自定义组件模型文件,请检查以下路径是否存在:`, modelFile);
|
|
130
125
|
return null;
|
|
131
126
|
}
|
|
132
127
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
console.error(`模型文件 ${modelFile} 未导出有效的模型类`);
|
|
128
|
+
if (!CatchCustomCmpModelClass || typeof CatchCustomCmpModelClass !== 'function') {
|
|
129
|
+
console.error(`模型文件未导出有效模型方法(CatchCustomCmpModelClass),模型文件地址: ${modelFile} `);
|
|
136
130
|
return null;
|
|
137
131
|
}
|
|
138
132
|
|
|
133
|
+
// 先获取对应的模型类
|
|
134
|
+
const ModelClass = new CatchCustomCmpModelClass(cmpType);
|
|
139
135
|
// 实例化模型类
|
|
140
136
|
const modelInstance = new ModelClass();
|
|
141
137
|
|
|
138
|
+
if (!modelInstance) {
|
|
139
|
+
console.error(`模型文件未导出有效模型实例(${cmpType}),模型文件地址: ${modelFile} `);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
142
143
|
// 构建组件数据,合并模型实例的信息
|
|
143
144
|
const curCmpInfo = {
|
|
144
145
|
...cmpInfo,
|
|
@@ -160,11 +161,19 @@ const buildComponentData = (assetsRoot, cmpInfo) => {
|
|
|
160
161
|
enableDuplicate: modelInstance.enableDuplicate !== undefined ? modelInstance.enableDuplicate : true
|
|
161
162
|
};
|
|
162
163
|
|
|
164
|
+
console.log('自定义组件模型信息(${cmpType}):', curCmpInfo);
|
|
163
165
|
return curCmpInfo;
|
|
164
166
|
} catch (error) {
|
|
165
167
|
console.error(`自定义组件模型文件解析失败 (${modelFile || '未知路径'}):`, error.message);
|
|
166
168
|
console.error(error.stack);
|
|
167
169
|
return null;
|
|
170
|
+
} finally {
|
|
171
|
+
// 恢复原始的 window 对象(如果之前存在)
|
|
172
|
+
if (originalWindow === undefined) {
|
|
173
|
+
delete globalThis.window;
|
|
174
|
+
} else {
|
|
175
|
+
globalThis.window = originalWindow;
|
|
176
|
+
}
|
|
168
177
|
}
|
|
169
178
|
};
|
|
170
179
|
|
|
@@ -202,9 +211,9 @@ const publishCmp = async (config, cmpType) => {
|
|
|
202
211
|
|
|
203
212
|
// 步骤 4: 构建组件数据
|
|
204
213
|
spinner.text = '发布自定义组件:构建组件数据...';
|
|
205
|
-
const componentInfo = buildComponentData(config.assetsRoot, cmpInfo);
|
|
214
|
+
const componentInfo = await buildComponentData(config.assetsRoot, cmpInfo);
|
|
206
215
|
if (!componentInfo) {
|
|
207
|
-
throw new Error(
|
|
216
|
+
throw new Error(`构建组件数据失败,未获取到自定义组件模型信息。(${cmpType})`);
|
|
208
217
|
}
|
|
209
218
|
|
|
210
219
|
// 步骤 5: 保存组件信息
|
|
@@ -218,5 +227,4 @@ const publishCmp = async (config, cmpType) => {
|
|
|
218
227
|
}
|
|
219
228
|
};
|
|
220
229
|
|
|
221
|
-
module.exports = publishCmp;
|
|
222
|
-
|
|
230
|
+
module.exports = publishCmp;
|
|
@@ -34,8 +34,8 @@ const defaultNEOConfig = {
|
|
|
34
34
|
template: resolveByDirname('../initData/defaultTemplate.html'), // 默认使用neo-widget提供的页面模板(会启动页面设计器)
|
|
35
35
|
sassResources: [],
|
|
36
36
|
babelPlugins: [
|
|
37
|
-
['import', { libraryName: 'antd', style: 'css' }]
|
|
38
|
-
]
|
|
37
|
+
['import', { libraryName: 'antd', style: 'css' }] // 配置 antd 的样式按需引入
|
|
38
|
+
]
|
|
39
39
|
},
|
|
40
40
|
envParams: {
|
|
41
41
|
// 项目系统环境变量
|
|
@@ -132,6 +132,18 @@ const defaultNEOConfig = {
|
|
|
132
132
|
bucket: 'neo-widgets' // 存储桶名称
|
|
133
133
|
},
|
|
134
134
|
assetsRoot: resolve('dist') // 上传指定目录下的脚本文件
|
|
135
|
+
},
|
|
136
|
+
publishCmp: {
|
|
137
|
+
output: {
|
|
138
|
+
filename: '[name].js',
|
|
139
|
+
library: {
|
|
140
|
+
type: 'var', // webpack 5 中生成 IIFE 格式的 type 配置
|
|
141
|
+
export: 'default'
|
|
142
|
+
},
|
|
143
|
+
globalObject: 'this' // 定义全局变量,兼容node和浏览器运行,避免出现"window is not defined"的情况
|
|
144
|
+
},
|
|
145
|
+
cssExtract: false, // 不额外提取css文件
|
|
146
|
+
assetsRoot: resolve('dist') // 上传指定目录下的脚本文件
|
|
135
147
|
}
|
|
136
148
|
};
|
|
137
149
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内置 UMD 模块,用于在 Node cli 端加载自定义组件模型
|
|
3
|
+
* 特别说明:这个文件内容会通过 AddNeoRequirePlugin 插入的构建代码中。
|
|
4
|
+
*/
|
|
5
|
+
(function (root, factory) {
|
|
6
|
+
// AMD 环境检测
|
|
7
|
+
if (typeof define === 'function' && define.amd) {
|
|
8
|
+
define([], factory);
|
|
9
|
+
}
|
|
10
|
+
// CommonJS 环境检测
|
|
11
|
+
else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
|
|
12
|
+
module.exports = factory();
|
|
13
|
+
}
|
|
14
|
+
// 浏览器全局变量
|
|
15
|
+
else {
|
|
16
|
+
root.NeoCustomCmpModel = factory();
|
|
17
|
+
}
|
|
18
|
+
}(typeof self !== 'undefined' ? self : this, function () {
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
const getCustomModels = (cmpType) => {
|
|
22
|
+
// 自定义组件模型列表
|
|
23
|
+
const NEOEditorCustomModels = window.NEOEditorCustomModels || {};
|
|
24
|
+
return NEOEditorCustomModels[cmpType] || {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 导出模块
|
|
28
|
+
return getCustomModels;
|
|
29
|
+
}));
|
package/src/neo/neoService.js
CHANGED
|
@@ -8,9 +8,9 @@ const updatePublishLog = require('../cmpUtils/updatePublishLog');
|
|
|
8
8
|
// NeoCRM 平台默认 API 配置
|
|
9
9
|
const NeoCrmAPI = {
|
|
10
10
|
neoBaseURL: 'https://crm.xiaoshouyi.com', // 平台根地址
|
|
11
|
-
tokenAPI: 'https://login.
|
|
11
|
+
tokenAPI: 'https://login.xiaoshouyi.com/auc/oauth2/token', // Token 获取接口地址
|
|
12
12
|
uploadAPI: '/rest/metadata/v3.0/ui/customComponents/actions/upload', // 文件上传接口地址
|
|
13
|
-
saveAPI: '/rest/metadata/v3.0/ui/customComponents/actions/
|
|
13
|
+
saveAPI: '/rest/metadata/v3.0/ui/customComponents/actions/saveOrUpdateComponent' // 创建或者保存接口地址
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -101,29 +101,30 @@ class NeoService {
|
|
|
101
101
|
console.info('使用缓存的 token');
|
|
102
102
|
return this.tokenCache.token;
|
|
103
103
|
}
|
|
104
|
+
console.info('获取 token...');
|
|
104
105
|
|
|
105
|
-
|
|
106
|
+
// 构建表单数据格式的请求参数
|
|
107
|
+
const formData = new URLSearchParams();
|
|
108
|
+
formData.append('grant_type', 'password');
|
|
109
|
+
formData.append('client_id', this.authorization.client_id);
|
|
110
|
+
formData.append('client_secret', this.authorization.client_secret);
|
|
111
|
+
formData.append('username', this.authorization.username);
|
|
112
|
+
formData.append('password', this.authorization.password);
|
|
113
|
+
|
|
114
|
+
const tokenUrl = this.buildFullUrl(this.tokenAPI);
|
|
106
115
|
|
|
107
116
|
try {
|
|
108
|
-
|
|
109
|
-
const params = new URLSearchParams();
|
|
110
|
-
params.append('grant_type', 'password');
|
|
111
|
-
params.append('client_id', this.authorization.client_id);
|
|
112
|
-
params.append('client_secret', this.authorization.client_secret);
|
|
113
|
-
params.append('username', this.authorization.username);
|
|
114
|
-
params.append('password', this.authorization.password);
|
|
115
|
-
|
|
116
|
-
const tokenUrl = this.buildFullUrl(this.tokenAPI);
|
|
117
|
-
const response = await axios.post(tokenUrl, params, {
|
|
117
|
+
const response = await axios.post(tokenUrl, formData.toString(), {
|
|
118
118
|
headers: {
|
|
119
119
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
120
120
|
}
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
-
const { access_token, expires_in } = response.data;
|
|
123
|
+
const { access_token, expires_in } = response.data || {};
|
|
124
124
|
|
|
125
125
|
if (!access_token) {
|
|
126
|
-
|
|
126
|
+
console.error('获取 token 失败:响应中未包含 access_token', response.data);
|
|
127
|
+
process.exit(1);
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
// 缓存 token(提前 60 秒过期,避免边缘情况)
|
|
@@ -132,15 +133,15 @@ class NeoService {
|
|
|
132
133
|
token: access_token,
|
|
133
134
|
expiresAt: Date.now() + (expiresIn - 60) * 1000
|
|
134
135
|
};
|
|
135
|
-
|
|
136
|
-
console.info('token 获取成功');
|
|
137
136
|
return access_token;
|
|
138
137
|
} catch (error) {
|
|
139
138
|
console.error('获取 token 失败:', error.message);
|
|
139
|
+
console.error('\ntoken 授权地址:', tokenUrl);
|
|
140
|
+
console.error('\ntoken 请求参数:', formData);
|
|
140
141
|
if (error.response) {
|
|
141
142
|
console.error('响应数据:', error.response.data);
|
|
142
143
|
}
|
|
143
|
-
|
|
144
|
+
process.exit(1);
|
|
144
145
|
}
|
|
145
146
|
}
|
|
146
147
|
|
|
@@ -163,7 +164,9 @@ class NeoService {
|
|
|
163
164
|
* @returns {Promise<string>} 有效的 token
|
|
164
165
|
*/
|
|
165
166
|
async ensureValidToken() {
|
|
166
|
-
if (this.
|
|
167
|
+
if (!this.tokenCache.token) {
|
|
168
|
+
return await this.getToken();
|
|
169
|
+
} else if (this.isTokenExpired()) {
|
|
167
170
|
console.info('token 已过期,正在刷新...');
|
|
168
171
|
return await this.refreshToken();
|
|
169
172
|
}
|
|
@@ -174,59 +177,155 @@ class NeoService {
|
|
|
174
177
|
* 上传文件到 Neo 平台
|
|
175
178
|
* @param {string} filePath 文件路径
|
|
176
179
|
* @param {object} options 可选配置
|
|
177
|
-
* @param {string} options.fieldName 表单字段名,默认为 '
|
|
180
|
+
* @param {string} options.fieldName 表单字段名,默认为 'customComponentCode'
|
|
181
|
+
* @param {number} options.maxSize 最大文件大小(字节),默认 50MB
|
|
182
|
+
* @param {number} options.timeout 超时时间(毫秒),默认 60000
|
|
178
183
|
* @returns {Promise<string>} CDN 地址或文件 URL
|
|
179
184
|
*/
|
|
180
185
|
async uploadFile(filePath, options = {}) {
|
|
181
186
|
// 确保 token 有效
|
|
182
187
|
const token = await this.ensureValidToken();
|
|
183
188
|
|
|
189
|
+
// 验证文件路径
|
|
190
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
191
|
+
throw new Error(`文件路径无效: ${filePath}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 检查文件是否存在
|
|
184
195
|
if (!fs.existsSync(filePath)) {
|
|
185
196
|
throw new Error(`文件不存在: ${filePath}`);
|
|
186
197
|
}
|
|
187
198
|
|
|
188
|
-
|
|
199
|
+
// 检查文件状态
|
|
200
|
+
const fileStat = fs.statSync(filePath);
|
|
201
|
+
if (!fileStat.isFile()) {
|
|
202
|
+
throw new Error(`路径不是文件: ${filePath}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 检查文件大小
|
|
206
|
+
const maxSize = options.maxSize || 50 * 1024 * 1024; // 默认 50MB
|
|
207
|
+
if (fileStat.size > maxSize) {
|
|
208
|
+
const sizeMB = (fileStat.size / 1024 / 1024).toFixed(2);
|
|
209
|
+
const maxSizeMB = (maxSize / 1024 / 1024).toFixed(2);
|
|
210
|
+
throw new Error(`文件大小超过限制: ${sizeMB}MB > ${maxSizeMB}MB`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (fileStat.size === 0) {
|
|
214
|
+
throw new Error(`文件为空: ${filePath}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const fileName = path.basename(filePath);
|
|
218
|
+
const fileSizeKB = (fileStat.size / 1024).toFixed(2);
|
|
219
|
+
console.info(`正在上传文件: ${fileName} (${fileSizeKB}KB)...`);
|
|
189
220
|
|
|
190
221
|
try {
|
|
222
|
+
// 创建 FormData
|
|
191
223
|
const formData = new FormData();
|
|
192
224
|
const fieldName = options.fieldName || 'customComponentCode';
|
|
193
|
-
|
|
225
|
+
|
|
226
|
+
// 使用文件流而不是读取整个文件到内存(对大文件更友好)
|
|
227
|
+
const fileContent = fs.createReadStream(filePath);
|
|
194
228
|
|
|
229
|
+
// 追加文件到 FormData,第三个参数指定文件名
|
|
230
|
+
formData.append(fieldName, fileContent, fileName);
|
|
231
|
+
|
|
232
|
+
// 构建完整的上传 API 地址
|
|
195
233
|
const fullUploadAPI = this.uploadAPI();
|
|
196
|
-
|
|
234
|
+
|
|
235
|
+
// 配置请求选项
|
|
236
|
+
const timeout = options.timeout || 60000; // 默认 60 秒
|
|
237
|
+
const requestConfig = {
|
|
197
238
|
headers: {
|
|
198
239
|
Authorization: `Bearer ${token}`,
|
|
240
|
+
'xsy-inner-source': 'bff',
|
|
241
|
+
// 无需手动设置 Content-Type,formData.getHeaders() 会自动设置正确的 multipart/form-data 和 boundary
|
|
199
242
|
...formData.getHeaders()
|
|
200
|
-
}
|
|
201
|
-
|
|
243
|
+
},
|
|
244
|
+
timeout,
|
|
245
|
+
// 确保 axios 正确处理大文件和流
|
|
246
|
+
maxContentLength: Infinity, // 不限制响应内容长度
|
|
247
|
+
maxBodyLength: Infinity // 不限制请求体长度(适用于文件上传)
|
|
248
|
+
};
|
|
202
249
|
|
|
203
|
-
//
|
|
250
|
+
// 发送上传请求
|
|
251
|
+
const response = await axios.post(fullUploadAPI, formData, requestConfig);
|
|
252
|
+
|
|
253
|
+
// 处理响应数据
|
|
204
254
|
let resultData;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
resultData = response.data.data;
|
|
211
|
-
} else {
|
|
212
|
-
resultData = response.data;
|
|
255
|
+
const responseData = response.data;
|
|
256
|
+
|
|
257
|
+
// 检查响应状态码
|
|
258
|
+
if (response.status !== 200 && response.status !== 201) {
|
|
259
|
+
throw new Error(`上传失败: HTTP ${response.status}`);
|
|
213
260
|
}
|
|
214
261
|
|
|
215
|
-
|
|
216
|
-
|
|
262
|
+
// 处理不同的响应格式
|
|
263
|
+
if (typeof responseData === 'string') {
|
|
264
|
+
// 如果响应是字符串,直接使用
|
|
265
|
+
resultData = responseData.trim();
|
|
266
|
+
} else if (responseData && typeof responseData === 'object') {
|
|
267
|
+
// 检查是否有错误码
|
|
268
|
+
if (responseData.code !== undefined && responseData.code !== 200 && responseData.code !== 0) {
|
|
269
|
+
const errorMsg = responseData.message || responseData.msg || '未知错误';
|
|
270
|
+
throw new Error(`上传失败: ${errorMsg} (code: ${responseData.code})`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// 提取数据
|
|
274
|
+
if (responseData.data !== undefined) {
|
|
275
|
+
resultData = responseData.data;
|
|
276
|
+
} else if (responseData.url !== undefined) {
|
|
277
|
+
resultData = responseData.url;
|
|
278
|
+
} else if (responseData.fileUrl !== undefined) {
|
|
279
|
+
resultData = responseData.fileUrl;
|
|
280
|
+
} else {
|
|
281
|
+
resultData = responseData;
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
throw new Error(`响应数据格式不正确: ${typeof responseData}`);
|
|
217
285
|
}
|
|
218
286
|
|
|
219
|
-
|
|
220
|
-
|
|
287
|
+
// 验证返回的文件地址
|
|
288
|
+
if (!resultData) {
|
|
289
|
+
throw new Error(`返回的文件地址为空`);
|
|
221
290
|
}
|
|
222
291
|
|
|
223
|
-
|
|
292
|
+
// 格式化文件 URL
|
|
293
|
+
let fileUrl;
|
|
294
|
+
if (typeof resultData === 'string') {
|
|
295
|
+
fileUrl = resultData;
|
|
296
|
+
} else if (resultData && typeof resultData === 'object' && resultData.url) {
|
|
297
|
+
fileUrl = resultData.url;
|
|
298
|
+
}
|
|
299
|
+
console.info(`文件上传成功: ${fileName} -> ${fileUrl}`);
|
|
224
300
|
return fileUrl;
|
|
225
301
|
} catch (error) {
|
|
226
|
-
console.error(
|
|
302
|
+
console.error(`上传文件失败: ${error.message}`);
|
|
303
|
+
console.error(`文件路径: ${filePath}`);
|
|
304
|
+
|
|
305
|
+
// 输出详细的错误信息
|
|
227
306
|
if (error.response) {
|
|
228
|
-
|
|
307
|
+
const status = error.response.status;
|
|
308
|
+
const statusText = error.response.statusText;
|
|
309
|
+
const responseData = error.response.data;
|
|
310
|
+
const requestUrl = error.config?.url || this.uploadAPI();
|
|
311
|
+
|
|
312
|
+
console.error(`\n========== 上传请求详情 ==========`);
|
|
313
|
+
console.error(`请求 URL: ${requestUrl}`);
|
|
314
|
+
console.error(`HTTP 状态码: ${status} ${statusText}`);
|
|
315
|
+
console.error(`响应数据:`, responseData);
|
|
316
|
+
console.error(`==================================\n`);
|
|
317
|
+
|
|
318
|
+
if (status === 404) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
`上传 API 不存在 (404): ${requestUrl}\n` +
|
|
321
|
+
`请检查 neo.config.js 中的 neoBaseURL 配置是否正确,或者 API 路径是否存在。\n` +
|
|
322
|
+
`当前配置的 API 路径: ${NeoCrmAPI.uploadAPI}`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
} else if (error.request) {
|
|
326
|
+
console.error('请求已发送但未收到响应,请检查网络连接或代理配置。');
|
|
229
327
|
}
|
|
328
|
+
|
|
230
329
|
throw error;
|
|
231
330
|
}
|
|
232
331
|
}
|
|
@@ -238,7 +337,7 @@ class NeoService {
|
|
|
238
337
|
* @param {array} fileExtensions 需要上传的文件类型,默认 ['.js', '.css']
|
|
239
338
|
*/
|
|
240
339
|
async publish2oss(cmpType, fileExtensions = ['.js', '.css']) {
|
|
241
|
-
if (!
|
|
340
|
+
if (!cmpType) {
|
|
242
341
|
console.error(`自定义组件名称不能为空: ${cmpType}`);
|
|
243
342
|
return;
|
|
244
343
|
}
|
|
@@ -262,7 +361,7 @@ class NeoService {
|
|
|
262
361
|
// const fileExt = path.extname(file);
|
|
263
362
|
const fileInfo = path.parse(filePath);
|
|
264
363
|
if (fileStat.isFile() && fileExtensions.includes(fileInfo.ext)) {
|
|
265
|
-
let widgetName = _.
|
|
364
|
+
let widgetName = _.camelCase(cmpType);
|
|
266
365
|
|
|
267
366
|
if (file.indexOf(widgetName) < 0) {
|
|
268
367
|
return;
|
|
@@ -271,7 +370,7 @@ class NeoService {
|
|
|
271
370
|
try {
|
|
272
371
|
// 上传文件
|
|
273
372
|
const fileUrl = await this.uploadFile(filePath);
|
|
274
|
-
|
|
373
|
+
|
|
275
374
|
if (file.indexOf('Model') > -1) {
|
|
276
375
|
curCmpInfo.modelAsset = fileUrl;
|
|
277
376
|
} else if (file.endsWith('.css')) {
|
|
@@ -279,15 +378,15 @@ class NeoService {
|
|
|
279
378
|
} else {
|
|
280
379
|
curCmpInfo.asset = fileUrl;
|
|
281
380
|
}
|
|
282
|
-
|
|
283
381
|
} catch (error) {
|
|
284
|
-
console.error(`文件上传失败(${file}):\n
|
|
382
|
+
console.error(`文件上传失败(${file}):\n`);
|
|
383
|
+
process.exit(1);
|
|
285
384
|
}
|
|
286
385
|
}
|
|
287
386
|
});
|
|
288
387
|
|
|
289
388
|
await Promise.all(uploadPromises);
|
|
290
|
-
|
|
389
|
+
|
|
291
390
|
if (curCmpInfo && curCmpInfo.cmpType) {
|
|
292
391
|
console.info('上传至 OSS 的文件信息:\n', curCmpInfo);
|
|
293
392
|
// 更新发布日志
|
|
@@ -298,7 +397,6 @@ class NeoService {
|
|
|
298
397
|
|
|
299
398
|
/**
|
|
300
399
|
* 更新自定义组件
|
|
301
|
-
* @param {string} updateAPI 更新接口地址(相对或绝对路径)
|
|
302
400
|
* @param {object} componentData 组件数据
|
|
303
401
|
* @returns {Promise<object>} 更新结果
|
|
304
402
|
*/
|
|
@@ -313,10 +411,12 @@ class NeoService {
|
|
|
313
411
|
console.info('正在更新自定义组件...');
|
|
314
412
|
|
|
315
413
|
try {
|
|
316
|
-
const fullUpdateAPI = this.
|
|
414
|
+
const fullUpdateAPI = this.saveAPI();
|
|
415
|
+
console.info('更新组件 API 地址:', fullUpdateAPI);
|
|
317
416
|
const response = await axios.post(fullUpdateAPI, componentData, {
|
|
318
417
|
headers: {
|
|
319
418
|
Authorization: `Bearer ${token}`,
|
|
419
|
+
'xsy-inner-source': 'bff',
|
|
320
420
|
'Content-Type': 'application/json'
|
|
321
421
|
}
|
|
322
422
|
});
|
|
@@ -332,7 +432,7 @@ class NeoService {
|
|
|
332
432
|
if (error.response) {
|
|
333
433
|
console.error('响应数据:', error.response.data);
|
|
334
434
|
}
|
|
335
|
-
|
|
435
|
+
process.exit(1);
|
|
336
436
|
}
|
|
337
437
|
}
|
|
338
438
|
|
|
@@ -361,6 +461,7 @@ class NeoService {
|
|
|
361
461
|
params,
|
|
362
462
|
headers: {
|
|
363
463
|
Authorization: `Bearer ${token}`,
|
|
464
|
+
'xsy-inner-source': 'bff',
|
|
364
465
|
...headers
|
|
365
466
|
}
|
|
366
467
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 注入 neoRequire 函数
|
|
3
3
|
* 备注:用于实现和 Neo 平台共享依赖
|
|
4
|
+
* 特别说明:这个文件只是用于说明 AddNeoRequirePlugin 中会插入的代码内容,实际内容内置在 AddNeoRequirePlugin 方法中。
|
|
4
5
|
*/
|
|
5
6
|
(function(NeoCustomCmpFileFactory) {
|
|
6
7
|
if (!window.neoRequire) {
|
|
@@ -12,4 +13,4 @@
|
|
|
12
13
|
* 这里放自定义组件相关内容代码
|
|
13
14
|
* 备注: 自定义组件代码中的 require 函数 已被替换成 neoRequire 函数(require === neoRequire)。
|
|
14
15
|
*/
|
|
15
|
-
});
|
|
16
|
+
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const { ConcatSource } = require('webpack-sources');
|
|
2
|
-
|
|
3
2
|
/**
|
|
4
3
|
* 注入 neoRequire 函数
|
|
5
4
|
* 备注:用于实现和 Neo 平台共享依赖
|
|
@@ -163,7 +162,9 @@ class AddNeoRequirePlugin {
|
|
|
163
162
|
})(function(require) {
|
|
164
163
|
`;
|
|
165
164
|
|
|
166
|
-
const
|
|
165
|
+
const NeoUMDContent = fs.readFileSync(path.join(__dirname, '../neo/NeoUMDContent.js'), 'utf8');
|
|
166
|
+
|
|
167
|
+
const Footer = `}); \n${NeoUMDContent}`;
|
|
167
168
|
|
|
168
169
|
// 创建新的资源
|
|
169
170
|
const newSource = new ConcatSource(Header, content, Footer);
|
|
@@ -127,9 +127,10 @@ module.exports = {
|
|
|
127
127
|
*/
|
|
128
128
|
},
|
|
129
129
|
publishCmp: {
|
|
130
|
+
libraryName: 'NeoCustomCmpModel', // 构建第三方功能包时最后导出的引用变量名
|
|
130
131
|
// 用于构建并发布至 NeoCRM 的相关配置
|
|
131
132
|
neoBaseURL: 'https://crm-cd.xiaoshouyi.com', // 平台根地址(默认:https://crm.xiaoshouyi.com)
|
|
132
|
-
tokenAPI: 'https://login
|
|
133
|
+
tokenAPI: 'https://login-cd.xiaoshouyi.com/auc/oauth2/token', // Token 获取接口地址(默认:https://login.xiaoshouyi.com/auc/oauth2/token)
|
|
133
134
|
// NeoCRM 授权配置
|
|
134
135
|
authorization: {
|
|
135
136
|
client_id: authConfig.client_id || 'xx', // 客户端 ID,从创建连接器的客户端信息中获取(Client_Id)
|
|
@@ -278,6 +278,8 @@ export default class EntityDetail extends React.PureComponent<
|
|
|
278
278
|
|
|
279
279
|
return (
|
|
280
280
|
<div className="entity-detail-container">
|
|
281
|
+
<img src="https://custom-widget2.oss-cn-beijing.aliyuncs.com/img.png" alt="logo" />
|
|
282
|
+
<img src="https://publicfront-1253467224.cos.ap-beijing.myqcloud.com/customComponent/crm-cd/tenant/3392331536784858/1761882606360/img.png" alt="logo" />
|
|
281
283
|
{showTitle && (
|
|
282
284
|
<div className="detail-header">
|
|
283
285
|
<div className="header-content">
|