neo-cmp-cli 1.9.28 → 1.9.30
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/dist/neo/neoLogin.js +1 -1
- package/dist/neo/neoService.js +1 -1
- package/dist/package.json.js +1 -1
- package/dist/utils/cmpUtils/createCmpByTemplate.js +1 -1
- package/dist/utils/tableLog.js +1 -0
- package/package.json +1 -1
- package/template/antd-custom-cmp-template/src/components/searchWidget__c/index.tsx +1 -1
- package/template/develop/demo1/index.jsx +84 -0
- package/template/develop/demo1/index1.jsx +81 -0
- package/template/empty-cmp/index.tsx +4 -1
- package/template/empty-custom-cmp-template/@types/neo-ui-common.d.ts +36 -0
- package/template/neo-custom-cmp-template/neo.config.js +7 -0
- package/template/neo-custom-cmp-template/src/components/entityCardList__c/index.tsx +6 -4
- package/template/neo-custom-cmp-template/src/components/entityForm__c/index.tsx +3 -2
- package/template/neo-custom-cmp-template/src/components/entityTable__c/index.tsx +2 -1
- package/template/neo-custom-cmp-template/src/components/simpleCmp__c/index.tsx +42 -0
- package/template/neo-custom-cmp-template/src/components/simpleCmp__c/model.ts +55 -0
- package/template/neo-custom-cmp-template/src/components/simpleCmp__c/style.scss +21 -0
- package/template/neo-custom-cmp-template/tsconfig.json +61 -42
package/dist/neo/neoLogin.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("axios"),t=require("node:fs"),n=require("node:path"),s=require("ora"),r=require("node:http"),o=require("node:url"),i=require("open"),a=require("node:net"),c=require("lodash"),l=require("../utils/common.js"),h=require("./neoEnvManager.js");var p,u;exports.__require=function(){if(u)return p;u=1;const d=e,g=t,m=n,y=s,k=r,_=o,f=i,T=a,w=c,{errorLog:U,successLog:$}=l.__require(),R=h.__require();return p=class{constructor(e){if(!e)return;const{loginURL:t,tokenURL:n}=e,s=R.getAuthConfig(),{redirectUri:r,authType:o,auth:i}=s;if(!t||!n)throw new Error("
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("axios"),t=require("node:fs"),n=require("node:path"),s=require("ora"),r=require("node:http"),o=require("node:url"),i=require("open"),a=require("node:net"),c=require("lodash"),l=require("../utils/common.js"),h=require("./neoEnvManager.js");var p,u;exports.__require=function(){if(u)return p;u=1;const d=e,g=t,m=n,y=s,k=r,_=o,f=i,T=a,w=c,{errorLog:U,successLog:$}=l.__require(),R=h.__require();return p=class{constructor(e){if(!e)return;const{loginURL:t,tokenURL:n}=e,s=R.getAuthConfig(),{redirectUri:r,authType:o,auth:i}=s;if(!t||!n)throw new Error("neo.config.js 配置不完整,需要包含 loginURL、tokenURL");this.loginURL=t,this.tokenURL=n,this.NeoCrmAPI=s,this.redirectUri=r,this.authType=o||"oauth2",this.response_type=i.response_type||"code",this.client_id=i.client_id,this.client_secret=i.client_secret,this.scope=i.scope||"all",this.oauthType=i.oauthType||"standard",this.access_type=i.access_type||"offline",this.grant_type=i.grant_type||"authorization_code",this.tokenDir=m.join(process.cwd(),".neo-cli"),this.tokenFile=m.join(this.tokenDir,"token.json"),this.pageDir=m.join(__dirname,"../../template/pageHtml"),this.authErrorTemplate=m.join(this.pageDir,"auth-error.html"),this.authSuccessTemplate=m.join(this.pageDir,"auth-success.html"),this.tokenErrorTemplate=m.join(this.pageDir,"token-error.html"),this.authFailedTemplate=m.join(this.pageDir,"auth-failed.html");try{R.setEnvConfig(w.pick(e,["neoBaseURL","loginURL","tokenURL"]))}catch(e){U(`保存环境配置失败: ${e.message||e.msg}`)}}ensureTokenDir(){g.existsSync(this.tokenDir)||g.mkdirSync(this.tokenDir,{recursive:!0})}saveToken(e){this.ensureTokenDir();const t={...e,savedAt:Date.now(),expiresAt:Date.now()+1e3*(e.expires_in||7200)};g.writeFileSync(this.tokenFile,JSON.stringify(t,null,2),"utf-8")}readToken(){if(!g.existsSync(this.tokenFile))return null;try{return JSON.parse(g.readFileSync(this.tokenFile,"utf-8"))}catch(e){return U(`读取 token 文件失败: ${e.message||e.msg}`),null}}isTokenExpired(e){return!e||!e.expiresAt||Date.now()>=e.expiresAt-3e5}clearToken(){g.existsSync(this.tokenFile)&&g.unlinkSync(this.tokenFile)}getRedirectURI(e){return`http://localhost:${e}`}buildAuthUrl(e){const t=new URLSearchParams({response_type:this.response_type,client_id:this.client_id,redirect_uri:e,scope:this.scope,oauthType:this.oauthType,access_type:this.access_type});return`${this.loginURL}?${t.toString()}`}async openBrowser(e){try{await f(e)}catch(t){U(`无法自动打开浏览器: ${t.message||t.msg}`),console.log(`\n请手动访问以下 URL 进行授权:\n${e}\n`)}}getHtmlTemplate(e,t={}){try{let n=g.readFileSync(e,"utf-8");return Object.keys(t).forEach(e=>{const s=`{{${e}}}`;n=n.replace(new RegExp(s,"g"),t[e])}),n}catch(e){return U(`读取 HTML 模板失败: ${e.message||e.msg}`),"<html><body><h1>页面加载失败</h1></body></html>"}}async isPortInUse(e){return new Promise(t=>{const n=T.createServer();n.once("error",e=>{"EADDRINUSE"===e.code?t(!0):t(!1)}),n.once("listening",()=>{n.once("close",()=>{t(!1)}),n.close()}),n.listen(e)})}async startCallbackServer(){let e=this.redirectUri,t=new URL(e),n=parseInt(t.port,10);await this.isPortInUse(n)&&(U(`\n警告: 端口 ${n} 已被占用,请调整 redirectUri 配置项,使其指向一个未被占用的端口。`),process.exit(1));const s=new Promise((s,r)=>{const o=k.createServer(async(n,i)=>{const a=_.parse(n.url,!0);if(a.pathname===t.pathname||"/"===a.pathname){const t=a.query.code,n=a.query.error;if(n){i.writeHead(400,{"Content-Type":"text/html; charset=utf-8"});const e=this.getHtmlTemplate(this.authErrorTemplate,{ERROR:n});return i.end(e),o.close(),void r(new Error(`授权失败: ${n}`))}if(t)try{const n=await this.getTokenByCode(t,e);this.saveToken(n);const r=n.userInfo.tenantName?`${n.userInfo.tenantName}(${n.tenant_id})`:n.tenant_id||"未返回",a=n.instance_uri||"未返回";i.writeHead(200,{"Content-Type":"text/html; charset=utf-8"});const c=this.getHtmlTemplate(this.authSuccessTemplate,{TENANT_ID:r,INSTANCE_URI:a});return i.end(c),o.close(),void s({code:t,tokenData:n})}catch(e){i.writeHead(500,{"Content-Type":"text/html; charset=utf-8"});const t=this.getHtmlTemplate(this.tokenErrorTemplate,{ERROR:e});return i.end(t),o.close(),void r(e)}i.writeHead(400,{"Content-Type":"text/html; charset=utf-8"});const c=this.getHtmlTemplate(this.authFailedTemplate);i.end(c),o.close(),r(new Error("未获取到授权码"))}else i.writeHead(404,{"Content-Type":"text/plain"}),i.end("Not Found")});o.on("error",e=>{"EADDRINUSE"===e.code?r(new Error(`端口 ${n} 已被占用,无法启动回调服务器`)):r(e)}),o.listen(n,()=>{console.log(`\n本地回调服务器已启动,监听端口: ${n}`),console.log(`回调地址: ${e}`)}),setTimeout(()=>{o.close(),r(new Error("授权超时,请重试"))},3e5)});return{redirectUri:e,getCodeAndTokenPromise:s}}async getTokenByCode(e,t){const n=y("正在获取 access token...").start();try{const s=new URLSearchParams;s.append("grant_type",this.grant_type),s.append("client_id",this.client_id),s.append("client_secret",this.client_secret),s.append("code",e),s.append("redirect_uri",t);const r=(await d.post(this.tokenURL,s.toString(),{headers:{"Content-Type":"application/x-www-form-urlencoded"}})).data;if(r&&r.access_token){const e=await this.getUserInfo(r.access_token);r.userInfo=e}else U("获取 token 失败:响应中未包含 access_token",n),U(`响应数据: ${JSON.stringify(r)}`),process.exit(1);return $("成功获取 access token",n),r}catch(e){U("获取 token 失败",n),U(`\n获取 token 失败: ${e.message||e.msg}`),e.response&&U(`响应数据: ${JSON.stringify(e.response.data)}`),process.exit(1)}}async refreshToken(e){const t=y("正在刷新授权信息(token)...").start();try{const n=new URLSearchParams;n.append("grant_type","refresh_token"),n.append("client_id",this.client_id),n.append("client_secret",this.client_secret),n.append("refresh_token",e);const s=(await d.post(this.tokenURL,n.toString(),{headers:{"Content-Type":"application/x-www-form-urlencoded"}})).data;if(!s||!s.access_token)return U("刷新授权信息失败:响应中未包含 access_token",t),U(`响应数据: ${JSON.stringify(s)}`),null;{const e=await this.getUserInfo(s.access_token);s.userInfo=e}return $("刷新授权信息成功(token)。",t),s}catch(e){return U("刷新授权信息失败",t),U(`\n刷新授权信息失败: ${e.message||e.msg}`),e.response&&U(`响应数据: ${JSON.stringify(e.response.data)}`),null}}async getUserInfo(e){let t={};try{let n=this.buildFullUrl(this.NeoCrmAPI.getUserInfoAPI);const s=await d.get(n,{headers:{Authorization:`Bearer ${e}`,"xsy-inner-source":"bff","Content-Type":"application/json"}}),{code:r,message:o,msg:i}=s.data||{};r&&"200"!==r&&(U(`获取用户信息失败: ${o||i||"未知错误"}`),process.exit(1)),t=s.data.result||s.data.data||{}}catch(e){const t=e.message||e.msg;U(t?`获取用户信息失败: ${t}`:`响应数据: ${JSON.stringify(e)}`),process.exit(1)}return t}async login(){console.log("\n========== NeoCRM 登录授权 ==========\n");try{const{redirectUri:e,getCodeAndTokenPromise:t}=await this.startCallbackServer(),n=this.buildAuthUrl(e);console.log("授权 URL:",n),console.log("\n正在打开浏览器进行授权..."),await this.openBrowser(n);const{code:s,tokenData:r}=await t;return $("\n✓ 已获取授权码"),console.log("\n========== 登录成功 ==========\n"),console.log(`实例地址: ${r.instance_uri||"未返回"}`),console.log(`当前租户信息: ${r.userInfo.tenantName||"未返回租户名称"}(${r.tenant_id||"未返回租户 ID"})`),console.log(`当前登录用户: ${r.userInfo.name||"未返回用户名"}`),r}catch(e){U(`\n登录失败: ${e.message||e.msg}`),process.exit(1)}}async logout(){if(console.log("\n========== NeoCRM 登出 ==========\n"),g.existsSync(this.tokenFile))try{R.clearEnvConfig(),this.clearToken(),$("已清除授权信息,下次登录需要重新授权。"),console.log("\n登出成功!\n")}catch(e){U(`登出失败: ${e.message||e.msg}`),process.exit(1)}else console.log("当前未登录,无需登出。")}async getValidToken(){const e=this.readToken();if(e||(U("未找到授权信息,请先执行 neo login 进行登录。"),process.exit(1)),this.isTokenExpired(e)){console.log("授权信息已过期,正在尝试刷新..."),e.refresh_token||(U("自动刷新授权信息失败,请重新登录(neo login)。"),process.exit(1));const t=await this.refreshToken(e.refresh_token);return t||(U("刷新授权信息失败,请重新登录 (neo login)"),process.exit(1)),this.saveToken(t),t}return e}async getAccessToken(){return await this.getValidToken()}buildFullUrl(e){return e.startsWith("http://")||e.startsWith("https://")?e:`${this.NeoCrmAPI.neoBaseURL}${e}`}}};
|
package/dist/neo/neoService.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("axios"),t=require("form-data"),s=require("node:fs"),o=require("node:path"),i=require("ora"),r=require("lodash"),n=require("akfun"),a=require("./neoLogin.js"),c=require("../utils/projectUtils/updatePublishLog.js"),l=require("../utils/common.js"),h=require("./neoEnvManager.js");var u,d;exports.__require=function(){if(d)return u;d=1;const p=e,m=t,f=s,y=o,w=i,g=r,{resolve:k}=n,A=a.__require(),$=c.__require(),{getFramework:C,errorLog:x,successLog:T}=l.__require(),F=h.__require(),L=["cmpType","label","componentCategory","description","framework","icon","iconUrl","orderNo","version","propsSchema","defaultProps","previewProps","events","functions","asset","modelAsset","cssAsset","codeLib"];return u=class{constructor(){const e=F.getEnvConfig(),{assetsRoot:t,auth:s,authType:o}=e||{};this.authType=o||"oauth2",this.NeoCrmAPI=e,"password"!==this.authType||s||(x("密码授权模式时,neo.config.js / neoConfig / auth 配置不能为空"),process.exit(1)),"password"===this.authType?s.client_id&&s.client_secret&&s.username&&s.password||(x("neo.config.js / neoConfig / auth 配置不完整(password 模式),需要包含 client_id、client_secret、username、password"),process.exit(1)):"oauth2"!=this.authType&&(x(`不支持的授权类型: ${this.authType},可选值:oauth2、password`),process.exit(1)),this.assetsRoot=t||k("dist"),this.auth=s,this.cmpList=[],this.cmpInfoMap={},this.tokenCache={token:null,expiresAt:null}}buildFullUrl(e){return e.startsWith("http://")||e.startsWith("https://")?e:`${this.NeoCrmAPI.neoBaseURL}${e}`}uploadAPI(){return this.buildFullUrl(this.NeoCrmAPI.uploadAPI)}saveAPI(){return this.buildFullUrl(this.NeoCrmAPI.saveAPI)}isTokenExpired(){return!this.tokenCache.token||!this.tokenCache.expiresAt||Date.now()>=this.tokenCache.expiresAt}async getToken(){return this.isTokenExpired()?"oauth2"===this.authType?await this.getTokenByOAuth2():await this.getTokenByPassword():this.tokenCache.token}async getTokenByPassword(){const e=w("获取 token(密码模式)...").start();if(!this.isTokenExpired())return T("使用缓存的 token。",e),this.tokenCache.token;const t=new URLSearchParams;t.append("grant_type","password"),t.append("client_id",this.auth.client_id),t.append("client_secret",this.auth.client_secret),t.append("username",this.auth.username),t.append("password",this.auth.password);const s=this.buildFullUrl(this.NeoCrmAPI.tokenURL);try{const o=await p.post(s,t.toString(),{headers:{"Content-Type":"application/x-www-form-urlencoded"}}),i=o.data||{},{access_token:r,expires_in:n}=i;r||(x("获取 token 失败(授权配置错误):响应中未包含 access_token,"+JSON.stringify(o.data),e),process.exit(1));const a=parseInt(n)||3600;return this.tokenCache={...i,token:r,expiresAt:Date.now()+1e3*(a-60)},e.clear(),e.stop(),r}catch(o){x("获取 token 失败",e),x(`\n获取 token 失败: ${o.message||o.msg}`),x(`\ntoken 授权地址: ${s}`),x(`\ntoken 请求参数: ${t}`),o.response&&x(`响应数据: ${JSON.stringify(o.response.data)}`),process.exit(1)}}async getTokenByOAuth2(){const e=w("获取 token(OAuth2 模式)...").start();try{const t={loginURL:this.NeoCrmAPI.loginURL,tokenURL:this.NeoCrmAPI.tokenURL},s=new A(t),o=await s.getAccessToken(),i=o.access_token;return this.tokenCache={...o,token:i,expiresAt:Date.now()+72e5},e.clear(),e.stop(),i}catch(t){x("获取 token 失败(OAuth2 模式)",e),x(`\n获取 token 失败: ${t.message||t.msg}`),t.response&&x(`响应数据: ${JSON.stringify(t.response.data)}`),process.exit(1)}}async refreshToken(){return this.tokenCache={token:null,expiresAt:null},await this.getToken()}async ensureValidToken(){if(!this.tokenCache.token)return await this.getToken();if(this.isTokenExpired()){const e=w("token 已过期,正在刷新...").start();try{const t=await this.refreshToken();return T("token 刷新成功。",e),t}catch(t){throw x("token 刷新失败。",e),t}}return this.tokenCache.token}async uploadFile(e,t={}){const s=await this.ensureValidToken();if(!e||"string"!=typeof e)throw new Error(`文件路径无效: ${e}`);if(!f.existsSync(e))throw new Error(`文件不存在: ${e}`);const o=f.statSync(e);if(!o.isFile())throw new Error(`路径不是文件: ${e}`);const i=t.maxSize||5242880;if(o.size>i){const e=(o.size/1024/1024).toFixed(2),t=(i/1024/1024).toFixed(2);throw new Error(`文件大小超过限制: ${e}MB > ${t}MB`)}if(0===o.size)throw new Error(`文件为空: ${e}`);const r=y.basename(e),n=(o.size/1024).toFixed(2),a=w(`正在上传文件: ${r} (${n}KB)...`).start();try{const o=new m,i=t.fieldName||"customComponentCode",n=f.createReadStream(e);o.append(i,n,r);const c=this.uploadAPI(),l=t.timeout||6e4,h={headers:{Authorization:`Bearer ${s}`,"xsy-inner-source":"bff",...o.getHeaders()},timeout:l,maxContentLength:1/0,maxBodyLength:1/0},u=await p.post(c,o,h);let d;const y=u.data;if(200!==u.status&&201!==u.status)throw new Error(`上传失败: HTTP ${u.status}`);if("string"==typeof y)d=y.trim();else{if(!y||"object"!=typeof y)throw new Error("响应数据格式不正确: "+typeof y);if(void 0!==y.code&&200!==y.code&&0!==y.code){const e=y.message||y.msg||"未知错误";throw new Error(`上传失败: ${e} (code: ${y.code})`)}d=void 0!==y.data?y.data:void 0!==y.url?y.url:void 0!==y.fileUrl?y.fileUrl:y}if(!d)throw new Error("返回的文件地址为空");let w;return"string"==typeof d?w=d:d&&"object"==typeof d&&d.url&&(w=d.url),T(`文件上传成功: ${r} -> ${w}`,a),w}catch(t){if(x(`上传文件失败: ${t.message||t.msg}, 文件路径: ${e}`,a),t.response){const e=t.response.status,s=t.response.statusText,o=t.response.data,i=t.config?.url||this.uploadAPI();if(x("\n========== 上传请求详情 =========="),x(`请求 URL: ${i}`),x(`HTTP 状态码: ${e} ${s}`),x(`响应数据: ${JSON.stringify(o)}`),x("==================================\n"),404===e)throw new Error(`上传 API 不存在 (404): ${i}\n请检查 neo.config.js 中的 neoBaseURL 配置是否正确,或者 API 路径是否存在。\n当前配置的 API 路径: ${this.NeoCrmAPI.uploadAPI}`)}else t.request&&x("请求已发送但未收到响应,请检查网络连接或代理配置。");throw t}}async publish2oss(e,t=[".js",".css",".zip"]){if(!e)return void x(`自定义组件名称不能为空: ${e}`);if(!f.existsSync(this.assetsRoot))return void x(`未找到自定义组件资源目录: ${this.assetsRoot}`);const s={cmpType:e},o=f.readdirSync(this.assetsRoot).map(async o=>{const i=y.join(this.assetsRoot,o),r=f.statSync(i),n=y.parse(i);if(r.isFile()&&t.includes(n.ext)){let t=g.camelCase(e);if(o.indexOf(t)<0)return;try{const e=await this.uploadFile(i);o.indexOf("Model")>-1?s.modelAsset=e:o.endsWith(".css")?s.cssAsset=e:o.endsWith(".zip")?s.codeLib=e:s.asset=e}catch(e){x(`文件上传失败(${o}):\n`),process.exit(1)}}});return await Promise.all(o),s&&s.cmpType&&(console.info("上传至 OSS 的文件信息:\n",s),$(s)),s}async getCmpAssets(e,t=[".js",".css",".zip"]){if(!e)return void x(`自定义组件名称不能为空: ${e}`);if(!f.existsSync(this.assetsRoot))return void x(`未找到自定义组件资源目录: ${this.assetsRoot}`);const s={cmpType:e};return f.readdirSync(this.assetsRoot).forEach(o=>{const i=y.join(this.assetsRoot,o),r=f.statSync(i),n=y.parse(i);if(r.isFile()&&t.includes(n.ext)){let t=g.camelCase(e);if(o.indexOf(t)<0)return;const n=5242880;if(r.size>n){const e=(r.size/1024/1024).toFixed(2),t=(n/1024/1024).toFixed(2);throw new Error(`${o} 文件大小超过限制: ${e}MB > ${t}MB`)}const a={fileContent:f.createReadStream(i),fileName:o,fileSize:r.size};o.indexOf("Model")>-1?s.modelAssetFile=a:o.endsWith(".css")?s.cssAssetFile=a:o.endsWith(".zip")?s.codeLibFile=a:s.assetFile=a}}),s}async updateCustomComponent(e){const t=await this.ensureValidToken();if(!e)throw new Error("componentData 不能为空");const s=w("正在保存自定义组件信息...").start();try{const o=this.saveAPI(),i=new m;e.assetFile&&i.append("assetFile",e.assetFile.fileContent,e.assetFile.fileName),e.modelAssetFile&&i.append("modelAssetFile",e.modelAssetFile.fileContent,e.modelAssetFile.fileName),e.cssAssetFile&&i.append("cssAssetFile",e.cssAssetFile.fileContent,e.cssAssetFile.fileName),e.codeLibFile&&i.append("codeLibFile",e.codeLibFile.fileContent,e.codeLibFile.fileName),i.append("component",JSON.stringify(g.omit(e,["assetFile","modelAssetFile","cssAssetFile","codeLibFile"])));const r=await p.post(o,i,{headers:{Authorization:`Bearer ${t}`,"xsy-inner-source":"bff",...i.getHeaders()},timeout:12e4,maxContentLength:1/0,maxBodyLength:1/0}),{code:n,message:a,msg:c}=r.data||{};n&&200!==n&&(x(`保存自定义组件信息失败: ${a||c||"未知错误"}`),process.exit(1)),s.clear(),s.stop()}catch(e){const t=e.message||e.msg;x(t?`保存自定义组件信息失败: ${t}`:`保存自定义组件信息失败: ${JSON.stringify(e)}`,s),process.exit(1)}}async getCustomCmpList(){const e=await this.ensureValidToken();try{let t=this.buildFullUrl(this.NeoCrmAPI.queryAll);t+=`&fields=${L.join(",")}`;const s=await p.get(t,{headers:{Authorization:`Bearer ${e}`,"xsy-inner-source":"bff","Content-Type":"application/json"}}),{code:o,message:i,msg:r}=s.data||{};o&&200!==o&&(x(`获取自定义组件列表失败: ${i||r||"未知错误"}`),process.exit(1)),this.updateCustomCmpList(s.data.data||[])}catch(e){const t=e.message||e.msg;x(t?`获取自定义组件列表失败: ${t}`:`响应数据: ${JSON.stringify(e)}`),process.exit(1)}return this.cmpList||[]}getCodeLibByCmpType(e){return e?this.buildFullUrl(this.NeoCrmAPI.getCodeLibAPI(e)):null}async deleteCmp(e){const t=await this.ensureValidToken();e||(x("自定义组件名称不能为空。"),process.exit(1));let s=this.buildFullUrl(this.NeoCrmAPI.delete);s+=`/${e}`;const o=await p.delete(s,{headers:{Authorization:`Bearer ${t}`,"xsy-inner-source":"bff","Content-Type":"application/json"}}),{code:i,message:r,msg:n}=o.data||{};return i&&200!==i&&(x(`删除自定义组件失败: ${r||n||"未知错误"}`),process.exit(1)),o.data}getCmpListByFramework(e){if(!e)return this.cmpList;const t=C(e);return this.cmpList.filter(e=>e.framework===t)}getCmpInfoByCmpType(e){return e&&this.cmpInfoMap[e]||null}updateCustomCmpList(e){e&&Array.isArray(e)&&(this.cmpList=e,e.forEach(e=>{this.cmpInfoMap[e.cmpType]=e}))}async request(e,t,s={}){const o=await this.ensureValidToken(),{data:i,headers:r={},params:n}=s,a=this.buildFullUrl(t);try{const t=await p({method:e,url:a,data:i,params:n,headers:{Authorization:`Bearer ${o}`,"xsy-inner-source":"bff",...r}});if(t.data&&t.data.code&&200!==t.data.code)throw new Error(`请求失败: ${t.data.message||t.data.msg||"未知错误"}`);return t.data}catch(s){throw x(`请求失败 [${e} ${t}]: ${s.message||s.msg}`),s.response&&x(`响应数据: ${JSON.stringify(s.response.data)}`),s}}async get(e,t={}){return await this.request("GET",e,t)}async post(e,t={}){return await this.request("POST",e,t)}async put(e,t={}){return await this.request("PUT",e,t)}async delete(e,t={}){return await this.request("DELETE",e,t)}}};
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("axios"),t=require("form-data"),s=require("node:fs"),o=require("node:path"),i=require("ora"),r=require("lodash"),n=require("akfun"),a=require("./neoLogin.js"),c=require("../utils/projectUtils/updatePublishLog.js"),l=require("../utils/common.js"),h=require("./neoEnvManager.js"),u=require("../utils/tableLog.js");var d,p;exports.__require=function(){if(p)return d;p=1;const m=e,f=t,y=s,g=o,w=i,k=r,{resolve:A}=n,$=a.__require(),C=c.__require(),{getFramework:x,errorLog:T,successLog:F}=l.__require(),L=h.__require(),P=u.__require(),b=["cmpType","label","componentCategory","description","framework","icon","iconUrl","orderNo","version","propsSchema","defaultProps","previewProps","events","functions","asset","modelAsset","cssAsset","codeLib"];return d=class{constructor(){const e=L.getEnvConfig(),{assetsRoot:t,auth:s,authType:o}=e||{};this.authType=o||"oauth2",this.NeoCrmAPI=e,"password"!==this.authType||s||(T("密码授权模式时,neo.config.js / neoConfig / auth 配置不能为空"),process.exit(1)),"password"===this.authType?s.client_id&&s.client_secret&&s.username&&s.password||(T("neo.config.js / neoConfig / auth 配置不完整(password 模式),需要包含 client_id、client_secret、username、password"),process.exit(1)):"oauth2"!=this.authType&&(T(`不支持的授权类型: ${this.authType},可选值:oauth2、password`),process.exit(1)),this.assetsRoot=t||A("dist"),this.auth=s,this.cmpList=[],this.cmpInfoMap={},this.tokenCache={token:null,expiresAt:null}}buildFullUrl(e){return e.startsWith("http://")||e.startsWith("https://")?e:`${this.NeoCrmAPI.neoBaseURL}${e}`}uploadAPI(){return this.buildFullUrl(this.NeoCrmAPI.uploadAPI)}saveAPI(){return this.buildFullUrl(this.NeoCrmAPI.saveAPI)}isTokenExpired(){return!this.tokenCache.token||!this.tokenCache.expiresAt||Date.now()>=this.tokenCache.expiresAt}async getToken(){return this.isTokenExpired()?"oauth2"===this.authType?await this.getTokenByOAuth2():await this.getTokenByPassword():this.tokenCache.token}async getTokenByPassword(){const e=w("获取 token(密码模式)...").start();if(!this.isTokenExpired())return F("使用缓存的 token。",e),this.tokenCache.token;const t=new URLSearchParams;t.append("grant_type","password"),t.append("client_id",this.auth.client_id),t.append("client_secret",this.auth.client_secret),t.append("username",this.auth.username),t.append("password",this.auth.password);const s=this.buildFullUrl(this.NeoCrmAPI.tokenURL);try{const o=await m.post(s,t.toString(),{headers:{"Content-Type":"application/x-www-form-urlencoded"}}),i=o.data||{},{access_token:r,expires_in:n}=i;r||(T("获取 token 失败(授权配置错误):响应中未包含 access_token,"+JSON.stringify(o.data),e),process.exit(1));const a=parseInt(n)||3600;return this.tokenCache={...i,token:r,expiresAt:Date.now()+1e3*(a-60)},e.clear(),e.stop(),r}catch(o){T("获取 token 失败",e),T(`\n获取 token 失败: ${o.message||o.msg}`),T(`\ntoken 授权地址: ${s}`),T(`\ntoken 请求参数: ${t}`),o.response&&T(`响应数据: ${JSON.stringify(o.response.data)}`),process.exit(1)}}async getTokenByOAuth2(){const e=w("获取 token(OAuth2 模式)...").start();try{const t={loginURL:this.NeoCrmAPI.loginURL,tokenURL:this.NeoCrmAPI.tokenURL},s=new $(t),o=await s.getAccessToken(),i=o.access_token;return this.tokenCache={...o,token:i,expiresAt:Date.now()+72e5},e.clear(),e.stop(),i}catch(t){T("获取 token 失败(OAuth2 模式)",e),T(`\n获取 token 失败: ${t.message||t.msg}`),t.response&&T(`响应数据: ${JSON.stringify(t.response.data)}`),process.exit(1)}}async refreshToken(){return this.tokenCache={token:null,expiresAt:null},await this.getToken()}async ensureValidToken(){if(!this.tokenCache.token)return await this.getToken();if(this.isTokenExpired()){const e=w("token 已过期,正在刷新...").start();try{const t=await this.refreshToken();return F("token 刷新成功。",e),t}catch(t){throw T("token 刷新失败。",e),t}}return this.tokenCache.token}async uploadFile(e,t={}){const s=await this.ensureValidToken();if(!e||"string"!=typeof e)throw new Error(`文件路径无效: ${e}`);if(!y.existsSync(e))throw new Error(`文件不存在: ${e}`);const o=y.statSync(e);if(!o.isFile())throw new Error(`路径不是文件: ${e}`);const i=t.maxSize||5242880;if(o.size>i){const e=(o.size/1024/1024).toFixed(2),t=(i/1024/1024).toFixed(2);throw new Error(`文件大小超过限制: ${e}MB > ${t}MB`)}if(0===o.size)throw new Error(`文件为空: ${e}`);const r=g.basename(e),n=(o.size/1024).toFixed(2),a=w(`正在上传文件: ${r} (${n}KB)...`).start();try{const o=new f,i=t.fieldName||"customComponentCode",n=y.createReadStream(e);o.append(i,n,r);const c=this.uploadAPI(),l=t.timeout||6e4,h={headers:{Authorization:`Bearer ${s}`,"xsy-inner-source":"bff",...o.getHeaders()},timeout:l,maxContentLength:1/0,maxBodyLength:1/0},u=await m.post(c,o,h);let d;const p=u.data;if(200!==u.status&&201!==u.status)throw new Error(`上传失败: HTTP ${u.status}`);if("string"==typeof p)d=p.trim();else{if(!p||"object"!=typeof p)throw new Error("响应数据格式不正确: "+typeof p);if(void 0!==p.code&&200!==p.code&&0!==p.code){const e=p.message||p.msg||"未知错误";throw new Error(`上传失败: ${e} (code: ${p.code})`)}d=void 0!==p.data?p.data:void 0!==p.url?p.url:void 0!==p.fileUrl?p.fileUrl:p}if(!d)throw new Error("返回的文件地址为空");let g;return"string"==typeof d?g=d:d&&"object"==typeof d&&d.url&&(g=d.url),F(`文件上传成功: ${r} -> ${g}`,a),g}catch(t){if(T(`上传文件失败: ${t.message||t.msg}, 文件路径: ${e}`,a),t.response){const e=t.response.status,s=t.response.statusText,o=t.response.data,i=t.config?.url||this.uploadAPI();if(T("\n========== 上传请求详情 =========="),T(`请求 URL: ${i}`),T(`HTTP 状态码: ${e} ${s}`),T(`响应数据: ${JSON.stringify(o)}`),T("==================================\n"),404===e)throw new Error(`上传 API 不存在 (404): ${i}\n请检查 neo.config.js 中的 neoBaseURL 配置是否正确,或者 API 路径是否存在。\n当前配置的 API 路径: ${this.NeoCrmAPI.uploadAPI}`)}else t.request&&T("请求已发送但未收到响应,请检查网络连接或代理配置。");throw t}}async publish2oss(e,t=[".js",".css",".zip"]){if(!e)return void T(`自定义组件名称不能为空: ${e}`);if(!y.existsSync(this.assetsRoot))return void T(`未找到自定义组件资源目录: ${this.assetsRoot}`);const s={cmpType:e},o=y.readdirSync(this.assetsRoot).map(async o=>{const i=g.join(this.assetsRoot,o),r=y.statSync(i),n=g.parse(i);if(r.isFile()&&t.includes(n.ext)){let t=k.camelCase(e);if(o.indexOf(t)<0)return;try{const e=await this.uploadFile(i);o.indexOf("Model")>-1?s.modelAsset=e:o.endsWith(".css")?s.cssAsset=e:o.endsWith(".zip")?s.codeLib=e:s.asset=e}catch(e){T(`文件上传失败(${o}):\n`),process.exit(1)}}});return await Promise.all(o),s&&s.cmpType&&(console.info("上传至 OSS 的文件信息:\n",s),C(s)),s}async getCmpAssets(e,t=[".js",".css",".zip"]){if(!e)return void T(`自定义组件名称不能为空: ${e}`);if(!y.existsSync(this.assetsRoot))return void T(`未找到自定义组件资源目录: ${this.assetsRoot}`);const s={cmpType:e};return y.readdirSync(this.assetsRoot).forEach(o=>{const i=g.join(this.assetsRoot,o),r=y.statSync(i),n=g.parse(i);if(r.isFile()&&t.includes(n.ext)){let t=k.camelCase(e);if(o.indexOf(t)<0)return;const n=5242880;if(r.size>n){const e=(r.size/1024/1024).toFixed(2),t=(n/1024/1024).toFixed(2);throw new Error(`${o} 文件大小超过限制: ${e}MB > ${t}MB`)}const a={fileContent:y.createReadStream(i),fileName:o,fileSize:r.size};o.indexOf("Model")>-1?s.modelAssetFile=a:o.endsWith(".css")?s.cssAssetFile=a:o.endsWith(".zip")?s.codeLibFile=a:s.assetFile=a}}),s}async updateCustomComponent(e){const t=await this.ensureValidToken();if(!e)throw new Error("componentData 不能为空");const s=w("正在保存自定义组件信息...").start();try{const o=this.saveAPI(),i=new f;e.assetFile&&i.append("assetFile",e.assetFile.fileContent,e.assetFile.fileName),e.modelAssetFile&&i.append("modelAssetFile",e.modelAssetFile.fileContent,e.modelAssetFile.fileName),e.cssAssetFile&&i.append("cssAssetFile",e.cssAssetFile.fileContent,e.cssAssetFile.fileName),e.codeLibFile&&i.append("codeLibFile",e.codeLibFile.fileContent,e.codeLibFile.fileName),i.append("component",JSON.stringify(k.omit(e,["assetFile","modelAssetFile","cssAssetFile","codeLibFile"])));const r=await m.post(o,i,{headers:{Authorization:`Bearer ${t}`,"xsy-inner-source":"bff",...i.getHeaders()},timeout:12e4,maxContentLength:1/0,maxBodyLength:1/0}),{code:n,message:a,msg:c}=r.data||{};n&&200!==n&&(T(`保存自定义组件信息失败: ${a||c||"未知错误"}`),process.exit(1)),s.clear(),s.stop()}catch(e){const t=e.message||e.msg;T(t?`保存自定义组件信息失败: ${t}`:`保存自定义组件信息失败: ${JSON.stringify(e)}`,s),process.exit(1)}}async getCustomCmpList(){const e=await this.ensureValidToken();try{let t=this.buildFullUrl(this.NeoCrmAPI.queryAll);t+=`&fields=${b.join(",")}`;const s=await m.get(t,{headers:{Authorization:`Bearer ${e}`,"xsy-inner-source":"bff","Content-Type":"application/json"}}),{code:o,message:i,msg:r}=s.data||{};o&&200!==o&&(T(`获取自定义组件列表失败: ${i||r||"未知错误"}`),process.exit(1)),this.updateCustomCmpList(s.data.data||[])}catch(e){const t=e.message||e.msg;T(t?`获取自定义组件列表失败: ${t}`:`响应数据: ${JSON.stringify(e)}`),process.exit(1)}return this.cmpList||[]}getCodeLibByCmpType(e){return e?this.buildFullUrl(this.NeoCrmAPI.getCodeLibAPI(e)):null}async deleteCmp(e){const t=await this.ensureValidToken();e||(T("自定义组件名称不能为空。"),process.exit(1));let s=this.buildFullUrl(this.NeoCrmAPI.delete);s+=`/${e}`;const o=await m.delete(s,{headers:{Authorization:`Bearer ${t}`,"xsy-inner-source":"bff","Content-Type":"application/json"}}),{code:i,message:r,msg:n,errorInfo:a}=o.data||{};return i&&200!==i&&(a&&a.length>0?P(a,{title:"删除自定义组件失败",columns:["reason","instance"],headers:{name:"原因",age:"实例"},align:{name:"left",city:"center"},padding:2,showBorder:!0}):T(`删除自定义组件失败: ${r||n||"未知错误"}`),process.exit(1)),o.data}getCmpListByFramework(e){if(!e)return this.cmpList;const t=x(e);return this.cmpList.filter(e=>e.framework===t)}getCmpInfoByCmpType(e){return e&&this.cmpInfoMap[e]||null}updateCustomCmpList(e){e&&Array.isArray(e)&&(this.cmpList=e,e.forEach(e=>{this.cmpInfoMap[e.cmpType]=e}))}async request(e,t,s={}){const o=await this.ensureValidToken(),{data:i,headers:r={},params:n}=s,a=this.buildFullUrl(t);try{const t=await m({method:e,url:a,data:i,params:n,headers:{Authorization:`Bearer ${o}`,"xsy-inner-source":"bff",...r}});if(t.data&&t.data.code&&200!==t.data.code)throw new Error(`请求失败: ${t.data.message||t.data.msg||"未知错误"}`);return t.data}catch(s){throw T(`请求失败 [${e} ${t}]: ${s.message||s.msg}`),s.response&&T(`响应数据: ${JSON.stringify(s.response.data)}`),s}}async get(e,t={}){return await this.request("GET",e,t)}async post(e,t={}){return await this.request("POST",e,t)}async put(e,t={}){return await this.request("PUT",e,t)}async delete(e,t={}){return await this.request("DELETE",e,t)}}};
|
package/dist/package.json.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});var e="1.9.
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});var e="1.9.29";const o={version:e};exports.default=o,exports.version=e;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("fs-extra"),r=require("node:path"),o=require("lodash"),s=require("../neoParams.js"),c=require("../replaceInFilesByMap.js"),i=require("./hasCmpTypeByDir.js"),t=require("../projectUtils/hasNeoProject.js"),n=require("../cmpTypeValidator.js"),m=require("../common.js");var p,a;exports.__require=function(){if(a)return p;a=1;const u=e,l=r,d=o,{consoleTag:_}=s.__require(),q=c.__require(),f=i.__require(),x=t.__require(),{validateApiName:g,ensureApiNameSuffix:j,removeApiNameSuffix:y}=n.__require(),{errorLog:N}=m.__require(),$={widgetInfo:{cmpName:"CustomCmp",modelName:"CmpModel",cmpClassName:"custom-cmp-container",cmpType:"xx-custom-cmp",cmpLabel:"xx组件"},dir:l.resolve(__dirname,"../../../template/empty-cmp")};return p=function(e,r="./src/components"){const o=$.dir;let s=e||"neoCustomCmp__c";s=j(s);const{isValid:c,errors:i}=g(s);c||(N(i.join("\n")),process.exit(1));const t=l.resolve(process.cwd(),r,s);x()||(console.error(`${_}当前(${process.cwd()})还不是自定义组件项目,请先创建一个自定义组件项目(neo init / neo create project)。`),process.exit(1)),f(s)&&(console.error(`${_}创建自定义组件失败,当前项目已经存在${s}自定义组件。`),process.exit(1)),u.copy(o,t).then(()=>{const e=d.camelCase(y(s)),r=s;q(t,{[$.widgetInfo.modelName]:`${e}Model`,[$.widgetInfo.cmpName]:e,[$.widgetInfo.cmpClassName]:`${r}-container`,[$.widgetInfo.cmpType]:r,[$.widgetInfo.cmpLabel]:
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("fs-extra"),r=require("node:path"),o=require("lodash"),s=require("../neoParams.js"),c=require("../replaceInFilesByMap.js"),i=require("./hasCmpTypeByDir.js"),t=require("../projectUtils/hasNeoProject.js"),n=require("../cmpTypeValidator.js"),m=require("../common.js");var p,a;exports.__require=function(){if(a)return p;a=1;const u=e,l=r,d=o,{consoleTag:_}=s.__require(),q=c.__require(),f=i.__require(),x=t.__require(),{validateApiName:g,ensureApiNameSuffix:j,removeApiNameSuffix:y}=n.__require(),{errorLog:N}=m.__require(),$={widgetInfo:{cmpName:"CustomCmp",modelName:"CmpModel",cmpClassName:"custom-cmp-container",cmpType:"xx-custom-cmp",cmpLabel:"xx组件"},dir:l.resolve(__dirname,"../../../template/empty-cmp")};return p=function(e,r="./src/components"){const o=$.dir;let s=e||"neoCustomCmp__c";s=j(s);const{isValid:c,errors:i}=g(s);c||(N(i.join("\n")),process.exit(1));const t=l.resolve(process.cwd(),r,s);x()||(console.error(`${_}当前(${process.cwd()})还不是自定义组件项目,请先创建一个自定义组件项目(neo init / neo create project)。`),process.exit(1)),f(s)&&(console.error(`${_}创建自定义组件失败,当前项目已经存在${s}自定义组件。`),process.exit(1)),u.copy(o,t).then(()=>{const e=d.camelCase(y(s)),r=s;q(t,{[$.widgetInfo.modelName]:`${e}Model`,[$.widgetInfo.cmpName]:e,[$.widgetInfo.cmpClassName]:`${r}-container`,[$.widgetInfo.cmpType]:r,[$.widgetInfo.cmpLabel]:e}),console.log(`${_}已创建自定义组件(${s})!`)}).catch(e=>console.error(`${_}自定义组件创建失败(${s}):`,e))}};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("chalk"),r=require("./neoParams.js");var o,n;exports.__require=function(){if(n)return o;n=1;const t=e,{consoleTag:l}=r.__require();function c(e,r,o,n){const t=function(e){const r=/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;return e.replace(r,"").length}(e),l=r-2*n,c=" ".repeat(n);if(t>=l)return c+e.slice(0,l)+c;const a=l-t;switch(o){case"right":return c+" ".repeat(a)+e+c;case"center":const r=Math.floor(a/2),o=a-r;return c+" ".repeat(r)+e+" ".repeat(o)+c;default:return c+e+" ".repeat(a)+c}}return o={tableLog:function(e,r={}){if(!Array.isArray(e)||0===e.length)return void console.log(`${l} ${t.yellow("警告: 数据为空或格式不正确")}`);const{columns:o,headers:n={},align:a={},padding:s=1,showBorder:g=!0,title:i}=r,u=(o||Object.keys(e[0])).filter(e=>!o||o.includes(e));if(0===u.length)return void console.log(`${l} ${t.yellow("警告: 没有可显示的列")}`);const $={};u.forEach(r=>{const o=n[r]||r;let t=String(o).length;e.forEach(e=>{const o=e[r],n=null!=o?String(o):"";t=Math.max(t,n.length)}),$[r]=t+2*s});const y=g?t.gray("─".repeat(u.reduce((e,r)=>e+$[r]+3,1))):"";i&&console.log(`${l} ${t.cyan.bold(i)}`),g&&y&&console.log(`${l} ${t.gray("┌")}${y.slice(1,-1)}${t.gray("┐")}`);const f=u.map((e,r)=>{const o=c(n[e]||e,$[e],a[e]||"left",s);return t.bold.cyan(o)}).join(g?t.gray(" │ "):" ");console.log(`${l} ${g?t.gray("│ "):""}${f}${g?t.gray(" │"):""}`),g&&y&&console.log(`${l} ${t.gray("├")}${y.slice(1,-1).replace(/─/g,"─")}${t.gray("┤")}`),e.forEach((e,r)=>{const o=u.map((r,o)=>{const n=e[r];return c(null!=n?String(n):"",$[r],a[r]||"left",s)}).join(g?t.gray(" │ "):" ");console.log(`${l} ${g?t.gray("│ "):""}${o}${g?t.gray(" │"):""}`)}),g&&y&&console.log(`${l} ${t.gray("└")}${y.slice(1,-1)}${t.gray("┘")}`)}}};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@ import * as React from 'react';
|
|
|
2
2
|
import { Input, message } from 'antd';
|
|
3
3
|
// 引入 neo-ui-common / NeoEvent
|
|
4
4
|
// @ts-ignore
|
|
5
|
-
import { NeoEvent } from 'neo-ui-common';
|
|
5
|
+
import { NeoEvent } from 'neo-ui-common';
|
|
6
6
|
import './style.scss';
|
|
7
7
|
|
|
8
8
|
const { Search } = Input;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useState, useRef, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
// ===== 子组件:核心是【手动触发原生事件】 =====
|
|
5
|
+
const Son = () => {
|
|
6
|
+
const [triggerTimes, setTriggerTimes] = useState(0);
|
|
7
|
+
// 1. 创建ref,绑定到子组件的按钮DOM,获取真实DOM
|
|
8
|
+
const sonBtnRef = useRef(null);
|
|
9
|
+
|
|
10
|
+
// 子组件核心方法:运行时【手动触发原生click事件】
|
|
11
|
+
const handleManualTrigger = () => {
|
|
12
|
+
const btnDom = sonBtnRef.current;
|
|
13
|
+
if (!btnDom) return;
|
|
14
|
+
|
|
15
|
+
// ✅ 方式A:推荐 - dispatchEvent 手动触发原生click事件(父子通信必须开启bubbles: true)
|
|
16
|
+
const clickEvent = new Event('click', { bubbles: true });
|
|
17
|
+
btnDom.dispatchEvent(clickEvent, {
|
|
18
|
+
detail: { // 子组件传递给父组件的自定义参数,任意格式都可以
|
|
19
|
+
sonMsg: '子组件手动触发了自定义原生事件',
|
|
20
|
+
timestamp: new Date().getTime()
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// ✅ 方式B:极简 - 等价写法,效果完全一致(只支持click)
|
|
25
|
+
// btnDom.click();
|
|
26
|
+
|
|
27
|
+
setTriggerTimes(prev => prev + 1);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div style={{ padding: '20px', border: '1px solid #f00', margin: '10px' }}>
|
|
32
|
+
<h4>子组件</h4>
|
|
33
|
+
<p>手动触发原生事件次数:{triggerTimes}</p>
|
|
34
|
+
{/* 真实DOM,绑定ref,无任何onClick合成事件 */}
|
|
35
|
+
<button ref={sonBtnRef} id="son-native-btn">子组件原生按钮(无合成事件)</button>
|
|
36
|
+
<br />
|
|
37
|
+
<button onClick={handleManualTrigger} style={{ marginTop: '10px' }}>
|
|
38
|
+
点击我 → 手动触发上方按钮的原生click事件
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// ===== 父组件:核心是【监听子组件手动触发的原生事件】 =====
|
|
45
|
+
const Father = () => {
|
|
46
|
+
const [msg, setMsg] = useState('');
|
|
47
|
+
// 1. 创建ref,绑定到父组件根DOM,获取真实DOM
|
|
48
|
+
const fatherRootRef = useRef(null);
|
|
49
|
+
|
|
50
|
+
// 父组件核心:原生事件处理函数,监听子组件冒泡上来的原生事件
|
|
51
|
+
const fatherNativeEventListener = (e) => {
|
|
52
|
+
const target = e.target;
|
|
53
|
+
console.log('targetEvent:', e);
|
|
54
|
+
console.log('target:', target, target.id, target.tagName);
|
|
55
|
+
// 2. 精准判断:事件是否来自子组件的指定DOM元素
|
|
56
|
+
if (target.id === 'son-native-btn') {
|
|
57
|
+
console.log('✅ 父组件监听到:子组件手动触发的【原生click事件】');
|
|
58
|
+
setMsg(`父组件捕获到子组件原生事件 → ${new Date().toLocaleTimeString()}`);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// 3. 组件挂载时绑定原生事件,卸载时解绑(防止内存泄漏,必写)
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
const fatherDom = fatherRootRef.current;
|
|
65
|
+
if (!fatherDom) return;
|
|
66
|
+
// 绑定原生事件:事件名是【小写click】,监听子组件冒泡的事件
|
|
67
|
+
fatherDom.addEventListener('click', fatherNativeEventListener);
|
|
68
|
+
// 卸载解绑,避免内存泄漏
|
|
69
|
+
return () => {
|
|
70
|
+
fatherDom.removeEventListener('click', fatherNativeEventListener);
|
|
71
|
+
};
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div ref={fatherRootRef} style={{ padding: '20px', border: '1px solid #000' }}>
|
|
76
|
+
<h3>父组件</h3>
|
|
77
|
+
<p style={{ color: 'red', fontSize: '16px' }}>{msg}</p>
|
|
78
|
+
{/* 子组件直接渲染,无需传递任何props!!! 完全解耦 */}
|
|
79
|
+
<Son />
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export default Father;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useState, useRef, useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
// ===== 子组件:手动触发【自定义原生事件】+ 传递参数 =====
|
|
5
|
+
const Son = () => {
|
|
6
|
+
const [sonData, setSonData] = useState('我是子组件的初始数据');
|
|
7
|
+
const sonDomRef = useRef(null); // 获取子组件真实DOM
|
|
8
|
+
|
|
9
|
+
// 子组件手动触发自定义事件的核心方法
|
|
10
|
+
const triggerCustomNativeEvent = () => {
|
|
11
|
+
const dom = sonDomRef.current;
|
|
12
|
+
if (!dom) return;
|
|
13
|
+
|
|
14
|
+
// ✅ 核心:创建自定义原生事件 + 传递参数,开启冒泡
|
|
15
|
+
const customNativeEvent = new CustomEvent('son-custom-native-event', {
|
|
16
|
+
bubbles: true,
|
|
17
|
+
detail: { // 子组件传递给父组件的自定义参数,任意格式都可以
|
|
18
|
+
sonMsg: '子组件手动触发了自定义原生事件',
|
|
19
|
+
sonData: sonData,
|
|
20
|
+
timestamp: new Date().getTime()
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
// 手动触发这个自定义原生事件
|
|
24
|
+
dom.dispatchEvent(customNativeEvent);
|
|
25
|
+
|
|
26
|
+
setSonData(`子组件数据-${Math.floor(Math.random() * 100)}`);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div ref={sonDomRef} style={{ padding: '20px', border: '1px solid #f00', margin: '10px' }} id="son-root-dom">
|
|
31
|
+
<h4>子组件</h4>
|
|
32
|
+
<p>子组件当前数据:{sonData}</p>
|
|
33
|
+
<button onClick={triggerCustomNativeEvent} style={{ marginTop: '10px' }}>
|
|
34
|
+
点击我 → 手动触发【自定义原生事件】并传参给父组件
|
|
35
|
+
</button>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ===== 父组件:监听【子组件的自定义原生事件】+ 接收参数 =====
|
|
41
|
+
const Father = () => {
|
|
42
|
+
const [receiveData, setReceiveData] = useState({});
|
|
43
|
+
const fatherDomRef = useRef(null);
|
|
44
|
+
|
|
45
|
+
// 父组件监听自定义原生事件的处理函数
|
|
46
|
+
const fatherCustomEventListener = (e) => {
|
|
47
|
+
const target = e.target;
|
|
48
|
+
console.log('targetEvent:', e);
|
|
49
|
+
console.log('target:', target, target.id, target.tagName);
|
|
50
|
+
// 1. 判断事件来源是子组件
|
|
51
|
+
if (e.target.closest('#son-root-dom')) {
|
|
52
|
+
console.log('✅ 父组件监听到子组件的【自定义原生事件】');
|
|
53
|
+
// 2. ✅ 核心:通过e.detail获取子组件传递的自定义参数
|
|
54
|
+
const sonParams = e.detail;
|
|
55
|
+
setReceiveData(sonParams);
|
|
56
|
+
console.log('子组件传递的参数:', sonParams);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
const fatherDom = fatherDomRef.current;
|
|
62
|
+
if (!fatherDom) return;
|
|
63
|
+
// ✅ 核心:监听子组件的【自定义事件名】,和子组件保持一致
|
|
64
|
+
fatherDom.addEventListener('son-custom-native-event', fatherCustomEventListener);
|
|
65
|
+
// 卸载解绑
|
|
66
|
+
return () => {
|
|
67
|
+
fatherDom.removeEventListener('son-custom-native-event', fatherCustomEventListener);
|
|
68
|
+
};
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div ref={fatherDomRef} style={{ padding: '20px', border: '1px solid #000' }}>
|
|
73
|
+
<h3>父组件</h3>
|
|
74
|
+
<p style={{ color: 'blue', fontSize: '16px' }}>监听到子组件自定义事件,接收参数:</p>
|
|
75
|
+
<pre>{JSON.stringify(receiveData, null, 2)}</pre>
|
|
76
|
+
<Son />
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default Father;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
// 引入 neo-ui-common / BaseCmp
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import { BaseCmp } from 'neo-ui-common';
|
|
2
5
|
import './style.scss'; // 组件内容样式
|
|
3
6
|
|
|
4
7
|
interface CustomCmpProps {
|
|
@@ -11,7 +14,7 @@ interface CustomCmpStates {
|
|
|
11
14
|
[key: string]: any;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
export default class CustomCmp extends
|
|
17
|
+
export default class CustomCmp extends BaseCmp<CustomCmpProps, CustomCmpStates> {
|
|
15
18
|
constructor(props: CustomCmpProps) {
|
|
16
19
|
super(props);
|
|
17
20
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BaseCmpProps 基础组件属性接口
|
|
5
|
+
*/
|
|
6
|
+
export interface BaseCmpProps {
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ScopedComponentType 作用域组件类型接口
|
|
12
|
+
*/
|
|
13
|
+
export interface ScopedComponentType {
|
|
14
|
+
[key: string]: any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* BaseCmp 基础组件类
|
|
19
|
+
* 继承自 React.PureComponent,提供基础组件功能
|
|
20
|
+
*/
|
|
21
|
+
export declare class BaseCmp<
|
|
22
|
+
T extends BaseCmpProps = BaseCmpProps,
|
|
23
|
+
S = any
|
|
24
|
+
> extends React.PureComponent<T, S> implements ScopedComponentType {
|
|
25
|
+
props: Readonly<T> & Readonly<{ children?: React.ReactNode }>;
|
|
26
|
+
state: Readonly<S>;
|
|
27
|
+
setState<K extends keyof S>(
|
|
28
|
+
state:
|
|
29
|
+
| ((prevState: Readonly<S>, props: Readonly<T>) => Pick<S, K> | S | null)
|
|
30
|
+
| (Pick<S, K> | S | null),
|
|
31
|
+
callback?: () => void
|
|
32
|
+
): void;
|
|
33
|
+
forceUpdate(callback?: () => void): void;
|
|
34
|
+
render(): React.ReactNode;
|
|
35
|
+
}
|
|
36
|
+
|
|
@@ -99,6 +99,13 @@ module.exports = {
|
|
|
99
99
|
}
|
|
100
100
|
*/
|
|
101
101
|
},
|
|
102
|
+
// 选择「自定义环境」时需要添加 NeoCRM 平台配置,可自定义对接的任何环境
|
|
103
|
+
neoConfig: {
|
|
104
|
+
// authType: 'oauth2', // 默认授权模式:OAuth2 授权码模式
|
|
105
|
+
neoBaseURL: 'https://crm-test.xiaoshouyi.com', // 平台根地址(默认:https://crm.xiaoshouyi.com)
|
|
106
|
+
loginURL: 'https://login-test.xiaoshouyi.com/auc/oauth2/auth', // 登录授权 URL(默认:https://login.xiaoshouyi.com/auc/oauth2/auth)
|
|
107
|
+
tokenURL: 'https://login-test.xiaoshouyi.com/auc/oauth2/token', // Token 获取接口地址(默认:https://login.xiaoshouyi.com/auc/oauth2/token)
|
|
108
|
+
},
|
|
102
109
|
pushCmp: {
|
|
103
110
|
// 用于构建并发布至 NeoCRM 的相关配置
|
|
104
111
|
/*
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { Card, Row, Col, Spin, Empty, Avatar, Button } from 'antd';
|
|
3
3
|
import { UserOutlined, PhoneOutlined, ReloadOutlined } from '@ant-design/icons';
|
|
4
|
+
// 引入 neo-ui-common / BaseCmp
|
|
4
5
|
// @ts-ignore
|
|
5
|
-
import {
|
|
6
|
+
import { BaseCmp } from 'neo-ui-common';
|
|
6
7
|
|
|
7
|
-
// Neo Open API
|
|
8
|
+
// 引入 Neo Open API
|
|
8
9
|
// @ts-ignore
|
|
9
|
-
import {
|
|
10
|
+
import { xObject } from 'neo-open-api';
|
|
10
11
|
|
|
11
12
|
// 引入 neo-ui-common / NeoEvent
|
|
12
13
|
// @ts-ignore
|
|
13
|
-
import { NeoEvent } from 'neo-ui-common';
|
|
14
|
+
import { NeoEvent } from 'neo-ui-common';
|
|
14
15
|
|
|
15
16
|
import './style.scss';
|
|
16
17
|
|
|
@@ -76,6 +77,7 @@ export default class EntityCardList extends BaseCmp<
|
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
@NeoEvent.function
|
|
79
81
|
async loadObjectData(parma1?: any) {
|
|
80
82
|
const { xObjectDataApi } = this.props;
|
|
81
83
|
const { autoFetchData } = xObjectDataApi || {};
|
|
@@ -29,7 +29,7 @@ import { xObject } from 'neo-open-api'; // Neo Open API
|
|
|
29
29
|
import isEqual from 'lodash/isEqual';
|
|
30
30
|
// 引入 neo-ui-common / NeoEvent
|
|
31
31
|
// @ts-ignore
|
|
32
|
-
import { NeoEvent } from 'neo-ui-common';
|
|
32
|
+
import { NeoEvent } from 'neo-ui-common';
|
|
33
33
|
import './style.scss';
|
|
34
34
|
|
|
35
35
|
const { Option } = Select;
|
|
@@ -157,6 +157,7 @@ export default class EntityForm extends React.PureComponent<
|
|
|
157
157
|
} else {
|
|
158
158
|
this.setState({
|
|
159
159
|
fieldList: [],
|
|
160
|
+
error: null,
|
|
160
161
|
});
|
|
161
162
|
}
|
|
162
163
|
}
|
|
@@ -189,7 +190,7 @@ export default class EntityForm extends React.PureComponent<
|
|
|
189
190
|
async loadFieldList() {
|
|
190
191
|
const { xObjectDataApi } = this.props || {};
|
|
191
192
|
|
|
192
|
-
this.setState({ loading: true });
|
|
193
|
+
this.setState({ loading: true, error: null });
|
|
193
194
|
|
|
194
195
|
try {
|
|
195
196
|
// 方式一:直接从 props.xObjectDataApi 中获取字段描述
|
|
@@ -40,7 +40,7 @@ import { BaseCmp } from 'neo-ui-common';
|
|
|
40
40
|
|
|
41
41
|
// 引入 neo-ui-common / NeoEvent
|
|
42
42
|
// @ts-ignore
|
|
43
|
-
import { NeoEvent } from 'neo-ui-common';
|
|
43
|
+
import { NeoEvent } from 'neo-ui-common';
|
|
44
44
|
|
|
45
45
|
import './style.scss';
|
|
46
46
|
|
|
@@ -343,6 +343,7 @@ export default class EntityTable extends BaseCmp<
|
|
|
343
343
|
* @param page 页码,默认为 1
|
|
344
344
|
* @param pageSize 每页条数,默认为 10
|
|
345
345
|
*/
|
|
346
|
+
@NeoEvent.function
|
|
346
347
|
async loadData(page = 1, pageSize = 10) {
|
|
347
348
|
const { xObjectApiKey, autoFetchData } = this.props.xObjectDataApi || {};
|
|
348
349
|
if (!xObjectApiKey) return;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
// 引入 neo-ui-common / BaseCmp
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import { BaseCmp } from 'neo-ui-common';
|
|
5
|
+
import './style.scss'; // 组件内容样式
|
|
6
|
+
|
|
7
|
+
interface CustomCmpProps {
|
|
8
|
+
description: string;
|
|
9
|
+
data?: any;
|
|
10
|
+
env?: any;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface CustomCmpStates {
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default class CustomCmp extends BaseCmp<CustomCmpProps, CustomCmpStates> {
|
|
19
|
+
constructor(props: CustomCmpProps) {
|
|
20
|
+
super(props);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
render() {
|
|
24
|
+
const { description, className, env } = this.props;
|
|
25
|
+
const ctx = (env || {}).ctx;
|
|
26
|
+
let languageCode = 'zh-CN'; // 默认中文
|
|
27
|
+
// 从上下文中 获取系统语言
|
|
28
|
+
if (ctx && ctx.system && ctx.system.language) {
|
|
29
|
+
languageCode = ctx.system.language;
|
|
30
|
+
}
|
|
31
|
+
console.log('当前自定义组件:', this.props);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className={`custom-cmp-container ${className}`}>
|
|
35
|
+
<div className="news-title">
|
|
36
|
+
{languageCode === 'zh-CN' ? '你好,销售易!' : 'Hello NeoCRM!'}
|
|
37
|
+
</div>
|
|
38
|
+
<p>{description}</p>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file 自定义组件对接编辑器的描述文件
|
|
3
|
+
*/
|
|
4
|
+
export class CmpModel {
|
|
5
|
+
// 组件名称,用于设置在编辑器左侧组件面板中展示的名称
|
|
6
|
+
label: string = '简单示例组件';
|
|
7
|
+
|
|
8
|
+
// 组件描述,用于设置在编辑器左侧组件面板中展示的描述
|
|
9
|
+
description: string = '暂无描述';
|
|
10
|
+
|
|
11
|
+
// 分类标签,用于设置在编辑器左侧组件面板哪个分类中展示
|
|
12
|
+
// tags: string[] = ['自定义组件'];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 用于设置组件支持的页面类型
|
|
16
|
+
*
|
|
17
|
+
* 当前 NeoCRM 平台存在的页面类型:
|
|
18
|
+
* all: 1 全页面
|
|
19
|
+
* entityFormPage: 4 实体表单页
|
|
20
|
+
* customPage: 6 自定义页面
|
|
21
|
+
*/
|
|
22
|
+
targetPage: string[] = ['all'];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 用于设置组件支持的终端类型
|
|
26
|
+
*
|
|
27
|
+
* 当前 NeoCRM 平台存在的终端类型:
|
|
28
|
+
* web: 网页端
|
|
29
|
+
* mobile: 移动端
|
|
30
|
+
*/
|
|
31
|
+
// targetDevice: string = 'web';
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
// 组件图标,用于设置在编辑器左侧组件面板中展示的图标
|
|
35
|
+
iconUrl: string = 'https://neo-widgets.bj.bcebos.com/custom-widget.svg';
|
|
36
|
+
|
|
37
|
+
// 初次插入页面的默认属性数据
|
|
38
|
+
defaultComProps = {
|
|
39
|
+
description: '营销服全场景智能CRM,帮助企业搭建数字化客户经营平台,实现业绩高质量增长。'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 组件面板配置,用于生成编辑器右侧属性配置面板内容
|
|
44
|
+
*/
|
|
45
|
+
propsSchema = [
|
|
46
|
+
{
|
|
47
|
+
type: 'textarea',
|
|
48
|
+
name: 'description',
|
|
49
|
+
label: '组件内容',
|
|
50
|
+
value: '',
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default CmpModel;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--padding-bottom: 12px;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.custom-cmp-container {
|
|
6
|
+
position: relative;
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
|
|
9
|
+
/* border-bottom: 1px solid #ececec; */
|
|
10
|
+
margin: 6px 12px;
|
|
11
|
+
padding: 6px var(--padding-bottom);
|
|
12
|
+
background-color: #fff;
|
|
13
|
+
|
|
14
|
+
.news-title {
|
|
15
|
+
padding: 6px 0;
|
|
16
|
+
font-family: PingFangSC-Regular;
|
|
17
|
+
font-size: 16px;
|
|
18
|
+
line-height: 22px;
|
|
19
|
+
color: #5f5e5e;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,49 +1,68 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"experimentalDecorators": true,
|
|
4
|
+
/* Basic Options */
|
|
4
5
|
"target": "esnext",
|
|
5
|
-
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
6
|
+
/* 指定编译之后的版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
|
|
7
|
+
"module": "esnext" /* 指定要使用的模板标准: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
|
8
|
+
// "lib": [], /* Specify library files to be included in the compilation. */
|
|
9
|
+
"allowJs": false /* 指定是否允许编译JS文件,默认false,即不编译JS文件. */,
|
|
10
|
+
// "checkJs": true, /* 指定是否检查和报告JS文件中的错误,默认false */
|
|
11
|
+
"jsx": "react" /* 指定jsx代码用于的开发环境:'preserve','react-native', or 'react'. */,
|
|
12
|
+
"declaration": false /* 指定是否在编译的时候生成相的d.ts声明文件 */,
|
|
13
|
+
// "declarationMap": true, /* 指定编译时是否生成.map文件 */
|
|
14
|
+
// "sourceMap": true, /* 指定编译时是否生成.map文件 */
|
|
15
|
+
// "outFile": "./", /* 指定输出文件合并为一个文件 */
|
|
16
|
+
// "outDir": "dist", /* 指定输出文件夹,值为一个文件夹路径字符串,输出的文件都将放置在这个文件夹*/
|
|
17
|
+
// "rootDir": "src", /* 指定编译文件的根目录,编译器会在根目录查找入口文件 */
|
|
18
|
+
// "composite": true, /* 是否编译构建引用项目 */
|
|
19
|
+
// "removeComments": true, /* 指定是否将编译后的文件注释删掉,设为true的话即删除注释,默认为false */
|
|
20
|
+
"noEmit": false /* 不生成编译文件 */,
|
|
21
|
+
"importHelpers": true /* 指定是否引入tslib里的复制工具函数,默认为false */,
|
|
22
|
+
// "downlevelIteration": true, /* 当target为"ES5"或"ES3"时,为"for-of" "spread"和"destructuring"中的迭代器提供完全支持 */
|
|
23
|
+
"isolatedModules": false /* 指定是否将每个文件作为单独的模块,默认为true */,
|
|
24
|
+
|
|
25
|
+
/* Strict Type-Checking Options */
|
|
26
|
+
"strict": false /* 指定是否启动所有类型检查 */,
|
|
27
|
+
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
|
28
|
+
"strictNullChecks": true /* Enable strict null checks. */,
|
|
29
|
+
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
|
30
|
+
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
|
31
|
+
"noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
|
|
32
|
+
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
|
33
|
+
|
|
34
|
+
/* Additional Checks */
|
|
35
|
+
"noUnusedLocals": false /* Report errors on unused locals. */,
|
|
36
|
+
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
|
37
|
+
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
|
|
38
|
+
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
|
39
|
+
|
|
40
|
+
/* Module Resolution Options */
|
|
41
|
+
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
|
|
42
|
+
"baseUrl": "./" /* Base directory to resolve non-absolute module names. */,
|
|
20
43
|
"paths": {
|
|
21
|
-
"@": [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"forceConsistentCasingInFileNames": true
|
|
44
|
+
"@": ["./src"]
|
|
45
|
+
} /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */,
|
|
46
|
+
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
|
47
|
+
/* 指定声明文件或文件夹的路径列表,如果指定了此项,则只有在这里列出的声明文件才会被加载 */
|
|
48
|
+
"typeRoots": ["./@types", "./node_modules/@types"],
|
|
49
|
+
// "types": [], /* 指定需要包含的模块,只有在这里列出的模块的声明文件才会被加载 */
|
|
50
|
+
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
|
|
51
|
+
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
|
52
|
+
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
|
53
|
+
|
|
54
|
+
/* Source Map Options */
|
|
55
|
+
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
|
56
|
+
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
|
|
57
|
+
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
|
58
|
+
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
|
59
|
+
|
|
60
|
+
/* Experimental Options */
|
|
61
|
+
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
|
62
|
+
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
|
63
|
+
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
|
64
|
+
// "suppressImplicitAnyIndexErrors": true /* Suppress --noImplicitAny errors for indexing objects lacking index signatures. See issue #1232 for more details. */
|
|
41
65
|
},
|
|
42
|
-
"include": [
|
|
43
|
-
|
|
44
|
-
"test"
|
|
45
|
-
],
|
|
46
|
-
"exclude": [
|
|
47
|
-
"node_modules"
|
|
48
|
-
]
|
|
66
|
+
"include": ["src", "test"],
|
|
67
|
+
"exclude": ["node_modules"]
|
|
49
68
|
}
|