update
This commit is contained in:
parent
932138e765
commit
f651d3c91b
499
config/CommercialApiALLAPI.text
Normal file
499
config/CommercialApiALLAPI.text
Normal file
@ -0,0 +1,499 @@
|
||||
swaggerswagger
|
||||
https://api.eshangtech.com/CommercialApi/swagger/docs/v1
|
||||
|
||||
api_key
|
||||
|
||||
Explore
|
||||
CommercialApi
|
||||
AbnormalAuditShow/HideList OperationsExpand Operations
|
||||
post /AbnormalAudit/GetCurrentEarlyWarning
|
||||
查询当日的异常稽核数据
|
||||
|
||||
post /AbnormalAudit/GetMonthEarlyWarning
|
||||
查询月度的异常稽核数据
|
||||
|
||||
AnalysisShow/HideList OperationsExpand Operations
|
||||
post /Analysis/GetANALYSISINSList
|
||||
获取分析说明表列表
|
||||
|
||||
get /Analysis/GetANALYSISINSDetail
|
||||
获取分析说明表明细
|
||||
|
||||
post /Analysis/SynchroANALYSISINS
|
||||
同步分析说明表
|
||||
|
||||
get /Analysis/GetShopRevenue
|
||||
获取门店营收数据
|
||||
|
||||
post /Analysis/GetShopRevenue
|
||||
获取门店营收数据
|
||||
|
||||
get /Analysis/GetShopMerchant
|
||||
获取门店商家信息
|
||||
|
||||
post /Analysis/GetShopMerchant
|
||||
获取门店商家信息
|
||||
|
||||
post /Analysis/SolidTransactionAnalysis
|
||||
生成时段客单交易分析数据
|
||||
|
||||
post /Analysis/GetTransactionAnalysis
|
||||
获取时段客单交易分析数据
|
||||
|
||||
get /Analysis/TranslateSentence
|
||||
解析语义内容中的关键字信息
|
||||
|
||||
get /Analysis/GetMapConfigByProvinceCode
|
||||
获取地图参数配置
|
||||
|
||||
post /Analysis/GetServerpartTypeAnalysis
|
||||
获取服务区分类定级情况
|
||||
|
||||
get /Analysis/verifyWXCode
|
||||
解析企业微信接口验证参数
|
||||
|
||||
post /Analysis/verifyWXCode
|
||||
解析企业微信接口验证参数
|
||||
|
||||
BaseInfoShow/HideList OperationsExpand Operations
|
||||
get /BaseInfo/GetShopCountList
|
||||
获取服务区门店商家数量列表(根据省份、服务区、区域、统计日期查询)
|
||||
|
||||
post /BaseInfo/GetShopCountList
|
||||
获取服务区门店商家数量列表(查询条件对象)
|
||||
|
||||
get /BaseInfo/RecordShopCount
|
||||
记录服务区门店商家数量
|
||||
|
||||
post /BaseInfo/RecordShopCount
|
||||
记录服务区门店商家数量
|
||||
|
||||
get /BaseInfo/RecordProvinceShopCount
|
||||
记录全省服务区门店商家数量
|
||||
|
||||
post /BaseInfo/RecordProvinceShopCount
|
||||
记录全省服务区门店商家数量
|
||||
|
||||
get /BaseInfo/GetBusinessTradeList
|
||||
获取经营业态列表(根据经营品牌子父级内码或者名称查询)
|
||||
|
||||
post /BaseInfo/GetBusinessTradeList
|
||||
获取经营业态列表(查询条件对象)
|
||||
|
||||
get /BaseInfo/GetBrandAnalysis
|
||||
服务区经营品牌分析
|
||||
|
||||
get /BaseInfo/GetSPRegionList
|
||||
获取片区列表
|
||||
|
||||
get /BaseInfo/GetServerpartList
|
||||
获取服务区列表
|
||||
|
||||
get /BaseInfo/GetServerpartInfo
|
||||
获取服务区基本信息
|
||||
|
||||
get /BaseInfo/GetServerInfoTree
|
||||
绑定区域服务区基本信息树
|
||||
|
||||
post /BaseInfo/GetServerpartServiceSummary
|
||||
获取服务区基础设施汇总数据
|
||||
|
||||
post /BaseInfo/GetBrandStructureAnalysis
|
||||
获取经营品牌结构分析
|
||||
|
||||
BigDataShow/HideList OperationsExpand Operations
|
||||
get /Revenue/GetBayonetEntryList
|
||||
服务区入区车流分析
|
||||
|
||||
get /Revenue/GetBayonetSTAList
|
||||
获取车辆停留时长分析
|
||||
|
||||
get /Revenue/GetBayonetOAList
|
||||
获取车辆归属地分析
|
||||
|
||||
get /Revenue/GetBayonetProvinceOAList
|
||||
获取车辆省份地市归属地分析
|
||||
|
||||
get /Revenue/GetSPBayonetList
|
||||
获取服务区车流量分析
|
||||
|
||||
get /Revenue/GetBayonetRankList
|
||||
获取服务区车流量排行
|
||||
|
||||
get /Revenue/GetAvgBayonetAnalysis
|
||||
获取服务区平均车流量分析
|
||||
|
||||
get /Revenue/GetProvinceAvgBayonetAnalysis
|
||||
获取全省平均车流量分析
|
||||
|
||||
get /Revenue/GetBayonetSTAnalysis
|
||||
获取服务区车辆时段停留时长分析
|
||||
|
||||
get /BigData/GetMonthAnalysis
|
||||
获取月度车流分析数据
|
||||
|
||||
get /BigData/GetProvinceMonthAnalysis
|
||||
获取全省月度车流分析数据
|
||||
|
||||
get /BigData/GetBayonetWarning
|
||||
获取车流预警数据
|
||||
|
||||
get /BigData/GetHolidayBayonetWarning
|
||||
获取节日车流预警数据
|
||||
|
||||
get /BigData/GetBayonetGrowthAnalysis
|
||||
获取当日服务区车流量分析
|
||||
|
||||
get /BigData/GetBayonetCompare
|
||||
获取服务区车流量同比分析
|
||||
|
||||
get /BigData/GetHolidayCompare
|
||||
获取节日服务区平均入区流量对比数据
|
||||
|
||||
get /BigData/GetBayonetOAAnalysis
|
||||
获取日均车流归属地数据分析
|
||||
|
||||
get /BigData/GetDateAnalysis
|
||||
获取日度车流分析数据
|
||||
|
||||
get /BigData/CorrectBayonet
|
||||
根据选择的时间范围补充卡口缺失的数据
|
||||
|
||||
get /BigData/JudgeBayonet
|
||||
判断当前月份是否有有效车流数据
|
||||
|
||||
post /BigData/CorrectBayonetFlow
|
||||
更新车流模拟值
|
||||
|
||||
get /BigData/GetBayonetOwnerAHTreeList
|
||||
获取车辆归属地统计汇总列表(树形)
|
||||
|
||||
get /BigData/GetProvinceVehicleTreeList
|
||||
获取各省入区车辆统计表(树形)
|
||||
|
||||
get /BigData/GetProvinceVehicleDetail
|
||||
获取各省入区车辆统计表明细
|
||||
|
||||
get /BigData/GetBaiDuTrafficInfo
|
||||
获取实况车流信息(百度API)
|
||||
|
||||
post /BigData/GetChargeStationList
|
||||
获取安徽驿达服务区充电桩数据
|
||||
|
||||
post /BigData/SyncChargeStationToRedis
|
||||
缓存安徽驿达服务区充电桩数据
|
||||
|
||||
post /BigData/GetCurBusyRank
|
||||
获取服务区繁忙排行
|
||||
|
||||
post /BigData/GetRevenueTrendChart
|
||||
获取今日营收趋势图
|
||||
|
||||
post /BigData/GetEnergyRevenueInfo
|
||||
获取当日全业态营收数据
|
||||
|
||||
post /BigData/GetOilPriceList
|
||||
获取安徽驿达服务区油价数据
|
||||
|
||||
BudgetShow/HideList OperationsExpand Operations
|
||||
post /Budget/GetBUDGETPROJECT_AHList
|
||||
获取安徽财务预算表列表
|
||||
|
||||
get /Budget/GetBUDGETPROJECT_AHDetail
|
||||
获取安徽财务预算表明细
|
||||
|
||||
post /Budget/SynchroBUDGETPROJECT_AH
|
||||
同步安徽财务预算表
|
||||
|
||||
get /Budget/DeleteBUDGETPROJECT_AH
|
||||
删除安徽财务预算表
|
||||
|
||||
post /Budget/DeleteBUDGETPROJECT_AH
|
||||
删除安徽财务预算表
|
||||
|
||||
get /Budget/GetBudgetProjectDetailList
|
||||
获取月度安徽财务预算明细表数据
|
||||
|
||||
get /Budget/GetBudgetMainShow
|
||||
获取安徽财务预算表明细
|
||||
|
||||
BusinessProcessShow/HideList OperationsExpand Operations
|
||||
get /BusinessProcess/GetBusinessProcessList
|
||||
获取业务审批列表
|
||||
|
||||
CommonShow/HideList OperationsExpand Operations
|
||||
get /Common/GetDecryptString
|
||||
解密字符串
|
||||
|
||||
get /Common/GetEncryptString
|
||||
加密字符串
|
||||
|
||||
ContractShow/HideList OperationsExpand Operations
|
||||
get /Contract/GetContractAnalysis
|
||||
获取经营合同分析
|
||||
|
||||
get /Contract/GetMerchantAccountSplit
|
||||
获取经营商户应收拆分数据
|
||||
|
||||
get /Contract/GetMerchantAccountDetail
|
||||
获取经营商户应收拆分明细数据
|
||||
|
||||
CustomerShow/HideList OperationsExpand Operations
|
||||
get /Customer/GetCustomerRatio
|
||||
获取客群分析占比
|
||||
|
||||
get /Customer/GetCustomerConsumeRatio
|
||||
获取客群消费能力占比
|
||||
|
||||
get /Customer/GetCustomerAgeRatio
|
||||
获取客群年龄层次占比
|
||||
|
||||
get /Customer/GetCustomerGroupRatio
|
||||
获取客群特征分析
|
||||
|
||||
get /Customer/GetAnalysisDescList
|
||||
获取客群分析说明表列表
|
||||
|
||||
get /Customer/GetAnalysisDescDetail
|
||||
获取客群分析说明表明细
|
||||
|
||||
get /Customer/GetCustomerSaleRatio
|
||||
获取客群消费偏好数据
|
||||
|
||||
ExamineShow/HideList OperationsExpand Operations
|
||||
post /Examine/GetEXAMINEList
|
||||
获取考核管理表列表
|
||||
|
||||
get /Examine/GetEXAMINEDetail
|
||||
获取考核管理表明细
|
||||
|
||||
post /Examine/GetMEETINGList
|
||||
获取晨会管理表列表
|
||||
|
||||
get /Examine/GetMEETINGDetail
|
||||
获取晨会管理表明细
|
||||
|
||||
post /Examine/GetPATROLList
|
||||
获取日常巡检表列表
|
||||
|
||||
get /Examine/GetPATROLDetail
|
||||
获取日常巡检表明细
|
||||
|
||||
get /Examine/WeChat_GetExamineList
|
||||
获取小程序考核列表
|
||||
|
||||
get /Examine/WeChat_GetExamineDetail
|
||||
获取小程序考核明细数据
|
||||
|
||||
get /Examine/WeChat_GetPatrolList
|
||||
获取小程序日常巡检列表
|
||||
|
||||
get /Examine/WeChat_GetMeetingList
|
||||
获取小程序晨会列表
|
||||
|
||||
get /Examine/GetPatrolAnalysis
|
||||
获取日常巡检分析数据
|
||||
|
||||
get /Examine/GetExamineAnalysis
|
||||
获取月度考核结果
|
||||
|
||||
get /Examine/GetExamineResultList
|
||||
获取驿达看板-首页考核列表, 可以按照全省、片区、服务区查询结果
|
||||
|
||||
get /Examine/GetPatrolResultList
|
||||
获取驿达看板-首页巡查列表, 可以按照全省、片区、服务区查询结果
|
||||
|
||||
post /Examine/GetEvaluateResList
|
||||
获取考评考核数据
|
||||
|
||||
RevenueShow/HideList OperationsExpand Operations
|
||||
get /Revenue/GetRevenuePushList
|
||||
获取营收推送数据表列表
|
||||
|
||||
get /Revenue/GetSummaryRevenue
|
||||
获取营收推送汇总数据
|
||||
|
||||
get /Revenue/GetSummaryRevenueMonth
|
||||
获取月度营收推送汇总数据
|
||||
|
||||
get /Revenue/GetWechatPushSalesList
|
||||
获取营收推送单品销售排行【甘肃营收推送】
|
||||
|
||||
get /Revenue/GetUnUpLoadShops
|
||||
查询服务区未上传结账信息的门店列表
|
||||
|
||||
get /Revenue/GetServerpartBrand
|
||||
获取服务区品牌营收
|
||||
|
||||
get /Revenue/GetServerpartEndAccountList
|
||||
查询服务区结账数据列表
|
||||
|
||||
get /Revenue/GetShopEndAccountList
|
||||
查询门店结账数据列表
|
||||
|
||||
get /Revenue/GetBudgetExpenseList
|
||||
获取预算费用表列表(GET)
|
||||
|
||||
post /Revenue/GetBudgetExpenseList
|
||||
获取预算费用表列表(POST)
|
||||
|
||||
get /Revenue/GetRevenueBudget
|
||||
获取计划营收数据
|
||||
|
||||
get /Revenue/GetProvinceRevenueBudget
|
||||
获取全省计划营收分析
|
||||
|
||||
get /Revenue/GetMobileShare
|
||||
获取移动支付分账数据
|
||||
|
||||
get /Revenue/GetMallDeliver
|
||||
获取商城配送数据
|
||||
|
||||
get /Revenue/GetTransactionAnalysis
|
||||
获取服务区客单交易分析
|
||||
|
||||
get /Revenue/GetTransactionTimeAnalysis
|
||||
获取服务区时段消费分析
|
||||
|
||||
get /Revenue/GetTransactionConvert
|
||||
获取消费转化对比分析
|
||||
|
||||
get /Revenue/GetBusinessTradeRevenue
|
||||
获取业态营收占比
|
||||
|
||||
get /Revenue/GetBusinessTradeLevel
|
||||
获取业态消费水平占比
|
||||
|
||||
get /Revenue/GetBusinessBrandLevel
|
||||
获取品牌消费水平占比
|
||||
|
||||
get /Revenue/GetRevenueCompare
|
||||
获取营收同比数据
|
||||
|
||||
get /Revenue/GetRevenueTrend
|
||||
获取营收趋势图
|
||||
|
||||
get /Revenue/GetRevenueReport
|
||||
获取服务区经营报表
|
||||
|
||||
get /Revenue/GetRevenueReportDetil
|
||||
获取服务区经营报表详情
|
||||
|
||||
get /Revenue/GetSalableCommodity
|
||||
获取商超畅销商品
|
||||
|
||||
get /Revenue/GetSPRevenueRank
|
||||
获取近日服务区营收排行
|
||||
|
||||
get /Revenue/GetRevenueYOY
|
||||
获取每日营收同比数据
|
||||
|
||||
get /Revenue/GetHolidayCompare
|
||||
获取节日营收同比数据
|
||||
|
||||
get /Revenue/GetAccountReceivable
|
||||
获取营收统计明细数据
|
||||
|
||||
get /Revenue/GetCurRevenue
|
||||
获取实时营收交易数据
|
||||
|
||||
get /Revenue/GetShopCurRevenue
|
||||
获取实时门店营收交易数据
|
||||
|
||||
get /Revenue/GetLastSyncDateTime
|
||||
获取最新的同步日期
|
||||
|
||||
get /Revenue/GetHolidayAnalysis
|
||||
获取节日营收数据对比分析
|
||||
|
||||
get /Revenue/GetHolidayAnalysisBatch
|
||||
获取多个服务区节日营收数据对比分析(批量)
|
||||
|
||||
get /Revenue/GetHolidaySPRAnalysis
|
||||
获取节假日区域对客分析
|
||||
|
||||
get /Revenue/GetHolidayDailyAnalysis
|
||||
获取节假日各类项目所有天数对客分析
|
||||
|
||||
get /Revenue/GetServerpartINCAnalysis
|
||||
获取服务区营收增幅分析
|
||||
|
||||
get /Revenue/GetShopINCAnalysis
|
||||
获取门店营收增幅分析
|
||||
|
||||
get /Revenue/GetMonthlyBusinessAnalysis
|
||||
获取月度经营增幅分析
|
||||
|
||||
get /Revenue/GetMonthlySPINCAnalysis
|
||||
获取服务区营收增幅分析
|
||||
|
||||
get /Revenue/GetMonthINCAnalysis
|
||||
月度服务区门店营收对比分析
|
||||
|
||||
get /Revenue/GetMonthINCAnalysisSummary
|
||||
汇总月度经营项目预警数值
|
||||
|
||||
get /Revenue/StorageMonthINCAnalysis
|
||||
固化月度经营预警数据
|
||||
|
||||
get /Revenue/GetShopSABFIList
|
||||
月度服务区门店商业适配指数(SABFI)
|
||||
|
||||
get /Revenue/GetShopMonthSABFIList
|
||||
获取门店每月商业适配指数(SABFI)
|
||||
|
||||
get /Revenue/GetTransactionDetailList
|
||||
获取实时交易明细
|
||||
|
||||
get /Revenue/GetHolidayRevenueRatio
|
||||
获取节日营收占比
|
||||
|
||||
post /Revenue/GetBusinessRevenueList
|
||||
获取云南24年经营数据分析
|
||||
|
||||
post /Revenue/GetMonthlyBusinessRevenue
|
||||
获取云南月度经营数据分析
|
||||
|
||||
get /Revenue/GetCompanyRevenueReport
|
||||
按照安徽驿达子公司运营的门店返回经营数据报表
|
||||
|
||||
SuggestionShow/HideList OperationsExpand Operations
|
||||
get /Suggestion/GetMemberUnreadData
|
||||
获取用户投诉建议浏览日志
|
||||
|
||||
get /Suggestion/RecordReadingLog
|
||||
记录用户浏览投诉建议的日志
|
||||
|
||||
post /Suggestion/RecordReadingLog
|
||||
记录用户浏览投诉建议的日志
|
||||
|
||||
SupplyChainShow/HideList OperationsExpand Operations
|
||||
post /SupplyChain/GetMemberDashboard
|
||||
获取会员总览数据统计
|
||||
|
||||
post /SupplyChain/GetSupplierTypeList
|
||||
获取供应商分类情况
|
||||
|
||||
post /SupplyChain/GetSupplierList
|
||||
获取供应商列表
|
||||
|
||||
post /SupplyChain/GetMallOrderSummary
|
||||
获取线上商城统计分析数据
|
||||
|
||||
post /SupplyChain/GetWelFareSummary
|
||||
获取员工福利汇总数据
|
||||
|
||||
UserBehaviorShow/HideList OperationsExpand Operations
|
||||
post /UserBehavior/GetBEHAVIORRECORDList
|
||||
获取用户行为记录表列表
|
||||
|
||||
get /UserBehavior/GetBEHAVIORRECORDDetail
|
||||
获取用户行为记录表明细
|
||||
|
||||
post /UserBehavior/SynchroBEHAVIORRECORD
|
||||
同步用户行为记录表
|
||||
|
||||
get /UserBehavior/AddUserBehavior
|
||||
记录用户行为操作记录
|
||||
|
||||
[ base url: /CommercialApi , api version: v1 ]
|
||||
4377
config/allApi.text
Normal file
4377
config/allApi.text
Normal file
File diff suppressed because it is too large
Load Diff
25647
config/api_log_2026-02-14T03-42-31-135Z.json
Normal file
25647
config/api_log_2026-02-14T03-42-31-135Z.json
Normal file
File diff suppressed because it is too large
Load Diff
83604
config/api_log_2026-02-14T05-49-12-310Z.json
Normal file
83604
config/api_log_2026-02-14T05-49-12-310Z.json
Normal file
File diff suppressed because one or more lines are too long
103748
config/api_log_2026-02-14T06-25-47-082Z.json
Normal file
103748
config/api_log_2026-02-14T06-25-47-082Z.json
Normal file
File diff suppressed because it is too large
Load Diff
83867
config/api_log_2026-02-14T06-41-08-871Z.json
Normal file
83867
config/api_log_2026-02-14T06-41-08-871Z.json
Normal file
File diff suppressed because it is too large
Load Diff
50804
config/api_log_2026-02-14T06-51-17-090Z.json
Normal file
50804
config/api_log_2026-02-14T06-51-17-090Z.json
Normal file
File diff suppressed because it is too large
Load Diff
84301
config/api_log_2026-02-14T07-00-31-841Z.json
Normal file
84301
config/api_log_2026-02-14T07-00-31-841Z.json
Normal file
File diff suppressed because it is too large
Load Diff
48839
config/api_mapping.json
Normal file
48839
config/api_mapping.json
Normal file
File diff suppressed because it is too large
Load Diff
8649
config/api_mapping.md
Normal file
8649
config/api_mapping.md
Normal file
File diff suppressed because it is too large
Load Diff
104785
config/api_mapping_merged.json
Normal file
104785
config/api_mapping_merged.json
Normal file
File diff suppressed because it is too large
Load Diff
112348
config/api_mapping_merged.md
Normal file
112348
config/api_mapping_merged.md
Normal file
File diff suppressed because it is too large
Load Diff
1249
config/menu.json
Normal file
1249
config/menu.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -55,94 +55,13 @@ export default [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/ahjgMenu',
|
||||
name: 'ahjgMenu',
|
||||
routes: [
|
||||
{
|
||||
path: 'busniessproject',
|
||||
name: 'project.list',
|
||||
component: './BussinessProject/list',
|
||||
},
|
||||
{
|
||||
path: 'supplierList',
|
||||
name: 'supplier',
|
||||
component: './merchantManagement/supplier/Management/index',
|
||||
},
|
||||
{
|
||||
path: 'servicePartAudit',
|
||||
name: 'servicePartAudit',
|
||||
component: './ahjgPage/servicePartAudit/index',
|
||||
},
|
||||
// 商品采购查询
|
||||
{
|
||||
path: 'shopProcurement',
|
||||
name: 'shopProcurement',
|
||||
component: './ahjgPage/shopProcurement/index',
|
||||
},
|
||||
// 调拨流程查询
|
||||
{
|
||||
path: 'transferProcess',
|
||||
name: 'transferProcess',
|
||||
component: './ahjgPage/transferProcess/index',
|
||||
},
|
||||
// 退货流程查询
|
||||
{
|
||||
path: 'returnProcess',
|
||||
name: 'returnProcess',
|
||||
component: './ahjgPage/returnProcess/index',
|
||||
},
|
||||
// 领用流程查询
|
||||
{
|
||||
path: 'receivingProcess',
|
||||
name: 'receivingProcess',
|
||||
component: './ahjgPage/receivingProcess/index',
|
||||
},
|
||||
// 采购入库统计表 入库退货统计表
|
||||
{
|
||||
path: 'purchaseReceiving',
|
||||
name: 'purchaseReceiving',
|
||||
component: './ahjgPage/purchaseReceiving/index',
|
||||
},
|
||||
// 商品退货统计表
|
||||
{
|
||||
path: 'productReturn',
|
||||
name: 'productReturn',
|
||||
component: './ahjgPage/productReturn/index',
|
||||
},
|
||||
// 库存信息查询
|
||||
{
|
||||
path: 'inventoryInformation',
|
||||
name: 'inventoryInformation',
|
||||
component: './ahjgPage/inventoryInformation/index',
|
||||
},
|
||||
// 进销存类别报表
|
||||
{
|
||||
path: 'InventoryCategory',
|
||||
name: 'InventoryCategory',
|
||||
component: './ahjgPage/InventoryCategory/index',
|
||||
},
|
||||
// 进销存明细报表
|
||||
{
|
||||
path: 'InventoryDetails',
|
||||
name: 'InventoryDetails',
|
||||
component: './ahjgPage/InventoryDetails/index',
|
||||
},
|
||||
// 一品多码进销存报表
|
||||
{
|
||||
path: 'oneProductMultipleSizes',
|
||||
name: 'oneProductMultipleSizes',
|
||||
component: './ahjgPage/oneProductMultipleSizes/index',
|
||||
},
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/busniessproject',
|
||||
name: 'busniessproject',
|
||||
icon: 'FileProtectOutlined',
|
||||
routes: [
|
||||
{
|
||||
// 经营合同查询
|
||||
path: 'contract',
|
||||
name: 'contract.list',
|
||||
component: './contract/list',
|
||||
@ -1422,21 +1341,25 @@ export default [
|
||||
component: './merchantManagement/reports/RevenueSummary',
|
||||
},
|
||||
{
|
||||
// 我的商品云库
|
||||
path: 'commodity',
|
||||
name: 'commodity',
|
||||
component: './merchantManagement/commodity/BaseInfo',
|
||||
},
|
||||
{
|
||||
// 申请商品上架
|
||||
path: 'upSaleCommodity',
|
||||
name: 'upSaleCommodity',
|
||||
component: './merchantManagement/commodity/Update',
|
||||
},
|
||||
{
|
||||
// 商品类别设置
|
||||
path: 'category/:id',
|
||||
name: 'category',
|
||||
component: './merchantManagement/category',
|
||||
},
|
||||
{
|
||||
// 在售商品查询
|
||||
path: 'onsale',
|
||||
name: 'onsale',
|
||||
component: './merchantManagement/commodity/OnSale',
|
||||
@ -1462,11 +1385,13 @@ export default [
|
||||
component: './merchantManagement/Workplace/index',
|
||||
},
|
||||
{
|
||||
// 供应商管理
|
||||
path: 'supplier/list',
|
||||
name: 'supplier',
|
||||
component: './merchantManagement/supplier/Management/index',
|
||||
},
|
||||
{
|
||||
// 供应商资质
|
||||
path: 'supplier/qualifications',
|
||||
name: 'qualifications',
|
||||
component: './merchantManagement/supplier/Qualifications',
|
||||
@ -1497,11 +1422,13 @@ export default [
|
||||
component: './merchantManagement/assessment/assessmentSummary/index',
|
||||
},
|
||||
{
|
||||
// 我的门店管理
|
||||
path: 'shops',
|
||||
name: 'shops',
|
||||
component: './merchantManagement/Shops',
|
||||
},
|
||||
{
|
||||
// 经营品牌管理
|
||||
path: 'brand',
|
||||
name: 'brand',
|
||||
component: './merchantManagement/brand',
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ant-design-pro",
|
||||
"version": "4.5.82",
|
||||
"version": "4.5.85",
|
||||
"private": true,
|
||||
"description": "An out-of-box UI solution for enterprise applications",
|
||||
"scripts": {
|
||||
|
||||
105
scripts/add_api_names.js
Normal file
105
scripts/add_api_names.js
Normal file
@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 从多个 Swagger API 文件提取接口中文名称,写入 api_mapping_merged.md
|
||||
* 规则:根据完整URL中的关键字匹配不同的API源文件
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// API 源配置:每项包含文件名和URL中需要匹配的关键字
|
||||
const API_SOURCES = [
|
||||
{ file: 'allApi.text', urlKeyword: 'EShangApiMain' },
|
||||
{ file: 'CommercialApiALLAPI.text', urlKeyword: 'CommercialApi' },
|
||||
];
|
||||
|
||||
// 1. 解析所有 API 源文件,构建 { urlKeyword: { exact: {}, lower: {} } } 映射
|
||||
function parseApiFile(filePath) {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const lines = content.split('\n').map(l => l.trim()).filter(l => l);
|
||||
const exact = {};
|
||||
const lower = {};
|
||||
|
||||
for (let i = 0; i < lines.length - 1; i++) {
|
||||
const match = lines[i].match(/^(get|post)\s+(\/\S+)$/i);
|
||||
if (match) {
|
||||
const apiPath = match[2];
|
||||
const nextLine = lines[i + 1];
|
||||
if (nextLine && !/^(get|post)\s+\//i.test(nextLine)) {
|
||||
if (!exact[apiPath]) exact[apiPath] = nextLine;
|
||||
const lp = apiPath.toLowerCase();
|
||||
if (!lower[lp]) lower[lp] = nextLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { exact, lower };
|
||||
}
|
||||
|
||||
const sourceMaps = {};
|
||||
for (const src of API_SOURCES) {
|
||||
const filePath = path.join(__dirname, '..', 'config', src.file);
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.log(`⚠️ 文件不存在,跳过: ${src.file}`);
|
||||
continue;
|
||||
}
|
||||
sourceMaps[src.urlKeyword] = parseApiFile(filePath);
|
||||
console.log(`📖 ${src.file} 解析完成: ${Object.keys(sourceMaps[src.urlKeyword].exact).length} 个接口映射 (关键字: ${src.urlKeyword})`);
|
||||
}
|
||||
|
||||
// 2. 处理 api_mapping_merged.md
|
||||
const mdPath = path.join(__dirname, '..', 'config', 'api_mapping_merged.md');
|
||||
const mdContent = fs.readFileSync(mdPath, 'utf-8');
|
||||
const lines = mdContent.split('\n');
|
||||
|
||||
const newLines = [];
|
||||
let addedCount = 0;
|
||||
let skippedCount = 0;
|
||||
let notFoundCount = 0;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
newLines.push(lines[i]);
|
||||
|
||||
// 查找 - **接口**: `/xxx/xxx` 这一行
|
||||
const interfaceMatch = lines[i].match(/^- \*\*接口\*\*:\s*`([^`]+)`/);
|
||||
if (!interfaceMatch) continue;
|
||||
|
||||
const apiPath = interfaceMatch[1];
|
||||
|
||||
// 检查下一行是否已有接口名称(避免重复)
|
||||
if (i + 1 < lines.length && lines[i + 1].includes('**接口名称**')) {
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 往后找完整URL行,确定匹配哪个API源
|
||||
let matchedKeyword = null;
|
||||
for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) {
|
||||
if (lines[j].includes('**完整URL**')) {
|
||||
for (const src of API_SOURCES) {
|
||||
if (lines[j].includes(src.urlKeyword)) {
|
||||
matchedKeyword = src.urlKeyword;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (lines[j].match(/^####\s/)) break;
|
||||
}
|
||||
|
||||
if (!matchedKeyword || !sourceMaps[matchedKeyword]) continue;
|
||||
|
||||
// 在对应映射中查找: 先精确匹配,再忽略大小写匹配
|
||||
const maps = sourceMaps[matchedKeyword];
|
||||
const chineseName = maps.exact[apiPath] || maps.lower[apiPath.toLowerCase()];
|
||||
if (chineseName) {
|
||||
newLines.push(`- **接口名称**: \`${chineseName}\``);
|
||||
addedCount++;
|
||||
} else {
|
||||
notFoundCount++;
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync(mdPath, newLines.join('\n'), 'utf-8');
|
||||
|
||||
console.log(`\n✅ 完成!`);
|
||||
console.log(` 已添加接口名称: ${addedCount}`);
|
||||
console.log(` 已跳过(已有名称): ${skippedCount}`);
|
||||
console.log(` 未找到匹配: ${notFoundCount}`);
|
||||
320
scripts/annotate_params.js
Normal file
320
scripts/annotate_params.js
Normal file
@ -0,0 +1,320 @@
|
||||
/**
|
||||
* 为 api_mapping_merged.md 中的请求体 JSON 字段添加中文注释
|
||||
* 直接在 MD 文件上操作,在每个 JSON key 后面追加 // 注释
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 字段名 → 中文注释 映射表
|
||||
const FIELD_COMMENTS = {
|
||||
// ===== 通用分页/排序 =====
|
||||
'pageIndex': '页码索引',
|
||||
'PageIndex': '页码索引',
|
||||
'pageindex': '页码索引',
|
||||
'pageSize': '每页条数',
|
||||
'pagesize': '每页条数',
|
||||
'current': '当前页码',
|
||||
'SortStr': '排序字段',
|
||||
'sortstr': '排序字段',
|
||||
'sortStr': '排序字段',
|
||||
'keyWord': '搜索关键词',
|
||||
'SearchParameter': '查询参数对象',
|
||||
'searchParameter': '查询参数对象',
|
||||
|
||||
// ===== 关键词搜索对象 =====
|
||||
'Key': '搜索字段名',
|
||||
'Value': '搜索值',
|
||||
'key': '搜索字段名',
|
||||
'value': '搜索值',
|
||||
|
||||
// ===== 用户/员工信息 =====
|
||||
'USER_ID': '用户ID',
|
||||
'USER_NAME': '用户姓名',
|
||||
'USER_PASSPORT': '用户账号',
|
||||
'USER_MOBILEPHONE': '用户手机号',
|
||||
'USER_LOGINIP': '登录IP地址',
|
||||
'USER_LOGINPLACE': '登录地点',
|
||||
'USER_STATUS': '用户状态',
|
||||
'USER_PATTERN': '用户模式',
|
||||
'USER_PROVINCE': '用户所属省份',
|
||||
'UserTypeIds': '用户类型ID集合',
|
||||
'STAFF_ID': '员工ID',
|
||||
'STAFF_NAME': '员工姓名',
|
||||
'Staff_ID': '员工ID',
|
||||
|
||||
// ===== 行为记录 =====
|
||||
'BEHAVIORRECORD_ROUT': '行为记录路由',
|
||||
'BEHAVIORRECORD_ROUTNAME': '行为记录路由名称',
|
||||
'BEHAVIORRECORD_TIME': '行为记录时间',
|
||||
'BEHAVIORRECORD_TYPE': '行为记录类型',
|
||||
'BEHAVIORRECORD_TIME_Start': '行为记录开始时间',
|
||||
'BEHAVIORRECORD_TIME_End': '行为记录结束时间',
|
||||
|
||||
// ===== 企业/商户 =====
|
||||
'OWNERUNIT_ID': '业主单位ID',
|
||||
'OWNERUNIT_NAME': '业主单位名称',
|
||||
'OWNERUNIT_NATURE': '业主单位性质',
|
||||
'OWNER_NAME': '业主名称',
|
||||
'BUSINESSMAN_ID': '商户ID',
|
||||
'BUSINESS_ID': '业务ID',
|
||||
'PROVINCE_CODE': '省份编码',
|
||||
'PROVINCE_CODES': '省份编码集合',
|
||||
'OPERATE_DATE': '操作日期',
|
||||
'OPERATE_DATE_Start': '操作起始日期',
|
||||
'OPERATE_DATE_End': '操作结束日期',
|
||||
|
||||
// ===== 服务商/门店 =====
|
||||
'SERVERPART_ID': '服务商ID',
|
||||
'SERVERPART_IDS': '服务商ID集合',
|
||||
'ServerpartIds': '服务商ID集合',
|
||||
'Serverpart_IDS': '服务商ID集合',
|
||||
'ServerpartId': '服务商ID',
|
||||
'SERVERPARTSHOP_ID': '门店ID',
|
||||
'SERVERPARTSHOP_IDS': '门店ID集合',
|
||||
'ServerpartShop_ID': '门店ID',
|
||||
'ServerpartShopId': '门店ID',
|
||||
'SERVERPARTSTATICTYPE_ID': '门店静态类型ID',
|
||||
'SHOPCODES': '门店编码集合',
|
||||
'SHOPTRADE': '门店业态',
|
||||
'SPREGIONTYPE_IDS': '区域类型ID集合',
|
||||
|
||||
// ===== 消息 =====
|
||||
'RECSTAFF_ID': '接收人员工ID',
|
||||
'MESSAGE_STATE': '消息状态',
|
||||
'NOTICEINFO_STATE': '公告状态',
|
||||
'NOTICEINFO_TYPES': '公告类型集合',
|
||||
|
||||
// ===== 合同相关 =====
|
||||
'REGISTERCOMPACT_ID': '合同ID',
|
||||
'REGISTERCOMPACT_HOSTID': '主合同ID',
|
||||
'COMPACT_STATE': '合同状态',
|
||||
'COMPACT_TYPE': '合同类型',
|
||||
'COMPACT_STARTDATE': '合同开始日期',
|
||||
'COMPACT_ENDDATE': '合同结束日期',
|
||||
'COMPACT_DETAILS': '合同详情',
|
||||
'AbnormalContract': '是否异常合同',
|
||||
|
||||
// ===== 项目相关 =====
|
||||
'BUSINESSPROJECT_ID': '经营项目ID',
|
||||
'BUSINESSPROJECT_IDS': '经营项目ID集合',
|
||||
'BUSINESSPROJECTSPLIT_STATE': '项目拆分状态',
|
||||
'PROJECT_VALID': '项目有效状态',
|
||||
'PROJECT_STARTDATE': '项目开始日期',
|
||||
'PROJECT_ENDDATE': '项目结束日期',
|
||||
'ProjectStateSearch': '项目状态搜索',
|
||||
'ProjectTypeSearch': '项目类型搜索',
|
||||
'CalcAccumulate': '是否计算累计',
|
||||
'ShowAccount': '是否显示账户',
|
||||
'ShowRevenue': '是否显示营收',
|
||||
'ShowShare': '是否显示分润',
|
||||
|
||||
// ===== 门店费用 =====
|
||||
'SHOPEXPENSE_ID': '门店费用ID',
|
||||
'SHOPEXPENSE_STATE': '门店费用状态',
|
||||
'SHOPEXPENSE_TYPE': '门店费用类型',
|
||||
|
||||
// ===== 提成/分润 =====
|
||||
'SHOPROYALTY_ID': '提成方案ID',
|
||||
'ShopRoyaltyId': '提成方案ID',
|
||||
'SHOPROYALTYDETAIL_STATE': '提成明细状态',
|
||||
|
||||
// ===== 商品相关 =====
|
||||
'COMMODITY_STATE': '商品状态',
|
||||
'COMMODITY_TYPE': '商品类型',
|
||||
|
||||
// ===== 供应商 =====
|
||||
'SUPPLIER_ID': '供应商ID',
|
||||
'SUPPLIER_IDS': '供应商ID集合',
|
||||
'SUPPLIER_STATE': '供应商状态',
|
||||
'SUPPLIEREVALUATION_ID': '供应商评价ID',
|
||||
|
||||
// ===== 资质 =====
|
||||
'QUALIFICATION_ID': '资质ID',
|
||||
'QUALIFICATION_ENDDATE': '资质到期日期',
|
||||
'QUALIFICATION_STARTDATE': '资质开始日期',
|
||||
'QUALIFICATION_ENDDATE_Start': '资质到期起始日期',
|
||||
'QUALIFICATION_ENDDATE_End': '资质到期结束日期',
|
||||
'QUALIFICATION_STATESEARCH': '资质状态搜索',
|
||||
|
||||
// ===== 审批流程 =====
|
||||
'BusinessProcess_State': '审批状态',
|
||||
'BusinessProcess_StateSearch': '审批状态搜索',
|
||||
'BusinessProcess_StartDate': '审批开始日期',
|
||||
'BusinessProcess_EndDate': '审批结束日期',
|
||||
'PendState': '待办状态',
|
||||
'Operation_Type': '操作类型',
|
||||
'OPERATION_TYPES': '操作类型集合',
|
||||
|
||||
// ===== 销售相关 =====
|
||||
'SELLER_ID': '销售人员ID',
|
||||
'SELLMASTER_DATE': '销售主单日期',
|
||||
'SELL_ENDDATE_Start': '销售结束起始日期',
|
||||
'SELL_ENDDATE_End': '销售结束结束日期',
|
||||
|
||||
// ===== 财务/账单 =====
|
||||
'ACCOUNT_TYPE': '账户类型',
|
||||
'BillState': '账单状态',
|
||||
'BILL_DATE_Start': '账单起始日期',
|
||||
'BILL_DATE_End': '账单结束日期',
|
||||
'SETTLEMENT_MODES': '结算方式',
|
||||
|
||||
// ===== 日期相关 =====
|
||||
'STARTDATESearch': '开始日期搜索',
|
||||
'ENDDATESearch': '结束日期搜索',
|
||||
'Start_DATE': '开始日期',
|
||||
'Start_Date': '开始日期',
|
||||
'End_DATE': '结束日期',
|
||||
'End_Date': '结束日期',
|
||||
'START_DATE_Start': '开始日期(起)',
|
||||
'START_DATE_End': '开始日期(止)',
|
||||
'END_DATE_Start': '结束日期(起)',
|
||||
'DueDate_End': '到期日期(止)',
|
||||
'Due_StartDate': '到期开始日期',
|
||||
'Due_EndDate': '到期结束日期',
|
||||
'EFFECT_STARTDATE_End': '生效开始日期(止)',
|
||||
'EFFECT_ENDDATE_Start': '生效结束日期(起)',
|
||||
'dateRange': '日期范围',
|
||||
'STATISTICS_DATE_Start': '统计起始日期',
|
||||
'STATISTICS_DATE_End': '统计结束日期',
|
||||
'STATISTICS_MONTH_Start': '统计起始月份',
|
||||
'STATISTICS_MONTH_End': '统计结束月份',
|
||||
'STATISTICS_TYPE': '统计类型',
|
||||
|
||||
// ===== 验核/审核 =====
|
||||
'CHECK_STARTDATE': '审核开始日期',
|
||||
'CHECK_ENDDATE': '审核结束日期',
|
||||
'CHECK_STARTDATE_SEARCH': '审核开始日期搜索',
|
||||
'CHECK_ENDDATE_SEARCH': '审核结束日期搜索',
|
||||
'CHECK_TYPE': '审核类型',
|
||||
|
||||
// ===== 合作商户 =====
|
||||
'COOPMERCHANTS_ID': '合作商户ID',
|
||||
'COOPMERCHANTS_STATE': '合作商户状态',
|
||||
'LINKER_STATE': '联系人状态',
|
||||
|
||||
// ===== 评价 =====
|
||||
'EVALUATIONLEVEL_TYPE': '评价等级类型',
|
||||
'EVALUATIONRULES_STATE': '评价规则状态',
|
||||
'EVALUATION_DEPARTMENT_IDS': '评价部门ID集合',
|
||||
|
||||
// ===== 仓库 =====
|
||||
'WAREHOUSE_STATE': '仓库状态',
|
||||
|
||||
// ===== 品牌/业态 =====
|
||||
'BRAND_INDUSTRY': '品牌行业',
|
||||
|
||||
// ===== 投诉/建议 =====
|
||||
'SUGGESTION_STATES': '建议状态',
|
||||
'SUGGESTION_TYPES': '建议类型',
|
||||
'TREATMENT_MARKSTATE': '处理标记状态',
|
||||
'EXCEPTION_TYPE': '异常类型',
|
||||
'ERROR_RATE': '错误率',
|
||||
|
||||
// ===== 投标/招商 =====
|
||||
'BID_STATES': '投标状态',
|
||||
|
||||
// ===== 收银/支付 =====
|
||||
'CASHWORKER_TYPE': '收银员类型',
|
||||
'SOURCE_PLATFORMS': '来源平台集合',
|
||||
|
||||
// ===== 日志/操作记录 =====
|
||||
'OPERATELOG_TYPES': '操作日志类型集合',
|
||||
'BUSINESSLOG_TYPE': '业务日志类型',
|
||||
'TABLE_NAME': '数据表名',
|
||||
|
||||
// ===== 其他通用 =====
|
||||
'VALID': '是否有效',
|
||||
'ValidState': '有效状态',
|
||||
'ISVALID': '是否有效',
|
||||
'ShowWholePower': '是否显示全部权限',
|
||||
'HasImage': '是否有图片',
|
||||
'Worker_ValId': '工作人员有效ID',
|
||||
'name': '名称',
|
||||
'notExist': '排除条件',
|
||||
'TYPE_STATE': '类型状态',
|
||||
'USERDEFINEDTYPE_STATE': '自定义类型状态',
|
||||
'HOLIDAY_TYPE_IDS': '假期类型ID集合',
|
||||
'ENUM_LABEL': '枚举标签',
|
||||
|
||||
// ===== 数据源相关参数 =====
|
||||
'DataType': '数据类型',
|
||||
'StartTime': '开始时间',
|
||||
'EndTime': '结束时间',
|
||||
'StartDate': '开始日期',
|
||||
'EndDate': '结束日期',
|
||||
'ServerpartShopIds': '门店ID集合',
|
||||
'ExcludeOff': '是否排除关闭',
|
||||
'DataSourceType': '数据来源类型',
|
||||
'GroupByDaily': '是否按日分组',
|
||||
'ShowRevenueSplit': '是否显示营收拆分',
|
||||
'AccountDate': '账户日期',
|
||||
'SearchKeyName': '搜索字段名',
|
||||
'SortDesc': '是否降序',
|
||||
};
|
||||
|
||||
// 读取 MD 文件
|
||||
const mdPath = path.join(__dirname, '..', 'config', 'api_mapping_merged.md');
|
||||
let content = fs.readFileSync(mdPath, 'utf-8');
|
||||
|
||||
// 处理 JSON 代码块中的每一行
|
||||
// 匹配格式: "fieldName": "type" 或 "fieldName": {
|
||||
const lines = content.split('\n');
|
||||
let inJsonBlock = false;
|
||||
let isDataBody = false;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
// 检测进入 JSON 代码块
|
||||
if (line.trim() === '```json') {
|
||||
inJsonBlock = true;
|
||||
// 检查上一行是否是请求体标记
|
||||
if (i > 0 && (lines[i - 1].includes('请求体 (data/body)') || lines[i - 1].includes('查询参数 (params/query)'))) {
|
||||
isDataBody = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检测离开 JSON 代码块
|
||||
if (line.trim() === '```') {
|
||||
inJsonBlock = false;
|
||||
isDataBody = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 只处理 JSON 块内的行
|
||||
if (!inJsonBlock || !isDataBody) continue;
|
||||
|
||||
// 跳过已有注释的行
|
||||
if (line.includes('//')) continue;
|
||||
|
||||
// 匹配 JSON key: "fieldName": value
|
||||
const match = line.match(/^(\s*)"([^"]+)":\s*(.+)$/);
|
||||
if (match) {
|
||||
const indent = match[1];
|
||||
const fieldName = match[2];
|
||||
const rest = match[3];
|
||||
|
||||
const comment = FIELD_COMMENTS[fieldName];
|
||||
if (comment) {
|
||||
// 去掉行尾逗号后添加注释,再加回逗号
|
||||
const trimmedRest = rest.trimEnd();
|
||||
const hasComma = trimmedRest.endsWith(',');
|
||||
const valueStr = hasComma ? trimmedRest.slice(0, -1) : trimmedRest;
|
||||
|
||||
// 对于对象开始 { 的情况
|
||||
if (valueStr.trim() === '{' || valueStr.trim() === '{}' || valueStr.trim() === '{}') {
|
||||
lines[i] = `${indent}"${fieldName}": ${trimmedRest} // ${comment}`;
|
||||
} else {
|
||||
lines[i] = `${indent}"${fieldName}": ${valueStr}${hasComma ? ',' : ''} // ${comment}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content = lines.join('\n');
|
||||
fs.writeFileSync(mdPath, content, 'utf-8');
|
||||
|
||||
// 统计注释添加数
|
||||
const commentCount = (content.match(/\/\/ /g) || []).length;
|
||||
console.log(`✅ 完成!共添加 ${commentCount} 处注释`);
|
||||
671
scripts/extract_apis.js
Normal file
671
scripts/extract_apis.js
Normal file
@ -0,0 +1,671 @@
|
||||
/**
|
||||
* 批量提取所有页面接口地址的自动化脚本
|
||||
*
|
||||
* 处理流程:
|
||||
* 1. 解析 menu.json 提取所有 SYSTEMMODULE_URL
|
||||
* 2. 解析 routes.ts 构建嵌套路径映射(子父级拼接)
|
||||
* 3. 定位组件文件夹及所有子组件
|
||||
* 4. 提取 import 的 service 文件
|
||||
* 5. 在 service 文件中提取 request() 路径,并根据引用的 request 工具确定 prefix
|
||||
* 6. 输出结果到 config/api_mapping.json 和 config/api_mapping.md
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 项目根目录
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const SRC = path.join(ROOT, 'src');
|
||||
const PAGES = path.join(SRC, 'pages');
|
||||
|
||||
// ============ 第一部分:request prefix 映射 ============
|
||||
// 根据 service 文件 import 的 request 来源确定 prefix
|
||||
const REQUEST_PREFIX_MAP = {
|
||||
'request': 'https://eshangtech.com:18900/EShangApiMain',
|
||||
'requestUpLoad': 'http://220.180.35.180:8000/EShangApiMain',
|
||||
'requestTest': 'http://dev.eshangtech.com:8900/EShangApiMain',
|
||||
'requestTestTest': 'http://dev.eshangtech.com:8001/EShangApiMain',
|
||||
'requestNoPrefix': 'https://eshangtech.com:18900/',
|
||||
'requestPythonTest': 'http://192.168.1.207:8002/',
|
||||
'requestNewTest': 'https://eshangtech.com:18900/EShangApiMain',
|
||||
'requestNewJava': 'https://java.es.eshangtech.com:443',
|
||||
'requestnew': '/EShangApiDashboard',
|
||||
'requestEncryption': 'https://eshangtech.com:18900/MemberApi',
|
||||
'requestJava': 'https://java.es.eshangtech.com',
|
||||
'requestDashboard': 'https://eshangtech.com:18900/EshangApiDashboard',
|
||||
'requestCodeBuilder': 'http://dev.eshangtech.com:8001/CodeBuilderApi',
|
||||
'requestCode': 'https://eshangtech.com:18900/CommercialApi',
|
||||
'requestAHYD': 'https://ahyd.eshangtech.com/EShangApiMain',
|
||||
};
|
||||
|
||||
// ============ 第二部分:解析 routes.ts 构建路径映射 ============
|
||||
|
||||
/**
|
||||
* 解析 routes.ts 文件,构建 fullPath → component 映射
|
||||
* 处理嵌套路由的路径拼接
|
||||
*/
|
||||
function parseRoutes() {
|
||||
const routesFile = path.join(ROOT, 'config', 'routes.ts');
|
||||
const content = fs.readFileSync(routesFile, 'utf-8');
|
||||
|
||||
// 用正则提取所有路由对象(简化处理:逐行匹配 path 和 component)
|
||||
const routeMap = {};
|
||||
|
||||
// 使用栈来跟踪嵌套路径
|
||||
const pathStack = [];
|
||||
let braceDepth = 0;
|
||||
const depthToPath = {}; // 记录每个深度对应的 path 值
|
||||
|
||||
const lines = content.split('\n');
|
||||
let currentPath = null;
|
||||
let currentComponent = null;
|
||||
let currentObjStartDepth = -1;
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
// 计算花括号深度
|
||||
for (const ch of trimmed) {
|
||||
if (ch === '{') braceDepth++;
|
||||
if (ch === '}') {
|
||||
// 当离开一个层级时,清除该层级的 path
|
||||
if (depthToPath[braceDepth]) {
|
||||
delete depthToPath[braceDepth];
|
||||
}
|
||||
braceDepth--;
|
||||
}
|
||||
}
|
||||
|
||||
// 匹配 path 属性
|
||||
const pathMatch = trimmed.match(/^path:\s*['"]([^'"]+)['"]/);
|
||||
if (pathMatch) {
|
||||
const pathVal = pathMatch[1];
|
||||
depthToPath[braceDepth] = pathVal;
|
||||
}
|
||||
|
||||
// 匹配 component 属性
|
||||
const compMatch = trimmed.match(/^component:\s*['"]([^'"]+)['"]/);
|
||||
if (compMatch) {
|
||||
const comp = compMatch[1];
|
||||
// 跳过 layout 组件
|
||||
if (comp.includes('Layout') || comp.includes('layout')) continue;
|
||||
|
||||
// 构建完整路径:从 depthToPath 中按层级拼接
|
||||
const fullPath = buildFullPath(depthToPath, braceDepth);
|
||||
if (fullPath && fullPath !== '/') {
|
||||
routeMap[fullPath] = comp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return routeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据深度映射构建完整路径
|
||||
*/
|
||||
function buildFullPath(depthToPath, currentDepth) {
|
||||
// 收集所有当前有效的路径段
|
||||
const segments = [];
|
||||
const depths = Object.keys(depthToPath).map(Number).sort((a, b) => a - b);
|
||||
|
||||
for (const d of depths) {
|
||||
if (d <= currentDepth) {
|
||||
segments.push(depthToPath[d]);
|
||||
}
|
||||
}
|
||||
|
||||
if (segments.length === 0) return null;
|
||||
|
||||
// 拼接路径
|
||||
let result = '';
|
||||
for (const seg of segments) {
|
||||
if (seg.startsWith('/')) {
|
||||
// 绝对路径,重新开始
|
||||
result = seg;
|
||||
} else {
|
||||
// 相对路径,拼接
|
||||
if (result.endsWith('/')) {
|
||||
result += seg;
|
||||
} else {
|
||||
result += '/' + seg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ============ 第三部分:备用路径匹配 ============
|
||||
|
||||
/**
|
||||
* 当 routes.ts 中找不到完全匹配时,尝试通过 URL 路径推断组件位置
|
||||
* 扫描 src/pages 目录查找匹配的文件夹
|
||||
*/
|
||||
function findComponentByUrl(menuUrl) {
|
||||
// 从 URL 提取最后一段作为组件名
|
||||
const segments = menuUrl.split('/').filter(Boolean);
|
||||
if (segments.length === 0) return null;
|
||||
|
||||
const lastSegment = segments[segments.length - 1];
|
||||
|
||||
// 在 pages 目录下递归搜索包含该名称的文件夹
|
||||
const found = findDirRecursive(PAGES, lastSegment);
|
||||
return found;
|
||||
}
|
||||
|
||||
function findDirRecursive(dir, targetName) {
|
||||
if (!fs.existsSync(dir)) return null;
|
||||
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory()) {
|
||||
// 不区分大小写匹配
|
||||
if (entry.name.toLowerCase() === targetName.toLowerCase()) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
// 确认有 index.tsx 或 index.ts
|
||||
if (fs.existsSync(path.join(fullPath, 'index.tsx')) ||
|
||||
fs.existsSync(path.join(fullPath, 'index.ts'))) {
|
||||
return fullPath;
|
||||
}
|
||||
}
|
||||
// 递归搜索子目录
|
||||
const result = findDirRecursive(path.join(dir, entry.name), targetName);
|
||||
if (result) return result;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ============ 第四部分:定位组件并提取所有被引用的文件 ============
|
||||
|
||||
/**
|
||||
* 根据 component 路径解析到实际文件夹
|
||||
* 处理两种情况:
|
||||
* 1. component 指向目录(如 './contract' → src/pages/contract/)
|
||||
* 2. component 指向文件(如 './contract/list' → src/pages/contract/list.tsx)
|
||||
* 此时返回文件所在的父目录
|
||||
*/
|
||||
function resolveComponentDir(componentPath) {
|
||||
let resolved = componentPath.replace(/^\.\//, '');
|
||||
const fullPath = path.join(PAGES, resolved);
|
||||
|
||||
// 情况1:路径本身是目录(如 src/pages/contract/)
|
||||
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
// 情况2:路径指向文件(如 src/pages/contract/list → 实际是 list.tsx)
|
||||
// 检查是否存在同名的 .tsx / .ts 文件
|
||||
const extensions = ['.tsx', '.ts', '.jsx', '.js'];
|
||||
for (const ext of extensions) {
|
||||
if (fs.existsSync(fullPath + ext)) {
|
||||
// 文件存在,返回其父目录作为组件目录
|
||||
return path.dirname(fullPath);
|
||||
}
|
||||
}
|
||||
|
||||
// 情况3:尝试去掉末尾的 /index
|
||||
if (resolved.endsWith('/index')) {
|
||||
resolved = resolved.replace(/\/index$/, '');
|
||||
const altPath = path.join(PAGES, resolved);
|
||||
if (fs.existsSync(altPath) && fs.statSync(altPath).isDirectory()) {
|
||||
return altPath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件目录下所有 .tsx / .ts 文件(包括 components 子目录)
|
||||
*/
|
||||
function getAllComponentFiles(dir) {
|
||||
const files = [];
|
||||
if (!fs.existsSync(dir)) return files;
|
||||
|
||||
function scan(currentDir) {
|
||||
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(currentDir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
scan(fullPath);
|
||||
} else if (entry.name.endsWith('.tsx') || entry.name.endsWith('.ts')) {
|
||||
// 排除 service.ts 本身和 .d.ts
|
||||
if (!entry.name.endsWith('.d.ts')) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scan(dir);
|
||||
return files;
|
||||
}
|
||||
|
||||
// ============ 第五部分:提取 service 导入和接口路径 ============
|
||||
|
||||
/**
|
||||
* 从组件文件中提取所有 import 的 service 路径
|
||||
*/
|
||||
function extractServiceImports(filePath) {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const imports = [];
|
||||
|
||||
// 匹配各种 import 模式
|
||||
// import { xxx } from '../xxx/service'
|
||||
// import { xxx } from '@/pages/xxx/service'
|
||||
// import { xxx } from '@/services/xxx'
|
||||
const importRegex = /import\s+\{([^}]+)\}\s+from\s+['"](.*?)['"]/g;
|
||||
let match;
|
||||
|
||||
while ((match = importRegex.exec(content)) !== null) {
|
||||
const functions = match[1].split(',').map(f => f.trim()).filter(Boolean);
|
||||
let importPath = match[2];
|
||||
|
||||
// 只关注 service 文件、services 目录、options 目录的导入
|
||||
if (importPath.includes('service') || importPath.includes('services') ||
|
||||
importPath.includes('options')) {
|
||||
imports.push({
|
||||
functions,
|
||||
importPath,
|
||||
sourceFile: filePath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 import 路径到实际文件路径
|
||||
*/
|
||||
function resolveImportPath(importPath, sourceFile) {
|
||||
let resolved;
|
||||
|
||||
if (importPath.startsWith('@/')) {
|
||||
// @/ 别名 → src/
|
||||
resolved = path.join(SRC, importPath.replace('@/', ''));
|
||||
} else if (importPath.startsWith('.')) {
|
||||
// 相对路径
|
||||
resolved = path.resolve(path.dirname(sourceFile), importPath);
|
||||
} else {
|
||||
return null; // node_modules 等,跳过
|
||||
}
|
||||
|
||||
// 尝试各种扩展名
|
||||
const extensions = ['.ts', '.tsx', '/index.ts', '/index.tsx', '.js', '/index.js'];
|
||||
for (const ext of extensions) {
|
||||
const candidate = resolved + ext;
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果直接存在
|
||||
if (fs.existsSync(resolved)) {
|
||||
if (fs.statSync(resolved).isDirectory()) {
|
||||
for (const ext of ['/index.ts', '/index.tsx', '/index.js']) {
|
||||
const idx = resolved + ext;
|
||||
if (fs.existsSync(idx)) return idx;
|
||||
}
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 缓存已解析的 service 文件
|
||||
const serviceCache = {};
|
||||
|
||||
/**
|
||||
* 从 service 文件中提取所有 request() 调用的路径
|
||||
* 同时确定引用的是哪个 request 工具(确定 prefix)
|
||||
* 并记录每个 request() 调用所属的函数名
|
||||
*/
|
||||
function extractApisFromService(serviceFilePath) {
|
||||
if (serviceCache[serviceFilePath]) {
|
||||
return serviceCache[serviceFilePath];
|
||||
}
|
||||
|
||||
if (!fs.existsSync(serviceFilePath)) {
|
||||
serviceCache[serviceFilePath] = [];
|
||||
return [];
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(serviceFilePath, 'utf-8');
|
||||
const apis = [];
|
||||
|
||||
// 1. 确定该 service 文件使用的 request 来源
|
||||
let prefix = REQUEST_PREFIX_MAP['request']; // 默认 prefix
|
||||
|
||||
// 匹配 import request from '@/utils/requestXXX'
|
||||
const requestImportRegex = /import\s+\w+\s+from\s+['"]@\/utils\/(\w+)['"]/g;
|
||||
let reqMatch;
|
||||
while ((reqMatch = requestImportRegex.exec(content)) !== null) {
|
||||
const requestName = reqMatch[1];
|
||||
if (REQUEST_PREFIX_MAP[requestName] !== undefined) {
|
||||
prefix = REQUEST_PREFIX_MAP[requestName];
|
||||
}
|
||||
}
|
||||
|
||||
// 也匹配相对路径的 request 导入
|
||||
const relRequestRegex = /import\s+\w+\s+from\s+['"]\.\.?\/.*?(\w+)['"].*?/g;
|
||||
while ((reqMatch = relRequestRegex.exec(content)) !== null) {
|
||||
const fileName = reqMatch[1];
|
||||
if (REQUEST_PREFIX_MAP[fileName] !== undefined) {
|
||||
prefix = REQUEST_PREFIX_MAP[fileName];
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 逐行扫描,追踪当前函数名,并提取 request() 调用
|
||||
const lines = content.split('\n');
|
||||
let currentFuncName = '(unknown)';
|
||||
|
||||
// 函数定义的正则匹配模式:
|
||||
// export async function handleXxx(...)
|
||||
// export function handleXxx(...)
|
||||
// export const handleXxx = async (...)
|
||||
// export const handleXxx = (...)
|
||||
const funcDefRegex = /^export\s+(?:async\s+)?function\s+(\w+)|^export\s+const\s+(\w+)\s*=/;
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
|
||||
// 检查是否是函数定义行
|
||||
const funcMatch = trimmed.match(funcDefRegex);
|
||||
if (funcMatch) {
|
||||
currentFuncName = funcMatch[1] || funcMatch[2];
|
||||
}
|
||||
|
||||
// 检查是否有 request() 调用
|
||||
const requestCallRegex = /request\(\s*['"`]([^'"`\$]+)['"`]/g;
|
||||
let apiMatch;
|
||||
while ((apiMatch = requestCallRegex.exec(trimmed)) !== null) {
|
||||
const apiPath = apiMatch[1];
|
||||
// 构建完整 URL
|
||||
let fullUrl;
|
||||
if (prefix.endsWith('/')) {
|
||||
fullUrl = prefix + apiPath.replace(/^\//, '');
|
||||
} else {
|
||||
fullUrl = prefix + apiPath;
|
||||
}
|
||||
|
||||
apis.push({
|
||||
path: apiPath,
|
||||
fullUrl: fullUrl,
|
||||
prefix: prefix,
|
||||
functionName: currentFuncName,
|
||||
serviceFile: path.relative(ROOT, serviceFilePath)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 也检查该 service 文件是否还 import 了其他 service(二级引用)
|
||||
const serviceImports = extractServiceImports(serviceFilePath);
|
||||
for (const imp of serviceImports) {
|
||||
// 避免循环引用
|
||||
const resolvedPath = resolveImportPath(imp.importPath, serviceFilePath);
|
||||
if (resolvedPath && resolvedPath !== serviceFilePath && !serviceCache[resolvedPath]) {
|
||||
const subApis = extractApisFromService(resolvedPath);
|
||||
apis.push(...subApis);
|
||||
}
|
||||
}
|
||||
|
||||
serviceCache[serviceFilePath] = apis;
|
||||
return apis;
|
||||
}
|
||||
|
||||
// ============ 第六部分:解析 menu.json 提取所有模块 ============
|
||||
|
||||
function extractModules(items, parentPath = '') {
|
||||
const modules = [];
|
||||
for (const item of items) {
|
||||
if (item.SYSTEMMODULE_URL && item.SYSTEMMODULE_URL !== '.' && item.SYSTEMMODULE_URL !== '/') {
|
||||
// 跳过外部链接
|
||||
if (!item.SYSTEMMODULE_URL.startsWith('http')) {
|
||||
modules.push({
|
||||
name: item.SYSTEMMODULE_NAME,
|
||||
url: item.SYSTEMMODULE_URL,
|
||||
parentMenu: parentPath
|
||||
});
|
||||
}
|
||||
}
|
||||
if (item.children) {
|
||||
const menuName = item.SYSTEMMENU_NAME || '';
|
||||
const newParent = parentPath ? `${parentPath} > ${menuName}` : menuName;
|
||||
modules.push(...extractModules(item.children, newParent));
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
// ============ 第七部分:主流程 ============
|
||||
|
||||
function main() {
|
||||
console.log('🚀 开始批量提取页面接口...\n');
|
||||
|
||||
// 1. 读取 menu.json
|
||||
const menuData = require(path.join(ROOT, 'config', 'menu.json'));
|
||||
const modules = extractModules(menuData);
|
||||
console.log(`📋 共发现 ${modules.length} 个有效页面模块\n`);
|
||||
|
||||
// 2. 解析 routes.ts 构建路径映射
|
||||
const routeMap = parseRoutes();
|
||||
console.log(`🗺️ 路由映射构建完成,共 ${Object.keys(routeMap).length} 条路由\n`);
|
||||
|
||||
// 3. 逐个处理每个模块
|
||||
const results = [];
|
||||
let matchedCount = 0;
|
||||
let unmatchedCount = 0;
|
||||
let totalApis = 0;
|
||||
|
||||
for (const mod of modules) {
|
||||
const result = {
|
||||
menuName: mod.name,
|
||||
menuUrl: mod.url,
|
||||
parentMenu: mod.parentMenu,
|
||||
componentPath: null,
|
||||
componentDir: null,
|
||||
apiCount: 0,
|
||||
apis: [],
|
||||
status: 'unmatched'
|
||||
};
|
||||
|
||||
// 3.1 在路由映射中查找 component
|
||||
let component = routeMap[mod.url];
|
||||
|
||||
// 3.2 如果没有直接匹配,尝试带参数路径的匹配
|
||||
if (!component) {
|
||||
// 处理带动态参数的路由,比如 /setting/department/:id 匹配 /setting/department/userstype
|
||||
for (const [routePath, comp] of Object.entries(routeMap)) {
|
||||
if (routePath.includes(':')) {
|
||||
const routeRegex = routePath.replace(/:[^/]+/g, '[^/]+');
|
||||
if (new RegExp(`^${routeRegex}$`).test(mod.url)) {
|
||||
component = comp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (component) {
|
||||
result.componentPath = component;
|
||||
|
||||
// 3.3 解析 component 路径到实际文件夹
|
||||
const componentDir = resolveComponentDir(component);
|
||||
if (componentDir) {
|
||||
result.componentDir = path.relative(ROOT, componentDir);
|
||||
result.status = 'matched';
|
||||
matchedCount++;
|
||||
|
||||
// 3.4 获取所有组件文件
|
||||
const componentFiles = getAllComponentFiles(componentDir);
|
||||
|
||||
// 3.5 提取所有 service 导入
|
||||
const allApis = new Set(); // 用 Set 去重
|
||||
const apiDetails = [];
|
||||
|
||||
for (const file of componentFiles) {
|
||||
const serviceImports = extractServiceImports(file);
|
||||
|
||||
for (const imp of serviceImports) {
|
||||
const resolvedService = resolveImportPath(imp.importPath, file);
|
||||
if (resolvedService) {
|
||||
const apis = extractApisFromService(resolvedService);
|
||||
for (const api of apis) {
|
||||
if (!allApis.has(api.fullUrl)) {
|
||||
allApis.add(api.fullUrl);
|
||||
apiDetails.push(api);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.apiCount = apiDetails.length;
|
||||
result.apis = apiDetails.map(a => ({
|
||||
fullUrl: a.fullUrl,
|
||||
path: a.path,
|
||||
prefix: a.prefix,
|
||||
functionName: a.functionName,
|
||||
serviceFile: a.serviceFile
|
||||
}));
|
||||
totalApis += apiDetails.length;
|
||||
} else {
|
||||
result.status = 'dir_not_found';
|
||||
unmatchedCount++;
|
||||
}
|
||||
} else {
|
||||
// 3.6 备用:通过 URL 最后一段搜索
|
||||
const fallbackDir = findComponentByUrl(mod.url);
|
||||
if (fallbackDir) {
|
||||
result.componentDir = path.relative(ROOT, fallbackDir);
|
||||
result.status = 'matched_fallback';
|
||||
matchedCount++;
|
||||
|
||||
const componentFiles = getAllComponentFiles(fallbackDir);
|
||||
const allApis = new Set();
|
||||
const apiDetails = [];
|
||||
|
||||
for (const file of componentFiles) {
|
||||
const serviceImports = extractServiceImports(file);
|
||||
for (const imp of serviceImports) {
|
||||
const resolvedService = resolveImportPath(imp.importPath, file);
|
||||
if (resolvedService) {
|
||||
const apis = extractApisFromService(resolvedService);
|
||||
for (const api of apis) {
|
||||
if (!allApis.has(api.fullUrl)) {
|
||||
allApis.add(api.fullUrl);
|
||||
apiDetails.push(api);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.apiCount = apiDetails.length;
|
||||
result.apis = apiDetails.map(a => ({
|
||||
fullUrl: a.fullUrl,
|
||||
path: a.path,
|
||||
prefix: a.prefix,
|
||||
functionName: a.functionName,
|
||||
serviceFile: a.serviceFile
|
||||
}));
|
||||
totalApis += apiDetails.length;
|
||||
} else {
|
||||
unmatchedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
// 4. 输出统计
|
||||
console.log('========== 统计结果 ==========');
|
||||
console.log(`✅ 匹配成功: ${matchedCount} 个页面`);
|
||||
console.log(`❌ 未匹配: ${unmatchedCount} 个页面`);
|
||||
console.log(`📡 总接口数: ${totalApis} 个`);
|
||||
console.log('');
|
||||
|
||||
// 5. 输出 JSON 文件
|
||||
const jsonOutput = path.join(ROOT, 'config', 'api_mapping.json');
|
||||
fs.writeFileSync(jsonOutput, JSON.stringify(results, null, 2), 'utf-8');
|
||||
console.log(`📄 JSON 已输出: ${jsonOutput}`);
|
||||
|
||||
// 6. 输出 Markdown 文件
|
||||
const mdOutput = path.join(ROOT, 'config', 'api_mapping.md');
|
||||
let md = '# 系统页面接口映射表\n\n';
|
||||
md += `> 自动生成时间: ${new Date().toLocaleString()}\n\n`;
|
||||
md += `> 总页面数: ${modules.length} | 匹配成功: ${matchedCount} | 未匹配: ${unmatchedCount} | 总接口数: ${totalApis}\n\n`;
|
||||
|
||||
// 按父级菜单分组
|
||||
const groupedByParent = {};
|
||||
for (const r of results) {
|
||||
const parent = r.parentMenu || '未分类';
|
||||
if (!groupedByParent[parent]) {
|
||||
groupedByParent[parent] = [];
|
||||
}
|
||||
groupedByParent[parent].push(r);
|
||||
}
|
||||
|
||||
for (const [parentMenu, items] of Object.entries(groupedByParent)) {
|
||||
md += `## ${parentMenu}\n\n`;
|
||||
|
||||
for (const item of items) {
|
||||
if (item.status === 'unmatched' || item.status === 'dir_not_found') {
|
||||
md += `### ❌ ${item.menuName}\n`;
|
||||
md += `- 路径: \`${item.menuUrl}\`\n`;
|
||||
md += `- 状态: 未找到对应组件\n\n`;
|
||||
continue;
|
||||
}
|
||||
|
||||
md += `### ${item.menuName}\n`;
|
||||
md += `- 路径: \`${item.menuUrl}\`\n`;
|
||||
md += `- 组件: \`${item.componentDir || item.componentPath}\`\n`;
|
||||
md += `- 调用了 **${item.apiCount}** 个接口\n\n`;
|
||||
|
||||
if (item.apis.length > 0) {
|
||||
md += '| # | 函数名 | 接口路径 | 完整地址 | 来源 |\n';
|
||||
md += '|---|--------|----------|----------|------|\n';
|
||||
|
||||
item.apis.forEach((api, idx) => {
|
||||
md += `| ${idx + 1} | \`${api.functionName || '-'}\` | \`${api.path}\` | \`${api.fullUrl}\` | \`${api.serviceFile}\` |\n`;
|
||||
});
|
||||
|
||||
md += '\n';
|
||||
}
|
||||
}
|
||||
|
||||
md += '---\n\n';
|
||||
}
|
||||
|
||||
// 7. 添加未匹配页面汇总
|
||||
const unmatched = results.filter(r => r.status === 'unmatched' || r.status === 'dir_not_found');
|
||||
if (unmatched.length > 0) {
|
||||
md += '## ⚠️ 未匹配页面汇总\n\n';
|
||||
md += '| # | 页面名称 | 路径 | 所属菜单 |\n';
|
||||
md += '|---|----------|------|----------|\n';
|
||||
unmatched.forEach((item, idx) => {
|
||||
md += `| ${idx + 1} | ${item.menuName} | \`${item.menuUrl}\` | ${item.parentMenu} |\n`;
|
||||
});
|
||||
md += '\n';
|
||||
}
|
||||
|
||||
fs.writeFileSync(mdOutput, md, 'utf-8');
|
||||
console.log(`📝 Markdown 已输出: ${mdOutput}`);
|
||||
|
||||
// 8. 打印部分结果预览
|
||||
console.log('\n========== 结果预览(前 10 个有接口的页面)==========\n');
|
||||
const withApis = results.filter(r => r.apiCount > 0).slice(0, 10);
|
||||
for (const r of withApis) {
|
||||
console.log(`📌 【${r.menuName}】调用了 ${r.apiCount} 个接口,分别为:`);
|
||||
r.apis.forEach((api, idx) => {
|
||||
console.log(` ${idx + 1}. ${api.fullUrl}`);
|
||||
});
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
32
scripts/inject_api_logger.js
Normal file
32
scripts/inject_api_logger.js
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 批量修正 apiLogger source 名称为文件名
|
||||
* 将 logApiCall(url, options, 'request') 修正为 logApiCall(url, options, '文件名')
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const utilsDir = path.join(__dirname, '..', 'src', 'utils');
|
||||
const requestFiles = fs.readdirSync(utilsDir)
|
||||
.filter(f => f.startsWith('request') && f.endsWith('.ts') && f !== 'request.ts');
|
||||
|
||||
let fixCount = 0;
|
||||
|
||||
for (const file of requestFiles) {
|
||||
const filePath = path.join(utilsDir, file);
|
||||
let content = fs.readFileSync(filePath, 'utf-8');
|
||||
const sourceName = file.replace('.ts', '');
|
||||
|
||||
// 替换 logApiCall 中不正确的 source 名称
|
||||
const oldPattern = /logApiCall\(url, options, '(\w+)'\)/;
|
||||
const match = content.match(oldPattern);
|
||||
if (match && match[1] !== sourceName) {
|
||||
content = content.replace(oldPattern, `logApiCall(url, options, '${sourceName}')`);
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
console.log(` ✅ ${file}: '${match[1]}' → '${sourceName}'`);
|
||||
fixCount++;
|
||||
} else {
|
||||
console.log(` ⏭ ${file}: 已正确`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n修正完成:${fixCount} 个文件`);
|
||||
278
scripts/merge_api_data.js
Normal file
278
scripts/merge_api_data.js
Normal file
@ -0,0 +1,278 @@
|
||||
/**
|
||||
* 合并运行时 API 日志与静态 api_mapping 数据
|
||||
*
|
||||
* 匹配逻辑:通过 URL 匹配(运行时日志的 url 对应 api_mapping 中的 fullUrl)
|
||||
* 输出:带入参结构的增强版 api_mapping
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 读取数据
|
||||
const configDir = path.join(__dirname, '..', 'config');
|
||||
const apiMapping = JSON.parse(fs.readFileSync(path.join(configDir, 'api_mapping.json'), 'utf-8'));
|
||||
|
||||
// 自动扫描所有 api_log_*.json 文件,合并数据
|
||||
const logFiles = fs.readdirSync(configDir).filter(f => f.startsWith('api_log_') && f.endsWith('.json'));
|
||||
let apiLog = [];
|
||||
for (const file of logFiles) {
|
||||
const data = JSON.parse(fs.readFileSync(path.join(configDir, file), 'utf-8'));
|
||||
console.log(` 📄 ${file}: ${data.length} 条记录`);
|
||||
apiLog = apiLog.concat(data);
|
||||
}
|
||||
|
||||
console.log(`📊 api_mapping: ${apiMapping.length} 个页面`);
|
||||
console.log(`📊 api_log 总计: ${apiLog.length} 条运行时记录(来自 ${logFiles.length} 个文件)`);
|
||||
|
||||
// 1. 构建运行时数据索引
|
||||
// 按文件时间顺序处理,新文件有数据则覆盖旧的,没数据则保留旧的
|
||||
// 双重索引:pageKey:url → 记录列表(按页面区分)
|
||||
// 全局索引:url → 记录列表(兜底用)
|
||||
const pageIndex = {}; // { "页面名::URL" → [records] }
|
||||
const globalIndex = {}; // { "URL" → [records] }
|
||||
|
||||
// 按文件名排序(文件名含时间戳,自然排序即为时间顺序)
|
||||
logFiles.sort();
|
||||
|
||||
for (const file of logFiles) {
|
||||
const data = JSON.parse(fs.readFileSync(path.join(configDir, file), 'utf-8'));
|
||||
|
||||
for (const record of data) {
|
||||
const baseUrl = record.url.split('?')[0];
|
||||
const pageName = record.currentPageName || '';
|
||||
const hasData = (record.dataStructure && typeof record.dataStructure === 'object' && Object.keys(record.dataStructure).length > 0) ||
|
||||
(record.paramsStructure && typeof record.paramsStructure === 'object' && Object.keys(record.paramsStructure).length > 0);
|
||||
|
||||
// 全局索引:新文件有数据则覆盖,无数据则追加(不覆盖已有的)
|
||||
if (!globalIndex[baseUrl]) {
|
||||
globalIndex[baseUrl] = [record];
|
||||
} else if (hasData) {
|
||||
// 新记录有入参,覆盖旧的
|
||||
globalIndex[baseUrl] = [record];
|
||||
}
|
||||
// 新记录无入参,保留旧的,不做操作
|
||||
|
||||
// 页面索引
|
||||
if (pageName && pageName !== '空白页') {
|
||||
const pageKey = `${pageName}::${baseUrl}`;
|
||||
if (!pageIndex[pageKey]) {
|
||||
pageIndex[pageKey] = [record];
|
||||
} else if (hasData) {
|
||||
// 新记录有入参,覆盖旧的
|
||||
pageIndex[pageKey] = [record];
|
||||
}
|
||||
// 新记录无入参,保留旧的
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`📊 运行时唯一接口数(去参数): ${Object.keys(globalIndex).length}`);
|
||||
console.log(`📊 页面级索引条目数: ${Object.keys(pageIndex).length}`);
|
||||
|
||||
/**
|
||||
* 合并多条运行时记录的入参结构(取字段并集)
|
||||
* 这样不同调用传了不同的可选参数都能被收集到
|
||||
*/
|
||||
function mergeStructures(records) {
|
||||
let mergedData = null;
|
||||
let mergedParams = null;
|
||||
let exampleData = null;
|
||||
let exampleParams = null;
|
||||
let method = 'GET';
|
||||
let requestType = '';
|
||||
let requestSource = '';
|
||||
|
||||
for (const r of records) {
|
||||
method = (r.method || method);
|
||||
requestType = r.requestType || requestType;
|
||||
requestSource = r.requestSource || requestSource;
|
||||
|
||||
// 合并 dataStructure
|
||||
if (r.dataStructure && typeof r.dataStructure === 'object') {
|
||||
if (!mergedData) mergedData = {};
|
||||
deepMergeKeys(mergedData, r.dataStructure);
|
||||
}
|
||||
// 合并 paramsStructure
|
||||
if (r.paramsStructure && typeof r.paramsStructure === 'object') {
|
||||
if (!mergedParams) mergedParams = {};
|
||||
deepMergeKeys(mergedParams, r.paramsStructure);
|
||||
}
|
||||
|
||||
// 收集示例数据 (dataFull/paramsFull)
|
||||
// 取最后一条有数据的记录作为示例
|
||||
if (r.dataFull && Object.keys(r.dataFull).length > 0) {
|
||||
exampleData = r.dataFull;
|
||||
}
|
||||
if (r.paramsFull && Object.keys(r.paramsFull).length > 0) {
|
||||
exampleParams = r.paramsFull;
|
||||
}
|
||||
}
|
||||
|
||||
return { method, requestType, requestSource, mergedData, mergedParams, exampleData, exampleParams };
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度合并 key(取并集),保留类型信息
|
||||
*/
|
||||
function deepMergeKeys(target, source) {
|
||||
for (const key of Object.keys(source)) {
|
||||
if (!(key in target)) {
|
||||
target[key] = source[key];
|
||||
} else if (typeof target[key] === 'object' && target[key] !== null &&
|
||||
typeof source[key] === 'object' && source[key] !== null &&
|
||||
!Array.isArray(target[key]) && !Array.isArray(source[key])) {
|
||||
// 两边都是对象,递归合并
|
||||
deepMergeKeys(target[key], source[key]);
|
||||
}
|
||||
// 如果类型不同或已有值,保留原值(先到先得)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 合并数据
|
||||
let matchedApiCount = 0;
|
||||
let unmatchedApiCount = 0;
|
||||
let matchedPageCount = 0;
|
||||
|
||||
for (const page of apiMapping) {
|
||||
if (!page.apis || page.apis.length === 0) continue;
|
||||
|
||||
let pageHasMatch = false;
|
||||
const pageName = page.menuName || '';
|
||||
|
||||
for (const api of page.apis) {
|
||||
const baseUrl = api.fullUrl.split('?')[0];
|
||||
|
||||
// 优先用页面级索引(精确匹配当前页面的记录)
|
||||
const pageKey = `${pageName}::${baseUrl}`;
|
||||
let records = pageIndex[pageKey];
|
||||
|
||||
// 兜底用全局索引
|
||||
if (!records || records.length === 0) {
|
||||
records = globalIndex[baseUrl];
|
||||
}
|
||||
|
||||
if (records && records.length > 0) {
|
||||
// 合并该页面下所有同接口调用的入参(取并集)
|
||||
const merged = mergeStructures(records);
|
||||
|
||||
api.httpMethod = merged.method;
|
||||
api.dataStructure = merged.mergedData;
|
||||
api.paramsStructure = merged.mergedParams;
|
||||
api.exampleData = merged.exampleData;
|
||||
api.exampleParams = merged.exampleParams;
|
||||
api.requestType = merged.requestType;
|
||||
api.runtimeSource = merged.requestSource;
|
||||
api.runtimePage = pageName;
|
||||
api.hasRuntimeData = true;
|
||||
// 标记是否为当前页面的精确匹配
|
||||
api.exactPageMatch = !!pageIndex[pageKey];
|
||||
|
||||
matchedApiCount++;
|
||||
pageHasMatch = true;
|
||||
} else {
|
||||
api.hasRuntimeData = false;
|
||||
unmatchedApiCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (pageHasMatch) matchedPageCount++;
|
||||
}
|
||||
|
||||
console.log(`\n✅ 合并结果:`);
|
||||
console.log(` 已匹配接口: ${matchedApiCount}`);
|
||||
console.log(` 未匹配接口: ${unmatchedApiCount}`);
|
||||
console.log(` 有运行时数据的页面: ${matchedPageCount}`);
|
||||
|
||||
// 3. 输出合并后的 JSON
|
||||
const jsonOutput = path.join(__dirname, '..', 'config', 'api_mapping_merged.json');
|
||||
fs.writeFileSync(jsonOutput, JSON.stringify(apiMapping, null, 2), 'utf-8');
|
||||
console.log(`\n📁 JSON 已输出: ${jsonOutput}`);
|
||||
|
||||
// 4. 输出 Markdown 报告
|
||||
const mdOutput = path.join(__dirname, '..', 'config', 'api_mapping_merged.md');
|
||||
let md = '';
|
||||
|
||||
md += `# API 接口映射报告(含入参)\n\n`;
|
||||
md += `> 生成时间: ${new Date().toLocaleString('zh-CN')}\n\n`;
|
||||
md += `## 统计\n\n`;
|
||||
md += `| 指标 | 数值 |\n`;
|
||||
md += `|------|------|\n`;
|
||||
md += `| 总页面数 | ${apiMapping.length} |\n`;
|
||||
md += `| 有运行时数据的页面 | ${matchedPageCount} |\n`;
|
||||
md += `| 已匹配接口 | ${matchedApiCount} |\n`;
|
||||
md += `| 未匹配接口(待采集) | ${unmatchedApiCount} |\n\n`;
|
||||
|
||||
// 按父菜单分组
|
||||
const groups = {};
|
||||
for (const item of apiMapping) {
|
||||
const parent = item.parentMenu || '未分类';
|
||||
if (!groups[parent]) groups[parent] = [];
|
||||
groups[parent].push(item);
|
||||
}
|
||||
|
||||
for (const [groupName, items] of Object.entries(groups)) {
|
||||
md += `## ${groupName}\n\n`;
|
||||
|
||||
for (const item of items) {
|
||||
if (item.status === 'unmatched' || item.status === 'dir_not_found') continue;
|
||||
|
||||
md += `### ${item.menuName}\n`;
|
||||
md += `- 路径: \`${item.menuUrl}\`\n`;
|
||||
md += `- 组件: \`${item.componentDir || item.componentPath}\`\n`;
|
||||
md += `- 调用了 **${item.apiCount}** 个接口\n\n`;
|
||||
|
||||
if (item.apis && item.apis.length > 0) {
|
||||
item.apis.forEach((api, idx) => {
|
||||
const method = api.httpMethod || '-';
|
||||
const status = api.hasRuntimeData ? '✅ 已采集' : '⏳ 待采集';
|
||||
|
||||
md += `#### ${idx + 1}. \`${api.functionName || '-'}\`\n\n`;
|
||||
md += `- **方法**: \`${method}\`\n`;
|
||||
md += `- **接口**: \`${api.path}\`\n`;
|
||||
md += `- **完整URL**: \`${api.fullUrl}\`\n`;
|
||||
md += `- **来源**: \`${api.serviceFile || '-'}\`\n`;
|
||||
md += `- **状态**: ${status}\n`;
|
||||
|
||||
// 详细入参结构
|
||||
if (api.dataStructure && Object.keys(api.dataStructure).length > 0) {
|
||||
md += `- **请求体 (data/body)**:\n`;
|
||||
md += '```json\n';
|
||||
md += JSON.stringify(api.dataStructure, null, 2) + '\n';
|
||||
md += '```\n';
|
||||
}
|
||||
if (api.paramsStructure && Object.keys(api.paramsStructure).length > 0) {
|
||||
md += `- **查询参数 (params/query)**:\n`;
|
||||
md += '```json\n';
|
||||
md += JSON.stringify(api.paramsStructure, null, 2) + '\n';
|
||||
md += '```\n';
|
||||
}
|
||||
|
||||
// 示例请求参数展示
|
||||
if ((api.exampleData && Object.keys(api.exampleData).length > 0) ||
|
||||
(api.exampleParams && Object.keys(api.exampleParams).length > 0)) {
|
||||
md += `- **示例请求参数 (运行时真实数据)**:\n`;
|
||||
md += '```json\n';
|
||||
const fullExample = {};
|
||||
if (api.exampleData) fullExample.body = api.exampleData;
|
||||
if (api.exampleParams) fullExample.query = api.exampleParams;
|
||||
md += JSON.stringify(fullExample, null, 2) + '\n';
|
||||
md += '```\n';
|
||||
}
|
||||
if (!api.hasRuntimeData) {
|
||||
md += `- **入参**: 暂无运行时数据\n`;
|
||||
} else if (
|
||||
(!api.dataStructure || Object.keys(api.dataStructure).length === 0) &&
|
||||
(!api.paramsStructure || Object.keys(api.paramsStructure).length === 0)
|
||||
) {
|
||||
md += `- **入参**: 无参数\n`;
|
||||
}
|
||||
|
||||
md += '\n';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
md += '---\n\n';
|
||||
}
|
||||
|
||||
fs.writeFileSync(mdOutput, md, 'utf-8');
|
||||
console.log(`📝 Markdown 已输出: ${mdOutput}`);
|
||||
18
scripts/refresh_api_docs.ps1
Normal file
18
scripts/refresh_api_docs.ps1
Normal file
@ -0,0 +1,18 @@
|
||||
# API 映射文档自动化刷新脚本
|
||||
# 顺序执行:合并 -> 参数注释 -> 接口命名
|
||||
|
||||
Write-Host "🚀 开始刷新 API 映射文档..." -ForegroundColor Cyan
|
||||
|
||||
# 1. 合并日志数据
|
||||
Write-Host "`n[1/3] 正在合并运行时日志..." -ForegroundColor Yellow
|
||||
node scripts/merge_api_data.js
|
||||
|
||||
# 2. 添加参数字段中文注释
|
||||
Write-Host "`n[2/3] 正在添加参数字段注释..." -ForegroundColor Yellow
|
||||
node scripts/annotate_params.js
|
||||
|
||||
# 3. 添加接口中文名称 (Swagger)
|
||||
Write-Host "`n[3/3] 正在关联接口中文名称..." -ForegroundColor Yellow
|
||||
node scripts/add_api_names.js
|
||||
|
||||
Write-Host "`n✅ 文档刷新完成!请查看 config/api_mapping_merged.md" -ForegroundColor Green
|
||||
@ -349,6 +349,10 @@ const Detail = ({ id, showTabs, tabActive, smallTabActive, isPayment, price, dat
|
||||
// 拿到关联合同信息
|
||||
// 拿到关联合同列表
|
||||
const handleGetRelatedContracts = async (type: number, id: number) => {
|
||||
if (!id) {
|
||||
setRelatedContractsList([])
|
||||
return
|
||||
}
|
||||
let req = {}
|
||||
if (type === 1000) {
|
||||
req = {
|
||||
@ -377,7 +381,7 @@ const Detail = ({ id, showTabs, tabActive, smallTabActive, isPayment, price, dat
|
||||
const list: any = []
|
||||
data.data.forEach((item: any) => {
|
||||
// if (type !== 1000 || (type === 1000 && item.COMPACT_DETAILS !== 1000)) {
|
||||
if (item.COMPACT_DETAILS === 2000) {
|
||||
if (item.COMPACT_DETAILS === 2000 || item.COMPACT_DETAILS === 1090) {
|
||||
list.push(item)
|
||||
}
|
||||
})
|
||||
|
||||
@ -103,6 +103,7 @@ function getMenuDom(data: ServerpartTree[], callback?: Function) {
|
||||
// 项目页面主体
|
||||
// const ProjecetTable: React.FC = () => {
|
||||
const ProjecetTable: React.FC<{ currentUser?: CurrentUser }> = (props) => {
|
||||
const serverpartObj = session.get('serverpartObj')
|
||||
const { currentUser } = props
|
||||
// props
|
||||
// useState 为umi封装的hooks方法
|
||||
@ -277,6 +278,17 @@ const ProjecetTable: React.FC<{ currentUser?: CurrentUser }> = (props) => {
|
||||
},
|
||||
initialValue: '1'
|
||||
},
|
||||
{
|
||||
title: '服务区',
|
||||
dataIndex: "SERVERPART_IDS",
|
||||
hideInTable: true,
|
||||
valueType: 'select',
|
||||
valueEnum: serverpartObj,
|
||||
fieldProps: {
|
||||
showSearch: true, // 支持输入文字搜索
|
||||
filterOption: (input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase()),
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
dataIndex: 'STAFF_NAME',
|
||||
@ -396,6 +408,7 @@ const ProjecetTable: React.FC<{ currentUser?: CurrentUser }> = (props) => {
|
||||
// 发起请求,return 请求返回的数据
|
||||
const list = await getProjectList({
|
||||
...params,
|
||||
SERVERPART_IDS: params?.SERVERPART_IDS || "",
|
||||
sortstr: sortstr.length ? sortstr.toString() : params?.sortstr,
|
||||
keyWord: params.searchKey ? { key: "BUSINESSPROJECT_NAME,BUSINESSPROJECT_DESC,MERCHANTS_NAME,SERVERPARTSHOP_NAME", value: params.searchKey } : null, // 关键词查询
|
||||
pagesize: params.pageSize,
|
||||
|
||||
@ -308,6 +308,36 @@ const DataDictionary: React.FC<{ currentUser: CurrentUser }> = (props) => {
|
||||
: []
|
||||
);
|
||||
};
|
||||
|
||||
const handleExtractModuleTree = (menuList, childKey = 'children') => {
|
||||
if (!Array.isArray(menuList)) return [];
|
||||
|
||||
return menuList.map(menu => {
|
||||
const node = {
|
||||
SYSTEMMENU_ID: menu.SYSTEMMENU_ID,
|
||||
SYSTEMMENU_NAME: menu.SYSTEMMENU_NAME,
|
||||
children: []
|
||||
};
|
||||
|
||||
// 1️⃣ 处理子菜单(如果存在)
|
||||
if (Array.isArray(menu[childKey]) && menu[childKey].length) {
|
||||
node.children.push(...handleExtractModuleTree(menu[childKey], childKey));
|
||||
}
|
||||
|
||||
// 2️⃣ 处理模块 → 直接作为 children
|
||||
if (Array.isArray(menu.SystemModuleList) && menu.SystemModuleList.length) {
|
||||
const modules = menu.SystemModuleList.map(m => ({
|
||||
SYSTEMMODULE_NAME: m.SYSTEMMODULE_NAME,
|
||||
SYSTEMMODULE_URL: m.SYSTEMMODULE_URL
|
||||
}));
|
||||
|
||||
node.children.push(...modules);
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<PageContainer header={{
|
||||
title: '',
|
||||
@ -327,6 +357,8 @@ const DataDictionary: React.FC<{ currentUser: CurrentUser }> = (props) => {
|
||||
ShowModule: true,
|
||||
SystemMenuPID: currentUser?.ID === 4653 ? '95' : '-1'
|
||||
})
|
||||
let res = handleExtractModuleTree(data)
|
||||
console.log('data32131', res);
|
||||
setMenuTree(data)
|
||||
|
||||
return { data, success: true }
|
||||
|
||||
@ -1033,11 +1033,9 @@ const YearExamineProcess = ({ currentUser, onShow, setOnShow, parentRow, setPare
|
||||
if (parentRow?.ENDDATE && new Date(parentRow?.ENDDATE).getTime() < new Date('2025-04-01 00:00:00').getTime()) {
|
||||
list = list.filter((item: any) => item.value !== 1802);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (currentUser?.ID === 2785) {
|
||||
list.push({ label: '严琅杰', value: 2785 })
|
||||
}
|
||||
|
||||
132
src/utils/apiLogger.ts
Normal file
132
src/utils/apiLogger.ts
Normal file
@ -0,0 +1,132 @@
|
||||
/**
|
||||
* API 请求记录器
|
||||
*
|
||||
* 在所有 request 拦截器中注入,记录每次请求的 URL、方法、入参等信息。
|
||||
* 用户浏览系统时自动收集数据,通过控制台命令导出。
|
||||
*
|
||||
* 使用方式:
|
||||
* - 浏览系统各页面,请求自动被记录
|
||||
* - 浏览器控制台执行 window.__exportApiLog() 导出 JSON 文件
|
||||
* - 浏览器控制台执行 window.__getApiLogStats() 查看统计信息
|
||||
* - 浏览器控制台执行 window.__clearApiLog() 清空记录
|
||||
*/
|
||||
|
||||
// 全局请求日志存储
|
||||
const apiLog: any[] = [];
|
||||
|
||||
/**
|
||||
* 提取对象的 key 结构(递归,最多3层),只记录 key 和类型,不记录值
|
||||
*/
|
||||
const extractKeys = (obj: any, depth = 0): any => {
|
||||
if (!obj || typeof obj !== 'object' || depth > 3) return typeof obj;
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.length > 0 ? [extractKeys(obj[0], depth + 1)] : '[]';
|
||||
}
|
||||
const result: Record<string, any> = {};
|
||||
for (const key of Object.keys(obj)) {
|
||||
result[key] = extractKeys(obj[key], depth + 1);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 记录一次 API 请求
|
||||
* @param url 请求完整 URL
|
||||
* @param options 请求选项(含 method, data, params 等)
|
||||
* @param requestSource 请求来源标识(如 'request', 'requestCode' 等)
|
||||
*/
|
||||
export function logApiCall(url: string, options: any, requestSource: string) {
|
||||
try {
|
||||
// 获取当前页面路径(开发模式 hash 路由)
|
||||
let currentPage = '';
|
||||
let currentPageName = '';
|
||||
if (typeof window !== 'undefined') {
|
||||
// hash 模式下从 hash 获取路径,如 #/contract/list -> /contract/list
|
||||
currentPage = (window.location.hash || '').replace(/^#/, '') || window.location.pathname || '';
|
||||
// 尝试从 sessionStorage 获取当前菜单名称
|
||||
try {
|
||||
const menuStr = sessionStorage.getItem('currentMenu') || '{}';
|
||||
const currentMenu = JSON.parse(menuStr);
|
||||
currentPageName = currentMenu?.name || '';
|
||||
} catch (e) {
|
||||
// 忽略解析错误
|
||||
}
|
||||
}
|
||||
|
||||
const record = {
|
||||
url,
|
||||
method: (options?.method || 'GET').toUpperCase(),
|
||||
requestSource,
|
||||
currentPage,
|
||||
currentPageName,
|
||||
timestamp: new Date().toISOString(),
|
||||
// 记录完整入参(含值)
|
||||
dataFull: options?.data ? JSON.parse(JSON.stringify(options.data)) : null,
|
||||
paramsFull: options?.params ? JSON.parse(JSON.stringify(options.params)) : null,
|
||||
// 记录入参结构(只有 key 和类型)
|
||||
dataStructure: options?.data ? extractKeys(options.data) : null,
|
||||
paramsStructure: options?.params ? extractKeys(options.params) : null,
|
||||
requestType: options?.requestType || '',
|
||||
};
|
||||
apiLog.push(record);
|
||||
} catch (e) {
|
||||
// 记录失败不影响正常请求
|
||||
console.warn('[API Logger] 记录失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出 API 日志为 JSON 文件下载
|
||||
*/
|
||||
function exportApiLog() {
|
||||
const dataStr = JSON.stringify(apiLog, null, 2);
|
||||
const blob = new Blob([dataStr], { type: 'application/json' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = blobUrl;
|
||||
a.download = `api_log_${new Date().toISOString().replace(/[:.]/g, '-')}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
console.log(`[API Logger] 已导出 ${apiLog.length} 条记录`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看统计信息
|
||||
*/
|
||||
function getApiLogStats() {
|
||||
const uniqueUrls = new Set(apiLog.map((r: any) => r.url));
|
||||
const bySource: Record<string, number> = {};
|
||||
const byPage: Record<string, number> = {};
|
||||
apiLog.forEach((r: any) => {
|
||||
bySource[r.requestSource] = (bySource[r.requestSource] || 0) + 1;
|
||||
const pageKey = r.currentPageName || r.currentPage || '(unknown)';
|
||||
byPage[pageKey] = (byPage[pageKey] || 0) + 1;
|
||||
});
|
||||
console.log('=== API Logger 统计 ===');
|
||||
console.log(`总请求数: ${apiLog.length}`);
|
||||
console.log(`唯一接口数: ${uniqueUrls.size}`);
|
||||
console.log(`已覆盖页面数: ${Object.keys(byPage).length}`);
|
||||
console.log('按来源统计:', bySource);
|
||||
console.log('按页面统计:', byPage);
|
||||
return { total: apiLog.length, unique: uniqueUrls.size, bySource, byPage };
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空日志
|
||||
*/
|
||||
function clearApiLog() {
|
||||
apiLog.length = 0;
|
||||
console.log('[API Logger] 日志已清空');
|
||||
}
|
||||
|
||||
// 挂载到 window 全局对象
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as any).__exportApiLog = exportApiLog;
|
||||
(window as any).__getApiLogStats = getApiLogStats;
|
||||
(window as any).__clearApiLog = clearApiLog;
|
||||
(window as any).__apiLog = apiLog;
|
||||
}
|
||||
|
||||
export default logApiCall;
|
||||
@ -1,4 +1,4 @@
|
||||
// 由 scripts/writeVersion.js 自动生成
|
||||
export const VERSION = "4.5.82";
|
||||
export const GIT_HASH = "d02155b";
|
||||
export const BUILD_TIME = "2026-02-12T07:10:55.081Z";
|
||||
export const VERSION = "4.5.85";
|
||||
export const GIT_HASH = "932138e";
|
||||
export const BUILD_TIME = "2026-02-28T10:44:14.639Z";
|
||||
|
||||
104
walkthrough.md.resolved
Normal file
104
walkthrough.md.resolved
Normal file
@ -0,0 +1,104 @@
|
||||
# 历史数据回溯 — 变更检测功能开发工作日志
|
||||
|
||||
## 项目概述
|
||||
|
||||
项目路径:`e:\workfile\JAVA\AI-Python\AI-Python`
|
||||
|
||||
这是一个 Django + React 前后端分离项目,核心功能是从第三方 API(驿达商业平台)拉取数据并存储到本地数据库(达梦/Oracle)。项目包含:
|
||||
- **数据更新定时任务**(`SCHEDULED_UPDATE_TASK` + `DATA_UPDATE_CONFIG`):按配置自动从第三方 API 拉取数据并 upsert 到本地表
|
||||
- **历史数据回溯**(`HISTORY_BACKFILL_CONFIG`):定期回溯检查历史月份数据是否有变动,按需同步
|
||||
- **动态查询 API**(`API_CONFIG`):根据配置动态生成本地数据查询接口
|
||||
|
||||
## 本次会话完成的工作
|
||||
|
||||
### 1. 修复本地数据获取 endpoint 错误 ✅
|
||||
|
||||
**问题**:`_fetch_local_check_data` 使用 `DATA_UPDATE_CONFIG.NAME`(中文描述名如"更新python语义营收")构造 URL,导致 404。
|
||||
|
||||
**修复**:
|
||||
- 修改 [history_backfill_service.py](file:///e:/workfile/JAVA/AI-Python/AI-Python/api/history_backfill_service.py) 的 `_get_data_update_config()` 方法,增加子查询 JOIN `API_CONFIG`,通过 `TARGET_TABLE = TABLE_NAME` 获取正确的 `local_endpoint`
|
||||
- 修改 `_check_month_changed()` 使用 `duc_config['local_endpoint']` 替代 `duc_config['name']`
|
||||
- 同步修复 [history_backfill_views.py](file:///e:/workfile/JAVA/AI-Python/AI-Python/api/history_backfill_views.py) 的 `BackfillCheckPreviewView`
|
||||
|
||||
### 2. 清理 DEBUG 日志 ✅
|
||||
|
||||
删除 [dynamic_query_service.py](file:///e:/workfile/JAVA/AI-Python/AI-Python/api/dynamic_query_service.py) 中所有 `[DEBUG]` 级别的 print 语句(约 30 处),包括:SQL 参数、字段匹配、汇总包裹、模糊查询等
|
||||
|
||||
### 3. 改进变更检测日志输出 ✅
|
||||
|
||||
修改 `_check_month_changed()` 的对比逻辑,无论字段匹配与否都输出日志:
|
||||
```
|
||||
[DETECT] 202501 | 对客销售 = 一致 | 本地=12345 | 远端=12345
|
||||
[DETECT] 202501 | 年度累计 ≠ 不同 | 本地=99999 | 远端=88888
|
||||
[DETECT] 202501 结论: 发现差异,需要全量同步
|
||||
```
|
||||
|
||||
### 4. 调整超时时间 ✅
|
||||
|
||||
变更检测的 API 调用超时从 30 秒调整为 120 秒(与全量同步一致)。
|
||||
|
||||
### 5. MonthINCAnalysisProxyView 修改 ✅
|
||||
|
||||
将 `/api/revenue/month-inc-analysis/` 接口的 `ServerpartId` 从必填改为可选。
|
||||
|
||||
### 6. 修复 NEWGETSUMMARYREVENUEMONTH 展平问题(部分完成)
|
||||
|
||||
在 [data_processing_utils.py](file:///e:/workfile/JAVA/AI-Python/AI-Python/api/data_processing_utils.py) 的 `process_downloaded_data` 中,对 `NEWGETSUMMARYREVENUE` 和 `NEWGETSUMMARYREVENUEMONTH` 表跳过 `extract_serverpart_data` 步骤。
|
||||
|
||||
---
|
||||
|
||||
## 🚨 当前待解决问题
|
||||
|
||||
### 问题:变更检测与全量同步的数据粒度不匹配
|
||||
|
||||
**现象**:回溯执行"更新驿达看板首页数据缓存"(NEWGETSUMMARYREVENUEMONTH 表)时,变更检测判定"有差异"后触发全量同步,但全量同步结果全部是"跳过"(数据一致),没有任何实际更新。
|
||||
|
||||
**根因分析**:
|
||||
|
||||
| 阶段 | 调用方式 | 数据粒度 |
|
||||
|---|---|---|
|
||||
| 变更检测(本地) | `GET /api/dynamic/ahydDIBData/?StatisticsStartMonth=202501&StatisticsEndMonth=202501`(不传 ServerpartId) | **省级汇总**(123 条聚合成 1 条) |
|
||||
| 变更检测(远端) | `GET GetSummaryRevenueMonth?pushProvinceCode=340000&StatisticsMonth=202501`(不传 ServerpartId) | **省级汇总**(需确认返回结构) |
|
||||
| 全量同步 | `execute_update_internal` → 按 133 个 ServerpartId 逐个调用第三方 API | **服务区级**(每次 1 条) |
|
||||
|
||||
**变更检测的两个子问题**:
|
||||
1. **远端值为 None**:之前字段路径配置错误(用 `CashPay` 而非 `MonthRevenueModel.CashPay`),用户已手动修正为嵌套路径
|
||||
2. **数据口径不一致**:本地汇总是 dynamic API 的聚合计算结果,远端是第三方 API 不传 ServerpartId 时的返回值,两者可能不是同一个统计口径
|
||||
|
||||
**全量同步"跳过123"**:
|
||||
- `execute_update_internal` 调用 `process_downloaded_data` 处理数据
|
||||
- upsert 按 `STATISTICS_MONTH + SERVERPART_ID` 匹配后,逐字段比对发现数据与数据库完全一致
|
||||
- 这说明数据确实没变化,跳过是正确行为
|
||||
- 但如果变更检测一直误判为"有差异",就会每次执行无效的全量同步(浪费资源)
|
||||
|
||||
---
|
||||
|
||||
## 关键文件说明
|
||||
|
||||
| 文件 | 用途 |
|
||||
|---|---|
|
||||
| `api/history_backfill_service.py` | 回溯服务核心:调度、变更检测、执行全量同步 |
|
||||
| `api/history_backfill_views.py` | 回溯 API 视图(CRUD、手动触发、检测预览) |
|
||||
| `api/data_update_internal.py` | 定时任务内部执行函数(被回溯服务调用) |
|
||||
| `api/data_update_views.py` | 数据更新视图(手动执行入口,有完整的行内特殊处理) |
|
||||
| `api/data_processing_utils.py` | 数据处理工具(展平、提取、upsert) |
|
||||
| `api/dynamic_query_service.py` | 动态查询服务(本地数据 API) |
|
||||
| `api/revenue_proxy_views.py` | 第三方 API 代理视图 |
|
||||
| `frontend/src/components/BackfillConfigPage.tsx` | 回溯配置前端页面 |
|
||||
|
||||
## 数据库表关系
|
||||
|
||||
```
|
||||
SCHEDULED_UPDATE_TASK (定时任务)
|
||||
├── CONFIG_ID → DATA_UPDATE_CONFIG (数据源配置:API URL、目标表、参数映射)
|
||||
└── HISTORY_BACKFILL_CONFIG (回溯配置:TASK_ID → 关联任务)
|
||||
|
||||
DATA_UPDATE_CONFIG.TARGET_TABLE = API_CONFIG.TABLE_NAME (1:1 或 1:N 映射)
|
||||
API_CONFIG.ENDPOINT → 动态查询 API 的 endpoint 名
|
||||
```
|
||||
|
||||
## 后续建议
|
||||
|
||||
1. **修正变更检测逻辑**:要么让变更检测也按服务区级别对比(但太慢),要么确认不传 ServerpartId 时第三方 API 返回的省级汇总字段路径完全正确
|
||||
2. **考虑方案 B**:变更检测直接查数据库比对,绕过 HTTP 调用,避免数据结构适配问题
|
||||
3. **注意 `data_update_views.py` vs `process_downloaded_data` 的差异**:前者有更完善的行内特殊处理(字段优先级保护、智能唯一键补充),后者是简化版。当前回溯使用后者
|
||||
Loading…
x
Reference in New Issue
Block a user