neo-cmp-cli 1.13.13 → 1.13.16

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.
Files changed (67) hide show
  1. package/README.md +1 -1
  2. package/dist/neo/neoEnvManager.js +1 -1
  3. package/dist/neo/neoLogin.js +1 -1
  4. package/dist/neo/neoRequire.js +1 -1
  5. package/dist/package.json.js +1 -1
  6. package/docs//351/200/232/347/224/250/344/273/243/347/220/206/346/216/245/345/217/243/forward.zip +0 -0
  7. package/docs//351/200/232/347/224/250/344/273/243/347/220/206/346/216/245/345/217/243//350/207/252/345/256/232/344/271/211API:/351/200/232/347/224/250/344/273/243/347/220/206/346/216/245/345/217/243/344/275/277/347/224/250/350/257/264/346/230/216.md +13 -0
  8. package/package.json +1 -1
  9. package/template/antd-custom-cmp-template/package.json +1 -1
  10. package/template/asset-manage-template/package.json +1 -1
  11. package/template/echarts-custom-cmp-template/package.json +1 -1
  12. package/template/empty-custom-cmp-template/package.json +1 -1
  13. package/template/map-custom-cmp-template/package.json +1 -1
  14. package/template/neo-bi-cmps/package.json +1 -1
  15. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/index.tsx +17 -10
  16. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/model.ts +47 -6
  17. package/template/neo-bi-cmps/src/components/filterBar__c/index.tsx +21 -7
  18. package/template/neo-bi-cmps/src/components/forecastChart__c/index.tsx +6 -9
  19. package/template/neo-bi-cmps/src/components/forecastChart__c/model.ts +2 -1
  20. package/template/neo-bi-cmps/src/components/forecastGrid__c/model.ts +32 -4
  21. package/template/neo-bi-cmps/src/components/gapCloser__c/index.tsx +7 -2
  22. package/template/neo-bi-cmps/src/components/gapCloser__c/model.ts +6 -3
  23. package/template/neo-bi-cmps/src/components/kpiCards__c/model.ts +18 -3
  24. package/template/neo-bi-cmps/src/components/oppList__c/index.tsx +70 -13
  25. package/template/neo-bi-cmps/src/components/oppList__c/model.ts +50 -4
  26. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/index.tsx +3 -1
  27. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/model.ts +28 -4
  28. package/template/neo-bi-cmps/src/components/stageSwitch__c/index.tsx +21 -6
  29. package/template/neo-bi-cmps/src/components/stageSwitch__c/model.ts +60 -5
  30. package/template/neo-bi-cmps/src/components/stageTimeChart__c/model.ts +26 -4
  31. package/template/neo-custom-cmp-template/package.json +1 -1
  32. package/template/neo-h5-cmps/package.json +1 -1
  33. package/template/neo-order-cmps/package.json +1 -1
  34. package/template/neo-web-entity-grid/package.json +1 -1
  35. package/template/neo-web-form/package.json +1 -1
  36. package/template/neo-web-form/src/components/batchAddTable__c/index.tsx +17 -17
  37. package/template/react-custom-cmp-template/package.json +1 -1
  38. package/template/react-ts-custom-cmp-template/package.json +1 -1
  39. package/template/vue2-custom-cmp-template/package.json +1 -1
  40. package/template/neo-bi-cmps/docs/gartner-pipeline-apis.md +0 -279
  41. package/template/neo-bi-cmps/docs/gartner-pipeline-prd.md +0 -389
  42. package/template/neo-bi-cmps/docs/neo-backend-dev/SKILL.md +0 -188
  43. package/template/neo-bi-cmps/docs/neo-backend-dev/references/01-Trigger/345/274/200/345/217/221.md +0 -183
  44. package/template/neo-bi-cmps/docs/neo-backend-dev/references/02-/350/207/252/345/256/232/344/271/211API/345/274/200/345/217/221.md +0 -196
  45. package/template/neo-bi-cmps/docs/neo-backend-dev/references/03-SDK/345/267/245/345/205/267/347/261/273/346/216/245/345/217/243.md +0 -346
  46. package/template/neo-bi-cmps/docs/neo-backend-dev/references/04-/350/256/241/345/210/222/344/275/234/344/270/232/345/274/200/345/217/221.md +0 -188
  47. package/template/neo-bi-cmps/docs/neo-backend-dev/references/05-/351/241/265/351/235/242/345/274/200/345/217/221.md +0 -293
  48. package/template/neo-bi-cmps/docs/neo-backend-dev/references/06-/346/265/201/347/250/213/346/211/251/345/261/225/345/274/200/345/217/221.md +0 -175
  49. package/template/neo-bi-cmps/docs/neo-backend-dev/references/PaaS/345/271/263/345/217/260/345/274/200/345/217/221/346/211/213/345/206/214/350/247/243/350/257/273.md +0 -313
  50. package/template/neo-bi-cmps/docs/neo-backend-dev/references/auth-config.md +0 -77
  51. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/deploy_server_script.py +0 -118
  52. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/download_server_script.py +0 -74
  53. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/gen_entity_desc.py +0 -69
  54. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/gen_entitylist.py +0 -87
  55. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/query_crm.py +0 -65
  56. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/uninstall_server_script.py +0 -48
  57. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/update_model_jar.py +0 -49
  58. package/template/neo-bi-cmps/docs/neo-frontend-dev/SKILL.md +0 -138
  59. package/template/neo-bi-cmps/docs/neo-frontend-dev/references/auth-config.md +0 -77
  60. package/template/neo-bi-cmps/docs/neo-frontend-dev/references/component-dev.md +0 -205
  61. package/template/neo-bi-cmps/docs/neo-frontend-dev/references/entityTable-example.md +0 -167
  62. package/template/neo-bi-cmps/docs/neo-frontend-dev/references/templates.md +0 -38
  63. package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/gen_entity_desc.py +0 -69
  64. package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/gen_entitylist.py +0 -87
  65. package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/query_crm.py +0 -65
  66. package/template/neo-bi-cmps/docs/prototype-pipeline-forecasting.html +0 -2453
  67. package/template/neo-bi-cmps/docs//350/264/246/345/217/267/347/233/270/345/205/263/344/277/241/346/201/257.md +0 -10
