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.
- package/README.md +1 -1
- package/dist/neo/neoEnvManager.js +1 -1
- package/dist/neo/neoLogin.js +1 -1
- package/dist/neo/neoRequire.js +1 -1
- package/dist/package.json.js +1 -1
- package/docs//351/200/232/347/224/250/344/273/243/347/220/206/346/216/245/345/217/243/forward.zip +0 -0
- 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
- package/package.json +1 -1
- package/template/antd-custom-cmp-template/package.json +1 -1
- package/template/asset-manage-template/package.json +1 -1
- package/template/echarts-custom-cmp-template/package.json +1 -1
- package/template/empty-custom-cmp-template/package.json +1 -1
- package/template/map-custom-cmp-template/package.json +1 -1
- package/template/neo-bi-cmps/package.json +1 -1
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/index.tsx +17 -10
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/model.ts +47 -6
- package/template/neo-bi-cmps/src/components/filterBar__c/index.tsx +21 -7
- package/template/neo-bi-cmps/src/components/forecastChart__c/index.tsx +6 -9
- package/template/neo-bi-cmps/src/components/forecastChart__c/model.ts +2 -1
- package/template/neo-bi-cmps/src/components/forecastGrid__c/model.ts +32 -4
- package/template/neo-bi-cmps/src/components/gapCloser__c/index.tsx +7 -2
- package/template/neo-bi-cmps/src/components/gapCloser__c/model.ts +6 -3
- package/template/neo-bi-cmps/src/components/kpiCards__c/model.ts +18 -3
- package/template/neo-bi-cmps/src/components/oppList__c/index.tsx +70 -13
- package/template/neo-bi-cmps/src/components/oppList__c/model.ts +50 -4
- package/template/neo-bi-cmps/src/components/pipelineFunnel__c/index.tsx +3 -1
- package/template/neo-bi-cmps/src/components/pipelineFunnel__c/model.ts +28 -4
- package/template/neo-bi-cmps/src/components/stageSwitch__c/index.tsx +21 -6
- package/template/neo-bi-cmps/src/components/stageSwitch__c/model.ts +60 -5
- package/template/neo-bi-cmps/src/components/stageTimeChart__c/model.ts +26 -4
- package/template/neo-custom-cmp-template/package.json +1 -1
- package/template/neo-h5-cmps/package.json +1 -1
- package/template/neo-order-cmps/package.json +1 -1
- package/template/neo-web-entity-grid/package.json +1 -1
- package/template/neo-web-form/package.json +1 -1
- package/template/neo-web-form/src/components/batchAddTable__c/index.tsx +17 -17
- package/template/react-custom-cmp-template/package.json +1 -1
- package/template/react-ts-custom-cmp-template/package.json +1 -1
- package/template/vue2-custom-cmp-template/package.json +1 -1
- package/template/neo-bi-cmps/docs/gartner-pipeline-apis.md +0 -279
- package/template/neo-bi-cmps/docs/gartner-pipeline-prd.md +0 -389
- package/template/neo-bi-cmps/docs/neo-backend-dev/SKILL.md +0 -188
- package/template/neo-bi-cmps/docs/neo-backend-dev/references/01-Trigger/345/274/200/345/217/221.md +0 -183
- 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
- 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
- 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
- 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
- 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
- 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
- package/template/neo-bi-cmps/docs/neo-backend-dev/references/auth-config.md +0 -77
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/deploy_server_script.py +0 -118
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/download_server_script.py +0 -74
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/gen_entity_desc.py +0 -69
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/gen_entitylist.py +0 -87
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/query_crm.py +0 -65
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/uninstall_server_script.py +0 -48
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/update_model_jar.py +0 -49
- package/template/neo-bi-cmps/docs/neo-frontend-dev/SKILL.md +0 -138
- package/template/neo-bi-cmps/docs/neo-frontend-dev/references/auth-config.md +0 -77
- package/template/neo-bi-cmps/docs/neo-frontend-dev/references/component-dev.md +0 -205
- package/template/neo-bi-cmps/docs/neo-frontend-dev/references/entityTable-example.md +0 -167
- package/template/neo-bi-cmps/docs/neo-frontend-dev/references/templates.md +0 -38
- package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/gen_entity_desc.py +0 -69
- package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/gen_entitylist.py +0 -87
- package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/query_crm.py +0 -65
- package/template/neo-bi-cmps/docs/prototype-pipeline-forecasting.html +0 -2453
- 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` | 数值指标组件模板:
|
|
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()
|
|
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)}}};
|
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: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){
|
|
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}`}}};
|
package/dist/neo/neoRequire.js
CHANGED
|
@@ -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",
|
|
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}}};
|
package/dist/package.json.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});var e="1.13.
|
|
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;
|
package/docs//351/200/232/347/224/250/344/273/243/347/220/206/346/216/245/345/217/243/forward.zip
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -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">
|
|
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}
|
|
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">
|
|
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 ${
|
|
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' &&
|
|
168
|
-
|
|
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 =
|
|
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
|
-
{
|
|
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
|
-
{
|
|
19
|
-
|
|
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
|
-
{
|
|
23
|
-
|
|
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) =>
|
|
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
|
|
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 ${
|
|
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">
|
|
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
|
|
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 ${
|
|
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">
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
-
{
|
|
12
|
-
|
|
13
|
-
|
|
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"
|
|
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={{
|
|
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 =
|
|
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:
|
|
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:
|
|
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
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|