package/README.md CHANGED
@@ -32,7 +32,7 @@ neo-cmp-cli 是 Neo 自定义组件开发工具,基于 [AKFun](https://github.
32
32
  | `neo-web-entity-grid` | Web 端列表组件模板:含基础大列表、Picker 列表等示例组件 | (随 CLI 内置) |
33
33
  | `neo-h5-cmps` | H5 端业务组件模板:含全局搜索、数据列表、数据 Tabs、打开 AI 对话页等示例组件 | [neo-h5-cmps](https://github.com/wibetter/neo-h5-cmps) |
34
34
  | `neo` | 自定义业务组件模板:含实体表单、实体数据详情、实体数据表格等示例组件 | [neo-custom-cmp-template](https://github.com/wibetter/neo-custom-cmp-template) |
35
- | `neo-bi-cmps` | 数值指标组件模板: 可配置展示实体数据源中关键数值指标 | [neo-bi-cmps](https://github.com/xsy-neoui/neo-bi-cmps) |
35
+ | `neo-bi-cmps` | 数值指标组件模板: 可配置展示实体数据源中指标数据 | [neo-bi-cmps](https://github.com/xsy-neoui/neo-bi-cmps) |
36
36
  | `echarts` | ECharts 组件模板:含基于 ECharts 的图表示例组件(地图场景请使用 `amap` 模板) | [echarts-custom-cmp-template](https://github.com/wibetter/echarts-custom-cmp-template) |
37
37
  | `antd` | Ant Design 组件模板:含数据仪表板、搜索组件等示例 | [antd-custom-cmp-template](https://github.com/wibetter/antd-custom-cmp-template) |
38
38
  | `amap` | 地图组件模板:含基于高德地图 API 的示例组件 | [map-custom-cmp-template](https://github.com/wibetter/map-custom-cmp-template) |
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("node:fs"),t=require("node:path"),n=require("../utils/common.js"),i=require("../config/index.js"),r=require("../config/auth.config.js"),s=require("./env.js");var o,u;exports.__require=function(){if(u)return o;u=1;const c=e,g=t,{errorLog:a}=n.__require(),f=i.__require(),h=r.__require(),{DefaultNeoCrmAPI:l}=s.__require();return o=class{static getEnvFilePath(){return g.join(process.cwd(),".neo-cli","env.json")}static ensureEnvDir(){const e=g.dirname(this.getEnvFilePath());c.existsSync(e)||c.mkdirSync(e,{recursive:!0})}static getAuthConfig(){return{...l,...f.neoConfig,...h}}static getEnvConfig(){let e={};const t=this.getEnvFilePath(),n=this.getAuthConfig();if(!c.existsSync(t))return n;try{const n=c.readFileSync(t,"utf-8");e=JSON.parse(n)}catch(e){a(`读取环境配置文件失败: ${e.message||e.msg}`)}return{...n,...e}}static getLinKDebugURL(e){const t=this.getEnvConfig(),n=t.neoBaseURL;return"web"===e?`${n}${t.pcLinkDebugUrl}`:`${n}${t.h5LinkDebugUrl}`}static setEnvConfig(e){if(!e||"object"!=typeof e)throw new Error("环境配置必须是一个对象");this.ensureEnvDir();const t={...this.getEnvConfig(),...e,updatedAt:Date.now()},n=this.getEnvFilePath();return c.writeFileSync(n,JSON.stringify(t,null,2),"utf-8"),t}static updateEnvConfig(e){return this.setEnvConfig(e)}static clearEnvConfig(){const e=this.getEnvFilePath();c.existsSync(e)&&c.unlinkSync(e)}}};
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("node:fs"),t=require("node:path"),n=require("../utils/common.js"),i=require("../config/index.js"),r=require("../config/auth.config.js"),s=require("./env.js");var o,u;exports.__require=function(){if(u)return o;u=1;const c=e,g=t,{errorLog:a}=n.__require(),f=i.__require(),h=r.__require(),{DefaultNeoCrmAPI:l}=s.__require();return o=class{static getEnvFilePath(){return g.join(process.cwd(),".neo-cli","env.json")}static ensureEnvDir(){const e=g.dirname(this.getEnvFilePath());c.existsSync(e)||c.mkdirSync(e,{recursive:!0})}static getAuthConfig(){return{...l,...f.neoConfig,...h}}static getEnvConfig(){let e={};const t=this.getEnvFilePath(),n=this.getAuthConfig();if(!c.existsSync(t))return n;try{const n=c.readFileSync(t,"utf-8");e=JSON.parse(n)}catch(e){a(`读取环境配置文件失败: ${e.message||e.msg}`)}return{...n,...e}}static getLinKDebugURL(e){const t=this.getEnvConfig();let n=t.neoBaseURL;return n.endsWith("/")&&(n=n.substring(0,n.length-1)),"web"===e?`${n}${t.pcLinkDebugUrl}`:`${n}${t.h5LinkDebugUrl}`}static setEnvConfig(e){if(!e||"object"!=typeof e)throw new Error("环境配置必须是一个对象");this.ensureEnvDir();const t={...this.getEnvConfig(),...e,updatedAt:Date.now()},n=this.getEnvFilePath();return c.writeFileSync(n,JSON.stringify(t,null,2),"utf-8"),t}static updateEnvConfig(e){return this.setEnvConfig(e)}static clearEnvConfig(){const e=this.getEnvFilePath();c.existsSync(e)&&c.unlinkSync(e)}}};
@@ -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:R}=l.__require(),$=h.__require();return p=class{constructor(e){if(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"),!e)return;const{neoBaseURL:t,loginURL:n,tokenURL:s}=e,r=$.getAuthConfig(),{redirectUri:o,authType:i,auth:a}=r;if(!n||!s)throw new Error("neo.config.js 配置不完整,需要包含 loginURL、tokenURL");this.neoBaseURL=t,this.loginURL=n,this.tokenURL=s,this.NeoCrmAPI=r,this.redirectUri=o,this.authType=i||"oauth2",this.response_type=a.response_type||"code",this.client_id=a.client_id,this.client_secret=a.client_secret,this.scope=a.scope||"all",this.oauthType=a.oauthType||"standard",this.access_type=a.access_type||"offline",this.grant_type=a.grant_type||"authorization_code";try{$.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){r&&r.instance_uri&&r.instance_uri!==this.neoBaseURL&&(this.neoBaseURL=r.instance_uri,$.setEnvConfig({neoBaseURL:r.instance_uri}));const e=await this.getUserInfo(r.access_token);e&&e.tenantName&&e.name?r.userInfo=e:U("获取用户信息失败:",e)}else U("获取 token 失败:响应中未包含 access_token",n),U(`响应数据: ${JSON.stringify(r)}`),process.exit(1);return R("成功获取 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 R("刷新授权信息成功(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 R("\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{$.clearEnvConfig(),this.clearToken(),R("已清除授权信息,下次登录需要重新授权。"),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.neoBaseURL}${e}`}}};
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:R}=l.__require(),$=h.__require();return p=class{constructor(e){if(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"),!e)return;const{neoBaseURL:t,loginURL:n,tokenURL:s}=e,r=$.getAuthConfig(),{redirectUri:o,authType:i,auth:a}=r;if(!n||!s)throw new Error("neo.config.js 配置不完整,需要包含 loginURL、tokenURL");this.neoBaseURL=t,this.loginURL=n,this.tokenURL=s,this.NeoCrmAPI=r,this.redirectUri=o,this.authType=i||"oauth2",this.response_type=a.response_type||"code",this.client_id=a.client_id,this.client_secret=a.client_secret,this.scope=a.scope||"all",this.oauthType=a.oauthType||"standard",this.access_type=a.access_type||"offline",this.grant_type=a.grant_type||"authorization_code";try{$.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){r&&r.instance_uri&&r.instance_uri!==this.neoBaseURL&&(this.neoBaseURL=r.instance_uri,$.setEnvConfig({neoBaseURL:r.instance_uri}));const e=await this.getUserInfo(r.access_token);e&&e.tenantName&&e.name?r.userInfo=e:U("获取用户信息失败:",e)}else U("获取 token 失败:响应中未包含 access_token",n),U(`响应数据: ${JSON.stringify(r)}`),process.exit(1);return R("成功获取 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 R("刷新授权信息成功(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 R("\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{$.clearEnvConfig(),this.clearToken(),R("已清除授权信息,下次登录需要重新授权。"),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){if(e.startsWith("http://")||e.startsWith("https://"))return e;let t=this.neoBaseURL;return t.endsWith("/")&&(t=t.substring(0,t.length-1)),`${t}${e}`}}};
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("lodash");var e,n;exports.__require=function(){if(n)return e;n=1;const s=o,m={react:"^16.13.1","react-dom":"^16.13.1",mobx:"^6.3.0","mobx-react":"^7.0.0","mobx-state-tree":"^5.4.0",axios:"^1.7.0",classnames:"^2.3.2",qs:"^6.11.0",lodash:"^4.17.23","neo-open-api":"^1.1.9",amis:"^1.1.5","neo-ui-component-web":"^1.0.0","neo-ui-component-h5":"^1.0.0","neo-ui-common":"^1.0.0"};return e={initNeoRequire:()=>{window.neoRequire||(window.neoRequire=o=>window.__NeoCommonModules[o]||window[o])},addNeoCommonModules:o=>{if(window.__NeoCommonModules||(window.__NeoCommonModules={}),isPlainObject(o)){Object.keys(o).forEach(e=>{const n=o[e],s=window.__NeoCommonModules[e];if(!Object.isFrozen(s)&&s&&Object.keys(s).length<3)try{window.__NeoCommonModules[e]=Object.assign(s,n),void 0!==n.__esModule&&(window.__NeoCommonModules[e].__esModule=n.__esModule),void 0!==n.default&&(window.__NeoCommonModules[e].default=n.default)}catch(o){console.warn(`window.__NeoCommonModules[${e}] Object.assign error:`,o),window.__NeoCommonModules[e]=n}else window.__NeoCommonModules[e]=n})}},addNeoRemoteDeps:o=>{window.__NeoCommonModules||(window.__NeoCommonModules={}),window.__NeoCommonModules.__neoRemoteDeps||(window.__NeoCommonModules.__neoRemoteDeps={}),s.isPlainObject(o)&&(window.__NeoCommonModules.__neoRemoteDeps=Object.assign(window.__NeoCommonModules.__neoRemoteDeps,o))},getExternalsByNeoCommonModules:o=>{const e={};return Object.keys(m).forEach(o=>{e[o]=`commonjs ${o}`}),o&&o.length>0&&o.forEach(o=>{e[o]=`commonjs ${o}`}),e}}};
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("lodash");var e,n;exports.__require=function(){if(n)return e;n=1;const s=o,m={react:"^16.13.1","react-dom":"^16.13.1",mobx:"^6.3.0","mobx-react":"^7.0.0","mobx-state-tree":"^5.4.0",axios:"^1.7.0",classnames:"^2.3.2",qs:"^6.11.0",lodash:"^4.17.23",amis:"^1.1.5","neo-ui-component-web":"^1.0.0","neo-ui-component-h5":"^1.0.0","neo-ui-common":"^1.0.0"};return e={initNeoRequire:()=>{window.neoRequire||(window.neoRequire=o=>window.__NeoCommonModules[o]||window[o])},addNeoCommonModules:o=>{if(window.__NeoCommonModules||(window.__NeoCommonModules={}),isPlainObject(o)){Object.keys(o).forEach(e=>{const n=o[e],s=window.__NeoCommonModules[e];if(!Object.isFrozen(s)&&s&&Object.keys(s).length<3)try{window.__NeoCommonModules[e]=Object.assign(s,n),void 0!==n.__esModule&&(window.__NeoCommonModules[e].__esModule=n.__esModule),void 0!==n.default&&(window.__NeoCommonModules[e].default=n.default)}catch(o){console.warn(`window.__NeoCommonModules[${e}] Object.assign error:`,o),window.__NeoCommonModules[e]=n}else window.__NeoCommonModules[e]=n})}},addNeoRemoteDeps:o=>{window.__NeoCommonModules||(window.__NeoCommonModules={}),window.__NeoCommonModules.__neoRemoteDeps||(window.__NeoCommonModules.__neoRemoteDeps={}),s.isPlainObject(o)&&(window.__NeoCommonModules.__neoRemoteDeps=Object.assign(window.__NeoCommonModules.__neoRemoteDeps,o))},getExternalsByNeoCommonModules:o=>{const e={};return Object.keys(m).forEach(o=>{e[o]=`commonjs ${o}`}),o&&o.length>0&&o.forEach(o=>{e[o]=`commonjs ${o}`}),e}}};
@@ -1 +1 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});var e="1.13.13";const o={version:e};exports.default=o,exports.version=e;
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});var e="1.13.15";const o={version:e};exports.default=o,exports.version=e;
@@ -0,0 +1,13 @@
1
+ ## 一、说明
2
+ 这是一个用于实现代理转发第三方接口的自定义API。
3
+
4
+ ## 二、使用方式
5
+
6
+ ### 步骤1
7
+ 在 admin 管理端,打开 开发/业务逻辑代码,并切换到 代码包 tab面板,点击「新建代码包」,按界面提示输入:
8
+ 代码名称: 通用代理接口
9
+ Package: other.xsy.proxy
10
+ 代码描述: 用于代理转发第三方API。
11
+
12
+ ### 步骤2
13
+ 将 forward.zip 到上传到刚创建的「代码包」上。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo-cmp-cli",
3
- "version": "1.13.13",
3
+ "version": "1.13.16",
4
4
  "description": "Neo 自定义组件开发工具,支持react 和 vue2.0技术栈。",
5
5
  "keywords": [
6
6
  "neo-cli",
@@ -48,7 +48,7 @@
48
48
  "@commitlint/config-conventional": "^18.0.0",
49
49
  "@types/react": "^16.9.11",
50
50
  "@types/react-dom": "^16.9.15",
51
- "neo-cmp-cli": "^1.13.13",
51
+ "neo-cmp-cli": "^1.13.16",
52
52
  "husky": "^4.2.5",
53
53
  "lint-staged": "^10.2.9",
54
54
  "prettier": "^2.0.5"
@@ -52,7 +52,7 @@
52
52
  "@types/react": "^16.9.11",
53
53
  "@types/react-dom": "^16.9.15",
54
54
  "@types/axios": "^0.14.0",
55
- "neo-cmp-cli": "^1.13.13",
55
+ "neo-cmp-cli": "^1.13.16",
56
56
  "husky": "^4.2.5",
57
57
  "lint-staged": "^10.2.9",
58
58
  "prettier": "^2.0.5",
@@ -48,7 +48,7 @@
48
48
  "@commitlint/config-conventional": "^18.0.0",
49
49
  "@types/react": "^16.9.11",
50
50
  "@types/react-dom": "^16.9.15",
51
- "neo-cmp-cli": "^1.13.13",
51
+ "neo-cmp-cli": "^1.13.16",
52
52
  "husky": "^4.2.5",
53
53
  "lint-staged": "^10.2.9",
54
54
  "prettier": "^2.0.5",
@@ -41,7 +41,7 @@
41
41
  "@commitlint/config-conventional": "^18.0.0",
42
42
  "@types/react": "^16.9.11",
43
43
  "@types/react-dom": "^16.9.15",
44
- "neo-cmp-cli": "^1.13.13",
44
+ "neo-cmp-cli": "^1.13.16",
45
45
  "husky": "^4.2.5",
46
46
  "lint-staged": "^10.2.9",
47
47
  "prettier": "^2.0.5"
@@ -47,7 +47,7 @@
47
47
  "@commitlint/config-conventional": "^18.0.0",
48
48
  "@types/react": "^16.9.11",
49
49
  "@types/react-dom": "^16.9.15",
50
- "neo-cmp-cli": "^1.13.13",
50
+ "neo-cmp-cli": "^1.13.16",
51
51
  "husky": "^4.2.5",
52
52
  "lint-staged": "^10.2.9",
53
53
  "prettier": "^2.0.5"
@@ -50,7 +50,7 @@
50
50
  "@types/react": "^16.9.11",
51
51
  "@types/react-dom": "^16.9.15",
52
52
  "@types/axios": "^0.14.0",
53
- "neo-cmp-cli": "^1.13.13",
53
+ "neo-cmp-cli": "^1.13.16",
54
54
  "husky": "^4.2.5",
55
55
  "lint-staged": "^10.2.9",
56
56
  "prettier": "^2.0.5"
@@ -75,7 +75,9 @@ class AiCommitDrawer extends BaseCmp<AiCommitDrawerProps, AiCommitDrawerState> {
75
75
  <div className="deal-card-header">
76
76
  <div className="deal-info">
77
77
  <div className="deal-name">{deal.name}</div>
78
- <div className="deal-account">{deal.account} · {deal.amount}</div>
78
+ <div className="deal-account">
79
+ {deal.account} · {deal.amount}
80
+ </div>
79
81
  <div className="deal-meta">
80
82
  <span className="deal-stage">{deal.stage}</span>
81
83
  <span> · Close: {deal.closeDate}</span>
@@ -93,10 +95,7 @@ class AiCommitDrawer extends BaseCmp<AiCommitDrawerProps, AiCommitDrawerState> {
93
95
  >
94
96
  Prioritize
95
97
  </button>
96
- <button
97
- className="btn-dismiss"
98
- onClick={() => onDismiss?.(deal)}
99
- >
98
+ <button className="btn-dismiss" onClick={() => onDismiss?.(deal)}>
100
99
  Dismiss
101
100
  </button>
102
101
  </div>
@@ -132,12 +131,16 @@ class AiCommitDrawer extends BaseCmp<AiCommitDrawerProps, AiCommitDrawerState> {
132
131
  >
133
132
  <div className="drawer-header">
134
133
  <h3>✨ AI Forecast</h3>
135
- <button className="close-btn" onClick={this.handleClose}>✕</button>
134
+ <button className="close-btn" onClick={this.handleClose}>
135
+
136
+ </button>
136
137
  </div>
137
138
 
138
139
  <div className="drawer-summary">
139
140
  <span className="summary-amount">{totalAmount}</span>
140
- <span className="summary-desc">Based on deal health and engagement signals</span>
141
+ <span className="summary-desc">
142
+ Based on deal health and engagement signals
143
+ </span>
141
144
  </div>
142
145
 
143
146
  {/* Closed Won 汇总 */}
@@ -154,7 +157,9 @@ class AiCommitDrawer extends BaseCmp<AiCommitDrawerProps, AiCommitDrawerState> {
154
157
  {tabs.map((tab) => (
155
158
  <button
156
159
  key={tab.key}
157
- className={`ai-commit-tab ${activeTab === tab.key ? 'active' : ''}`}
160
+ className={`ai-commit-tab ${
161
+ activeTab === tab.key ? 'active' : ''
162
+ }`}
158
163
  onClick={() => this.handleTabChange(tab.key)}
159
164
  >
160
165
  {tab.label} · {tab.amount} · {tab.count} deals
@@ -164,8 +169,10 @@ class AiCommitDrawer extends BaseCmp<AiCommitDrawerProps, AiCommitDrawerState> {
164
169
 
165
170
  {/* Tab内容 */}
166
171
  <div className="drawer-content">
167
- {activeTab === 'pipeline' && pipelineDeals.map((deal) => this.renderDealCard(deal))}
168
- {activeTab === 'commit' && commitDeals.map((deal) => this.renderDealCard(deal))}
172
+ {activeTab === 'pipeline' &&
173
+ pipelineDeals.map((deal) => this.renderDealCard(deal))}
174
+ {activeTab === 'commit' &&
175
+ commitDeals.map((deal) => this.renderDealCard(deal))}
169
176
  </div>
170
177
  </div>
171
178
  </div>
@@ -1,6 +1,7 @@
1
1
  export class AiCommitDrawerModel {
2
2
  label: string = 'AI推荐抽屉';
3
- description: string = '展示AI推荐的Commit商机,支持From Pipeline和From Commit两个Tab';
3
+ description: string =
4
+ '展示AI推荐的Commit商机,支持From Pipeline和From Commit两个Tab';
4
5
  iconUrl: string = 'https://custom-widgets.bj.bcebos.com/aiCommitDrawer.svg';
5
6
  targetPage: string[] = ['all'];
6
7
  targetDevice: string = 'all';
@@ -11,16 +12,56 @@ export class AiCommitDrawerModel {
11
12
  closedAmount: '$3,200,000',
12
13
  closedCount: 5,
13
14
  tabs: [
14
- { key: 'pipeline', label: 'From Open Pipeline', amount: '$2,500,000', count: 2 },
15
+ {
16
+ key: 'pipeline',
17
+ label: 'From Open Pipeline',
18
+ amount: '$2,500,000',
19
+ count: 2,
20
+ },
15
21
  { key: 'commit', label: 'From Commit', amount: '$1,800,000', count: 2 },
16
22
  ],
17
23
  pipelineDeals: [
18
- { id: '1', name: 'Starlight Initiative', account: 'Tencent', amount: '$500,000', stage: 'Pipeline', closeDate: '2026-05-20', aiScore: '52%', description: 'Strong engagement signals and past wins with this account' },
19
- { id: '2', name: 'Galaxy Partnership', account: 'Alibaba', amount: '$2,000,000', stage: 'Best Case', closeDate: '2026-05-10', aiScore: '45%', description: 'High product usage and active engagement in last 7 days' },
24
+ {
25
+ id: '1',
26
+ name: 'Starlight Initiative',
27
+ account: 'Tencent',
28
+ amount: '$500,000',
29
+ stage: 'Pipeline',
30
+ closeDate: '2026-05-20',
31
+ aiScore: '52%',
32
+ description:
33
+ 'Strong engagement signals and past wins with this account',
34
+ },
35
+ {
36
+ id: '2',
37
+ name: 'Galaxy Partnership',
38
+ account: 'Alibaba',
39
+ amount: '$2,000,000',
40
+ stage: 'Best Case',
41
+ closeDate: '2026-05-10',
42
+ aiScore: '45%',
43
+ description: 'High product usage and active engagement in last 7 days',
44
+ },
20
45
  ],
21
46
  commitDeals: [
22
- { id: '3', name: 'Aurora Solution', account: 'ByteDance', amount: '$800,000', stage: 'Commit', closeDate: '2026-04-30', aiScore: '91%' },
23
- { id: '4', name: 'Apollo Project', account: 'Huawei Tech', amount: '$1,000,000', stage: 'Commit', closeDate: '2026-04-15', aiScore: '86%' },
47
+ {
48
+ id: '3',
49
+ name: 'Aurora Solution',
50
+ account: 'ByteDance',
51
+ amount: '$800,000',
52
+ stage: 'Commit',
53
+ closeDate: '2026-04-30',
54
+ aiScore: '91%',
55
+ },
56
+ {
57
+ id: '4',
58
+ name: 'Apollo Project',
59
+ account: 'Huawei Tech',
60
+ amount: '$1,000,000',
61
+ stage: 'Commit',
62
+ closeDate: '2026-04-15',
63
+ aiScore: '86%',
64
+ },
24
65
  ],
25
66
  };
26
67
 
@@ -96,7 +96,9 @@ class FilterBar extends BaseCmp<FilterBarProps, FilterBarState> {
96
96
  <label>{filter.label}</label>
97
97
  <select
98
98
  value={values[filter.name] || ''}
99
- onChange={(e) => this.handleFilterChange(filter.name, e.target.value)}
99
+ onChange={(e) =>
100
+ this.handleFilterChange(filter.name, e.target.value)
101
+ }
100
102
  >
101
103
  {filter.options?.map((opt) => (
102
104
  <option key={opt.value} value={opt.value}>
@@ -131,16 +133,22 @@ class FilterBar extends BaseCmp<FilterBarProps, FilterBarState> {
131
133
  <div className="owner-section-label">People</div>
132
134
  {['Current User', 'Alice', 'Steve', 'Chloe']
133
135
  .filter((name) =>
134
- name.toLowerCase().includes(this.state.ownerSearchText.toLowerCase())
136
+ name
137
+ .toLowerCase()
138
+ .includes(this.state.ownerSearchText.toLowerCase()),
135
139
  )
136
140
  .map((name) => (
137
141
  <div
138
142
  key={name}
139
- className={`owner-item ${values[filter.name] === name ? 'selected' : ''}`}
143
+ className={`owner-item ${
144
+ values[filter.name] === name ? 'selected' : ''
145
+ }`}
140
146
  data-name={name}
141
147
  onClick={() => this.handleOwnerSelect(name)}
142
148
  >
143
- <span className="owner-check">{values[filter.name] === name ? '✓' : ''}</span>
149
+ <span className="owner-check">
150
+ {values[filter.name] === name ? '✓' : ''}
151
+ </span>
144
152
  <span className="owner-icon">👤</span>
145
153
  {name}
146
154
  </div>
@@ -148,16 +156,22 @@ class FilterBar extends BaseCmp<FilterBarProps, FilterBarState> {
148
156
  <div className="owner-section-label">Departments</div>
149
157
  {['Sales Dept', 'Enterprise Team']
150
158
  .filter((name) =>
151
- name.toLowerCase().includes(this.state.ownerSearchText.toLowerCase())
159
+ name
160
+ .toLowerCase()
161
+ .includes(this.state.ownerSearchText.toLowerCase()),
152
162
  )
153
163
  .map((name) => (
154
164
  <div
155
165
  key={name}
156
- className={`owner-item ${values[filter.name] === name ? 'selected' : ''}`}
166
+ className={`owner-item ${
167
+ values[filter.name] === name ? 'selected' : ''
168
+ }`}
157
169
  data-name={name}
158
170
  onClick={() => this.handleOwnerSelect(name)}
159
171
  >
160
- <span className="owner-check">{values[filter.name] === name ? '✓' : ''}</span>
172
+ <span className="owner-check">
173
+ {values[filter.name] === name ? '✓' : ''}
174
+ </span>
161
175
  <span className="owner-icon">🏢</span>
162
176
  {name}
163
177
  </div>
@@ -120,23 +120,20 @@ class ForecastChart extends BaseCmp<ForecastChartProps, ForecastChartState> {
120
120
  </div>
121
121
 
122
122
  {/* AI基准线 */}
123
- <div
124
- className="chart-ai-line"
125
- style={{ top: `${aiPos}%` }}
126
- >
123
+ <div className="chart-ai-line" style={{ top: `${aiPos}%` }}>
127
124
  <span className="ai-label">✨ AI {aiValue}</span>
128
125
  </div>
129
126
 
130
127
  {/* 柱状图 */}
131
128
  <div className="chart-bars">
132
129
  {columns.map((col, index) => {
133
- const heightPercent = this.getScaleHeight(col.value, quotaValue);
130
+ const heightPercent = this.getScaleHeight(
131
+ col.value,
132
+ quotaValue,
133
+ );
134
134
  return (
135
135
  <div key={index} className="chart-bar">
136
- <span
137
- className="bar-value"
138
- style={{ color: col.color }}
139
- >
136
+ <span className="bar-value" style={{ color: col.color }}>
140
137
  {col.value}
141
138
  </span>
142
139
  <div
@@ -1,6 +1,7 @@
1
1
  export class ForecastChartModel {
2
2
  label: string = '预测看板图表';
3
- description: string = '展示预测数据的堆叠柱状图,包含Closed、Commit、Best Case、Pipeline等';
3
+ description: string =
4
+ '展示预测数据的堆叠柱状图,包含Closed、Commit、Best Case、Pipeline等';
4
5
  iconUrl: string = 'https://custom-widgets.bj.bcebos.com/forecastChart.svg';
5
6
  targetPage: string[] = ['all'];
6
7
  targetDevice: string = 'all';
@@ -1,6 +1,7 @@
1
1
  export class ForecastGridModel {
2
2
  label: string = '预测矩阵表格';
3
- description: string = '展示预测矩阵数据,包含Quota、AI Forecast、Closed、Commit、Best Case、Pipeline等';
3
+ description: string =
4
+ '展示预测矩阵数据,包含Quota、AI Forecast、Closed、Commit、Best Case、Pipeline等';
4
5
  iconUrl: string = 'https://custom-widgets.bj.bcebos.com/forecastGrid.svg';
5
6
  targetPage: string[] = ['all'];
6
7
  targetDevice: string = 'all';
@@ -8,9 +9,36 @@ export class ForecastGridModel {
8
9
  defaultComProps = {
9
10
  title: 'Forecast Grid',
10
11
  rows: [
11
- { name: 'Alice', quota: '$3,000,000', aiForecast: '$2,400,000', closed: '$1,200,000', commit: '$1,800,000', bestCase: '$2,500,000', pipeline: '$8,400,000', coverage: '2.8x' },
12
- { name: 'Steve', quota: '$3,500,000', aiForecast: '$2,800,000', closed: '$800,000', commit: '$2,200,000', bestCase: '$3,000,000', pipeline: '$10,850,000', coverage: '3.1x' },
13
- { name: 'Chloe', quota: '$3,500,000', aiForecast: '$1,600,000', closed: '$1,200,000', commit: '$1,500,000', bestCase: '$2,300,000', pipeline: '$8,750,000', coverage: '2.5x' },
12
+ {
13
+ name: 'Alice',
14
+ quota: '$3,000,000',
15
+ aiForecast: '$2,400,000',
16
+ closed: '$1,200,000',
17
+ commit: '$1,800,000',
18
+ bestCase: '$2,500,000',
19
+ pipeline: '$8,400,000',
20
+ coverage: '2.8x',
21
+ },
22
+ {
23
+ name: 'Steve',
24
+ quota: '$3,500,000',
25
+ aiForecast: '$2,800,000',
26
+ closed: '$800,000',
27
+ commit: '$2,200,000',
28
+ bestCase: '$3,000,000',
29
+ pipeline: '$10,850,000',
30
+ coverage: '3.1x',
31
+ },
32
+ {
33
+ name: 'Chloe',
34
+ quota: '$3,500,000',
35
+ aiForecast: '$1,600,000',
36
+ closed: '$1,200,000',
37
+ commit: '$1,500,000',
38
+ bestCase: '$2,300,000',
39
+ pipeline: '$8,750,000',
40
+ coverage: '2.5x',
41
+ },
14
42
  ],
15
43
  };
16
44
 
@@ -75,12 +75,17 @@ class GapCloser extends BaseCmp<GapCloserProps, GapCloserState> {
75
75
  </div>
76
76
  <div className="gap-card-amount">
77
77
  <span className="amount-value">{card.amount}</span>
78
- <span className="amount-count">· {card.dealCount} deals {card.type === 'rescue' ? 'at risk' : 'to pull in'}</span>
78
+ <span className="amount-count">
79
+ · {card.dealCount} deals{' '}
80
+ {card.type === 'rescue' ? 'at risk' : 'to pull in'}
81
+ </span>
79
82
  </div>
80
83
  <div className="gap-card-desc">{card.description}</div>
81
84
  <button
82
85
  className="gap-card-btn"
83
- style={{ backgroundColor: card.type === 'rescue' ? '#f59e0b' : '#6366f1' }}
86
+ style={{
87
+ backgroundColor: card.type === 'rescue' ? '#f59e0b' : '#6366f1',
88
+ }}
84
89
  onClick={() => onViewDeals?.(card.type)}
85
90
  >
86
91
  View Deals
@@ -1,6 +1,7 @@
1
1
  export class GapCloserModel {
2
2
  label: string = 'Gap Closer';
3
- description: string = '展示Rescue Commit和Upgrade Candidates两类Gap Closer卡片';
3
+ description: string =
4
+ '展示Rescue Commit和Upgrade Candidates两类Gap Closer卡片';
4
5
  iconUrl: string = 'https://custom-widgets.bj.bcebos.com/gapCloser.svg';
5
6
  targetPage: string[] = ['all'];
6
7
  targetDevice: string = 'all';
@@ -12,14 +13,16 @@ export class GapCloserModel {
12
13
  title: 'Rescue Commit',
13
14
  amount: '$1,200,000',
14
15
  dealCount: 3,
15
- description: 'Committed deals not fully AI-backed, still worth rescuing',
16
+ description:
17
+ 'Committed deals not fully AI-backed, still worth rescuing',
16
18
  },
17
19
  {
18
20
  type: 'upgrade' as const,
19
21
  title: 'Upgrade Candidates',
20
22
  amount: '$2,500,000',
21
23
  dealCount: 4,
22
- description: 'High win-rate deals from Open Pipeline worth upgrading to Commit',
24
+ description:
25
+ 'High win-rate deals from Open Pipeline worth upgrading to Commit',
23
26
  },
24
27
  ],
25
28
  };
@@ -9,9 +9,24 @@ export class KpiCardsModel {
9
9
  columns: 2,
10
10
  items: [
11
11
  { label: 'Quota', value: '$10,000,000' },
12
- { label: 'Closed', value: '$3,200,000', subLabel: 'Gap: $6,800,000', subDirection: 'negative' },
13
- { label: 'Forecast', value: '$7,500,000', subLabel: 'Gap: $2,500,000', subDirection: 'negative' },
14
- { label: '✨ AI Forecast', value: '$6,800,000', subLabel: 'Gap: $3,200,000', subDirection: 'negative' },
12
+ {
13
+ label: 'Closed',
14
+ value: '$3,200,000',
15
+ subLabel: 'Gap: $6,800,000',
16
+ subDirection: 'negative',
17
+ },
18
+ {
19
+ label: 'Forecast',
20
+ value: '$7,500,000',
21
+ subLabel: 'Gap: $2,500,000',
22
+ subDirection: 'negative',
23
+ },
24
+ {
25
+ label: '✨ AI Forecast',
26
+ value: '$6,800,000',
27
+ subLabel: 'Gap: $3,200,000',
28
+ subDirection: 'negative',
29
+ },
15
30
  ],
16
31
  };
17
32