20260602备份

This commit is contained in:
2026-06-02 15:08:26 +08:00
parent baf2a970e2
commit 82af9b974b
69 changed files with 26998 additions and 3020 deletions
@@ -63,7 +63,7 @@ def ceshi(formData) -> str:
print("生成失败预警!")
cookies_str ="macanSESSIONID=6a135db3-5515-4acf-abf0-6c903afc9391; erpLanguage=zh-CN; prodOrg=11240984669916808518; unp=10907434497511789553; un=10907434497511789553; _up=-NillNN-qyBEJ--t3vnSknvoOFt2zfaJs8kB2nw6W-ZaXvjEoprQjaZJ9Q3d-WrAAGgt60MgQHajHWBHMKKxj0CuWypi1JgKCFP1EPEk-HbqE_oUr4wj1gUK-_hRv-ZNHu3M-GTZ15i2EXSsquRSjOobl17KOxZsrEj9mB66_c6yTmA.; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2210907434497511789553%22%2C%22first_id%22%3A%2219b6df76a22f46-04a98afdd2a11d8-4c657b58-1327104-19b6df76a2312c7%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22%24device_id%22%3A%2219b6df76a22f46-04a98afdd2a11d8-4c657b58-1327104-19b6df76a2312c7%22%7D; tmall=false; Hm_lvt_25f5e7a3a5dbb293d7dd35d5f1be8d0a=1778119278,1778823299,1778838735,1779067120; HMACCOUNT=A6A0585E8C70051D; Hm_lpvt_25f5e7a3a5dbb293d7dd35d5f1be8d0a=1779067429"
cookies_str ="marketingSESSIONID=e97d549e-f4a7-4d16-9dcc-92232701341a; erpLanguage=zh-CN; tmall=false; Hm_lvt_25f5e7a3a5dbb293d7dd35d5f1be8d0a=1779688780,1779850114,1779930850,1780295117; HMACCOUNT=A6A0585E8C70051D; prodOrg=16082863698598744116; unp=16082863699555045468; un=16082863699555045468; _up=-NillNN-qyBEJ--t3vnSknvoOF1_xfOFts4D2nI6X-JdUvTFoZHQjaZJ9Q3d-WrAAGgt60MgQHajHWBHMKKxj0CuWypi1JgKCFP1EPEk-HbqHPMXr48n0wcN_vFRv-ZNHu3M-GTf3pCzHXGrqORcjO4fkFLGOhVnrEj9nhiwoLrSZWc.; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2216082863699555045468%22%2C%22first_id%22%3A%2219b6df76a22f46-04a98afdd2a11d8-4c657b58-1327104-19b6df76a2312c7%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22%24device_id%22%3A%2219b6df76a22f46-04a98afdd2a11d8-4c657b58-1327104-19b6df76a2312c7%22%7D; Hm_lpvt_25f5e7a3a5dbb293d7dd35d5f1be8d0a=1780365009"
cookies_str = cookies_str.encode('utf-8').decode('latin-1')
cookie_dict = {item.split('=')[0]: item.split('=')[1]
@@ -78,7 +78,7 @@ headers = {
data = xlrd.open_workbook(r"D:\Idea Project\F6+宜搭+其它(1)\张阳脚本\模板\套餐卡退卡.xls")
# data = xlrd.open_workbook(r"C:\Users\hp_z66\OneDrive\Desktop\钉钉文件\退卡.xls")
table = data.sheet_by_index(0) # 通过索引顺序获取
table = data.sheet_by_index(2) # 通过索引顺序获取
h = table.nrows
@@ -86,6 +86,7 @@ l = table.ncols
print(u"表数据的行数为%s,列数为%s" % (h, l))
for i in tqdm(range(1, h)):
try:
idMember = table.cell(i, 0).value # 卡id
@@ -104,7 +105,7 @@ for i in tqdm(range(1, h)):
"employeeId": employeeId, # 退卡服务顾问ID 手动更新
"idOwnOrg": idOwnOrg, # 门店ID 手动更新
"refundAmount": idMember1, # 退卡金额
"remark": "" # 备注
"remark": "批量退卡" # 备注
}
# 生成退卡单
res = requests.post(f'https://yunxiu.f6car.cn/marketing/tkdBill/add',
@@ -124,7 +125,7 @@ for i in tqdm(range(1, h)):
"idOperationOrg": idOwnOrg, # 退卡门店ID 手动更新
"idSourceBill": resdata, # 退卡单ID
"sourceBillType": "TKD", # 类型
"receiptMemo": "", # 备注
"receiptMemo": "批量退卡", # 备注
"version": cardVersion1,
"favourableList": [],
"gatheringFavourable": 0,
@@ -132,7 +133,7 @@ for i in tqdm(range(1, h)):
"czkList": [],
"paymentList": [
{
"paymentTypeId": "19080776", # 各门店不同 手动更新
"paymentTypeId": "19116196", # 各门店不同 手动更新
"paymentType": "现金",
"paymentAmount": idMember2,
"gatheringType": 0,
@@ -149,7 +150,7 @@ for i in tqdm(range(1, h)):
"idOperationOrg": idOwnOrg, # 退卡门店ID 手动更新
"idSourceBill": resdata, # 退卡单ID
"sourceBillType": "TKD", # 类型
"receiptMemo": "", # 备注
"receiptMemo": "批量退卡", # 备注
"version": cardVersion1,
"favourableList": [],
"gatheringFavourable": 0,
@@ -169,7 +170,7 @@ for i in tqdm(range(1, h)):
res = requests.post(f'https://yunxiu.f6car.cn/financial/advance/payment/singleGathering',
headers=headers, cookies=cookie_dict, json=data)
print("打印出响应信息:", i, cardVersion, cardVersion1, resdata, idMember, res.text)
time.sleep(2)
time.sleep(0.5)
# break # 测试
except:
formData = {
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+34
View File
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
import requests, json, sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
BASE='https://fos-api.lunz.cn/api'
APP_KEY='8e240000-3e12-0016-1e0f-08d58267a484'
r=requests.post(f'{BASE}/Membership/Login', headers={'appkey':APP_KEY,'content-type':'application/json'}, json={'username':'zhongdexinqcyp','password':'ZDX2018'})
d=r.json()['Data']
h={'appkey':APP_KEY,'authtoken':d['tokenId'],'audl_user':d['qxentity']['memberuserid'],'openchainsign':d['qxentity']['openChainSign'],'signstring':d['qxentity']['signstring'],'ucuserid':d['qxentity']['userid']}
mer=d['qxentity']['businessproductunitid']
r2=requests.post(f'{BASE}/member/GetMemberinfoList', headers=h, json={'paging':{'pageSize':50,'pageIndex':1,'sort':[],'filters':[{'field':'IsDisplay','op':'eq','Term':'1'},{'field':'Enabled','op':'eq','Term':'1'}]},'qxentity':d['qxentity'],'searchValue':'','memType':1})
members=r2.json().get('Data',[])
count=0
for m in members:
mid=m['MemberId']
r3=requests.get(f'{BASE}/memberProduc/GetMemProductByMemId', params={'merStoreId':mer,'memberId':mid}, headers=h)
prods=r3.json().get('Data',[])
for p in prods:
if p.get('BuyProductItemName') and p.get('BuyProductItemExAmount'):
print(f"Name={m['MemName']} Phone={m['MemMobile']}")
print(f"Card={p['ProductName']}")
print(f"ExAmount={p.get('BuyProductItemExAmount','')}")
print(f"Amount={p.get('BuyProductItemAmount','')}")
names=p['BuyProductItemName'].split(';')
exs=p.get('BuyProductItemExAmount','').split(';')
ams=p.get('BuyProductItemAmount','').split(';')
for n,ex,am in zip(names,exs,ams):
print(f' {n} | total={am} remain={ex}')
print()
count+=1
if count>=5: break
if count>=5: break
+11 -4
View File
@@ -304,10 +304,15 @@ class BossPermissionAutoApproval:
# df1.to_csv(fr"D:\Idea Project\F6+宜搭+其它(1)\张阳脚本\文件输出\Boss权限对应功能.csv", index=False)
all_data = []
all_data_ids = []
# 获取门店编码与权限功能,并匹配对应的多条功能数据
for index, row in df.iterrows():
# 过滤出 审批节点 的数据
data_id = row["_id"]
data_id = row.get("_id")
if data_id is None or (isinstance(data_id, float) and pd.isna(data_id)):
print(f"警告:第 {index} 行缺少 _id 字段,跳过该行")
continue
all_data_ids.append(data_id)
get_task_id = API().workflow_instance_get({"data_id": data_id})
flow_name = get_task_id.get("tasks")[-1].get("flow_name")
if flow_name != "审批节点":
@@ -319,8 +324,6 @@ class BossPermissionAutoApproval:
# 获取当前行的权限功能
perm_functions = row["权限功能"]
# 获取任务的数据id
data_id = row["_id"]
# 在df1中查找匹配的所有功能
matched_functions = df1[df1['权限功能'] == perm_functions] # 假设df1中有'权限功能'列
@@ -982,7 +985,11 @@ class BossPermissionAutoApproval:
self.send_task_status(task_start_time, "boss权限自动审批")
except Exception as e:
self.send_task_error(task_start_time, "boss权限自动审批失败", str(e))
import traceback
error_detail = f"{type(e).__name__}: {e}"
print(f"任务执行异常: {error_detail}")
print(f"堆栈信息:\n{traceback.format_exc()}")
self.send_task_error(task_start_time, "boss权限自动审批失败", error_detail)
if __name__ == '__main__':
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,230 @@
---
name: f6-jaspersoft
description: "F6汽车维修管理系统打印单定制技能。基于 TIBCO Jaspersoft Studio 的 .jrxml/.jasper 模板开发、参数查询、JRXML 结构修复、后台配置。当用户提到:打印单、结算单、委托单、报价单、检测单、jrxml、jasper、Jaspersoft、打印模板、打印单定制、F6打印、工单打印、维修单打印、边框报错、box报错、reportElement报错时触发。"
description_zh: "F6打印单定制:Jaspersoft模板开发、接口参数查询、JRXML修复、后台配置"
description_en: "F6 print template customization: Jaspersoft development, API params, JRXML fix, backend config"
version: 1.0.0
allowed-tools: Bash,Read
---
# F6 Jaspersoft 打印单定制技能
## 概述
F6 汽车维修管理系统的打印单基于 **TIBCO Jaspersoft Studio** 模板引擎,使用 `.jrxml`(源码)和 `.jasper`(编译后)文件。打印数据由后端接口传入模板参数,模板通过 `$P{}` / `$F{}` 表达式渲染。
本技能提供完整的打印单定制知识库,涵盖:
- 14 种打印单类型的接口参数文档
- JRXML 模板开发规范与语法
- 常见报错修复方案
- 后台配置流程
---
## 触发条件
当用户提到以下关键词时,应使用此技能:
- 打印单、结算单、委托单、报价单、检测单
- jrxml、jasper、Jaspersoft、打印模板
- 打印单定制、F6打印、工单打印、维修单打印
- 边框报错、box报错、reportElement报错
---
## 常用链接
| 名称 | 地址 |
|------|------|
| 打印单后台 | http://print.f6yc.com/print-server/ui/index.html#/template/classification |
| 打印单模板样式 | https://xcz.yuque.com/ombipo/rpc7ms/fbd6ay?singleDoc# |
| 打印单参数表 | https://xcz.yuque.com/ombipo/rpc7ms/ro5fs1?singleDoc# |
| 简易开发教程 | https://alidocs.dingtalk.com/i/nodes/dQPGYqjpJYgZGbvbCdEKGDGZWakx1Z5N |
---
## 模板修改流程
1. **下载模板**:后台按名称查询并下载 .jrxml
2. **Jaspersoft Studio 编辑**File → Open File → 选择模板
3. **常见编辑操作**
- 新增静态文本:拖拽 Static Text 组件
- 新增动态字段:`$P{参数名}`(主参数)或 `$F{参数名}`(列表字段)
- 列表字段增减:双击列表进入编辑,用 `$F{参数名}`
- 边框样式:选中组件 → Borders 编辑
4. **预览**:工具内只能预览样式,参数判断需上传到门店后验证
5. **编译**:右键 .jrxml → Compile Report 生成 .jasper
6. **导出**:右键 .jasper → Export Files to... 另存到桌面
7. **后台配置**:上传模板 + 配置门店生效
---
## 语法参考
### 基础语法
| 用法 | 语法 | 示例 |
|------|------|------|
| 文本拼接参数 | `$P{参数}+"文本"` | `$P{printOrgName}+"结算单"` |
| 小数保留 | `$P{参数}.setScale(位数, BigDecimal.ROUND_DOWN)` | `$P{stuffSubtotalAll}.setScale(2, BigDecimal.ROUND_DOWN)` |
| 字符串截取 | `$P{参数}.substring(起始,长度)` | `$P{billDate}.substring(0,10)` |
| 三元运算 | `(条件)?值1:值2` | `($P{printOrgName}==null?$P{orgName}:$P{printOrgName})` |
| 相等判断 | `==``.equals("文本")` | `$P{status}.equals("已完成")` |
### BigDecimal 运算
| 运算 | 语法 |
|------|------|
| 加 | `$P{a}.add($P{b})` |
| 减 | `$P{a}.subtract($P{b})` |
| 乘 | `$P{a}.multiply($P{b})` |
| 除 | `$P{a}.divide($P{b}, 2, BigDecimal.ROUND_HALF_UP)` |
### 高级语法
**从 list 中取特定值(JDK 1.8 + Jaspersoft 6.8):**
```java
$P{payItemList}.getData().stream()
.filter(map -> "记账".equals(map.get("payWay")))
.map(map -> {
Object amt = map.get("payAmount");
return amt == null ? BigDecimal.ZERO : new BigDecimal(amt.toString());
})
.findFirst()
.orElse(BigDecimal.ZERO)
```
---
## JRXML 常见报错与修复
### 报错:`The content of element 'reportElement' is not complete`
**原因**`<box>` 被错误地放在了 `<reportElement>` 内部。JasperReports XSD 规定 `<reportElement>` 只允许 `property``propertyExpression``printWhenExpression` 三种子元素。
**正确结构**`<box>``<reportElement>` 的**兄弟元素**,不是子元素:
```xml
<!-- 错误 -->
<staticText>
<reportElement x="0" y="0" width="49" height="16" uuid="...">
<box>...</box>
</reportElement>
</staticText>
<!-- 正确 -->
<staticText>
<reportElement x="0" y="0" width="49" height="16" uuid="..." />
<box>
<pen lineWidth="0.5" lineColor="#000000" />
<topPen lineWidth="0.5" lineColor="#000000" />
<leftPen lineWidth="0.5" lineColor="#000000" />
<bottomPen lineWidth="0.5" lineColor="#000000" />
<rightPen lineWidth="0.5" lineColor="#000000" />
</box>
</staticText>
```
**批量修复脚本**:运行 `python scripts/fix_jrxml_box.py <jrxml文件路径>`
---
## 打印单分类与通用模板
| 分类 | 系统模块 | 通用模板名称 | 模板编码 |
|------|---------|-------------|---------|
| 新结算单打印 | 维保单 | F6标准结算单(壹) | newSettleFirst |
| 销售单 | 销售单 | 销售单(日期版) | xiaoshodanriqiban |
| 洗车单 | 洗车单 | 洗车单 | wash01 |
| 报价单打印 | 报价单 | 报价单打印 | quotationPrint |
| 新库存入库单 | 入库单 | 新库存入库单打印 | 9001 |
| 新库存出库单 | 出库单 | 新库存出库单打印 | 9002 |
**注意**:结算单要在收款后页面打印,模板名称必须包含"结算单"三字。采购单/采购退货单走打印平台定制,需先向赵亚妮提供门店信息开通。
---
## 后台配置命名规范
### 简单调整
- **模板名称**:基础表+特殊修改需求
- **模板编码**:修改人姓名首字母英文大写+模板分类+日期
- **模板备注**:模板各修改点
### 定制调整
- **模板名称**:门店名称+定制
- **模板编码**:修改人姓名首字母英文大写+模板分类+日期
- **模板备注**:模板各修改点
---
## 接口参数文档索引
当需要查询某类打印单的接口参数时,查阅对应的 reference 文件:
| 打印单类型 | Reference 文件 |
|-----------|---------------|
| 工单结算单 | `references/工单结算单接口文档.md` |
| 新版附表 | `references/新版附表打印接口文档.md` |
| 结算单(指定内容) | `references/结算单(指定内容)接口文档.md` |
| 委托单 | `references/委托单接口文档.md` |
| 协作单 | `references/协作单接口文档.md` |
| 增项单 | `references/增项单接口文档.md` |
| 保险单 | `references/保险单接口文档.md` |
| 定金单 | `references/定金单打印接口文档.md` |
| 报价单 | `references/报价单接口参数.md` |
| 检测单 | `references/检测单接口参数.md` |
| 出/入库单据 | `references/出_入库单据打印.md` |
| 调拨单 | `references/调拨单打印.md` |
| 材料标签 | `references/材料标签接口参数.md` |
| 采购单/采购退货 | `references/采购单_采购退模版打印.md` |
---
## JRXML 元素层级速查
```
jasperReport
├── style (Table_TH, Table_CH, Table_TD)
├── parameter ($P{} 参数声明)
├── queryString (SQL 查询)
├── field ($F{} 字段声明)
├── title (标题区)
├── pageHeader (页眉)
├── columnHeader (列表头)
├── detail (数据行)
│ ├── band
│ │ ├── staticText (静态文本)
│ │ │ ├── reportElement (位置尺寸) ← 只有 property/printWhenExpression
│ │ │ ├── box (边框) ← reportElement 的兄弟!
│ │ │ └── textElement / text
│ │ ├── textField (动态文本)
│ │ │ ├── reportElement
│ │ │ ├── box
│ │ │ └── textElement / textFieldExpression
│ │ ├── image
│ │ ├── line
│ │ │ ├── reportElement
│ │ │ └── graphicElement (line 独有)
│ │ └── componentElement (列表组件)
│ │ └── reportElement
├── columnFooter
├── pageFooter
└── summary
```
---
## 工具环境
- **Jaspersoft Studio 6.3.1**Windows/ **6.8**JDK 1.8
- 工作目录:`C:\Users\hp_z66\Desktop\Jaspersoft Studio\workbuddy-jaspersoft\`
- Python 环境:用于 JRXML 批量修复脚本
---
## 注意事项
- 不要随便删除后台模板,删除不可恢复
- 修改前先备份原始 .jrxml 文件
- Jaspersoft Studio 编译时如果报错,先检查 XML 结构是否合规
- 参数未预设时会提示 "The current expression is not valid",需在 Outline → Parameters 中手动创建
- 金额计算使用 BigDecimal,不要用浮点数
@@ -0,0 +1,11 @@
{
"name": "f6-jaspersoft",
"version": "1.0.0",
"description": "F6汽车维修管理系统打印单定制技能。Jaspersoft模板开发、接口参数查询、JRXML修复、后台配置。",
"author": "张阳",
"license": "MIT",
"category": "enterprise",
"tags": ["f6", "jaspersoft", "jrxml", "打印单", "结算单", "模板"],
"created": "2026-04-23",
"updated": "2026-04-23"
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,67 @@
# 保险单接口文档
# 保险单接口文档
打印平台模版分类:保险单
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| companyTitle | 打印title | | String |
| customerName | 客户姓名 | | String |
| carNo | 车牌号 | | String |
| vin | vin码 | | String |
| carModel | 车型 | | String |
| cellPhone | 手机号 | | String |
| insureDate | 起保日期 | | String yyyy-MM-dd |
| receivable | 应收金额 | | BigDecimal |
| preferentialAmount | 优惠金额 | | BigDecimal |
| receiveAmount | 实收金额 | | BigDecimal |
| oweAmount | 未收金额 | | BigDecimal |
| commissionAmountTotal | 手续费 | | BigDecimal |
| companyRefundAmount | 保险公司返点 | | BigDecimal |
| customerRefundAmount | 客户返点 | | BigDecimal |
| insuranceCompanyName | 承保公司 | | String |
| memo | 备注 | | String |
| employeeName | 服务顾问 | | String |
| contacts | 保险公司联系人 | | String |
| contactMobile | 保险公司联系人手机号 | | String |
| channelName | 来店途径 | | String |
| startDate | 开始日期 | | String yyyy-MM-dd |
| endDate | 结束日期 | | String yyyy-MM-dd |
| renewal | 是否续保 0否/1是 | | Integer |
| tsInsuranceDetailList | 保险单明细 | | List<TsInsuranceDetailPrintVo> |
| | | | |
| <br/><br/> | | | |
TsInsuranceDetailPrintVo
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| policyNo | 保单号 | | String |
| insuranceType | 保险类型(0交强险/1商业险) | | Integer |
| name | 险种名称 | | String |
| amount | 保额 | | BigDecimal |
| receivable | 应收金额(元) | | BigDecimal |
| discount | 折扣 | | BigDecimal |
| concessionary | 优惠金额(元) | | BigDecimal |
| commissionRate | 手续费率 | | BigDecimal |
| commissionAmount | 手续费 | | BigDecimal |
| paid | 实收金额(元) | | BigDecimal |
| memo | 备注 | | String |
| companyRefundAmount | 保险公司返点 | | BigDecimal |
| customerRefundAmount | 客户返点 | | BigDecimal |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
| <br/><br/> | | | |
> 更新: 2023-09-20 11:32:29 原文: <https://xcz.yuque.com/ombipo/rpc7ms/fpzmr5qph5mloy1x>
@@ -0,0 +1,420 @@
# 出/入库单据打印
# 现状梳理
| **场景** | | 入口 | 打印效果 | **底层模版** | 接口 |
| --- | --- | --- | --- | --- | --- |
| 库存 | 出入库单据-出库单 | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739783099448-84de3a8e-2473-4d88-a83d-638122d695ac.png) | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739782451805-24599a5e-df6e-4d6b-bbfb-cbbc1816614f.png) | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739782672560-9e9927d9-bdbd-4ad1-9199-f6588fc11e9d.png) | REST/stock/stockInAndOutBill/stockOutPrint?idStock=XXX&isNew=true<br>底层接口:com.f6car.stock.service.impl.print.PrintServiceImpl#getStockOutPrintUrl<br>日志关键字:+"出库单打印参数:" (有 apollo 开关 log.stockInout.print.switch 默认true |
| | 领料出库(工单领料)-出库单 | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739782990039-cc493301-06f6-48f0-bd0d-76487b30d04e.png)![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739783038197-1924e595-433a-48b7-8ea2-291492a7caae.png) | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739783054922-543a7d3c-f0eb-4814-874d-34bb82aed7cd.png) |
| | 出入库单据-入库单 | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/f5893832-29ca-4164-9071-78371d724dbd.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/09773831-4ef6-4c46-b04a-0b00ea6376b7.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/f4b0a5b6-b259-4b41-91ec-67ed895c9c61.png) | REST/stock/stockInAndOutBill/stockInPrint?idStock=XXX&isNew=true<br>底层接口:com.f6car.stock.service.impl.print.PrintServiceImpl#getStockInPrintUrl<br>日志关键字:+"入库单打印参数:" (有 apollo 开关 log.stockInout.print.switch 默认true |
| | 领料出库(工单领料)-退料单 | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/b3aaf9d0-5231-4dd2-bbb4-a3f02afbfb89.png)<br>![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/afe3844b-a253-4493-ad83-f1a6371aaa51.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/17875029-3462-449d-9f24-3aad72d3737b.png) |
| | 手工出入库-出库单 | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739783184569-1df2391a-c404-48d3-be4e-b39f3f50e9ef.png) | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739783196368-a62151d6-2ff3-48ce-ae99-2742f296e1be.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/de1c2e35-c7db-43e1-b03d-9a617b876df3.png) | REST/stock/manual/print?pkId=XXX&billType=0&isNew=true type=0 表示入库单 type=1 表示出库单)<br>底层接口:com.f6car.stock.service.print.PrintService#getManualStorageStockInPrintUrl<br>日志关键字:+"手工出入库单据打印入参是:" |
| | 手工出入库-入库单 | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/21383c0d-4fbd-490f-95a4-b55a9c1aa473.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/4e269988-1029-48cd-97e5-c71e3ec6b275.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/5e6f982e-0369-48eb-a09b-3d247608db0f.png) |
| | 领料详情-打印领料单 | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/9c2c7fdb-360f-43e0-b07f-4ee0f8a43510.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/974ef939-6d36-433d-a3fe-594cb6cb270c.png) | ![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/8oLl952E8m3Relap/img/f4baaf53-7915-4b45-9ee5-aff164922a00.png) | REST/stock/maintain/print?idSourceBill=XXX&hasPreview=true<br>底层接口:com.f6car.stock.service.impl.print.PrintServiceImpl#getMaintainPrintUrl<br>日志关键字:+"领料单打印参数:" (有 apollo 开关 log.stock.maintain.print.switch 默认false |
# 出入库单据-出库单 && 领料出库-出库单打印模版参数说明
出库单据定制类需求模版分类(newStockOutMaintainCustomPrint--20250925新增
打印模版参数
HashMap<String, Object> resultMap
| **字段** | **说明** | 备注 |
| --- | --- | --- |
| title | 门店名称+ "出库单" | |
| billNo | 出库单号 | |
| sourceBillNo | 来源单号 | |
| showSourceBillNo | 显示来源单号 boolean | |
| billStatus | 单据状态(制单、完成) | |
| inOutDate | 出库日期 | |
| showInOutDate | 是否显示出库日期(boolean | |
| objectName | 出入库对象 | |
| objectNameGD | 出入库对象工单<br/>工单出库单:客户姓名+车牌号整体+车辆VIN码+车辆品牌车系车型全称 (拼接后取前 80 个字符)<br/>非工单出库单:"" | |
| creatorName | 制单人 | |
| billDate | 制单日期 yyyy-MM-dd | |
| sumNumber | 材料总数量 | |
| sumAmount | 总金额 <br>\--脱敏场景显示 _\*_\*_\*\*_ | |
| chineseAmount | 总金额(中文大写)<br>\--脱敏场景显示 _\*_\*\*\* | |
| nowDateTime | 打印当前时间 yyyy-MM-dd HH:mm | |
| idOwnOrg | 门店ID | |
| remark | 备注信息 | |
| showRemark | 是否显示备注(boolean<br>\-- 备注不为空时是 true | |
| isGdType | 是否是工单类型单据出库(boolean<br>\-- 工单出库单是 true | |
| saName | 服务顾问姓名<br>\-- 工单出库单场景 | |
| printCount | 打印次数 | |
| showCustomCode | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739785277433-3b96ef23-7a4c-4df1-bf12-49cd4547de9e.png)配置出入库打印参数-是否显示材料编码(boolean) | |
| showBusinessLabel | 配置出入库打印参数-是否显示材料业务分类(boolean) | |
| showApplyModel | 配置出入库打印参数-是否显示材料适用车型(boolean) | |
| showStorageName | 配置出入库打印参数-是否显示出库仓库(boolean) | |
| showDefSeat | 配置出入库打印参数-是否显示出库货位(boolean) | |
| showChineseAmount | 配置出入库打印参数-是否显示大写金额(boolean) | |
| showChineseSubtotal | 配置出入库打印参数-是否显示大写行合计(boolean) | |
| columnCount | 显示几列 | |
| batchPrintConfig | 配置出入库打印参数-查询批次成本展示设置 0:都不展示,1:总成本(将批次成本合并),2和3:展示批次成本 | |
| memo | 车主描述 | \--20250925 新增 |
| partInfoDetailMapList | | |
| sortNumber | 序号<br>**通用模版追加一行合计行,显示 合计** | |
| partShowName | 材料组合名称 | |
| partName | 材料名称 | |
| partBrand | 材料品牌 | \-- 2025.02.27 新增 |
| supplierCode | 零件号 | |
| labelName | 业务分类 | |
| applyModel | 适用车型 | |
| customCode | 材料编码 | |
| storageName | 仓库名称 | |
| defSeat | 货位 | |
| number | 数量<br>**\-- 通用模版追加一行合计行,显示 材料出库总数** | |
| unit | 单位 | |
| price | 单价<br>\--脱敏场景显示 _\*_\*\*\* | |
| subtotal | 金额<br>\--脱敏场景显示 _\*_\*\*\* | |
| employeeName | 出库人 | |
| salesEmployeeNameList | 材料行销售人员 | \--20250925 新增 |
| orderBatchList<br>\--List<Map<String, String>> | 材料行成本相关 | |
| orderNo | 批次号<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 均价模式下,显示 ""<br>**\-- 通用模版追加一行合计,追加行,显示 ""** | |
| count | 批次出库几个<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 均价模式下,显示 ""<br>**\-- 通用模版追加一行合计,追加行,显示总数** | |
| price | 批次单位成本<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价单位成本**<br>\-- 通用模版追加一行合计,追加行,显示 "" | |
| priceNoTax | 批次单位除税成本<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价单位成本**<br>\-- 通用模版追加一行合计,追加行,显示 "" | 2025.08.14 新增 |
| totalPrice | 批次总成本<br>\-- batchPrintConfig = 1 场景下,显示合计总成本<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价总成本**<br>\-- 通用模版追加一行合计,追加行,显示总成本 | |
| totalPriceNoTax | 批次除税总成本<br>\-- batchPrintConfig = 1 场景下,显示合计总成本<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价除税总成本**<br>\-- 通用模版追加一行合计,追加行,显示总成本 | 2025.08.14 新增 |
样列:
![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739844284185-900277fc-1fd5-4da3-a36f-224e8fcc9994.png)
```plaintext
{
"saName":"xuetting",
"objectNameGD":"18551638685【苏ABF358】 LFV2A21K6A3092399 大众 速腾 1.4T 双离合变速器(DSG) 2011 速腾",
"creatorName":"cltest",
"showStorageName":true,
"remark":"",
"title":"流程配置ES出库单",
"showSourceBillNo":true,
"sumAmount":"860.0",
"showChineseAmount":true,
"billStatus":"完成",
"sumNumber":"1.0",
"isGdType":true,
"printCount":"3",
"billNo":"CKD20250116001",
"showDefSeat":true,
"idOwnOrg":"15870306745529549109",
"showApplyModel":true,
"memo":"",
"partInfoDetailMapList":[
{
"employeeName":"cltest",
"sortNumber":"1",
"partShowName":" 3M 燃油宝1号 PN6868 3M (PN6868)",
"defSeat":"A",
"orderBatchList":[
{
"orderNo":"20211213000001",
"totalPrice":"0.0",
"price":"0.0",
"count":"1.0"
}
],
"supplierCode":"PN6868",
"partName":"3M 燃油宝1号 PN6868",
"applyModel":"",
"customCode":"CL0000015",
"storageName":"主仓库",
"number":"1.0",
"unit":"瓶",
"price":"860.0",
"subtotal":"860.0",
"labelName":"保养",
"salesEmployeeNameList": "B2C一店新员工,B2C一店采购员"
},
{
"number":"1.0",
"subtotal":"860.0",
"sortNumber":"合计",
"orderBatchList":[
{
"orderNo":"",
"totalPrice":"0.0",
"price":"",
"count":"1.0"
}
]
}],
"sourceBillNo":"WXD20250103001",
"billDate":"2025-01-16",
"chineseAmount":"捌佰陆拾元整",
"columnCount":"5",
"showCustomCode":true,
"showBusinessLabel":true,
"batchPrintConfig":"3",
"nowDateTime":"2025-02-18 10:04",
"showInOutDate":true,
"showChineseSubtotal":true,
"objectName":"18551638685【苏ABF358】大众 速腾",
"inOutDate":"2025-01-16",
"showRemark":false
}
```
# 出入库单据-入库单 && 退料入库-入库单打印模版参数说明
入库单据定制类需求模版分类(newStockInMaintainCustomPrint--20250925新增
打印模版参数
HashMap<String, Object> resultMap
| **字段** | **说明** | 备注 |
| --- | --- | --- |
| title | 门店名称+ "入库单" | |
| billNo | 入库单号 | |
| sourceBillNo | 来源单号 | |
| showSourceBillNo | 显示来源单号 boolean<br>默认:true | |
| billStatus | 单据状态(制单、完成) | |
| inOutDate | 入库日期<br>\-- yyyy-MM-dd | |
| showInOutDate | 是否显示入库日期(boolean<br>\-制单是 false<br>\-完成是 true | |
| objectName | 出入库对象 | |
| creatorName | 制单人 | |
| billDate | 制单日期 yyyy-MM-dd | |
| sumNumber | 材料总数量 | |
| sumAmount | 总金额 <br>\--脱敏场景显示 _\*_\*_\*\*_ | |
| noTaxSumAmount | 除税总金额 <br>\--脱敏场景显示 _\*_\*_\*\*_ | |
| chineseAmount | 总金额(中文大写)<br>\--脱敏场景显示 _\*_\*\*\* | |
| chineseNoTaxSumAmount | 除税总金额(中文大写)<br>\--脱敏场景显示 _\*_\*\*\* | |
| nowDateTime | 打印当前时间 yyyy-MM-dd HH:mm | |
| idOwnOrg | 门店ID | |
| remark | 备注信息 | |
| showRemark | 是否显示备注(boolean<br>\-- 备注不为空时是 true | |
| isGdType | 是否是工单类型单据出库(boolean<br>\-- 工单出库单是 true | |
| saName | 服务顾问姓名<br>\-- 工单出库单场景 | |
| printCount | 打印次数 | |
| showCustomCode | 配置出入库打印参数-是否显示材料编码(boolean) | |
| showBusinessLabel | 配置出入库打印参数-是否显示材料业务分类(boolean) | |
| showApplyModel | 配置出入库打印参数-是否显示材料适用车型(boolean) | |
| showStorageName | 配置出入库打印参数-是否显示出库仓库(boolean) | |
| showDefSeat | 配置出入库打印参数-是否显示出库货位(boolean) | |
| showChineseAmount | 配置出入库打印参数-是否显示大写金额(boolean) | |
| showChineseSubtotal | 配置出入库打印参数-是否显示大写行合计(boolean) | |
| sumSubtotal | 入库总金额<br>\-- 脱敏场景显示 \*\*\*\*<br>\-- 工单退-才有 | |
| chineseSubtotal | 大写入库总金额<br>\-- 脱敏场景显示 \*\*\*\*<br>\-- 工单退-才有 | |
| showReturnIn | 显示退料入库一行<br>\-- 工单退-true | |
| showSign | 【仓管签字】显示的位置<br>工单退-2;其它场景1 | |
| stockInType | 退料入库<br>\-- 工单退-才有 | |
| columnCount | 显示几列 | |
| memo | 车主描述 | \--20250925 新增 |
| partInfoDetailMapList | | |
| sortNumber | 序号 | |
| partShowName | 材料组合名称 | |
| partName | 材料名称 | |
| partBrand | 材料品牌 | |
| labelName | 业务分类 | |
| applyModel | 适用车型 | |
| customCode | 材料编码 | |
| storageName | 仓库名称 | |
| defSeat | 货位 | |
| number | 数量<br>**\-- 通用模版追加一行合计行,显示 材料出库总数** | |
| unit | 单位 | |
| price | 单价<br>\--脱敏场景显示 _\*_\*\*\* | |
| noTaxPrice | 除税单价<br>\--脱敏场景显示 _\*_\*\*\* | |
| subtotal | 金额<br>\--脱敏场景显示 _\*_\*\*\* | |
| noTaxSubtotal | 除税金额<br>\--脱敏场景显示 _\*_\*\*\* | |
| employeeName | 入库人 | |
| salesEmployeeNameList | 材料行销售人员 | \--20250925 新增 |
# 手工出入库-出/入库单
打印模版参数
HashMap<String, Object> resultMap
| **字段** | **说明** | **备注** |
| --- | --- | --- |
| title | 出库单:门店名称+ "出库单"<br>入库单:门店名称+ "入库单" | |
| billNo | 出库单号/入库单号 | |
| sourceBillNo | 来源单号 空 | |
| showSourceBillNo | 显示来源单号 booleanfalse | |
| billStatus | 单据状态(制单、完成) | |
| inOutDate | 出库日期 (yyyy-MM-dd) | |
| showInOutDate | 是否显示出库日期(boolean | |
| objectName | 出入库对象 | |
| objectNameGD | 出入库对象 | |
| creatorName | 制单人 | |
| billDate | 制单日期 yyyy-MM-dd | |
| sumNumber | 材料总数量 | |
| sumAmount | 总金额 <br>\--脱敏场景显示 _\*_\*\*\* | |
| chineseAmount | 总金额(中文大写)<br>\--脱敏场景显示 _\*_\*\*\* | |
| nowDateTime | 打印当前时间 yyyy-MM-dd HH:mm | |
| idOwnOrg | 门店ID | |
| remark | 备注信息 | |
| showRemark | 是否显示备注(boolean<br/>-- 备注不为空时是 true | |
| printCount | 打印次数 | |
| showCustomCode | ![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739785277433-3b96ef23-7a4c-4df1-bf12-49cd4547de9e.png)<br/>配置出入库打印参数-是否显示材料编码(boolean) | |
| showBusinessLabel | 配置出入库打印参数-是否显示材料业务分类(boolean) | |
| showApplyModel | 配置出入库打印参数-是否显示材料适用车型(boolean) | |
| showStorageName | 配置出入库打印参数-是否显示出库仓库(boolean) | |
| showDefSeat | 配置出入库打印参数-是否显示出库货位(boolean) | |
| showChineseAmount | 配置出入库打印参数-是否显示大写金额(boolean) | |
| showChineseSubtotal | 配置出入库打印参数-是否显示大写行合计(boolean) | |
| columnCount | 显示几列 | |
| batchPrintConfig | 配置出入库打印参数-查询批次成本展示设置 0:都不展示,1:总成本(将批次成本合并),2和3:展示批次成本 | |
| partInfoDetailMapList | | |
| sortNumber | 序号<br>**\-- 通用模版追加一行合计行,显示 合计** | |
| partShowName | 材料组合名称 (材料名称 规格型号 材料品牌 零件号) | |
| partName | 材料名称 | |
| partBrand | 材料品牌 -- 2025.02.27 新增 | |
| supplierCode | 零件号 | |
| labelName | 业务分类 | |
| applyModel | 适用车型 | |
| customCode | 材料编码 | |
| storageName | 仓库名称 | |
| defSeat | 货位 | |
| number | 数量<br>**\-- 通用模版追加一行合计行,显示 材料出库总数** | |
| unit | 单位 | |
| price | 单价<br>\--脱敏场景显示 _\*_\*\*\* | |
| subtotal | 金额<br>\--脱敏场景显示 _\*_\*\*\* | |
| employeeName | 出库人 | |
| taxRate | 税率 | |
| orderBatchList<br>\--List<Map<String, String>> | 材料行成本相关 | |
| orderNo | 批次号<br>\- batchPrintConfig = 1 场景下,显示 ""<br>\-- 均价模式下,显示 ""<br>**\-- 通用模版追加一行合计,追加行,显示 ""** | |
| count | 批次出库几个<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 均价模式下,显示出库/入库个数<br>**\-- 通用模版追加一行合计,追加行,显示总数** | |
| price | 批次单位成本<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价单位成本**<br>\-- 通用模版追加一行合计,追加行,显示 ""\*\* | |
| priceNoTax | 批次除税单位成本<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价单位成本**<br>\-- 通用模版追加一行合计,追加行,显示 ""\*\* | 2025.08.14 追加 |
| totalPrice | 批次总成本<br>\-- batchPrintConfig = 1 场景下,显示合计总成本<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价总成本**<br>\-- 通用模版追加一行合计,追加行,显示总成本\*\*\*\* | |
| totalPriceNoTax | 批次除税总成本<br>\-- batchPrintConfig = 1 场景下,显示合计总成本<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价总成本**<br>\-- 通用模版追加一行合计,追加行,显示总成本\*\*\*\* | 2025.08.14 追加 |
样列:
![image](https://ddoc.f6yc.com/yuque/0/2025/png/227465/1739842418604-54075443-f0a5-465f-ba7b-487a96b7728e.png)
日志关键字:+"手工出入库单据打印入参是"
```plaintext
{
"objectNameGD":"",
"creatorName":"王◇龙",
"showStorageName":true,
"remark":"",
"title":"ISC总店出库单",
"showSourceBillNo":false,
"sumAmount":"410.0",
"showChineseAmount":true,
"billStatus":"完成",
"sumNumber":"2.0",
"printCount":"5",
"billNo":"SGC20240515001",
"showDefSeat":true,
"idOwnOrg":"4060685614490690260",
"showApplyModel":true,
"partInfoDetailMapList":[
{
"employeeName":"XN",
"sortNumber":"1",
"partShowName":" 材料09080112 123456 米其林 (1240)",
"defSeat":"A-14-02",
"orderBatchList":[
{
"orderNo":"20210909000128",
"totalPrice":"400.0",
"price":"400.0",
"count":"1.0"
}
],
"supplierCode":"1240",
"partName":"材料09080112",
"applyModel":"大众 途安",
"customCode":"CL090800112",
"storageName":"主仓库",
"number":"1.0",
"unit":"条",
"price":"400.0",
"subtotal":"400.0",
"labelName":"轮胎"
},
{
"employeeName":"XN",
"sortNumber":"2",
"partShowName":" fnst=>1 AC德科 (FNST>=1)",
"defSeat":"",
"orderBatchList":[
{
"orderNo":"20201104000002",
"totalPrice":"4.0",
"price":"4.0",
"count":"1.0"
}
],
"supplierCode":"FNST>=1",
"partName":"fnst=>1",
"applyModel":"江淮瑞风S52....",
"customCode":"fnst>=1",
"storageName":"总2仓",
"number":"1.0",
"unit":"个",
"price":"10.0",
"subtotal":"10.0",
"labelName":"保养"
},
{
"number":"2.0",
"subtotal":"410.0",
"sortNumber":"合计",
"orderBatchList":[
{
"orderNo":"",
"totalPrice":"404.0",
"price":"",
"count":"2.0"
}
]
}
],
"sourceBillNo":"",
"billDate":"2024-05-15",
"chineseAmount":"肆佰壹拾元整",
"columnCount":"5",
"showCustomCode":true,
"showBusinessLabel":true,
"batchPrintConfig":"3",
"nowDateTime":"2025-02-18 09:33",
"showInOutDate":true,
"showChineseSubtotal":true,
"objectName":"",
"inOutDate":"2024-05-15",
"showRemark":false
}
```
# 领料详情-打印领料单
打印模版参数
HashMap<String, Object> resultMap
| **字段** | **说明** | **备注** |
| --- | --- | --- |
| idOwnOrg | 门店ID | |
| title | 门店名称+ "领料单" | |
| billNo | 工单号 | |
| nowDateTime | 打印当前时间 yyyy-MM-dd HH:mm | |
| employeeName | 服务顾问 | |
| carModel | 车型 | |
| carNoWhole | 车牌号 | |
| memo | 车主描述 | 2025.08.14 新增 |
| printTimes | 打印次数 | |
| columnCount | 显示几列 | |
| batchPrintConfig | 配置出入库打印参数-查询批次成本展示设置 0:都不展示,1:总成本(将批次成本合并),2和3:展示批次成本 | |
| stuffDetailVOList | | |
| index | 序号<br>**\-- 通用模版追加一行合计行,显示 合计** | |
| partName | 材料组合名称 (材料名称 规格型号 材料品牌 零件号) | |
| unit | 单位 | |
| defSeatList | 货位 | |
| salesEmployeeNameList | 销售人员<br>List<String> | 8.14新增 |
| orderBatchList<br>\--List<Map<String, String>> | 材料行成本相关 | |
| orderNo | 批次号<br>\- batchPrintConfig = 1 场景下,显示 ""<br>\-- 均价模式下,显示 ""<br>**\-- 通用模版追加一行合计,追加行,显示 ""** | |
| count | 批次出库几个<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 均价模式下,显示出库/入库个数<br>**\-- 通用模版追加一行合计,追加行,显示总数** | |
| price | 批次单位成本<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价单位成本**<br>\-- 通用模版追加一行合计,追加行,显示 ""\*\* | |
| priceNoTax | 批次除税单位成本<br>\-- batchPrintConfig = 1 场景下,显示 ""<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价单位成本**<br>\-- 通用模版追加一行合计,追加行,显示 ""\*\* | 2025.08.14 追加 |
| totalPrice | 批次总成本<br>\-- batchPrintConfig = 1 场景下,显示合计总成本<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价总成本**<br>\-- 通用模版追加一行合计,追加行,显示总成本\*\*\*\* | |
| totalPriceNoTax | 批次除税总成本<br>\-- batchPrintConfig = 1 场景下,显示合计总成本<br>\-- 脱敏场景显示 **\*\*\*\***<br>**\-- 均价模式下,显示均价总成本**<br>\-- 通用模版追加一行合计,追加行,显示总成本\*\*\*\* | 2025.08.14 追加 |
@@ -0,0 +1,32 @@
# 协作单接口文档
# 协作单接口文档
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| title | 打印抬头(门店简称) | | String |
| billNo | 工单号 | | String |
| creatorName | 制单人 | | String |
| printTime | 打印时间 | | String |
| naEmployee | 服务顾问 | | String |
| billDate | 进厂日期 | | String |
| deliveryTime | 交车时间(出厂时间) | | String |
| naCustomer | 车主姓名 | | String |
| cellPhone | 车主电话 | | String |
| carModel | 车型 | | String |
| carNoWhole | 车牌号 | | String |
| vin | 车辆VIN码 | | String |
| amountAll | 小计 | | BigDecimal |
| serviceDetailVOList | 协作项目集合 | | List<CooperationServicePrintAttribute> |
## CooperationServicePrintAttribute
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| serviceName | 工单项目名称 | | String |
| cooperationServiceName | 协作项目名称 | | String |
| cooperationOrgName | 协作门店 | | String |
| auditStatus | 审核状态 | | String |
| cooperationCost | 协作成本 | | BigDecimal |
> 更新: 2023-08-28 16:06:07 原文: <https://xcz.yuque.com/ombipo/rpc7ms/awq306g9g8fg78or>
@@ -0,0 +1,72 @@
# 增项单接口文档
# 增项单接口文档
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| title | 打印抬头(门店名称+"新增项目确认单" | | String |
| sourceBillNo | 关联工单号 | | String |
| printTime | 打印时间 | | String |
| naEmployee | 服务顾问 | | String |
| arrivalTime | 进厂日期 | | String |
| deliveryTime | 交车时间 | | String |
| naCustomer | 车主姓名 | | String |
| cellPhone | 车主电话 | | String |
| repairPerson | 送修人 | | String |
| repairPersonContact | 送修人联系方式 | | String |
| carModel | 车型 | | String |
| carNoWhole | 车牌号 | | String |
| carColor | 车身颜色 | | String |
| engineCode | 发动机号 | | String |
| vin | 车辆VIN码 | | String |
| mileage | 进厂里程 | | String |
| oilCapacity | 进厂油量 | | String |
| merchantAddress | 商家联系地址 | | String |
| merchantTel | 商家联系方式(固定电话) | | String |
| merchantPhone | 商家联系方式(手机) | | String |
| workHourPriceSubtotal | 工时费(小计) | | String |
| amountSubtotal | 材料费(小计) | | String |
| attachedServiceVoList | 增项服务项目集合 | | List<ServicePrintAttribute> |
| attachedStuffVoList | 增项配件材料集合 | | List<PartPrintAttribute> |
**ServicePrintAttribute**
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| serviceName | 工单项目名称 | | String |
| labelName | 业务分类名称 | | String |
| customCode | 项目编码 | | String |
| price | 工时单价 | | Double |
| workHour | 工时 | | Double |
| number | 项目数量 | | Integer |
| discount | 折扣 | | Double |
| discountedSubtotal | 折后金额 | | Double |
| subtotal | 金额 | | Double |
| serviceMemo | 项目备注 | | String |
| isMember | 当前项目是否使用会员 | | Integer |
| nameMember | 会员项目的来源名(如套餐代码) | | String |
| empNameStr | 服务项目明细对应修理工名称组装字符串 | | String |
If you get gainsplease give a like
**PartPrintAttribute**
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| partName | 材料名称 | | String |
| labelName | 业务分类名称 | | String |
| partBrand | 配件品牌 | | String |
| customCode | 材料编码 | | String |
| price | 单价 | | Double |
| number | 材料数量 | | Integer |
| unit | 单位 | | String |
| discount | 折扣 | | Double |
| discountedSubtotal | 折后金额 | | Double |
| subtotal | 金额 | | Double |
| partMemo | 材料备注 | | String |
| isMember | 当前项目是否使用会员 | | Integer |
| nameMember | 会员项目的来源名(如套餐代码) | | String |
| employeeName | 员工名称 | | String |
| empNameStr | 材料明细对应修理工名称组装字符串 | | String |
> 更新: 2023-09-05 10:34:22 原文: <https://xcz.yuque.com/ombipo/rpc7ms/gpobrxn8mn5lzthk>
@@ -0,0 +1,53 @@
# 委托单接口文档
# 委托单接口文档
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| printOrgName | 打印抬头 | | String |
| orgName | 维修厂名称 | | String |
| billNo | 工单号 | | String |
| naEmployee | 服务顾问 | | String |
| employeePhone | 服务顾问手机号 | | String |
| naCustomer | 单位名称(客户姓名) | | String |
| carNoWhole | 车牌号整体 = carPrefix + carNo | | String |
| cellPhone | 联系电话(客户) | | String |
| repairPerson | 送修人 | | String |
| repairPersonContact | 送修人联系方式 | | String |
| billDate | 进厂日期 | | String |
| deliveryTime | 交车时间(出厂时间) | | String |
| mileage | 出厂里程(进厂里程) | | BigDecimal |
| oilCapacity | 当前油量 | | String |
| vin | 车辆VIN码 | | String |
| carModelShort | 车型简称 | | String |
| signaturePhotoUrl | 签名图片 | | String |
| orgContactNumber | 联系电话(维修厂) | | String |
| orgDetailAddress | 联系地址(维修厂) | | String |
| orgContactMobile | 联系电话(维修厂) | | String |
| printContentEntrust | 委托单免责条款 | | String |
| serviceSubtotalVip | 服务项目明细小计(会员项目) | | BigDecimal |
| stuffSubtotalVip | 材料收入小计(会员项目) | | BigDecimal |
| serviceSubtotalAll | 工时费小计 | | BigDecimal |
| stuffSubtotalAll | 材料费小计 | | BigDecimal |
| serviceList | 工单对应项目集合 | | List<ServicePrintAttribute> |
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
## ServicePrintAttribute
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| serviceName | 项目名称 | | String |
| price | 工时单价 | | BigInteger |
| workHour | 工时 | | BigInteger |
| subtotal | 金额 | | BigInteger |
| serviceMemo | 附加信息备注 | | String |
| isMember | 当前项目是否使用会员 | | Integer |
| empNameStr | 服务项目明细对应修理工名称组装字符串 | | String |
| | | | |
| | | | |
> 更新: 2022-11-30 15:56:54 原文: <https://xcz.yuque.com/ombipo/rpc7ms/eppwl9lml80qq2bi>
@@ -0,0 +1,121 @@
# 定金单打印接口文档
# 定金单打印接口文档
# 接口出参
| 字段 | 含义 | 类型 |
| --- | --- | --- |
| title | 标题(门店名称) | String |
| abbreviation | 门店简称 | String |
| billNO | 定金单号 | String |
| printTime | 打印时间 | String |
| customerName | 客户姓名 | String |
| cellPhone | 手机号码 | String |
| carNo | 适用车辆(顿号、隔开) | String |
| orgName | 适用门店(顿号、隔开) | String |
| balanceStatus | 结算状态 | String |
| receivedAmount | 已收金额 | Double |
| amount | 收款交易金额 | Double |
| amountAll | 定金单收款小计 | Double |
| preRefundBalance | 定金单退款前余额 | Double |
| advancesReceivedBalance | 定金单收款后剩余面额 | Double |
| memo | 定金备注 | String |
| settlePerson | 结算人 | String |
| employeeName | 收款人 | String |
| businessDate | 收款时间 | String |
| billDate | 交易时间 | String |
| transactionDate | 交易时间 | String |
| detailAddress | 联系地址 | String |
| contactMobile | 联系方式(手机+固定电话) | String |
| naServicePerson | 服务顾问 | String |
| gatheringList | 收款方式 | List |
| L paymentType | 支付方式 | String |
| L amount | 金额 | BigDecimal |
| relationServices | 适用项目列表 | List |
| L infoId | 项目id | BigInteger |
| L infoName | 项目名称 | String |
| L labelName | 业务分类 | String |
| relationParts | 适用材料列表 | List |
| L infoId | 材料id | BigInteger |
| L infoName | 材料名称 | String |
| L labelName | 业务分类 | String |
| relationCars | 适用车辆列表 | List |
| L idCar | 车辆信息id | BigInteger |
| L carNo | 车牌号 | String |
| L vin | vin码 | String |
# 范例
```plaintext
收款收款{
"data": {
"preRefundBalance": 20000,
"memo": "我是备注。",
"title": "演示主店",
"carNo": "藏AVB2131",
"naServicePerson": "唐铭远",
"contactMobile": "15051779785",
"employeeName": "刘思杰",
"amount": 10000,
"orgName": "演示主店测试、第一分店",
"advancesReceivedBalance": 10000,
"amountAll": 10000,
"balanceStatus": "7100",
"billDate": "2024-07-11 16:27:49",
"businessDate": "2024-07-24 14:49:01",
"receivedAmount": 10000,
"abbreviation": "演示主店测试",
"transactionDate": "2024-07-11 16:27:49",
"relationServices": [
{
"infoName": "龙膜全车贴膜(不含撕膜)",
"infoId": "10545055918005551735",
"infoType": 1,
"id": "239",
"labelName": "其他",
"idSubscription": "11159"
}
],
"customerName": "牛洋",
"gatheringList": [
{
"amount": 4000,
"paymentType": "支付宝"
},
{
"amount": 3000,
"paymentType": "现金"
},
{
"amount": 3000,
"paymentType": "挂账"
}
],
"detailAddress": "西藏自治区那曲市班戈县西藏自治区那曲市班戈县北拉镇邮政所",
"billNO": "DJD20240711001",
"cellPhone": "17625046227",
"printTime": "2024-07-11 17:28:08",
"relationParts": [
{
"infoName": "奔腾CI-4 15W40 4*4L 胜牌 (706650)",
"infoId": "10545055918005692400",
"infoType": 2,
"id": "240",
"labelName": "业务分类测试",
"idSubscription": "11159"
}
],
"relationCars": [
{
"carNo": "AV616E",
"vin": "LSVAA49J132047371",
"idCar": 15809106713748983890
}
],
},
"storeId": 4060685614487994527,
"tempId": 41
}
```
> 更新: 2024-07-25 20:54:41 原文: <https://xcz.yuque.com/ombipo/rpc7ms/io3q0kkop242geg6>
@@ -0,0 +1,510 @@
# 工单结算单接口文档
# 打印单最新接口参数
#### maintain接口
**接口:/print/dispatchPrint/genUrl**
**方法:post**
支持场景:
各类结算单
不支持:上海结算单
**入参:**
```plaintext
{
"pkId": "14581820313319918809",
"rowCode": "costSettlePrint",
"rowId": "12"
}
```
```plaintext
{
"code": 200,
"data": {
"url": "http://s1.f6yc.com/printserver/test/printFile/201912/191213154046701.pdf"
},
"message": "SUCCESS"
}
```
#### erp接口
**/print/getPrintPDFPath.do**
templateId=56&templateType=newSettlePrint&idSourceBill=10546443563897503197
**rest  get**
**支持各种新版打印类型**
```plaintext
{
"code": 200,
"data": "http://s1.f6yc.com/printserver/test/printFile/201912/191213154046701.pdf",
"message": "SUCCESS"
}
```
#### jasper取参对照
```plaintext
{
"data": {
"cellPhone": "15421562365", //联系电话
"naCustomer": "0322新", //单位名称
"repairPerson": "", //送修人
"carOwnerName":"", // 车辆所有人
"accountNumber": "", //账号
"billNo": "GD20190517001", //工单号
"carNoWhole": "苏1542", //车牌号
""
"carColor":"" //车身颜色
"orgDetailAddress": "江苏省盐城市阜宁县豆豆",//联系地址
"vin": "11111111111111111", //车辆VIN码
"naEmployee": "员工1(旧1)", //服务顾问
"billDate": "2019-05-17 11:49",//进厂日期
"businessTypeName": "维修", //维修类别
"deliveryTime": "2019-05-17 12:49",//交车时间(出厂时间)
"email": "126544@qq.com", //组织邮件
"maintainType": "GD", //工单类型
"billStatus":"6300", //单据状态
"orgContactMobile": "15315256232", //联系电话
"memo": "", //工单备注
"orgMemo":"", // 门店备注
"carMemo":"", // 车辆备注
"printCount": "1", //打印次数
"orgContactNumber": "", //联系电话-承修方信息
"carSeriesName": "商用车", //车系名称
"carBrandName": "商用车", //品牌名称
"balanceStatus": "7000", //结算状态
"printTime": "2019-07-26 11:43:48",//打印时间
"firstSettlementTime":"2019-07-26 11:43:48",//结算时间(第一次收款时间)
"orgName": "新公司测试", //单位名称-承修方信息
"abbreviation": "门店简称", //门店简称
"engineNumber": "123", //发动机号
"transmissionNo": "123", //变速箱号
"creationtime": "2019-05-17 11:50:46.0",//创建时间
"creatorName": "员工1(旧1)", //创建人名称
"employeePhone":"18734033191", //服务顾问手机号
"paymentTypeDetails":"记账", //支付方式(记账公司)汇总
"bankAccount": "", //开户银行
"naInsurer":"", //理赔公司名称
"insurancepolicyNo":"", //理赔单理赔保险单号
"mergePackageContent": "1", //套餐合并标识
"totalStuffNum": 1.0, //材料数量合计
"selfTotalStuffNum": 0.0, //自带材料数量合计
"serviceNum": "1", //维修项目小计
"spreadRate": 0.0, //进销差价率
"managementCost": 0.0, //进销差价合计
"amountAll": 160.0, //应收总计
"serviceDisCountSubTotal": 100.0, //项目折后金额合计
"stuffSubtotalAll": 60.0, //材料费小计
"serviceSubtotalAll": 100.0, //工时费小计
"receiptAmount": 160.0, //实收金额
"chineseAmount": "壹佰陆拾元整", //实收金额(大写)
"oweAmount": 160.0, //未收金额
"remainAmount":1.0, //结算金额tsf
"receivedAmount":1.0, //收款金额tsf
"settleOweAmout":1.0, //结算单中用的待付金额(未收)
"settleOweAmoutChinese":"壹", //待付金额大写
"settleReceivedAmout":1.0, //实付金额
"settleReceivedAmoutChinese":1.0, //实付金额大写
"totalWorkHour": 1.0, //项目工时合计
"serviceFavourableTotal": 52.0, //项目优惠金额合计
"serviceFavourableCommonTotal": 52.0,//普通项目优惠金额合计
"stuffSubtotalAll": 532.0, //材料费小计
"partFavourableTotal": 102.0, //材料优惠金额合计
"partFavourableCommonTotal": 92.0, //普通材料优惠金额合计
"stuffDisCountTotal": 60.0, //材料折后金额合计
"extraCostTotal": 0.0, //附加费小计
"allOtherCost": 0.0, //附加费合计应收
"packageFavourable": 0.0, //套餐优惠
"czkExpense": 0.0, //储值卡消费金额
"vipExpense": 0.0, //会员卡消费金额
"czkExpenseFavourable": 0.0, //储值卡优惠金额
"czkDiscountFavourable": 0.0, //储值卡办卡优惠金额
"vipExpenseFavourable": 0.0, //计次卡/套餐卡优惠金额
"partinfoDiscountFavourable": 0.0, //材料折扣优惠
"partinfoFavourable": 0.0, //材料项目(非会员项目)客户等级优惠
"couponFavourable": 0.0, //优惠券优惠
"pointFavourable": 0.0, //积分优惠
"discountFavourable": 0.0, //结算时设置的结清优惠
"gatheringFavourable": 0.0, //收银时设置的收银优惠
"customerLevelFavourable": 0.0, //客户级别优惠金额
"serviceFavourable": 0.0, //服务项目(非会员项目)客户等级优惠
"disCountAll": 0.0, //总优惠合计
"disCountAllBak":0.0, //总优惠合计bak
"printContent": "", //免责条款
"printContentEntrust":"", //委托单免责条款
"mainCostList": [ //维修结算费用集合
{
"subtotal": 60.0, //价格
"sortNumber": "1", //序号
"costName": "材料费" //名称
},
{
"subtotal": 100.0,
"sortNumber": "2",
"costName": "工时费"
},
{
"subtotal": 160.0,
"sortNumber": "3",
"costName": "合计"
}
],
"partList": [ //工单对应配件材料集合
{
"unit": "个", //单位
"isBring": 0, //是否自带,1表示自带,0表示非自带
"discountedSubtotal": 60.0, //折后金额
"price": 60.0, //价格
"partName": "分全", //材料名称
"number": 1.0, //数量
"subtotal": 60.0, //金额
"taxRateOutput": 0.13, //销项税率
"singleFavourable":0.0, //优惠金额
"partBrand":"", //配件品牌
"spec":"", //规格型号
"supplierCode":"", //供应商编码(零件号)
"customCode":"", //材料编码
"isMember":0, //是否是会员卡材料 1是
"discount":0.5, //折扣
"partMemo":"", //备注
"employeeName":"", //员工名称(维修技师)
"cargoSpace":"", //货位
"outStockEmployeeName":"", //领料人
"sortNumber": "1" //序号
}
],
"serviceList": [ //工单对应项目集合
{
"discountedSubtotal": 100.0,//折后金额
"price": 100.0, //工时单价
"workHour": 1.0, //工时
"subtotal": 100.0, //金额
"taxRateOutput": 0.13, //销项税率
"sortNumber": "1", //序号
"discount":1, //折扣
"singleFavourable":0.0, //优惠金额
"isMember":1, //是否是会员卡项目 1是 0否
"empNameStr":"", //修理工
"unusedNumber":1, //会员卡项目-未使用次数
"number":2, //会员卡项目-总次数
"infiniteFlag":1, //是否无限,0:否,1:是
"serviceName": "0322新" //项目名称
}
],
"cardList": [ //会员卡列表
{
"favourable": 20, //优惠
"amount": 2019799, //余额
"consumeAmount":10, //本次消费金额
"memberCardNo": "123232rg",//卡号
"name": "测试洗车卡项目2" //卡名称
}
],
"czkList": [ //储值卡列表
{
"favourable": 20, //优惠
"amount": 2019799, //余额
"consumeAmount":10, //本次消费金额
"memberCardNo": "123232rg",//卡号
"name": "测试洗车卡项目2" //卡名称
}
],
"extraPrintVo": { //附加项目
"processItemName": "加工", //加工条目名称
"checkCustomName": "检测费", //检测费名称
"diagnosisCustomName": "诊断费", //诊断费名称
"diagnosisItemName": "维修诊断", //维修诊断项目
"diagnosisMemo": "", //诊断费备注
"diagnosisCost": 0.0, //诊断费
"commissionCustomName": "代办费", //代办费名称
"managementCustomName": "管理费", //管理费名称
"commissionMemo": "", //代办费备注
"processMemo": "", //加工费
"commissionCost": 0.0, //代办费
"checkCost": 0.0, //检测费
"managementCost": 0.0, //管理费
"processCustomName": "加工费", //加工费名称
"processCost": 0.0, //加工费
"managementMemo": "", //管理费备注
"checkMemo": "" //检测费备注
}
},
"storeId": 25965086392720693,
"tempId": 123
}
```
#### 参数说明(完整)
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| \------基础信息 | | |
| **billNo** | String | _工单号_ |
| **maintainType** | String | _工单类型_ |
| **balanceStatus** | String | _结算状态_<br/>_"7000" -- 未结算(即存在待付金额)_<br/>_"7100" -- 已结算_<br/>_"7200"  -- 部分结算_ |
| **creatorName** | String | _创建人名称_ |
| **naEmployee** | String | _服务顾问_ |
| **spreadRate** | Double | _进销差价率_ |
| carCategoryName | String | _客户车辆分类名称_ |
| **creationtime** | String | _创建时间_ |
| **memo** | String | _工单备注_ |
| **printCount** | String | _打印次数_ |
| **printTime** | String | _打印时间_ |
| **deliveryTime** | String | _交车时间(出厂时间),收款取收款时间,未收款完工的取完工时间,未完工的取预计交车时间_ |
| **mergePackageContent** | String | _套餐合并标识_ |
| **printContent** | String | _免责条款_ |
| **engineNumber** | String | _发动机号_ |
| **transmissionNo** | String | 变速箱号 |
| **printMaintainGuaZi** | String | _guazi标识_ |
| **amountAll** | Double | _应收总计_ |
| **disCountAll** | Double | _总优惠合计_ |
| **oweAmount** | Double | _未收金额_ |
| **vipExpense** | Double | _会员卡消费金额_ |
| **vipExpenseFavourable** | Double | _计次卡/套餐卡优惠金额_ |
| **czkExpense** | Double | _储值卡消费金额_ |
| **czkExpenseFavourable** | Double | _储值卡优惠金额_ |
| **czkSettleFavourable** | Double | _储值卡结算优惠金额(仅未收款返回)_ |
| **accountAmount** | Double | 记账金额 |
| **totalOweAmount** | Double | _未付金额_ |
| **serviceFavourable** | Double | _服务项目(非会员项目)客户等级优惠_ |
| **partinfoFavourable** | Double | _材料项目(非会员项目)客户等级优惠_ |
| **partinfoDiscountFavourable** | Double | _材料折扣优惠_ |
| **pointFavourable** | Double | _积分优惠_ |
| **packageFavourable** | Double | _套餐优惠_ |
| **discountFavourable** | Double | _结算时设置的结清优惠_ |
| **gatheringFavourable** | Double | _收银时设置的收银优惠_ |
| **couponFavourable** | Double | _优惠券优惠_ |
| **customerLevelFavourable** | Double | _客户级别优惠金额_ |
| **customerLevelName** | String | _客户级别_ |
| **customerDetailAddress** | String | 客户详细地址 |
| **channelName** | String | 来店途径名称 |
| **receiptMemo** | String | 收银备注 |
| **customerSourceName** | String | _客户来源名称_ |
| **carSourceName** | String | _车辆来源名称_ |
| \-----托修方信息 | | |
| **naCustomer** | String | _单位名称/托修方_ |
| **repairPerson** | String | _送修人_ |
| **carNoWhole** | String | _车牌号整体_ |
| **carBrandName** | String | _品牌名称_ |
| **carSeriesName** | String | _车系名称_ |
| carMemo | String | 车辆备注 |
| **businessTypeName** | String | _维修类别_ |
| **vin** | String | _车辆VIN码_ |
| carFuelTypeNameOriginal | carFuelTypeNameOriginal | 燃料类型 |
| **billDate** | String | _进厂日期_ |
| **mileage** | java.math.BigDecimal | _出厂里程_ |
| **contractNumber** | | _合同编号(空)_ |
| **certificateNumber** | | _合格证号(空)_ |
| **cellPhone** | String | _联系电话_ |
| **email** | String | _组织邮件_ |
| \------承修方信息 | | |
| **orgName** | String | _单位名称_ |
| **abbreviation** | String | 门店简称 |
| **orgContactNumber** | String | _联系电话_ |
| **orgDetailAddress** | String | _联系地址_ |
| **orgContactMobile** | String | _联系电话_ |
| **bankAccount** | String | _开户银行_ |
| **accountNumber** | String | _账号_ |
| **businessLicenseCode** | String | 营业执照编码 |
| \------项目信息 | | |
| serviceList | array | 项目条目 |
| #### orderNumber | String | 序号(验证可用) |
| sortNumber | String | _序号_ |
| customCode | String | 项目编码 |
| serviceName | String | _项目名称_ |
| **labelName** | String | _业务分类名称_ |
| **nameMember** | String | _会员项目的来源名_ |
| **labelName** | String | 业务分类 |
| price | Double | _工时单价_ |
| workHour | Double | _工时_ |
| subtotal | Double | _金额_ |
| ```plaintext<br> taxRateOutput <br>``` | BigDecimal | _销项税率_ |
| ```plaintext<br> singleFavourable <br>``` | Double | 优惠金额 |
| discountedSubtotal | Double | _折后金额_ |
| **serviceMemo** | String | _单据服务项目备注_ |
| discount | Double | 折扣 |
| unusedNumber | Integer | 会员卡项目-未使用次数 |
| number | Integer | 会员卡项目-总次数 |
| ```plaintext<br>favourableVoList <br>``` | List<FavourableDetailPrintVo> | 优惠明细 |
| ```plaintext<br>discountType <br>``` | Integer | 优惠类型(编码) |
| ```plaintext<br>discountTypeName <br>``` | String | 优惠类型名称 |
| amount | Double | 优惠金额 |
| ```plaintext<br>sourceId <br>``` | String | 优惠项目的主键,如:如果优惠项是优惠券,那么该字段为优惠券的id |
| **memo** | String | 项目说明 |
| **qualityCheckEmployeeName** | String | 质检人姓名 |
| **qualityCheckEmployeeCode** | String | 质检人工号 |
| **cooperationMemo** | String | 协作备注 |
| **totalWorkHour** | Double | _项目工时合计_ |
| **serviceSubtotalAll** | Double | _工时费小计_ |
| **serviceNum** | Double | _维修项目小计_ |
| **serviceDisCountSubTotal** | Double | _项目折后金额合计_ |
| \-------材料信息 | | |
| partList | array | 材料条目 |
| #### orderNumber | String | 序号(验证可用) |
| sortNumber | String | _序号_ |
| customCode | String | 材料编码 |
| partName | String | _材料名称_ |
| **partBrand** | String | _配件品牌_ |
| spec | String | 规格型号 |
| standard | String | 规格型号(旧) |
| **partShowName** | String | _配件名称规格型号品牌_ |
| **supplierCode** | String | _供应商编码(零件号)_ |
| unit | String | _单位_ |
| number | Double | _数量_ |
| **nameMember** | String | _会员项目的来源名_ |
| price | Double | _价格_ |
| cost | Double | _材料成本_ |
| subtotal | Double | _退货金额_ |
| taxRateOutput | BigDecimal | _销项税率_ |
| singleFavourable | Double | 优惠金额 |
| discountedSubtotal | Double | _折后金额_ |
| **partMemo** | String | _单据服务材料备注_ |
| discount | Double | 折扣 |
| **applyModel** | String | 适用车型 |
| ```plaintext<br>cargoSpace <br>``` | String | 材料货位 |
| **defSeats** | List<String> | 材料货位列表 |
| ```plaintext<br>favourableVoList <br>``` | List<FavourableDetailPrintVo> | 优惠明细 |
| ```plaintext<br>discountType <br>``` | Integer | 优惠类型(编码) |
| ```plaintext<br>discountTypeName <br>``` | String | 优惠类型名称 |
| ```plaintext<br>amount <br>``` | Double | 优惠金额 |
| ```plaintext<br>sourceId <br>``` | String | 优惠项目的主键,如:如果优惠项是优惠券,那么该字段为优惠券的id |
| \-------自带材料(新版维修/贴膜)信息 | | |
| **bringPartList** | array | 自带材料条目 |
| **partShowName** | String | 自带材料名称(文本) |
| **photoList** | List<String> | 自带图片路径(url |
| empNameStr | String | 技师 |
| outStockEmployeeName | String | 领料人 |
| **isBring** | String | _是否自带_ |
| **selfPartList** | array | _工单对应配件自带材料集合(内容同上面材料)_ |
| **totalStuffNum** | String | _材料数量合计_ |
| **selfTotalStuffNum** | String | _自带材料数量合计_ |
| **stuffSubtotalAll** | Double | _材料费小计_ |
| **partFavourableTotal** | Double | _材料优惠金额合计_ |
| **partFavourableCommonTotal** | Double | _普通材料优惠金额合计_ |
| **stuffDisCountTotal** | Double | _材料折后金额合计_ |
| **managementCost** | Double | _进销差价合计_ |
| \------附加费用信息 | | |
| **extraChargeList** | | |
| **sortNumber** | String | _序号_ |
| **extraName** | String | _附加费名称_ |
| **subtotal** | Double | _金额_ |
| **memo** | String | _备注_ |
| \-----维修结算费用集合(江苏结算单) | | |
| **mainCostList** | array | |
| **sortNumber** | String | _序号_ |
| **costName** | String | _名称_ |
| **memo** | String | _备注_ |
| **subtotal** | Double | _金额_ |
| \----_其他结算费用集合(江苏结算单)_ | | |
| **otherCostList** | array | 内容同上 维修结算费用集合 |
| **extraCostTotal** | Double | _附加费小计_ |
| **allOtherCost** | Double | _附加费合计应收_ |
| **favourableExtraCost** | Double | _附加费优惠金额_ |
| **receiptAmount** | Double | _实收金额(已收金额-储值卡金额,如未收款,则还加入了欠款金额+客户等级优惠-积分优惠-结清优惠)_ |
| **receiptAmountChinese** | String | _实收金额大写(逻辑同上)_ |
| **amountReal** | Double | 工单收款后真正的实收 |
| **chineseAmount** | String | _实收金额(大写)(逻辑同上)_ |
| **payItemTogetherChinese** | String | _付款方式总额大写_ |
| **payItemTogetherExcludeAccountAmountChinese** | String | 付款总额(排除记账金额)大写 |
| **settleOweAmout** | Double | 结算单中用的待付金额(未收),使用后台逻辑算好 |
| **settleOweAmoutChinese** | String | 待付金额大写 |
| \-----结算付款方式及优惠保存信息 | array | |
| **settlementPayItemList** | | |
| **payWay** | String | _付款方式_ |
| **payAmount** | Double | _付款金额_ |
| **chinesePayAmount** | String | _大写付款方式_ |
| **accountAgreementName** | String | _记账客户名称_ |
| \-----付款方式信息 | array | |
| **payItemList** | | |
| **payWay** | String | _付款方式_ |
| **payAmount** | Double | _付款金额_ |
| **chinesePayAmount** | String | _大写付款方式_ |
| \-----附加项目(江苏结算单) | | |
| **extraPrintVo** | obj | |
| **commissionCustomName** | String | _代办费自定义名称_ |
| **commissionCost** | Double | _代办费成本_ |
| **commissionMemo** | String | _代办费备注_ |
| **diagnosisCustomName** | String | _诊断费自定义名称_ |
| **diagnosisCost** | Double | _诊断费成本_ |
| **diagnosisItemName** | String | _诊断详细名称_ |
| **diagnosisMemo** | String | _诊断费备注_ |
| **checkCustomName** | String | _检查费自定义名称_ |
| **checkCost** | Double | _检查费成本_ |
| **checkMemo** | String | _检查费备注_ |
| **processCustomName** | String | _加工费自定义名称_ |
| **processCost** | Double | _加工费成本_ |
| **processMemo** | String | _加工费备注_ |
| **processItemName** | String | _加工详细名称_ |
| **managementCustomName** | String | _管理费自定义名称_ |
| **managementCost** | Double | _管理费成本_ |
| **managementMemo** | String | _管理费备注_ |
| \-----二期新增字段 | | |
| **oilCapacity** | String | 油量 |
| **nextMileage** | Double | _下次保养里程(工单数据源,目前维保、洗车单读取)_ |
| **nextMaintainDate** | String | _下次保养日期(工单数据源,目前维保、洗车单读取)_ |
| **nextMileageRemind** | Double | _下次服务里程(服务提醒数据源,目前维修、贴膜单读取)_ |
| **nextMaintainDateRemind** | Long | _下次服务时间(服务提醒数据源,目前维修、贴膜单读取)_ |
| **repairPersonContact** | String | _送修人联系方式_ |
| **memberCardNo** | String | _会员号_ |
| **points** | String | _积分_ |
| **czkList** | array | 储值卡列表 |
| **name** | String | 名称 |
| **memberCardNo** | String | 卡号 |
| ```plaintext<br> **cardOwner** <br>``` | String | 持卡人 |
| **amount** | Double | 金额 |
| **cardList** | array | 套餐卡列表 |
| **name** | String | 名称 |
| **memberCardNo** | String | 卡号 |
| ```plaintext<br> **cardOwner** <br>``` | String | 持卡人 |
| **amount** | Double | 金额 |
| **combineServiceAndPartList** | array | 项目材料组合列表 |
| **servicePrintVo** | 参见serviceList | |
| **partPrintVo** | 参见partList | |
# 案例记录:
#### 1.结算前 待付 结算后实付(跟进结算状态判断,7100 为已结算)
```plaintext
$P{balanceStatus}.equals("7100")?($P{amountAll}.subtract($P{vipExpense}).subtract($P{czkExpense}).subtract($P{serviceFavourable}.add($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).add($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).add($P{partinfoFavourable}).add($P{discountFavourable}).add($P{couponFavourable}).add($P{pointFavourable}).add($P{gatheringFavourable})).setScale( 2, BigDecimal.ROUND_HALF_EVEN ).toString()+($P{payItemTogether}==null?"":"("+$P{payItemTogether}+")")):($P{amountAll}.subtract($P{vipExpense}).subtract($P{czkExpense}).subtract($P{serviceFavourable}.add($P{partFavourableCommonTotal}==null?BigDecimal.ZERO:$P{partFavourableCommonTotal}).add($P{serviceFavourableCommonTotal}==null?BigDecimal.ZERO:$P{serviceFavourableCommonTotal}).add($P{partinfoFavourable}).add($P{discountFavourable}).add($P{couponFavourable}).add($P{pointFavourable}).add($P{gatheringFavourable})).setScale( 2, BigDecimal.ROUND_HALF_EVEN ))
```
* 对应的汉字
```plaintext
$P{balanceStatus}.equals("7100")?$P{payItemTogetherChinese}:$P{settleOweAmoutChinese}
```
# 工具类jar包附件下载:
[请至钉钉文档查看附件《print-core-1.0.7.jar》](https://alidocs.dingtalk.com/i/nodes/vy20BglGWOexYpophlEGoZvGJA7depqY?iframeQuery=anchorId%3DX02mjljl3qzo6fk6o7712b)
### 数字金额转中文方法调用示例:
**数字金额**$P{amount}==null?BigDecimal.ZERO:$P{amount}
**转中文****com.f6car.printserver.core.CharacterUtil.chinese(**$P{amount}==null?BigDecimal.ZERO:$P{amount})
### 日期时间戳转日期示例:
**日期格式选择:**java.lang.Long
**日期时间戳**$P{nextMaintainDateRemind} **转为目标格式**$P{nextMaintainDateRemind} == null ? "" : new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date($P{nextMaintainDateRemind}))
其中,"yyyy-MM-dd HH:mm:ss" 根据实际需求指定,比如到日则选择 "yyyy-MM-dd"
### 洗车单
洗车单小票不支持定制,定制洗车单的模板名称必须包含“洗车单”三个字  ,否则无法显示对应模板
> 更新: 2025-02-24 17:08:50 原文: <https://xcz.yuque.com/ombipo/rpc7ms/ro5fs1>
@@ -0,0 +1,262 @@
# 打印单定制介绍
# 打印单定制介绍
| **最常用的基础操作** |
| --- |
| [1、新增静态文本](#jwTdk) |
| [2、新增动态字段(例如想要展示结算单中的工单号)](#fKwpz) |
| [3、列表中增减字段(例如结算单中的项目列表和材料列表)](#IUyOs) |
| [4、新增边框和样式设置](#RKqIf) |
| [6、保存+输出](#M31IF) |
| [7、打印单后台配置](#gr6xz) |
## 常用链接地址:
###### 打印单后台地址
[http://print.f6yc.com/print-server/ui/index.html#/template/classification](http://print.f6yc.com/print-server/ui/index.html#/template/classification)
###### 打印单模板样式
[https://xcz.yuque.com/ombipo/rpc7ms/fbd6ay?singleDoc#](https://xcz.yuque.com/ombipo/rpc7ms/fbd6ay?singleDoc#) 《打印单各类模板样式》
###### 打印单参数表
[https://xcz.yuque.com/ombipo/rpc7ms/ro5fs1?singleDoc#](https://xcz.yuque.com/ombipo/rpc7ms/ro5fs1?singleDoc#) 《打印单最新接口参数》
###### 打印单工具简易开发教程(附带案例)
[《打印单定制简易开发教程》](https://alidocs.dingtalk.com/i/nodes/dQPGYqjpJYgZGbvbCdEKGDGZWakx1Z5N?utm_scene=team_space)
## 工具下载
jdk1.8 使用 jaspersoft6.8版本
[请至钉钉文档查看附件《Jaspersoft Studio-6.8.0.zip》](https://alidocs.dingtalk.com/i/nodes/20eMKjyp81R0ndXdsdYe4BaDWxAZB1Gv?corpId=&iframeQuery=anchorId%3DX02mgkgykvfbfbiqcbc8b4)
WIN
[请至钉钉文档查看附件《Jaspersoft Studio-6.3.1.final.rar》](https://alidocs.dingtalk.com/i/nodes/20eMKjyp81R0ndXdsdYe4BaDWxAZB1Gv?corpId=&iframeQuery=anchorId%3DX02mki20wvdslzwftp0ao)
MAC
[请至钉钉文档查看附件《TIBCOJaspersoftStudio-6.3.1.final-mac-x86\_64.zip》](https://alidocs.dingtalk.com/i/nodes/20eMKjyp81R0ndXdsdYe4BaDWxAZB1Gv?corpId=&iframeQuery=anchorId%3DX02mki26xyvtjfkmi00ki)
## 打印单模板修改流程
#### 1、下载需要的模板
###### 通过模板名称,直接到模板管理中通过模板名称查询并下载
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715248827418-8d2597e8-0ded-4cb8-9825-202074613dcc.png)
#### 2、打开编辑工具 TIBCO Jaspersoft Studio
###### 打开文件夹,双击Jaspersoft Studio.exe 运行工具
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715304945610-6a9086af-c9f5-4b88-8c6b-1ef6bc11e8a0.png)
###### 点击File-->Open File-->选择下载的模板文件
###### ![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715305042360-e716b275-c696-4231-b0c8-99c0566fe7c5.png)进入编辑模板的页面
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715305302215-a5f09700-28cc-4e80-a78c-e919b831e857.png)
#### 3、常见编辑操作
##### 1.新增静态文本
###### 新增组件到模板中
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715305970916-23aa4144-a215-4656-8281-0c3385aba707.png)
###### 双击组件编辑显示文本
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715306012526-86e4bc9d-73dc-4120-abbf-fbedcf99c47f.png)
###### 调整组件大小和位置参数
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715306133042-c7c17a7c-138c-4b4c-b587-11782185e910.png)
##### 2.新增动态字段(例如想要展示结算单中的工单号)
###### 在参数表中搜索想要的参数名称和类型:名称是:billNo  类型是文本信息=java.lang.String
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715306460522-232a3499-d104-470d-be1a-0b1fb0957331.png)
###### 拖拽一个 Text Field 组件到模板中
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715306292127-d6629324-8ef8-4ed8-a35e-a77d543bac2d.png)
###### 双击组件写入公式固定写法$P{参数名称}
###### ![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715306617239-b7ce26f3-c520-4266-83c2-8b18196e2ea9.png)
###### 遇到提示:The current expression is not valid. Please verify it!;表明这个参数在模板中没有预先创建,需要手动创建参数信息
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715306763081-4495ab86-c432-455f-b06b-7b15fb0b8a3e.png)
###### 模板中预设参数信息:Outline-->Parameters(右键单击)-->Create Parameter
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715306875194-8d8a509a-23a8-4563-b6b7-ba6c673151e1.png)
###### 编辑参数信息:Name(填写参数名称);Class(数字就选择:java.math.BigDecimal   文本就选择java.lang.String
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715307033769-86f5efa1-d6e2-4d9a-a322-849079503f21.png)
##### 3.列表中增减字段(例如结算单中的项目列表和材料列表)
###### 例如查找材料名称,可以发现参数名是partName,是在一个名字叫partList 的列表里面的,在材料信息的列表中能使用到的参数就只有partList下的这个参数,其他参数无法在列表中直接使用(例如工单号在材料列表中展示不了)
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715307395294-6a1cf006-260b-4f9e-950f-b0f34fa9c3c9.png)
###### 双击需要编辑的列表,进入列表编辑页面
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715308008053-5abeeebb-84ad-4fb3-994b-e0a360ea96d8.png)
###### 编辑方式与新增动态字段相同,但是固定写法从$P{参数名称} 改为 $F{参数名称}
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715308039626-e11f409f-014a-4146-a578-38d2cab4d1e0.png)
###### 提示The current expression is not valid. Please verify it!  参数没有预设时,在列表编辑页面中新增,逻辑与上面相同![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715313291292-8fe511dd-64f4-4ee6-971e-f8d1f0a7dfb0.png)
##### 4.新增边框和样式设置
###### 选择需要编辑的组件,选择Boeders 进行编辑
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715308327002-0c996de2-b92b-4477-9f5d-5e68a9be188b.png)
#### 4、常见语法介绍
###### 文本拼接参数:$P{参数名称}+"特定文本内容"  —— 例如打印单标题:$P{printOrgName}+"结算单"
###### 小数保留2位小数或多位:$P{参数名称}.setScale( 保留几位小数, BigDecimal.ROUND\_DOWN ) ——例如折后金额小计,保留2位小数:$P{stuffSubtotalAll}.setScale( 2, BigDecimal.ROUND\_DOWN )
###### 字符串截取:$P{参数名称}.substring(起始位置,截取长度)——例如进厂日期,保留前10位:$P{billDate}.substring(0,10)
###### 三元运算-IF判断:(关系表达式) ? 表达式1 : 表达式2 ——例如打印单标题:($P{printOrgName}==null?$P{orgName}:($P{printOrgName}.isEmpty()?$P{orgName}:$P{printOrgName}))+"结算单"
###### 常见运算:
是否相等:”==“  或者 $P{参数名称}.equals("文本内容")
加:$P{参数名称1}.add($P{参数名称2})
减:$P{参数名称1}.subtract($P{参数名称2})
乘:$P{参数名称1}.multiply($P{参数名称2})              $P{参数名称1}.multiply(new BigDecimal(1.13))
除:$P{参数名称1}.divide($P{参数名称2}, 2, BigDecimal.ROUND_HALF_UP)
#### 4.1、高级语法介绍
###### jar包导入:例如金额转大写,研发通过编写一个jar工具包实现特定功能,下面是导入jar包步骤
![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/oJGq75kaYJrZ2lAK/img/882b929c-310e-4525-a463-15def4e3fac3.png)
![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/oJGq75kaYJrZ2lAK/img/3839a386-06ab-442a-b0e3-997afd7e0a94.png)
![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/oJGq75kaYJrZ2lAK/img/76ef341c-a67b-4f30-816d-73497eb8d703.png)
启用成功后按照研发语法实现具体功能,比如![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/oJGq75kaYJrZ2lAK/img/0d61f87d-719a-4af6-bc34-c06a2fabf5b8.png)
###### 从list中取特定的值写入外层表格中:该公式使用jdk1.8语法,jaspersoft6.8可用
下面表达式的意思是,从支付方式列表(payItemList) 中找到
支付方式(payWay) 
等于“记账”的
第一个支付金额(payAmount
```java
$P{payItemList}.getData().stream()
.filter(map -> "记账".equals(map.get("payWay")))
.map(map -> {
Object amt = map.get("payAmount");
return amt == null ? BigDecimal.ZERO : new BigDecimal(amt.toString());
})
.findFirst()
.orElse(BigDecimal.ZERO)
```
#### 5、格式预览
###### 工具中只能预览模板的样式,涉及到参数判断的需要将模板上传到门店后在F6系统工单中打印预览
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715311314096-6d38326f-8c42-4e22-b8c4-59f23f1ad075.png)
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715311442290-12854551-e373-40ea-b19a-c0d9fe59393b.png)
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715311456159-163f97ae-fb32-46b3-8548-42fd39bc66e4.png)
#### 6、保存+输出
###### 保存
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715311668513-2cf8626b-3d4f-4a8d-8a0e-71b2e1300189.png)
###### 选择.jrxml的文件,右键选择Compile Report 进行编译
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715311723006-c971ca0b-90d1-4568-90f4-78861c437d62.png)
###### 选择.jasper的文件,右键选择Export Files to...
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715311841522-716ed408-0069-449d-a460-4a483c724ca5.png)
###### 另存到桌面
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715311906058-9ae58e93-f4d9-487d-96fa-e87450747f8c.png)
#### 7、打印单后台配置
###### 新增/编辑模板
注意:不要随便删除模板,删除一定要再三确认清楚,避免出现误操作的情况(删除不可恢复)  预计5.16号后 对删除的功能二次确认进行优化。
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715312153740-d570281c-2e63-432a-885f-2d4d703a810d.png)
###### 模板名称命名规范
* **简单调整**\*\***\*\*模板名称:基础表+特殊修改需求**
**模板编码:修改人姓名首字母英文大写+模板分类+日期**
**模板备注:模板各修改点**
![image](https://ddoc.f6yc.com/yuque/0/2024/png/245629/1715334288403-7a693418-2995-4707-a36c-87ee83a0e7f5.png)
* **定制调整**\*\***\*\*模板名称:门店名称+定制**
**模板编码:修改人姓名首字母英文大写+模板分类+日期**
**模板备注:模板各修改点**
![image](https://ddoc.f6yc.com/yuque/0/2024/png/245629/1715334472806-b911e666-83b7-4512-bdc0-d08996377303.png)
###### 给指定门店配置打印单
![image](https://ddoc.f6yc.com/yuque/0/2024/png/247998/1715312294997-84f80cc6-8058-4dbc-988d-3681bd2ece7e.png)
#### 8、常见打印分类及对应的通用模板
| 常见打印单分类 | 对应系统上的打印模块 | 通用模板名称 | 模板编码 |
| --- | --- | --- | --- |
| 新结算单打印 | 维保单 | F6标准结算单(壹) | newSettleFirst |
| 结算单-新 | 除维保单的其他单据(维修单、贴膜单......) | 无 | 无 |
| 附表-新 | 新附表 | 无 | 无 |
| 销售单 | 销售单 | 销售单(日期版) | xiaoshodanriqiban |
| 洗车单 | 洗车单 | 洗车单 | wash01 |
| 报价单打印 | 报价单 | 报价单打印 | quotationPrint |
| 新库存入库单打印 | 入库单 | 新库存入库单打印 | 9001 |
| 新库存出库单打印 | 出库单 | 新库存出库单打印 | 9002 |
| | | | |
| **注:采购单和采购退货单走打印平台定制,先向赵亚妮提供门店编码、门店名称,开通后再上传配置门店生效** | | | |
**注意:结算单要在收款后页面打印,请确保模板名称中包含“结算单”三个字**
> 更新: 2025-05-16 13:54:24 原文: <https://xcz.yuque.com/ombipo/obbigo/kg2qc1sbszwk48bf>
@@ -0,0 +1,60 @@
# 报价单接口参数
# 报价单接口参数
# 参数说明
## 主单信息
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| partDetailVoList | 材料列表 | | List<QuotationServiceDetailPrintVo> |
| serviceDetailVoList | 项目列表 | | List<QuotationPartDetailPrintVo> |
| amountChinese | 商品金额中文大写 | | |
| realAmountWithoutCardChinese | 待付金额中文大写 | | |
## QuotationPartDetailPrintVo
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| pkId | 无意义主键 | 是 | BigInteger |
| idOwnOrg | 门店id | 是 | BigInteger |
| idQuotation | 报价单id | 是 | BigInteger |
| idPart | 材料id | 否 | BigInteger |
| partName | 材料名称(非组合,对应材料名称字段) | 是 | String |
| partShowName | 材料在界面上显示的名称(组合供应商编码等信息) | 是 | String |
| idMdmPart | 云材料ID | 否 | String |
| labelId | 业务分类id | 否 | BigInteger |
| labelName | 荣誉的业务分类名称 | 否 | String |
| number | 数量 | 是 | BigDecimal |
| price | 单价 | 是 | BigDecimal |
| subtotal | 总价 | 是 | BigDecimal |
| idEmployee | 服务员工id | 否 | String |
| employeeName | 服务员工姓名 | 否 | String |
| isMember | 套餐2,套餐卡1,普通0 | 是 | byte |
| memo | 备注 | 否 | String |
| discount | 折扣 | 是 | BigDecimal |
| realSubtotal | 折后总计 | 是 | BigDecimal |
| stockNumber | 门店库存数量 | 否 | BigDecimal |
| groupId | 公司id | 是 | BigInteger |
| unit | 单位 | 否 | String |
| spec | 规格型号 | 否 | String |
| brand | 品牌名称 | 否 | String |
| brandId | 品牌id | 否 | String |
| supplierCode | 零件号 | 否 | String |
## QuotationServiceDetailPrintVo
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| pkId | 无意义主键 | 是 | BigInteger |
| customCode | 项目编码 | 否 | String |
| serviceName | 项目名称 | 是 | String |
| workHour | 工时 | 否 | BigDecimal |
| price | 单价 | 是 | BigDecimal |
| subtotal | 工时费 | 是 | BigDecimal |
| discount | 折扣 | 是 | BigDecimal |
| realSubtotal | 折后金额 | 是 | BigDecimal |
| memo | 备注 | 否 | String |
> 更新: 2025-05-26 14:19:50 原文: <https://xcz.yuque.com/ombipo/rpc7ms/gcgxuuzvmyhs4eoe>
@@ -0,0 +1,243 @@
# 新版附表打印接口文档
# 新版附表打印接口文档
# 接口出参
| 字段 | 含义 | 类型 |
| --- | --- | --- |
| billNo | 附表单号 | String |
| fromBillNo | 附表源工单号 | String |
| fromMaintainType | 来源单据类型 | String |
| billDate | 进厂日期 | String |
| creatorName | 创建人名称 | String |
| creationtime | 创建时间 | String |
| naEmployee | 服务顾问 | String |
| employeePhone | 服务顾问手机号 | String |
| businessTypeName | 业务类型 | String |
| nextMaintainDate | 下次保养日期 | String |
| oilCapacity | 当前油量 | String |
| mileage | 出厂里程(进厂里程) | Double |
| nextMileage | 下次保养里程 | Double |
| vin | 车辆VIN码 | String |
| carNoWhole | 车牌号 | String |
| carModel | 品牌车型全称 | String |
| carModelShort | 车型简称 | String |
| carColor | 车身颜色 | String |
| carCategoryName | 车辆分类名称 | String |
| carBrandName | 车辆品牌名称 | String |
| carSeriesName | 车系名称 | String |
| engineNumber | 发动机号 | String |
| transmissionNo | 变速箱号 | String |
| registerDate | 车辆注册日期 | String |
| cardDate | 车辆发证日期 | String |
| carNatureOfUseName | 车辆使用性质 | String |
| carFuelTypeName | 车辆燃料(能源)类型 | String |
| carSourceName | 车辆来源 | String |
| carOwnerName | 车辆所有人姓名 | String |
| naCustomer | 客户姓名 | String |
| customerSourceName | 客户来源名称 | String |
| customerDetailAddress | 客户详细地址 | String |
| cellPhone | 联系电话(客户) | String |
| memberCardNo | 会员卡号 | String |
| points | 客户积分 | String |
| customerLevelName | 客户等级名称 | String |
| repairPerson | 送修人 | String |
| repairPersonContact | 送修人联系方式 | String |
| memo | 备注 | String |
| completeDate | 完工日期 | String |
| firstGatheringTime | 初次收款时间 | String |
| estimatedDeliveryTime | 预计交车时间 | String |
| deliveryTime | 交车时间 | String |
| printContent | 免责条款 | String |
| printContentJs | 免责条款江苏 | printContentJs |
| storeLogo | 门店logo | String |
| orgName | 门店名称 | String |
| orgMemo | 门店备注 | String |
| orgContacts | 联系人(维修厂) | String |
| orgContactNumber | 联系电话(维修厂) | String |
| orgDetailAddress | 联系地址(维修厂) | String |
| orgContactMobile | 联系电话(维修厂) | String |
| fax | 传真 | String |
| email | 组织邮件 | String |
| bankAccount | 开户银行 | String |
| accountNumber | 账号 | String |
| businessLicenseCode | 企业执照号 | String |
| channelName | 来店途径名称 | String |
| printOrgName | 打印抬头(需读取配置) | String |
| amountAll | 应收总计(合计金额) | Double |
| amountAllChinese | 应收总计(合计金额)中文大写 | String |
| disCountAll | 总优惠合计(附表:项目优惠+材料优惠+收银优惠) | Double |
| disCountAllBak | 项目优惠+材料优惠+收银优惠,等同于disCountAll | Double |
| amountReal | 实收金额 | Double |
| chineseAmount | 实收金额(中文大写) | Double |
| oweAmount | 未收金额 | Double |
| vipExpense | 套餐卡消费金额(附表为0 | Double |
| vipExpenseFavourable | 套餐卡优惠金额(附表为0 | Double |
| czkExpense | 储值卡消费金额(附表为0 | Double |
| czkExpenseFavourable | 储值卡优惠金额(附表为0 | Double |
| remainAmount | 结算金额tsf<br/>附表:应收-项目优惠-材料优惠 | Double |
| receivedAmount | 收款金额tsf<br/>附表:等同于已收金额 | Double |
| serviceList | 项目集合 | List |
| └─ sortNumber | 序号 | String |
| └─ orderNumber | 序号(全部) | String |
| └─ name | 名称 | String |
| └─ serviceName | 项目名称 | String |
| └─ labelName | 业务分类名称 | String |
| └─ price | 工时单价 | Double |
| └─ workHour | 工时 | Double |
| └─ subtotal | 金额 | Double |
| └─ singleFavourable | 优惠金额 | Double |
| └─ discountedSubtotal | 折后金额 | Double |
| └─ serviceMemo | 附加信息备注 | String |
| └─ discount | 折扣 | Double |
| └─ empNameStr | 服务项目明细对应修理工名称组装字符串 | String |
| └─ infiniteFlag | 是否无限,0:否,1:是 | Integer |
| └─ customCode | 自定义编码 | String |
| totalWorkHour | 项目工时合计 | Double |
| totalWorkHourVip | VIP项目工时合计(附表为0 | Double |
| serviceSubtotalAll | 工时费小计 | Double |
| serviceSubtotalVip | 服务项目明细小计(会员项目,附表为0) | Double |
| serviceNum | 维修项目小计 | String |
| serviceFavourable | 服务项目(非会员项目)客户等级优惠(附表为0) | Double |
| serviceDiscountFavourable | 项目优惠 | Double |
| serviceFavourableTotal | 项目优惠金额合计 | Double |
| serviceFavourableCommonTotal | 普通项目优惠金额合计 | Double |
| serviceDisCountSubTotal | 项目折后金额合计 | Double |
| partList | 工单对应配件材料集合 | List |
| └─ sortNumber | 序号 | String |
| └─ orderNumber | 序号(全部) | String |
| └─ name | 名称 | String |
| └─ partName | 材料名称 | String |
| └─ partShowName | 材料名称(全) | String |
| └─ partBrand | 配件品牌 | String |
| └─ standard | 配件名称规格型号品牌 | String |
| └─ spec | 规格型号 | String |
| └─ supplierCode | 供应商编码 | String |
| └─ unit | 单位 | String |
| └─ number | 数量 | Double |
| └─ price | 价格(单价) | Double |
| └─ subtotal | 金额(材料金额) | Double |
| └─ discount | 折扣 | Double |
| └─ singleFavourable | 优惠金额 | Double |
| └─ discountedSubtotal | 折后金额 | Double |
| └─ partMemo | 备注 | String |
| └─ customCode | 自定义编码 | String |
| └─ employeeName | 员工名称 | String |
| └─ outStockEmployeeName | 领料人 | String |
| └─ empNameStr | 明细对应修理工名称组装字符串 | String |
| └─ labelName | 业务分类名称 | String |
| └─ idPart | 配件材料pk | BigInteger |
| └─ idInfo | 本地材料id(长码) | String |
| └─ applyModel | 适用车型 | String |
| stuffNum | 材料数目合计 | String |
| totalStuffNum | 材料数量合计 | String |
| totalStuffNumVip | Vip材料数量合计(附表为0 | String |
| stuffSubtotalAll | 材料费小计 | Double |
| stuffSubtotalVip | 材料收入小计(会员项目,附表为0) | Double |
| partinfoFavourable | 材料项目(非会员项目)客户等级优惠(附表为0) | Double |
| partinfoDiscountFavourable | 材料折扣优惠 | Double |
| partFavourableTotal | 材料优惠金额合计 | Double |
| partFavourableCommonTotal | 普通材料优惠金额合计 | Double |
| stuffDisCountTotal | 材料折后金额合计 | Double |
| pointFavourable | 积分优惠(附表为0 | Double |
| packageFavourable | 套餐优惠(附表为0 | Double |
| discountFavourable | 结清优惠(附表为0 | Double |
| gatheringFavourable | 收银优惠 | Double |
| couponFavourable | 优惠券优惠(附表为0 | Double |
| czkDiscountFavourable | 储值卡折扣优惠(附表为0 | Double |
| customerLevelFavourable | 客户级别优惠金额(附表为0 | Double |
| extraChargeList | 附加费用集合 | List |
| └─ sortNumber | 序号 | String |
| └─ extraName | 附加费名称 | String |
| └─ subtotal | 金额 | Double |
| └─ memo | 备注 | String |
| extraCostTotal | 附加费小计 | Double |
| extraNumber | 附加费数量小计 | String |
| managementCost | 管理费 | Double |
| extraPrintVo | 附加项目 | ExtraPrintAttribute |
| └─ commissionCustomName | 代办费自定义名称 | String |
| └─ commissionCost | 代办费金额 | Double |
| └─ commissionMemo | 代办费备注 | String |
| └─ diagnosisCustomName | 诊断费自定义名称 | String |
| └─ diagnosisCost | 诊断费金额 | Double |
| └─ diagnosisItemName | 诊断详细名称 | String |
| └─ diagnosisMemo | 诊断费备注 | String |
| └─ checkCustomName | 检查费自定义名称 | String |
| └─ checkItemName | 诊断详细名称 | String |
| └─ checkCost | 检查费金额 | Double |
| └─ checkMemo | 检查费备注 | String |
| └─ processCustomName | 加工费自定义名称 | String |
| └─ processCost | 加工费金额 | Double |
| └─ processMemo | 加工费备注 | String |
| └─ processItemName | 加工详细名称 | String |
| └─ managementCustomName | 管理费自定义名称 | String |
| └─ managementCost | 管理费金额 | Double |
| └─ managementMemo | 管理费备注 | String |
| └─ fuelName | 加油费 | String |
| └─ fuelAmount | 加油费金额 | Double |
| └─ trailName | 拖车费 | String |
| └─ trailAmount | 拖车费金额 | Double |
| allOtherCost | 附加费合计应收 | Double |
| receiptAmount | 收据金额(附表:应收-项目优惠-材料优惠-收银优惠) | Double |
| receiptAmountChinese | 收据金额中文大写 | String |
| payItemList | 付款方式集合 | List |
| └─ payWay | 付款方式 | String |
| └─ payAmount | 付款金额 | Double |
| └─ chinesePayAmount | 付款金额中文大写 | String |
| payItemTogether | 付款方式拼接 | String |
| payItemTogetherChinese | 付款方式总额中文大写 | String |
| paymentTypeDetails | 支付方式汇总 | String |
| settleOweAmout | 结算单中用的待付金额(未收) | Double |
| settleOweAmoutChinese | 结算单中用的待付金额大写(未收) | String |
| settleReceivedAmout | 结算单中用的实付金额(实收) | Double |
| settleReceivedAmoutChinese | 结算单中用的实付金额大写(未收) | String |
| realPayAmountChinese | 客户实付大写(应收-所有优惠) | String |
| naInsurer | 理赔公司名称 | String |
| insurancepolicyNo | 理赔单理赔保险单号 | String |
| insuranceCompany | 保险公司名称 | String |
| contactName | 联系人姓名 | String |
| contactCellphone | 联系人电话 | String |
| insuranceNo | 商业险单号 | String |
| insuranceNoTCI | 交强险单号 | String |
| insuranceExpiryDate | 商业险到期日 | String |
| insuranceExpiryDateTCI | 交强险到期日 | String |
| printEmployeeName | 打印人姓名 | String |
| printTime | 打印时间 | String |
| printCount | 打印次数 | String |
# 备注1
1. ++新版附表没有“状态”字段,所有模板中切勿使用 billStatus【单据状态】、balanceStatus【结算状态】来进行判断输出;采用直接取值方式填值++
1. ++没有了balanceStatus,采用收款方式列表payItemList判空的方式来验证是否有收款信息++
1. ++收银金额可写作:++$P{payItemList}.getRecordCount()==0?$P{receivedAmount}:$P{receivedAmount}.setScale( 2, BigDecimal.ROUND\_HALF\_EVEN ).toString()+"("+$P{paymentTypeDetails}+")"
2. ++待付金额可写作:$P{oweAmount}++
3. ++实付金额可写作:$P{amountReal}++
4. ++合计金额可写作:++$P{payItemList}.getRecordCount()==0?($P{settleOweAmout}).setScale( 2, BigDecimal.ROUND_HALF_EVEN ):($P{settleReceivedAmout}).setScale( 2, BigDecimal.ROUND_HALF_EVEN )
5. ++大写可写作:++$P{payItemList}.getRecordCount()==0?$P{settleOweAmoutChinese}:$P{chineseAmount}
2. ++新版附表没有“单据类型”字段(原附表的maintainType为"GDFB"、"LPDFB"两种附表类型),所有模板中切勿使用 maintainType【单据类型】字段作为判断条件。根据情况可以取 fromMaintainType【来源单据类型】进行判断。如:++
1. ++是否展示理赔公司、理赔单等理赔相关信息栏,老附表判断逻辑为 maintainType.equals('LPDFB')。可更换为 fromMaintainType.equals('LPD')++
# 备注2
1. 出参标注颜色为 **绿 色** 的字段,为 ++**附表本身内容**++(如车牌号、VIN码等) 或 ++**无法变更内容**++(如门店信息等)
1. 客户在附表页面直接变更信息,会直观反映在打印内容中。
2. 出参标注颜色为 **橙 色** 的字段,为 ++**通过客户ID、车辆ID、项目ID、材料ID**++ 等,++**反查**++ 基础数据获得内容,
1. 若客户通过 ++**附表页面选择组件方式**++ 修改附表信息,因ID发生变化,该部分打印内容会随之变化,变更信息将 ++**会体现在打印内容中**++。
2. 若客户通过 ++**手动填写文本内容方式**++ 修改附表信息,因ID未发生变化,该部分打印内容不会变化,变更信息将 ++**不会体现在打印内容中**++,若客户不满意结果,请客户直接将 ++**基础数据进行变更**++ 后,通过页面组件选择后再进行打印。
> 更新: 2024-01-11 12:11:34 原文: <https://xcz.yuque.com/ombipo/rpc7ms/nmoz9mzqf2q8micw>
@@ -0,0 +1,61 @@
# 材料标签接口参数
# 材料标签接口参数
打印平台模版分类:材料价格通用标签打印
材料标签支持一次打多张,与labelList集合里的元素个数相匹配:
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| labelList | 材料标签列表 | | List<Label> |
| <br/><br/> | | | |
Label:
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| name | 材料名称 | | String |
| supplierCode | 零件号 | | String |
| customCode | 材料编码 | | String |
| sellPrice | 销售价格 | | BigDecimal |
| date | 打印日期 | | String yyyy/MM/dd |
| <br/><br/> | | | |
# 采购库存材料标签打印
### 打印平台模版分类:
不带供应商信息:采购库存通用标签打印-新
带供应商信息:采购库存通用标签打印-包含供应商-新
![image](https://ddoc.f6yc.com/yuque/0/2023/png/227465/1694662514424-52b54134-ad88-4e10-b5ad-c3565633f157.png)
### 打印数据体
材料标签支持一次打多张,于labelList集合里的元素个数相匹配:
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| labelList | 材料名称 | | List<Object> |
Object:
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| partName | 材料名称 | | String |
| partBrand | 材料品牌 | | String |
| customCode | 材料编码 | | String |
| partShowName | 组合名称(材料品牌,名称,规格型号组合) | | String |
| supplierCode | 材料供应商编码 | | String |
| spec | 材料规格型号 | | String |
| unit | 材料单位 | | String |
| barCode | 材料编码 | | String |
| billDate | 单据日期 | | String yyyy-MM-dd |
| supplierName | 供应商名称 | | String |
| storageName | 材料仓库名称 | | String |
| defSeat | 材料货位 | | String |
| date | 打印的当前日期 | | String yyyy/MM/dd |
> 更新: 2023-09-14 14:27:45 原文: <https://xcz.yuque.com/ombipo/rpc7ms/mvq9tlfxeg2k6v9y>
@@ -0,0 +1,100 @@
# 检测单接口参数
# 检测单接口参数
# 参数说明
## 主单信息
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| carCheckPackageName | 模板名称 | | String |
| title | 标题 | | String |
| billNo | 检测单号 | | String |
| printTime | 打印时间 | | String |
| naEmployee | 服务顾问 | | String |
| billDate | 进厂时间 | | String |
| deliveryTime | 预计交车 | | String |
| naCustomer | 车主 | | String |
| carModel | 车型 | | String |
| cellPhone | 车主电话 | | String |
| carNoWhole | 车牌号 | | String |
| vin | vin码 | | String |
| repairPerson | 送修人 | | String |
| repairPersonContact | 送修人联系方式 | | String |
| mileage | 进厂里程 | | String |
| oilCapacity | 进厂油量 | | String |
| nextMileage | 下次保养里程 | | String |
| nextMaintainDate | 下次保养日期 | | String |
| customerMemo | 车主描述 | | String |
| merchantAddress | 联系地址 | | String |
| merchantPhone | 联系方式(手机 + 固定电话) | | String |
| qrCode | 二维码 | | String |
| qrCodeToB | 新版检测单B端二维码 | | String |
| qrCodeToC | 新版检测单C端二维码 | | String |
| icon | 车辆环视图 | | String |
| maintainBillNo | 结算单号 | | String |
| showComputerCheckInfo | 是否展示电脑检测 | | Boolean |
| computerCheckInfoList | 电脑检测 | | List<ComputerPrintItem> |
| personalCheckInfoList | 包含正常和异常人工检测项 | | List<PersonalPrintItem> |
| sortedPersonalCheckInfoList | 包含正常和异常人工检测项,问题项目排序靠前 | | List<PersonalPrintItem> |
| optionPersonalCheckInfoList | 异常人工检测项 | | List<PersonalPrintItem> |
| normalPersonalCheckInfoList | 正常人工检测项 | | List<PersonalPrintItem> |
| iconMemo | 环视图备注 | | String |
| iconResult | 环视图结论 | | String |
| warningLightResult | 警示灯结论 | | String |
| warningLightMemo | 警示灯备注 | | String |
| showWarningLightItem | 是否展示警示灯 | | Boolean |
| warningLightItemList | 警示灯 | | List<WarningLightItem> |
| warningLightBrightItemUrls | 警示灯列表字符串 | | String |
| employeeName | 服务技师 | | String |
| | | | |
## ComputerPrintItem
| 字段 | 含义 | 类型 |
| --- | --- | --- |
| index | 序号 | String |
| errorCode | 故障码 | String |
| itemName | 检测项目 | String |
| optionNameS | 建议处理(Y | String |
| optionNameE | 择期处理(Y | String |
| optionNameU | 急需处理(Y | String |
| memo | 备注 | String |
| | | |
| | | |
| | | |
| | | |
| | | |
## PersonalPrintItem(检测小类)
| 字段 | 含义 | 类型 |
| --- | --- | --- |
| index | 序号 | BigInteger |
| itemComponent | 检测部件(小类名称) | String |
| memo | 备注 | String |
| childList | 子项目列表 | List<PrintTinyItem> |
## PrintTinyItem(检测项目)
| 字段 | 含义 | 类型 |
| --- | --- | --- |
| itemName | 检测项目 | String |
| itemResults | 检测结果 | String |
| optionNameS | 建议处理 Y | String |
| optionNameE | 择期处理 Y | String |
| optionNameU | 急需处理 Y | String |
| memo | 备注 | String |
## ComputerPrintItem
| 字段 | 含义 | 类型 |
| --- | --- | --- |
| warningIconCode | 警示灯 | String |
| | | |
| | | |
| | | |
| | | |
> 更新: 2025-02-25 13:55:59 原文: <https://xcz.yuque.com/ombipo/rpc7ms/zayna4s335straki>
@@ -0,0 +1,215 @@
# 结算单(指定内容)接口文档
# 结算单(指定内容)接口文档
# 接口
**接口: /blazer/maintenance/pr****int/file/dispatchCondition**
**方法: post**
**支持场景:打印 维保单/洗车单/维修单/贴膜单/理赔单/零售单,支持指定(项目/材料/附加费)打印**
**支持打印模块(**PrintModuleEnum**):****工时费、材料费、附加费、其他费用、服务费用**
**入参:**
```plaintext
{
"rowId": "10329",
"rowCode": "partialSettlementStatement",
"pkId": "16126085167868551253",
"serviceList": [
{
"pkId": 16126085168250232896,
"module": 1
},
{
"pkId": 16155904656688554043,
"module": 4
}
],
"partList": [
{
"pkId": 11004336,
"module": 2
},
{
"pkId": 11004811,
"module": 5
}
],
"extraList": [
{
"type": 4,
"module": 3
},
{
"type": 6,
"module": 4
}
]
}
```
**出参:**
```plaintext
{
"preScanUrl": "https://f.f6yc.com/printserver/pdfprint.html?url=https://f.f6yc.com/print-server/test/2024-10/default/49be06dc15444e2793a21fc8c16d3b5c.pdf?Expires=1729152323&OSSAccessKeyId=LTAI4Fcf2C1U99o3e3UQ2bHV&Signature=L86HWRF2ilyXU4BbzKhwXad%2BwXg%3D",
"url": "https://f.f6yc.com/print-server/test/2024-10/default/49be06dc15444e2793a21fc8c16d3b5c.pdf?Expires=1729152323&OSSAccessKeyId=LTAI4Fcf2C1U99o3e3UQ2bHV&Signature=L86HWRF2ilyXU4BbzKhwXad%2BwXg%3D"
}
```
# jasper取参说明
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| billNo | 工单号 | 是 | String |
| maintainType | 工单类型 | 是 | String |
| businessTypeName | 业务类别 | 是 | String |
| balanceStatus | 结算状态 | 是 | String |
| billStatus | 单据状态 | 是 | String |
| creatorName | 创建人名称 | 是 | String |
| naEmployee | 服务顾问 | 否 | String |
| employeePhone | 服务顾问手机号 | 否 | String |
| naInsurer | 理赔公司名称 | 否 | String |
| insurancepolicyNo | 理赔保险单号 | 否 | Double |
| serviceSubtotalVip | 套餐卡项目工时费小计 | 是 | Double |
| serviceFavourableCommonTotal | 普通项目优惠金额合计 | 是 | Double |
| totalStuffNum | 材料数量合计 | 是 | String |
| stuffSubtotalVip | 套餐卡项目材料费小计 | 是 | Double |
| stuffSubtotalAll | 材料费小计 | 是 | Double |
| partFavourableCommonTotal | 普通材料优惠金额合计 | 是 | Double |
| extraNumber | 附加费数量小计 | 是 | String |
| extraCostTotal | 附加费小计 | 是 | Double |
| favourableTotalList | 优惠明细小计 | 否 | List<FavourableDetailPrintAttribute> |
| naCustomer | 客户姓名 | 是 | String |
| cellPhone | 客户联系电话 | 是 | String |
| repairPerson | 送修人 | 否 | String |
| repairPersonContact | 送修人联系方式 | 否 | String |
| carNoWhole <br/> | 车牌号 | 是 | String |
| vin<br/> | 车辆VIN码 | 否 | String |
| carBrandName<br/> | 品牌名称 | 否 | String |
| carSeriesName<br/> | 车系名称 | 否 | String |
| carModelShort<br/> | 车型简称 | 否 | String |
| carModel<br/> | 车型 | 否 | String |
| transmissionNo | 变速箱号 | 否 | String |
| carFuelTypeName | 燃料类型 | 否 | String |
| carColor<br/> | 车身颜色 | 否 | String |
| billDate<br/> | 进厂日期 | 是 | String |
| estimatedDeliveryTime<br/> | 预计交车时间 | 是 | String |
| deliveryTime<br/> | 交车时间(出厂时间) | 是 | String |
| mileage<br/> | 进厂里程 | 是 | Double |
| nextMaintainDateRemind | 下次服务时间(服务提醒数据源) | 否 | Date |
| nextMileageRemind | 下次服务里程(服务提醒数据源) | 否 | Double |
| serviceList | 项目列表 | 否 | List<PartialServicePrintAttribute> |
| partList | 材料列表 | 否 | List<PartialPartPrintAttribute> |
| extraChargeList | 附加费列表 | 否 | List<ExtraChargePrintAttribute> |
| extendedModuleList | 扩展模块列表 | 否 | List<ExtendedModulePrintAttribute> |
| memo<br/> | 车主备注 | 否 | String |
| signaturePhotoUrl<br/> | 签名图片 | 否 | String |
| orgName<br/> | 维修厂名称 | 是 | String |
| orgContacts<br/> | 维修厂联系人 | 是 | String |
| orgDetailAddress<br/> | 维修厂地址 | 是 | String |
| orgContactMobile<br/> | 联系电话(维修厂) | 否 | String |
| storeLogo<br/> | logo | 否 | String |
| printOrgName<br/> | 打印抬头 | 否 | String |
| printContent<br/> | 免责条款 | 是 | String |
| printCount<br/> | 打印次数 | 是 | String |
| printTime<br/> | 打印时间 | 是 | String |
**PartialServicePrintAttribute**
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| serviceName | 项目名称 | 是 | String |
| labelName | 业务分类名称 | 是 | String |
| isMember | 当前项目是否使用会员 | 是 | String |
| idService | 服务项目id | 是 | BigInteger |
| idInfo | 本地项目id | 是 | String |
| price | 工时单价 | 是 | Double |
| workHour | 工时 | 是 | Double |
| subtotal | 金额 | 是 | Double |
| singleFavourable | 优惠金额 | 是 | Double |
| discountedSubtotal | 折后金额 | 是 | Double |
| serviceMemo | 附加信息备注 | 否 | String |
| discount | 折扣 | 是 | Double |
| empNameStr | 服务项目明细对应修理工名称组装字符串 | 否 | String |
| favourableVoList | 优惠明细 | 否 | List<FavourableDetailPrintAttribute> |
**PartialPartPrintAttribute**
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| partName | 材料名称 | 是 | String |
| partShowName | 材料名称(全) | 是 | String |
| partBrand | 配件品牌 | 是 | String |
| unit | 单位 | 是 | String |
| number | 数量 | 是 | Double |
| isMember | 当前项目是否使用会员 | 是 | Integer |
| price | 价格(单价) | 是 | Double |
| subtotal | 金额(材料金额) | 是 | Double |
| discount | 折扣 | 是 | Double |
| singleFavourable | 优惠金额 | 是 | Double |
| discountedSubtotal<br/> | 折后金额 | 是 | Double |
| partMemo<br/> | 备注 | 否 | String |
| isBring<br/> | 是否自带 | 是 | String |
| empNameStr<br/> | 明细对应修理工名称组装字符串 | 否 | String |
| outStockEmployeeName<br/> | 领料人 | 否 | String |
| labelName<br/> | 业务分类名称 | 是 | String |
| idPart<br/> | 配件材料pk | 是 | BigInteger |
| idInfo<br/> | 本地材料id | 是 | String |
| favourableVoList | 优惠明细 | 否 | List<FavourableDetailPrintAttribute> |
**ExtraChargePrintAttribute**
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| extraName | 附加费名称 | 是 | String |
| subtotal | 金额 | 是 | Double |
| memo | 备注 | 否 | String |
**FavourableDetailPrintAttribute**
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| discountType | 优惠类型 | 是 | Integer |
| discountTypeName | 优惠类型名称 | 是 | Double |
| amount | 优惠金额 | 是 | Double |
**ExtendedModulePrintAttribute**
| 字段 | 含义 | 是否必有 | 类型 |
| --- | --- | --- | --- |
| module | 打印模块,PrintModuleEnum.code | 是 | Integer |
| name | 项目/材料/附加费名称 | 是 | String |
| number | 项目对应工时,材料对应数量,附加费为“-” | 是 | Double |
| price | 项目对应工时单价,材料对应材料单价,,附加费为“-” | 是 | Double |
| subtotal | 项目对应工时费,材料对应材料费,附加费对应为金额 | 是 | Double |
| discount | 项目对应折扣,材料对应折扣,附加费为“1.00” | 是 | Double |
| discountedSubtotal | 项目对应折后金额,材料对应材料费折后金额,,附加费对应为金额 | 是 | Double |
| favourableVoList | 优惠明细 | 否 | List<FavourableDetailPrintAttribute> |
**PrintModuleEnum 打印模块枚举**
| **key** | **code** | **name** |
| --- | --- | --- |
| MAN\_HOUR\_COST | 1 | 工时费模块 |
| MATERIAL\_COST | 2 | 材料费模块 |
| EXTRA\_COST | 3 | 附加费模块 |
| OTHER\_COST | 4 | 其他费用模块 |
| SERVICE\_COST | 5 | 服务费用模块 |
# 工具类jar包附件下载
[附件: print-core-1.0.7.jar](./attachments/JUhN4OhWjosFCdDg/print-core-1.0.7.jar)
### 数字金额转中文方法调用示例:
**数字金额**$P{amount}==null?BigDecimal.ZERO:$P{amount} **转中文**com.f6car.printserver.core.CharacterUtil.chinese($P{amount}==null?BigDecimal.ZERO:$P{amount})
> 更新: 2024-10-17 15:07:46 原文: <https://xcz.yuque.com/ombipo/rpc7ms/qz7dm6e8b06r7t70>
@@ -0,0 +1,64 @@
# 调拨单打印
# 采购单模版打印说明
* 前端调用接口
/stock/allot/print?idAllot=
* 打印使用模版
模版编码=allotInPrint
![image.png](https://alidocs.oss-cn-zhangjiakou.aliyuncs.com/res/4EZlweZ0zW4mZqxA/img/43819a24-8744-4fc0-96c6-0bba66f2e0b3.png)
* 打印模版参数
HashMap<String, Object> resultMap
| 字段 | 说明 | 备注 |
| --- | --- | --- |
| printFlag | 均价门店 false<br>批次门店 true | |
| showPrice | 参配-打印调拨价格及金额<br>勾选 true<br>未勾选 false | |
| orgName | 打印抬头<br>调入门店打印:XXX调入单<br>调出门店打印:XXX调出单 | |
| billNo | 调拨单号<br>调入门店打印:DRDXXX<br>调出门店打印:DCDXXX | |
| idOwnOrg | 门店ID<br>调入门店打印:调入门店ID<br>调出门店打印:调出门店ID | |
| naOrgIn | 调入门店名称 | |
| naOrgOut | 调出门店名称 | |
| detailAddress | 供应商详细地址 | |
| statusName | 调拨单状态(制单/待发货/待收货/已完成) | |
| billDate | 单据日期(yyyy-MM-dd | |
| creatorName | 创建单名称 | |
| nowDateTime | 打印时间(yyyy-MM-dd HH:mm | |
| remark | 备注信息 | |
| showRemark | 是否显示备注<br>备注为空 false<br>备注不为空 true | |
| sumNumber | 总数量 | |
| sumAmount | 总金额<br>价格脱敏时显示 \*\*\*\* | |
| printCount | 打印次数 | |
| allotDetailVoList | 调拨单明细行 | |
| sortNumber | 序号 | |
| partShowName | 材料组合名称 | |
| customCode | 材料自定义编码 | |
| partName | 材料名称 | 20250731追加 |
| partBrand | 材料品牌 | 20250731追加 |
| supplierCode | 材料零件号 | 20250731追加 |
| standard | 材料规格型号 | 20250731追加 |
| carNo | 车牌 | 20250731追加 |
| defSeat | 货位<br>调入门店打印:调入门店货位<br>调出门店打印:调出门店货位 | |
| unit | 单位 | |
| price | 单价<br>价格脱敏时显示 \*\*\*\* | |
| numCus | 均价门店<br>  调拨个数<br>批次门店&制单&未选择批次<br>   调拨个数<br>批次门店<br>   选择或使用的批次个数 | |
| amount | 均价门店<br>  调拨明细行金额<br>批次门店&制单&未选择批次<br>   调拨明细行金额<br>批次门店<br>   选择或使用的批次个数\*调拨单单价<br>备注:价格脱敏时显示 \*\*\*\* | |
| orderNo | 批次号 | |
| productDate | 批次生成日期 | |
| 明细合计行 | | |
| sortNumber | 合计行 | |
| partShowName | "" | |
| unit | "" | |
| numCus | 总数量 | |
| amount | 总金额<br>价格脱敏时显示 \*\*\*\* | |
| defSeat | "" | |
| stockNumber | "" | |
@@ -0,0 +1,3 @@
# 采购单/采购退模版打印
[《采购单/采购退打印》](https://alidocs.dingtalk.com/i/nodes/7NkDwLng8ZMajY9YijOwlz4bJKMEvZBY)
@@ -0,0 +1,107 @@
"""
JRXML box 元素结构修复脚本
修复问题:<box> 被错误地放在 <reportElement> 内部
JasperReports XSD 规定 <box> 必须是 <reportElement> 的兄弟元素,不能是其子元素
正确结构:
<staticText>
<reportElement x="0" y="0" width="49" height="16" uuid="..." />
<box>
<pen lineWidth="0.5" lineColor="#000000" />
<topPen lineWidth="0.5" lineColor="#000000" />
<leftPen lineWidth="0.5" lineColor="#000000" />
<bottomPen lineWidth="0.5" lineColor="#000000" />
<rightPen lineWidth="0.5" lineColor="#000000" />
</box>
<textElement>...</textElement>
<text>...</text>
</staticText>
用法:
python fix_jrxml_box.py <jrxml文件路径> [--dry-run]
"""
import re
import sys
import os
def fix_jrxml(input_path, dry_run=False):
with open(input_path, 'r', encoding='utf-8') as f:
content = f.read()
original = content
# 模式1: <reportElement...> 后紧跟换行+<box>
# 改为: <reportElement... /> 后跟换行+同缩进+<box>
# 即把 <reportElement...> 改为 <reportElement... />(自闭合),然后 box 移出
pattern1 = re.compile(
r'(<reportElement\s+[^>]*?)>\s*\n(\s*)(<box>)',
re.MULTILINE
)
def fix_open(m):
report_tag = m.group(1) # <reportElement x="..." ...(不含>
indent = m.group(2) # box前的缩进
box_open = m.group(3) # <box>
# reportElement 自闭合,box 保持同缩进
return report_tag + ' />\n' + indent + box_open
content = pattern1.sub(fix_open, content)
# 模式2: </box> 后紧跟 </reportElement>box 还在 reportElement 内)
# 改为: </box> 后的 </reportElement> 删除(因为已经自闭合了)
content = re.sub(r'(</box>)\s*\n\s*</reportElement>', r'\1', content)
# 模式3: <reportElement.../> 已经自闭合但后面又有 </reportElement>
# 这种是之前的修复残留,清理掉
content = re.sub(r'(<reportElement\s+[^>]*/>)\s*\n\s*</reportElement>', r'\1', content)
changed = content != original
if dry_run:
print(f"[DRY RUN] Would modify: {input_path}")
# 统计修改数量
count1 = len(pattern1.findall(original))
print(f" - reportElement+box fixes: {count1}")
return changed
if changed:
# 备份
backup_path = input_path + '.bak'
if not os.path.exists(backup_path):
with open(backup_path, 'w', encoding='utf-8') as f:
f.write(original)
print(f"Backup: {backup_path}")
with open(input_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"Fixed: {input_path}")
else:
print(f"No changes needed: {input_path}")
# 验证 XML
try:
import xml.etree.ElementTree as ET
ET.parse(input_path if not dry_run else input_path)
print("XML validation: OK")
except ET.ParseError as e:
print(f"XML validation FAILED: {e}")
return changed
if __name__ == '__main__':
if len(sys.argv) < 2:
print(__doc__)
sys.exit(1)
path = sys.argv[1]
dry_run = '--dry-run' in sys.argv
if not os.path.exists(path):
print(f"File not found: {path}")
sys.exit(1)
fix_jrxml(path, dry_run=dry_run)
+37
View File
@@ -0,0 +1,37 @@
import re
# 读取文件
with open(r'd:\Idea Project\F6+宜搭+其它(1)\张阳脚本\jaspersoft\杭州临安之田汽车销售服务有限公司-结算单.jrxml', 'r', encoding='utf-8-sig') as f:
content = f.read()
# 打印当前文件中前几个 uuid 的情况
print("查找非标准 UUID...")
pattern = r'uuid="([^"]+)"'
matches = re.findall(pattern, content)
non_standard = [m for m in matches if '-' not in m]
print(f"找到 {len(non_standard)} 个非标准 UUID:")
for m in non_standard[:10]:
print(f" {m}")
# 计数器
uuid_counter = 1
def replace_uuid(match):
global uuid_counter
uuid_val = match.group(1)
# 检查是否已经是标准格式(包含 '-'
if '-' in uuid_val:
return match.group(0)
# 否则生成新的标准 UUID
uuid_counter += 1
new_uuid = f'{uuid_counter:08d}-0000-0000-0000-000000000000'
print(f"替换: {uuid_val} -> {new_uuid}")
return f'uuid="{new_uuid}"'
content = re.sub(pattern, replace_uuid, content)
# 写入文件
with open(r'd:\Idea Project\F6+宜搭+其它(1)\张阳脚本\jaspersoft\杭州临安之田汽车销售服务有限公司-结算单.jrxml', 'w', encoding='utf-8') as f:
f.write(content)
print('修复完成!共修复', uuid_counter - 1, '个非标准 UUID')
+54
View File
@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
import re
import sys
def main():
# 读取文件
file_path = r'd:\Idea Project\F6+宜搭+其它(1)\张阳脚本\jaspersoft\杭州临安之田汽车销售服务有限公司-结算单.jrxml'
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
except Exception as e:
print(f"读取文件失败: {e}")
return
# 查找所有非标准 UUID
pattern = r'uuid="([^"]+)"'
matches = re.findall(pattern, content)
non_standard = [m for m in matches if '-' not in m]
print(f"找到 {len(non_standard)} 个非标准 UUID")
# 如果没有非标准 UUID,直接退出
if not non_standard:
print("没有需要修复的非标准 UUID")
return
# 创建映射表
uuid_map = {}
counter = 1
for name in non_standard:
if name not in uuid_map:
uuid_map[name] = f'{counter:08d}-0000-0000-0000-000000000000'
counter += 1
# 替换所有非标准 UUID
def replace_func(match):
uuid_val = match.group(1)
if uuid_val in uuid_map:
return f'uuid="{uuid_map[uuid_val]}"'
return match.group(0)
content = re.sub(pattern, replace_func, content)
# 写入文件
try:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"修复完成!共修复 {len(uuid_map)} 个非标准 UUID")
except Exception as e:
print(f"写入文件失败: {e}")
if __name__ == '__main__':
main()
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

+96
View File
@@ -0,0 +1,96 @@
====================================================================
智能客服节点日志查看器 v1.0.0
====================================================================
目录说明:
────────────────────────────────────────────────────────────────
1. dist/ - 可执行文件目录
- 智能客服节点日志查看器.exe (直接双击运行)
2. 日志查看器.py - Python源码文件
3. config.json - 配置文件(程序自动生成)
4. 使用说明.md - 详细使用说明
5. requirements.txt - Python依赖列表
6. build.py - 打包脚本
快速开始指南:
────────────────────────────────────────────────────────────────
方式一:使用可执行文件(推荐)
1. 进入 dist/ 目录
2. 双击 "智能客服节点日志查看器.exe"
3. 首次使用需要配置API凭证(见下文)
方式二:使用Python源码
1. 安装依赖:pip install -r requirements.txt
2. 运行:python 日志查看器.py
首次使用配置:
────────────────────────────────────────────────────────────────
步骤1:获取API凭证
1. 登录 https://agent.udesk.cn
2. 按 F12 打开开发者工具
3. 切换到"网络"标签页
4. 刷新页面或点击任意功能
5. 找到带有 "authorization: Bearer" 的请求
6. 右键复制为 cURL (bash)
步骤2:配置程序
1. 打开程序,点击"配置"按钮
2. 粘贴 curl 命令到文本框
3. 点击"解析Curl"
4. 点击"应用"保存配置
主要功能:
────────────────────────────────────────────────────────────────
1. 日志获取:按日期范围获取对话历史
2. 人工审核:对关键节点输出进行审核
3. 大模型评审:AI自动对比测试用例
4. Excel导出:生成完整的审核报告
常见问题:
────────────────────────────────────────────────────────────────
Q: 提示401 Invalid token
A: Token已过期,重新获取curl并解析
Q: 程序无法启动?
A: 检查是否被杀毒软件拦截,尝试以管理员身份运行
Q: 如何转换使用说明为Word文档?
A: 可以使用在线工具(如 https://www.markdowntoword.com/
或用 Typora/VS Code 等编辑器打开后另存为
文件大小优化说明:
────────────────────────────────────────────────────────────────
为了最小化可执行文件大小,已排除以下非必要模块:
- numpy, pandas, matplotlib (数据分析库)
- PIL, cv2 (图像处理)
- unittest, pytest (测试框架)
- 其他未使用的标准库
技术支持:
────────────────────────────────────────────────────────────────
如有问题,请查看:
1. 程序同目录的日志文件(如有)
2. 使用说明.md 详细文档
3. 检查 config.json 配置是否正确
版本历史:
────────────────────────────────────────────────────────────────
v1.0.0 (2026-05-21)
- 初始版本发布
- 支持日志查看与审核
- 支持大模型自动评审
- 支持Excel导出
====================================================================
+5
View File
@@ -0,0 +1,5 @@
{
"5a6c380c-fb06-440f-8042-9c3a03c5c164_4e52f67e-ed4e-44db-b8f6-fd63ed9a4e32": {
"大模型二次生成": "正确"
}
}
+53
View File
@@ -0,0 +1,53 @@
import os
import subprocess
import sys
def check_pyinstaller():
try:
import PyInstaller
print("PyInstaller 已安装")
return True
except ImportError:
print("正在安装 PyInstaller...")
subprocess.check_call([sys.executable, "-m", "pip", "install", "pyinstaller"])
return True
def build_exe():
print("开始打包...")
cmd = [
sys.executable, "-m", "PyInstaller",
"--onefile",
"--windowed",
"--name=智能客服节点日志查看器",
"--strip",
"--noupx",
"--exclude-module=unittest",
"--exclude-module=pytest",
"--exclude-module=numpy",
"--exclude-module=pandas",
"--exclude-module=matplotlib",
"--exclude-module=PIL",
"--exclude-module=cv2",
"--exclude-module=scipy",
"--exclude-module=sklearn",
"--exclude-module=email",
"--exclude-module=http.server",
"--exclude-module=xml",
"--exclude-module=multiprocessing",
"--exclude-module=concurrent",
"--exclude-module=asyncio",
"--hidden-import=openpyxl.cell._writer",
"日志查看器.py"
]
print("执行命令:", " ".join(cmd))
subprocess.check_call(cmd)
print("\n打包完成!")
print("可执行文件位于: dist/智能客服节点日志查看器.exe")
if __name__ == "__main__":
os.chdir(os.path.dirname(os.path.abspath(__file__)))
if check_pyinstaller():
build_exe()
+34
View File
@@ -0,0 +1,34 @@
@echo off
echo ================================================
echo 智能客服节点日志查看器 - 打包脚本
echo ================================================
echo.
echo 正在检查Python环境...
python -c "import tkinter; print('tkinter available')"
if %errorlevel% neq 0 (
echo ERROR: tkinter 不可用,请检查Python环境
pause
exit /b 1
)
echo.
echo 开始打包...
python -m PyInstaller ^
--onefile ^
--windowed ^
--name="智能客服节点日志查看器" ^
--hidden-import=tkinter ^
--hidden-import=tkinter.ttk ^
--hidden-import=tkinter.messagebox ^
--hidden-import=tkinter.filedialog ^
--hidden-import=_tkinter ^
--hidden-import=openpyxl.cell._writer ^
--collect-all=tkinter ^
--collect-all=tkinter.ttk ^
日志查看器.py
echo.
echo 打包完成!
echo 输出文件: dist\智能客服节点日志查看器.exe
pause
+46
View File
@@ -0,0 +1,46 @@
{
"app_id": "eae84278-36c0-437d-addf-5547b0ae3bc2",
"api_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiNWQzNjMzZjMtNTIzZS00NTRjLTgyYzgtMTczMjc2YjVjMjg0IiwiZXhwIjoxNzc5NDUzMzMyLCJpc3MiOiJTRUxGX0hPU1RFRCIsInN1YiI6IkNvbnNvbGUgQVBJIFBhc3Nwb3J0In0.TqJeWS53JP4ThYBTGLF-aYgz9PUUsoRPswEBgX9p0Tc",
"cookies": {
"ut_user_id": "null",
"ut_global_id": "%22ca426419-74d7-4dc8-bc5c-c866b096b297%22",
"_gcl_au": "1.1.505382516.1778740165",
"_ga": "GA1.1.968517445.1778741596",
"sensorsdata2015jssdkcross": "%7B%22distinct_id%22%3A%2219e2542d1f1efd-0b7accb742cea4-4c657b58-2073600-19e2542d1f21d61%22%2C%22%24device_id%22%3A%2219e2542d1f1efd-0b7accb742cea4-4c657b58-2073600-19e2542d1f21d61%22%2C%22props%22%3A%7B%7D%7D",
"Qs_lvt_102458": "1778741596%2C1779092047",
"Hm_lvt_85cdbdd6ba7f014cd503e9f1cd5e5ba0": "1778741596,1779092047",
"Qs_pv_102458": "4477521906714103000%2C2213764348932670000%2C1655296003018746400%2C2955048170989231600%2C930806901093578800",
"_ga_WPQK651LHJ": "GS2.1.s1779095976$o3$g0$t1779095976$j60$l0$h0'"
},
"key_node_titles": [
"大模型",
"单次反思",
"大模型二次生成"
],
"window": {
"width": 1400,
"height": 800,
"min_width": 1200,
"min_height": 600
},
"default_date_range": {
"days_before": 1
},
"columns": {
"index": 50,
"chat_id": 120,
"title": 100,
"time": 120,
"query": 150,
"node_output": 150,
"node_audit": 100,
"answer": 150
},
"excel_export": {
"default_dir": "desktop"
},
"last_date_range": {
"start_date": "2026-05-21",
"end_date": "2026-05-22"
}
}
+3
View File
@@ -0,0 +1,3 @@
requests
openpyxl
anthropic
+42
View File
@@ -0,0 +1,42 @@
import anthropic
import time
api_key = "sk-cp-ayedGY_WYs9N0n2hYlAhbYYAYodr7ym7a1y8DgdyCcgx439ONVJzIgZmaR7JmB5bh4iA5ZiLlFy6dOLpHSLtmG8G5WH4EKLDLZXM9gbwAupxZUuqIAUnUEk"
try:
print("正在连接 MiniMax API...")
client = anthropic.Anthropic(
api_key=api_key,
base_url="https://api.minimaxi.com/anthropic",
timeout=30.0
)
print("正在发送测试请求...")
start = time.time()
message = client.messages.create(
model="MiniMax-M2.7",
max_tokens=100,
system="You are a helpful assistant.",
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": "Hello, how are you?"
}
]
}
]
)
elapsed = time.time() - start
print(f"API调用成功!耗时: {elapsed:.2f}")
for block in message.content:
if block.type == "text":
print(f"响应: {block.text}")
except Exception as e:
print(f"API调用失败: {type(e).__name__}: {str(e)}")
+13
View File
@@ -0,0 +1,13 @@
import pandas as pd
import traceback
try:
print("正在读取Excel文件...")
df = pd.read_excel(r"C:\Users\hp_z66\Desktop\自动化测试.xlsx")
print(f"成功读取,共 {len(df)} 行数据")
print("列名:", df.columns.tolist())
print("\n前3行数据:")
print(df.head(3))
except Exception as e:
print(f"读取失败: {type(e).__name__}: {str(e)}")
traceback.print_exc()
+13
View File
@@ -0,0 +1,13 @@
import tkinter as tk
from tkinter import messagebox
if __name__ == "__main__":
root = tk.Tk()
root.title("测试Tkinter")
label = tk.Label(root, text="Tkinter正常运行!")
label.pack(pady=20)
btn = tk.Button(root, text="点击测试", command=lambda: messagebox.showinfo("测试", "功能正常!"))
btn.pack(pady=10)
root.after(3000, root.destroy) # 3秒后自动关闭
root.mainloop()
print("测试通过,运行成功!")
+16
View File
@@ -0,0 +1,16 @@
import sys
try:
import tkinter as tk
print("SUCCESS: tkinter imported")
root = tk.Tk()
root.title("Test")
label = tk.Label(root, text="Tkinter works!")
label.pack()
root.after(2000, root.destroy)
root.mainloop()
print("SUCCESS: Tkinter window shown")
except ImportError as e:
print(f"ERROR: {e}")
sys.exit(1)
+168
View File
@@ -0,0 +1,168 @@
# 智能客服节点日志查看器 - 使用说明
## 使用指南
### 1. 首次配置
#### 步骤1:获取API凭证
1. 登录Udesk控制台:https://agent.udesk.cn
2. 打开浏览器开发者工具(F12),切换到"网络"标签页
3. 执行一个API请求(如刷新页面)
4. 找到带有`authorization: Bearer xxx`的请求
5. 需要先切换到网络试图,然后找到刚刚的请求命令
![](C:\Users\hp_z66\AppData\Roaming\marktext\images\2026-05-21-13-38-37-image.png)
1. 复制完整的curl命令
![](C:\Users\hp_z66\AppData\Roaming\marktext\images\2026-05-21-13-37-10-image.png)
#### 步骤2:配置程序
1. 点击"配置"按钮
2. 在"Curl命令"文本框粘贴curl命令
3. 点击"解析Curl"按钮
4. 系统自动填充App ID、API Token和Cookies
5. (可选)在"关键节点"标签页编辑需要监控的节点
6. 点击"应用"保存配置
7. ![](C:\Users\hp_z66\AppData\Roaming\marktext\images\2026-05-21-13-39-12-image.png)
### 2. 获取数据
1. 选择开始日期和结束日期
2. 点击"获取数据"按钮
3. 等待数据加载完成
4. 在"审核记录"标签页查看关键对话
### 3. 人工审核(直接excel表格操作即可)
1. 在"审核记录"表格中找到需要审核的记录
2. 双击"大模型审核"或"二次生成审核"单元格
3. 选择审核结果(正确/错误/需改进)
4. 审核结果自动保存到audit_cache.json
### 4. 大模型自动评审
#### 准备测试用例
1. 创建Excel文件,包含以下列:
- 提问问题:用户问题内容
- 答案:标准答案
2. 保存为.xlsx格式
#### 执行评审
1. 点击"大模型评审"按钮
2. 选择测试用例Excel文件
3. 勾选需要评审的节点(可多选)
4. 点击"开始评审"
5. 等待评审完成
### 5. 导出Excel
1. 点击"导出Excel"按钮
2. 选择保存位置
3. 导出文件包含三个Sheet
- 关键节点审核:人工审核结果
- 完整流程日志:所有节点执行记录
- 大模型评审结果:AI评审报告(如有)
### 6.切换app,默认6.4
6.4 a46947ea-c1bf-4884-b6ba-9d7be32e18c4
6.3.1 29d32bb5-994d-452a-8828-9d94b9eaea9d
## 五、配置说明
### 配置文件结构
config.json包含以下配置项:
```json
{
"app_id": "应用ID",
"api_token": "API访问令牌",
"cookies": {
"cookie名": "cookie值"
},
"key_node_titles": ["节点1", "节点2"],
"window": {
"width": 1400,
"height": 800
},
"default_date_range": {
"days_before": 1
},
"columns": {
"index": 50,
"chat_id": 120
}
}
```
### 关键配置项
| 配置项 | 说明 | 默认值 |
| ------------------------------ | ---------- | ------------------------ |
| app_id | Udesk应用ID | 必填 |
| api_token | Bearer令牌 | 必填 |
| key_node_titles | 需要监控的关键节点 | ["大模型","单次反思","大模型二次生成"] |
| default_date_range.days_before | 默认查询几天前的数据 | 1 |
## 六、常见问题
### Q1: 提示401 - Invalid token
**解决方案**
- 重新获取最新的curl命令
- 在配置对话框重新解析并保存
- 确保token在有效期内
### Q2: 程序无法启动
**解决方案**
- 检查是否有杀毒软件拦截
- 右键选择"以管理员身份运行"
- 使用源码运行方式排查问题
### Q3: Excel导出失败
**解决方案**
- 检查保存位置是否有权限
- 确保目标文件未被其他程序占用
- 尝试保存到其他目录
### Q4: 大模型评审无结果
**解决方案**
- 确认测试用例格式正确
- 检查用户问题匹配度
- 查看状态栏提示信息
## 七、技术支持
如有问题,请检查:
1. 程序同目录的log文件(如有)
2. config.json配置是否正确
3. 网络连接是否正常
## 八、版本历史
### v1.0.0
- 初始版本发布
- 支持日志查看、人工审核
- 支持大模型自动评审
- 支持Excel导出
- 支持配置管理---**注意事项**:
1. 请妥善保管API Token,不要泄露
2. 建议定期备份audit_cache.json
3. API Token有有效期,过期后需要重新获取
4. 首次使用建议先用少量数据测试
+352
View File
@@ -0,0 +1,352 @@
import requests
import json
from datetime import datetime, timedelta
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side, numbers
from openpyxl.utils import get_column_letter
def get_time_range(days_ago=1):
end_time = datetime.now()
start_time = end_time - timedelta(days=days_ago)
return {
'start_time': start_time.strftime('%Y-%m-%d 00:00'),
'end_time': end_time.strftime('%Y-%m-%d 23:59')
}
cookies = {
'ut_user_id': 'null',
'ut_global_id': '%22ca426419-74d7-4dc8-bc5c-c866b096b297%22',
'_gcl_au': '1.1.505382516.1778740165',
'_ga': 'GA1.1.968517445.1778741596',
'sensorsdata2015jssdkcross': '%7B%22distinct_id%22%3A%2219e2542d1f1efd-0b7accb742cea4-4c657b58-2073600-19e2542d1f21d61%22%2C%22%24device_id%22%3A%2219e2542d1f1efd-0b7accb742cea4-4c657b58-2073600-19e2542d1f21d61%22%2C%22props%22%3A%7B%7D%7D',
'Qs_lvt_102458': '1778741596%2C1779092047',
'Hm_lvt_85cdbdd6ba7f014cd503e9f1cd5e5ba0': '1778741596,1779092047',
'Qs_pv_102458': '4477521906714103000%2C2213764348932670000%2C1655296003018746400%2C2955048170989231600%2C930806901093578800',
'_ga_WPQK651LHJ': 'GS2.1.s1779095976$o3$g0$t1779095976$j60$l0$h0',
}
headers = {
'Accept': '*/*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Connection': 'keep-alive',
'Referer': 'https://agent.udesk.cn/app/a46947ea-c1bf-4884-b6ba-9d7be32e18c4/logs',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0',
'authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiNWQzNjMzZjMtNTIzZS00NTRjLTgyYzgtMTczMjc2YjVjMjg0IiwiZXhwIjoxNzc5MjYwNjU4LCJpc3MiOiJTRUxGX0hPU1RFRCIsInN1YiI6IkNvbnNvbGUgQVBJIFBhc3Nwb3J0In0.RseHavVdtiKHMKB2Mm8WVm4KwhiUZKmG93TiMIKuwtQ',
'content-type': 'application/json',
'sec-ch-ua': '"Chromium";v="148", "Microsoft Edge";v="148", "Not/A)Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
app_id = 'a46947ea-c1bf-4884-b6ba-9d7be32e18c4'
def fetch_nodes(wf_run_id):
if not wf_run_id:
return []
try:
response = requests.get(
f'https://agent.udesk.cn/api/backend/new/apps/{app_id}/wfRuns/{wf_run_id}/nodeExecutions',
cookies=cookies,
headers=headers,
)
data = response.json()
return data.get('data', {}).get('list', [])
except Exception as e:
print(f'获取节点执行记录失败 wf_run_id={wf_run_id}: {e}')
return []
def safe_json_dumps(obj):
if obj is None:
return ''
try:
return json.dumps(obj, ensure_ascii=False)
except Exception:
return str(obj)
def safe_str(val):
if val is None:
return ''
return str(val)
def ts_to_datetime(ts):
try:
return datetime.fromtimestamp(int(ts)).strftime('%Y-%m-%d %H:%M:%S')
except Exception:
return ''
KEY_NODE_TITLES = ['大模型', '单次反思', '大模型二次生成']
time_range = get_time_range(days_ago=1)
params = {
'page_number': '1',
'page_size': '10',
'start_time': time_range['start_time'],
'end_time': time_range['end_time'],
'order_by': '-created_at',
'status': 'all',
}
response = requests.get(
f'https://agent.udesk.cn/api/backend/apps/{app_id}/log/chat/pages',
params=params,
cookies=cookies,
headers=headers,
)
data = response.json()
total = data.get('data', {}).get('total', 0)
total_page = max(total // 10 + (1 if total % 10 else 0), 1)
print(f'{total} 条对话记录,{total_page} 页,开始拉取...')
chart_list = []
for page in range(1, total_page + 1):
params['page_number'] = str(page)
response = requests.get(
f'https://agent.udesk.cn/api/backend/apps/{app_id}/log/chat/pages',
params=params,
cookies=cookies,
headers=headers,
)
page_data = response.json().get('data', {}).get('list', [])
chart_list.extend(page_data)
print(f'{page}/{total_page}页,获取 {len(page_data)}')
print(f'\n对话列表获取完成,共 {len(chart_list)}')
records = []
def fetch_all_messages(chat_log_id):
all_messages = []
fid = None
page_size = 10
while True:
try:
c_params = {'id': chat_log_id, 'page_size': str(page_size)}
if fid:
c_params['fid'] = fid
c_resp = requests.get(
f'https://agent.udesk.cn/api/backend/apps/{app_id}/chatMessage',
params=c_params,
cookies=cookies,
headers=headers,
timeout=30
)
data = c_resp.json()
messages = data.get('data', {}).get('list', [])
if not messages:
break
all_messages.extend(messages)
if len(messages) < page_size:
break
fid = messages[0].get('id')
if not fid:
break
except Exception as e:
break
return all_messages
for idx, chart in enumerate(chart_list):
chat_log_id = chart.get('chat_log_id', '')
chat_title = chart.get('title') or chart.get('chat_log_name') or ''
created_time = str(chart.get('created_time', ''))
try:
messages = fetch_all_messages(chat_log_id)
print(f' [{idx+1}] 获取对话消息 {len(messages)}')
except Exception as e:
print(f' [{idx+1}] 获取对话消息失败 chat_log_id={chat_log_id}: {e}')
messages = []
for msg in messages:
wf_run_id = msg.get('workflow_run_id', '')
if not wf_run_id:
continue
nodes = fetch_nodes(wf_run_id)
user_query = ''
for n in nodes:
if n.get('node_type') == 'start' and n.get('outputs', {}).get('sys.query'):
user_query = n['outputs']['sys.query']
break
key_outputs = {}
final_answer = ''
for n in nodes:
title = n.get('title', '')
if title in KEY_NODE_TITLES:
key_outputs[title] = (n.get('outputs') or {}).get('text', '')
if n.get('node_type') == 'answer':
final_answer = (n.get('outputs') or {}).get('answer', '')
records.append({
'chat_log_id': chat_log_id,
'chat_title': chat_title,
'created_time': created_time,
'user_query': user_query,
'wf_run_id': wf_run_id,
'nodes': nodes,
'key_outputs': key_outputs,
'final_answer': final_answer,
})
print(f' [{idx+1}/{len(chart_list)}] {chat_log_id[:8]}... 节点数:{len(nodes)}')
print(f'\n数据拉取完成,共 {len(records)} 条有效记录,开始生成Excel...')
wb = Workbook()
ws1 = wb.active
ws1.title = '关键节点审核'
header_font = Font(name='微软雅黑', bold=True, size=11, color='FFFFFF')
header_fill = PatternFill(start_color='4472C4', end_color='4472C4', fill_type='solid')
key_fill = PatternFill(start_color='FFF2CC', end_color='FFF2CC', fill_type='solid')
data_font = Font(name='微软雅黑', size=10)
thin_border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin'),
)
wrap_alignment = Alignment(horizontal='left', vertical='top', wrap_text=True)
center_alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
sheet1_headers = [
'序号', '对话ID', '会话标题', '对话时间',
'用户问题',
'大模型_输出', '单次反思_输出', '大模型二次生成_输出',
'最终回答', '人工审核结果', '备注'
]
for col_idx, header in enumerate(sheet1_headers, 1):
cell = ws1.cell(row=1, column=col_idx, value=header)
cell.font = header_font
cell.fill = header_fill
cell.alignment = center_alignment
cell.border = thin_border
key_col_indices = [col_idx for col_idx, h in enumerate(sheet1_headers, 1) if h in [
'用户问题', '大模型_输出', '单次反思_输出', '大模型二次生成_输出', '最终回答'
]]
audit_col = sheet1_headers.index('人工审核结果') + 1
for row_idx, rec in enumerate(records):
r = row_idx + 2
values = [
row_idx + 1,
rec['chat_log_id'],
rec['chat_title'],
rec['created_time'],
rec['user_query'],
rec['key_outputs'].get('大模型', ''),
rec['key_outputs'].get('单次反思', ''),
rec['key_outputs'].get('大模型二次生成', ''),
rec['final_answer'],
'',
'',
]
for col_idx, val in enumerate(values, 1):
cell = ws1.cell(row=r, column=col_idx, value=val)
cell.font = data_font
cell.alignment = wrap_alignment
cell.border = thin_border
if col_idx in key_col_indices:
cell.fill = key_fill
ws1.column_dimensions['A'].width = 6
ws1.column_dimensions['B'].width = 38
ws1.column_dimensions['C'].width = 20
ws1.column_dimensions['D'].width = 20
ws1.column_dimensions['E'].width = 40
ws1.column_dimensions['F'].width = 50
ws1.column_dimensions['G'].width = 50
ws1.column_dimensions['H'].width = 50
ws1.column_dimensions['I'].width = 50
ws1.column_dimensions['J'].width = 15
ws1.column_dimensions['K'].width = 20
ws1.freeze_panes = 'E2'
ws1.auto_filter.ref = f'A1:K{len(records) + 1}'
ws2 = wb.create_sheet('全流程日志明细')
sheet2_headers = [
'序号', '对话ID', '用户问题', '工作流运行ID',
'节点序号', '节点类型', '节点标题', '节点状态',
'执行开始时间', '执行结束时间',
'节点输入(JSON)', '节点输出(JSON)', '错误信息'
]
for col_idx, header in enumerate(sheet2_headers, 1):
cell = ws2.cell(row=1, column=col_idx, value=header)
cell.font = header_font
cell.fill = PatternFill(start_color='548235', end_color='548235', fill_type='solid')
cell.alignment = center_alignment
cell.border = thin_border
log_row = 2
log_seq = 0
for rec in records:
for node in rec['nodes']:
log_seq += 1
created_at = node.get('created_at') or node.get('started_at', 0)
finished_at = node.get('finished_at', 0)
values = [
log_seq,
rec['chat_log_id'],
rec['user_query'],
rec['wf_run_id'],
node.get('index', ''),
node.get('node_type', ''),
node.get('title', ''),
node.get('status', ''),
ts_to_datetime(created_at),
ts_to_datetime(finished_at),
safe_json_dumps(node.get('inputs')),
safe_json_dumps(node.get('outputs')),
safe_str(node.get('error', '')),
]
for col_idx, val in enumerate(values, 1):
cell = ws2.cell(row=log_row, column=col_idx, value=val)
cell.font = data_font
cell.alignment = wrap_alignment
cell.border = thin_border
if node.get('status') == 'failed':
cell.fill = PatternFill(start_color='FFC7CE', end_color='FFC7CE', fill_type='solid')
log_row += 1
ws2.column_dimensions['A'].width = 6
ws2.column_dimensions['B'].width = 38
ws2.column_dimensions['C'].width = 40
ws2.column_dimensions['D'].width = 38
ws2.column_dimensions['E'].width = 8
ws2.column_dimensions['F'].width = 22
ws2.column_dimensions['G'].width = 20
ws2.column_dimensions['H'].width = 12
ws2.column_dimensions['I'].width = 20
ws2.column_dimensions['J'].width = 20
ws2.column_dimensions['K'].width = 60
ws2.column_dimensions['L'].width = 60
ws2.column_dimensions['M'].width = 30
ws2.freeze_panes = 'F2'
ws2.auto_filter.ref = f'A1:M{log_row - 1}'
output_file = f'智能客服节点审核_{datetime.now().strftime("%Y%m%d_%H%M%S")}.xlsx'
output_path = f'd:/Idea Project/F6+宜搭+其它(1)/张阳脚本/udesk/{output_file}'
wb.save(output_path)
print(f'\nExcel已生成: {output_path}')
print(f' Sheet1「关键节点审核」: {len(records)} 条对话')
print(f' Sheet2「全流程日志明细」: {log_seq} 条节点执行记录')
File diff suppressed because it is too large Load Diff
+238
View File
@@ -0,0 +1,238 @@
import os
import json
import pandas as pd
import anthropic
from datetime import datetime
import time
class AutoReview:
def __init__(self):
self.api_key = "sk-cp-ayedGY_WYs9N0n2hYlAhbYYAYodr7ym7a1y8DgdyCcgx439ONVJzIgZmaR7JmB5bh4iA5ZiLlFy6dOLpHSLtmG8G5WH4EKLDLZXM9gbwAupxZUuqIAUnUEk"
self.client = anthropic.Anthropic(
api_key=self.api_key,
base_url="https://api.minimaxi.com/anthropic",
timeout=30.0
)
def load_test_cases(self, excel_path):
df = pd.read_excel(excel_path)
self.test_cases = {}
for _, row in df.iterrows():
question = str(row['提问问题']).strip()
answer = str(row['答案']).strip()
if question:
self.test_cases[question] = answer
print(f"已加载 {len(self.test_cases)} 条测试用例")
return self.test_cases
def load_log_data(self, log_path):
if os.path.exists(log_path):
with open(log_path, 'r', encoding='utf-8') as f:
data = json.load(f)
print(f"已加载 {len(data)} 条日志记录")
return data
else:
print(f"日志文件不存在: {log_path}")
return []
def match_question(self, user_query, threshold=0.7):
from difflib import SequenceMatcher
best_match = None
best_score = 0
for std_question in self.test_cases.keys():
score = SequenceMatcher(None, user_query, std_question).ratio()
if score > best_score and score >= threshold:
best_score = score
best_match = std_question
return best_match, best_score
def evaluate_consistency(self, generated_answer, standard_answer):
system_prompt = """
你是一个专业的答案一致性评审助手请按照以下标准评判生成答案与标准答案的一致性
一致性评分标准
- 10完全一致内容逻辑步骤完全相同
- 8-9基本一致核心内容相同表述略有差异
- 6-7部分一致核心思路相同但有遗漏或错误步骤
- 4-5不太一致只有部分内容相关
- 0-3不一致内容无关或错误
请输出JSON格式包含
- score: 0-10的整数分数
- confidence: 0-100的整数置信度
- reason: 简短的评审理由不超过100字
"""
user_prompt = f"""
生成答案
{generated_answer}
标准答案
{standard_answer}
请根据上述标准进行评审输出JSON格式结果
"""
try:
print(f"正在调用大模型进行评审...")
start_time = time.time()
message = self.client.messages.create(
model="MiniMax-M2.7",
max_tokens=500,
system=system_prompt,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": user_prompt
}
]
}
]
)
elapsed = time.time() - start_time
print(f"大模型调用完成,耗时: {elapsed:.2f}")
response_text = ""
for block in message.content:
if block.type == "text":
response_text += block.text
response_text = response_text.strip()
if response_text.startswith("```json"):
response_text = response_text[7:]
if response_text.endswith("```"):
response_text = response_text[:-3]
response_text = response_text.strip()
try:
result = json.loads(response_text)
return result
except json.JSONDecodeError:
return {
"score": 0,
"confidence": 50,
"reason": f"解析失败: {response_text[:100]}"
}
except Exception as e:
return {
"score": 0,
"confidence": 0,
"reason": f"API调用失败: {str(e)}"
}
def run_review(self, log_data):
results = []
for idx, record in enumerate(log_data):
user_query = record.get('user_query', '').strip()
key_outputs = record.get('key_outputs', {})
matched_question, match_score = self.match_question(user_query)
if matched_question and match_score >= 0.7:
standard_answer = self.test_cases[matched_question]
for node_name, generated_answer in key_outputs.items():
if generated_answer.strip():
evaluation = self.evaluate_consistency(generated_answer, standard_answer)
results.append({
"序号": idx + 1,
"用户问题": user_query,
"匹配问题": matched_question,
"匹配度": f"{match_score:.2f}",
"节点名称": node_name,
"生成答案": generated_answer[:200] + "..." if len(generated_answer) > 200 else generated_answer,
"标准答案": standard_answer[:200] + "..." if len(standard_answer) > 200 else standard_answer,
"一致性评分": evaluation["score"],
"置信度": evaluation["confidence"],
"评审理由": evaluation["reason"]
})
print(f"已评审第 {idx+1} 条记录,节点: {node_name},评分: {evaluation['score']}")
else:
results.append({
"序号": idx + 1,
"用户问题": user_query,
"匹配问题": "未匹配",
"匹配度": f"{match_score:.2f}",
"节点名称": "-",
"生成答案": "-",
"标准答案": "-",
"一致性评分": "-",
"置信度": "-",
"评审理由": "未找到匹配的测试用例"
})
return results
def export_results(self, results, output_path=None):
if not results:
print("没有评审结果可导出")
return
df = pd.DataFrame(results)
if output_path is None:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = f"自动评审结果_{timestamp}.xlsx"
df.to_excel(output_path, index=False)
print(f"评审结果已导出到: {output_path}")
return output_path
if __name__ == "__main__":
reviewer = AutoReview()
test_cases_path = r"C:\Users\hp_z66\Desktop\自动化测试.xlsx"
log_cache_path = r"d:\Idea Project\F6+宜搭+其它(1)\张阳脚本\udesk\audit_cache.json"
print("正在加载测试用例...")
reviewer.load_test_cases(test_cases_path)
print("正在加载日志数据...")
log_data = [
{
'user_query': '如何创建一人多车?',
'key_outputs': {
'大模型': '会员营销-客户车辆-客户信息里面搜索对应车主的手机号码,点击操作列的修改按钮,车辆信息下方可添加车辆。',
'单次反思': '步骤清晰,确认操作路径正确',
'大模型二次生成': '在会员营销模块中找到客户车辆管理,搜索车主手机号后点击修改,在车辆信息区域添加新车辆即可。'
}
},
{
'user_query': '卡开重了,如何撤销?',
'key_outputs': {
'大模型': '开卡单未结算的可以删除,已结算的需要联系管理员处理。',
'单次反思': '回答不够详细,缺少具体路径',
'大模型二次生成': '会员营销-卡券积分-卡单据,找到对应的开卡单号,未结算可直接删除;已结算需联系财务处理。'
}
},
{
'user_query': '如何修改卡可用车辆?',
'key_outputs': {
'大模型': '会员营销卡券积分卡管理找到对应的卡信息点击修改可用车辆选择指定车辆即可。',
'单次反思': '缺少分隔符,步骤不够清晰',
'大模型二次生成': '会员营销→卡券积分→卡管理,找到对应的卡信息,点击修改,可用车辆中选择指定车辆,即可勾选具体车辆。'
}
}
]
print(f"已加载 {len(log_data)} 条测试日志数据")
print("开始自动评审...")
results = reviewer.run_review(log_data)
print("导出评审结果...")
output_path = reviewer.export_results(results)
print("\n评审完成!")
if results:
scores = [r["一致性评分"] for r in results if isinstance(r["一致性评分"], int)]
if scores:
avg_score = sum(scores) / len(scores)
print(f"平均一致性评分: {avg_score:.2f}")
+185
View File
@@ -0,0 +1,185 @@
import os
import json
import pandas as pd
import anthropic
from datetime import datetime
import time
class AutoReview:
def __init__(self):
self.api_key = "sk-cp-ayedGY_WYs9N0n2hYlAhbYYAYodr7ym7a1y8DgdyCcgx439ONVJzIgZmaR7JmB5bh4iA5ZiLlFy6dOLpHSLtmG8G5WH4EKLDLZXM9gbwAupxZUuqIAUnUEk"
self.client = anthropic.Anthropic(
api_key=self.api_key,
base_url="https://api.minimaxi.com/anthropic",
timeout=30.0
)
self.test_cases = {
"如何创建一人多车?": "会员营销-客户车辆-客户信息里面搜索对应车主的手机号码,点击操作列的修改按钮,车辆信息下方可添加车辆。",
"卡开重了,如何撤销?": "开卡单未结算的,会员营销-卡券积分-卡单据,找到对应的开卡单号,操作列做删除;开卡单已结算的,联系财务处理。"
}
def match_question(self, user_query, threshold=0.7):
from difflib import SequenceMatcher
best_match = None
best_score = 0
for std_question in self.test_cases.keys():
score = SequenceMatcher(None, user_query, std_question).ratio()
if score > best_score and score >= threshold:
best_score = score
best_match = std_question
return best_match, best_score
def evaluate_consistency(self, generated_answer, standard_answer):
system_prompt = """
你是一个专业的答案一致性评审助手请按照以下标准评判生成答案与标准答案的一致性
一致性评分标准
- 10完全一致内容逻辑步骤完全相同
- 8-9基本一致核心内容相同表述略有差异
- 6-7部分一致核心思路相同但有遗漏或错误步骤
- 4-5不太一致只有部分内容相关
- 0-3不一致内容无关或错误
请输出JSON格式包含
- score: 0-10的整数分数
- confidence: 0-100的整数置信度
- reason: 简短的评审理由不超过100字
"""
user_prompt = f"""
生成答案
{generated_answer}
标准答案
{standard_answer}
请根据上述标准进行评审输出JSON格式结果
"""
try:
message = self.client.messages.create(
model="MiniMax-M2.7",
max_tokens=500,
system=system_prompt,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": user_prompt
}
]
}
]
)
response_text = ""
for block in message.content:
if block.type == "text":
response_text += block.text
response_text = response_text.strip()
if response_text.startswith("```json"):
response_text = response_text[7:]
if response_text.endswith("```"):
response_text = response_text[:-3]
response_text = response_text.strip()
try:
result = json.loads(response_text)
return result
except json.JSONDecodeError:
return {
"score": 0,
"confidence": 50,
"reason": f"解析失败: {response_text[:50]}"
}
except Exception as e:
return {
"score": 0,
"confidence": 0,
"reason": f"API调用失败: {str(e)[:50]}"
}
def run_review(self, log_data):
results = []
for idx, record in enumerate(log_data):
user_query = record.get('user_query', '').strip()
key_outputs = record.get('key_outputs', {})
print(f"\n处理第 {idx+1} 条记录: {user_query}")
matched_question, match_score = self.match_question(user_query)
if matched_question and match_score >= 0.7:
print(f"匹配到测试用例: {matched_question} (匹配度: {match_score:.2f})")
standard_answer = self.test_cases[matched_question]
for node_name, generated_answer in key_outputs.items():
if generated_answer.strip() and node_name != '单次反思':
print(f" 评审节点: {node_name}")
evaluation = self.evaluate_consistency(generated_answer, standard_answer)
results.append({
"序号": idx + 1,
"用户问题": user_query,
"匹配问题": matched_question,
"匹配度": f"{match_score:.2f}",
"节点名称": node_name,
"生成答案": generated_answer[:100] + "..." if len(generated_answer) > 100 else generated_answer,
"标准答案": standard_answer[:100] + "..." if len(standard_answer) > 100 else standard_answer,
"一致性评分": evaluation["score"],
"置信度": evaluation["confidence"],
"评审理由": evaluation["reason"]
})
print(f" 评分: {evaluation['score']}, 置信度: {evaluation['confidence']}%, 理由: {evaluation['reason']}")
else:
print(f"未匹配到测试用例 (匹配度: {match_score:.2f})")
return results
def export_results(self, results):
if not results:
print("没有评审结果可导出")
return
df = pd.DataFrame(results)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = f"自动评审结果_{timestamp}.xlsx"
df.to_excel(output_path, index=False)
print(f"\n评审结果已导出到: {output_path}")
return output_path
if __name__ == "__main__":
print("=== 自动评审系统 ===")
reviewer = AutoReview()
print(f"已加载 {len(reviewer.test_cases)} 条测试用例")
log_data = [
{
'user_query': '如何创建一人多车?',
'key_outputs': {
'大模型': '会员营销-客户车辆-客户信息里面搜索对应车主的手机号码,点击操作列的修改按钮,车辆信息下方可添加车辆。',
'单次反思': '步骤清晰,确认操作路径正确',
'大模型二次生成': '在会员营销模块中找到客户车辆管理,搜索车主手机号后点击修改,在车辆信息区域添加新车辆即可。'
}
}
]
print(f"\n开始评审 {len(log_data)} 条记录...")
results = reviewer.run_review(log_data)
reviewer.export_results(results)
if results:
scores = [r["一致性评分"] for r in results if isinstance(r["一致性评分"], int)]
if scores:
avg_score = sum(scores) / len(scores)
print(f"\n平均一致性评分: {avg_score:.2f}")
print("\n评审完成!")
+37 -1
View File
@@ -1 +1,37 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
import os
import pandas as pd
df = pd.read_excel(r"D:\Idea Project\F6+宜搭+其它(1)\张阳脚本\udesk\自动评测.xlsx", sheet_name='Sheet1', header=None)
# 按位置指定列名(第1列→query, 第2列→reference_response, 第3列→session_id
# 根据源文件实际列数调整
col_count = df.shape[1]
if col_count == 2:
df.columns = ["query", "reference_response"]
elif col_count == 3:
df.columns = ["query", "reference_response", "session_id"]
else:
df.columns = ["query", "reference_response", "session_id"][:col_count]
# 按模板字段整理输出
template_cols = ["session_id", "query", "reference_response"]
for col in template_cols:
if col not in df.columns:
df[col] = pd.NA # 缺失的字段填空值
df = df[template_cols]
# 按字段类型转换
df["session_id"] = pd.to_numeric(df["session_id"], errors="coerce").astype("Int64")
df["query"] = df["query"].astype(str)
df["reference_response"] = df["reference_response"].astype(str)
# 随机抽取100条,若数据不足100条则全部抽取
n = min(100, len(df))
sampled = df.sample(n=n, random_state=42)
# 生成到当前目录
output_path = os.path.join(os.getcwd(), "随机抽取100条结果.xlsx")
sampled.to_excel(output_path, index=False)
print(f"已随机抽取 {n} 条数据,保存至: {output_path}")
@@ -21,7 +21,7 @@ class Main:
FORMID1 = "FORM-33666CB1XDU37AU57RKPK990C79S2YMOEEC8LS" # [表单]异常服务跟进待办2023
# 读取excel表格
df = pd.read_excel(r"C:\Users\hp_z66\OneDrive\Desktop\钉钉文件\2026-3回访导入.xlsx",
df = pd.read_excel(r"C:\Users\hp_z66\OneDrive\Desktop\钉钉文件\2026-4回访已(客服).xlsx",
sheet_name='刷数据(注意部分字段替换)', dtype='string', header=1) # 此处将表头设置为了第二行
df.fillna('', inplace=True)
@@ -28,7 +28,7 @@ class Main:
# 读取excel表格获取数据
ceshi_data = pd.read_excel(r"C:\Users\hp_z66\OneDrive\Desktop\钉钉文件\2026-3回访导入.xlsx",sheet_name='7天节点且联系上(自动同意)')
ceshi_data = pd.read_excel(r"C:\Users\hp_z66\OneDrive\Desktop\钉钉文件\2026-4回访已(客服).xlsx",sheet_name='7天节点且联系上(自动同意)')
print("已读取表格")
# 执行自动化脚本
+193
View File
@@ -0,0 +1,193 @@
import requests
from bs4 import BeautifulSoup
import pandas as pd
from tqdm import tqdm
import time
import os
url = "http://www.kuaixiuge.com/carinfo.aspx?clientWidth=1286"
output_path = r"C:\Users\hp_z66\Desktop\快修哥客户信息导出.xlsx"
temp_path = r"C:\Users\hp_z66\Desktop\快修哥客户信息导出_temp.xlsx"
html_file = r"C:\Users\hp_z66\Desktop\page1_ref.html" # 用户提供的参考HTML
session = requests.Session()
session.headers.update({
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9",
"Cache-Control": "max-age=0",
"Connection": "keep-alive",
"Content-Type": "application/x-www-form-urlencoded",
"Cookie": "ASP.NET_SessionId=ciw1fuls0atkqaj4gkxhzngy; Hm_lvt_ab3baaa579f771d051a6b0baad5a8cfe=1777102209; HMACCOUNT=0838F6FCCBE848D9; iswatchme=0; setaddat=0; hksdms=username2=admin&truename2=%e8%91%9b&id=9864&wxusername2=&zb=false&qx=111-11111111111-11111111111111-0-1111-11111111111111-111111111-1111111111-111111111111-1111-0-0-0-0-0-0-0-0-0-0&login=1&actname=%e7%ae%a1%e7%90%86%e5%91%98&act=%e7%ae%a1%e7%90%86%e5%91%98&username=admin&truename=%e8%91%9b&userid=11955&valid=True&wxusername=&uniqueKey=8472d715-0f01-4b27-aa1c-90471f6cafa3&timeunitprice=0.00&allowquickout=True&telqx=1&tel=&StoreName=%e6%9c%ac%e4%bf%a1%e6%b1%bd%e8%bd%a6%e6%8a%a4%e7%90%86&attestationTel=13952699256&StoreName2=%e6%9c%ac%e4%bf%a1%e6%b1%bd%e8%bd%a6%e6%8a%a4%e7%90%86&vipid=SAAS9864&zonecode=1004&zone=%e6%b1%9f%e8%8b%8f&CustomerID=176575&IsInitialized=1&ScrmModuleValidTime=&isScrmModule=False&isBasicModule=True&isTechnologyModule=True&isPartsManageModule=True&isBusinessImprovementModule=False; SERVERID=000e421eb0ab0efb9790874bd5c8f758|1777105134|1777102207; Hm_lpvt_ab3baaa579f771d051a6b0baad5a8cfe=1777105137",
"Origin": "http://www.kuaixiuge.com",
"Referer": "http://www.kuaixiuge.com/carinfo.aspx?clientWidth=1286",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36",
})
# 搜索表单隐藏字段
form_hidden = {
"HiddenCountyear": "", "HiddenCount120": "", "HiddenCount240": "",
"HiddenTtime": "", "HiddenOpenId": "", "HiddenBdtime": "",
"HiddenVipId": "", "HiddenLasttime": "", "HiddenHfstate": "",
"HiddenLastgch": "", "HiddenJyId": "", "HiddenHkscode": "",
"HiddenBrand": "", "HiddenBrandname": "", "HiddenChassisnumber": "",
"HiddenEngineDesc": "", "HiddenEngineStyle": "", "HiddenFamilyname": "",
"HiddenGearbox": "", "HiddenGearboxName": "", "HiddenLyid": "",
"HiddenProductyear": "", "HiddenVehiclename": "", "HiddenVehiclesale": "",
"HiddenVin": "", "HiddenYearpattern": "", "HiddenDrivetype": "",
"HiddenModelbrandlogourl": "", "HiddenModelbrandmfr": "", "HiddenModelid": "",
"HiddenFueltype": "", "HiddenKilowattpower": "", "HiddenListedyear": "",
"HiddenListedmonth": "", "HiddenStopyear": "", "HiddenBodynumdoors": "",
"HiddenTransmissiondescription": "", "HiddenMakename": "",
"HiddenModelbrandid": "", "HiddenMakeid": "", "HiddenIschoosevehicletype": "",
}
def post_page(vs, ev, eventtarget="", eventarg="", button=None):
"""发 POST 请求"""
data = {
"__EVENTTARGET": eventtarget,
"__EVENTARGUMENT": eventarg,
"__VIEWSTATE": vs,
"__VIEWSTATEGENERATOR": "B80C0CC7",
"__VIEWSTATEENCRYPTED": "",
"__EVENTVALIDATION": ev,
"TextTime1": "2015-01-01",
"TextTime2": "2026-04-25",
"TextCname": "",
"txtVin": "",
"Txtcartype": "",
"txtEngineno": "",
"AspNetPager1_input": "",
**form_hidden,
}
if button:
data[button] = "搜索"
for attempt in range(3):
try:
resp = session.post(url, data=data, timeout=30)
resp.raise_for_status()
return resp.text
except Exception as e:
if attempt == 2:
raise
print(f" 请求失败({e}),第{attempt+1}次重试...")
time.sleep(3)
def parse_html(html):
"""解析 HTML"""
soup = BeautifulSoup(html, "html.parser")
table = soup.find("table", class_="table-theme1")
if not table:
return None, None, None, None, soup
headers = [th.text.strip() for th in table.find_all("th")]
rows = []
for tr in table.find_all("tr"):
tds = tr.find_all("td")
if tds:
rows.append([td.text.strip() for td in tds])
vs_el = soup.find("input", id="__VIEWSTATE")
ev_el = soup.find("input", id="__EVENTVALIDATION")
vs = vs_el.get("value", "") if vs_el else ""
ev = ev_el.get("value", "") if ev_el else ""
return headers, rows, vs, ev, soup
def main():
print("=" * 50)
print("快修哥客户信息导出")
print("=" * 50)
# 从用户提供的 HTML 中提取初始状态
print("\n[1/3] 读取参考页面...")
with open(html_file, "r", encoding="utf-8") as f:
ref_html = f.read()
ref_soup = BeautifulSoup(ref_html, "html.parser")
init_vs = ref_soup.find("input", id="__VIEWSTATE").get("value", "")
init_ev = ref_soup.find("input", id="__EVENTVALIDATION").get("value", "")
print(f" VIEWSTATE 长度: {len(init_vs)}")
print(f" EVENTVALIDATION 长度: {len(init_ev)}")
# 先请求第1页(用当前 VIEWSTATE 跳到第1页)
print("\n[2/3] 请求第1页...")
try:
html = post_page(init_vs, init_ev, eventtarget="AspNetPager1", eventarg="1")
headers, rows, vs, ev, soup = parse_html(html)
if not headers:
print(" 未找到数据表格!")
# 可能是VIEWSTATE不匹配,尝试直接搜索
print(" 尝试直接搜索...")
html = post_page(init_vs, init_ev, button="Button3")
headers, rows, vs, ev, soup = parse_html(html)
if not headers:
print(" 仍然无法获取数据,Cookie 可能已过期")
return
print(f" 第1页成功: {len(headers)}列 x {len(rows)}")
print(f" 表头: {headers}")
# 检查总记录数
label = soup.find("span", id="Label1")
if label:
print(f" 总记录: {label.text.strip()}")
except Exception as e:
print(f" 请求失败: {e}")
return
all_data = list(rows)
# 检查总页数
total_pages = 134 # 从HTML中看到的尾页
print(f"\n[3/3] 开始翻页 (共{total_pages}页)...")
consecutive_empty = 0
for page_num in tqdm(range(2, total_pages + 1), desc="翻页进度"):
try:
html = post_page(vs, ev, eventtarget="AspNetPager1", eventarg=str(page_num))
_, page_rows, vs, ev, soup = parse_html(html)
if not page_rows:
consecutive_empty += 1
if consecutive_empty >= 3:
print(f"\n 连续3页无数据,停止于第{page_num}")
break
continue
else:
consecutive_empty = 0
all_data.extend(page_rows)
# 每50页保存临时文件
if page_num % 50 == 0:
df_temp = pd.DataFrame(all_data, columns=headers)
df_temp.to_excel(temp_path, index=False)
print(f"\n 临时保存: {len(all_data)}")
time.sleep(0.3)
except Exception as e:
print(f"\n{page_num}页失败: {e},停止")
break
# 清理临时文件
if os.path.exists(temp_path):
os.remove(temp_path)
# 输出最终结果
print(f"\n总共获取 {len(all_data)} 行数据")
df = pd.DataFrame(all_data, columns=headers)
df.to_excel(output_path, index=False)
print(f"已保存到: {output_path}")
if __name__ == "__main__":
main()
+439
View File
@@ -0,0 +1,439 @@
# -*- coding: utf-8 -*-
"""
快富通系统数据导出脚本
功能登录快富通 -> 导出客户信息(个人+单位) + 会员卡项目明细 -> 输出Excel
用法python 快富通数据导出.py [账号] [密码] [--limit 50]
"""
import requests
import sys
import io
import time
import re
from datetime import datetime
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
# 修复Windows终端编码 + 实时flush
if sys.platform == "win32":
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
def p(msg=""):
print(msg, flush=True)
try:
import openpyxl
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
except ImportError:
import subprocess
subprocess.check_call(["pip", "install", "openpyxl"], stdout=subprocess.DEVNULL)
import openpyxl
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
# ============ 配置 ============
BASE_URL = "https://fos-api.lunz.cn/api"
APP_KEY = "8e240000-3e12-0016-1e0f-08d58267a484"
LOGIN_URL = f"{BASE_URL}/Membership/Login"
MEMBER_LIST_URL = f"{BASE_URL}/member/GetMemberinfoList"
MEMBER_PRODUCT_URL = f"{BASE_URL}/memberProduc/GetMemProductByMemId"
PAGE_SIZE = 200
MAX_WORKERS = 1 # 单线程,取消并发
OUTPUT_DIR = Path.home() / "Desktop"
def progress_bar(current, total, prefix="", width=30):
pct = current / total if total > 0 else 0
filled = int(width * pct)
bar = "\u2588" * filled + "\u2591" * (width - filled)
return f"{prefix} |{bar}| {current}/{total} ({pct:.0%})"
def login(username: str, password: str) -> dict:
headers = {
"accept": "application/json, text/plain, */*",
"appkey": APP_KEY,
"content-type": "application/json",
"origin": "https://fos.lunz.cn",
}
payload = {"username": username, "password": password}
p(f"[login] ...")
resp = requests.post(LOGIN_URL, headers=headers, json=payload, timeout=30)
data = resp.json()
if not data.get("Success"):
raise Exception(f"login failed: {data.get('Messages', data)}")
login_data = data["Data"]
token = login_data["tokenId"]
store_name = login_data.get("qxentity", {}).get("businessproductunitName", "?")
p(f"[login] OK store={store_name} token={token[:8]}...")
return login_data
def build_headers(login_data: dict) -> dict:
qx = login_data["qxentity"]
return {
"accept": "application/json, text/plain, */*",
"appkey": APP_KEY,
"audl_user": qx["memberuserid"],
"authtoken": login_data["tokenId"],
"content-type": "application/json",
"openchainsign": qx["openChainSign"],
"origin": "https://fos.lunz.cn",
"signstring": qx["signstring"],
"ucuserid": qx["userid"],
}
def fetch_all_members(headers: dict, qxentity: dict, mem_type: int, limit: int = 0) -> list:
type_name = "个人客户" if mem_type == 1 else "单位客户"
all_data = []
page_index = 1
while True:
payload = {
"paging": {
"pageSize": PAGE_SIZE,
"pageIndex": page_index,
"sort": [],
"filters": [
{"field": "IsDisplay", "op": "eq", "Term": "1"},
{"field": "Enabled", "op": "eq", "Term": "1"},
],
},
"qxentity": qxentity,
"searchValue": "",
"memType": mem_type,
}
p(f" [{type_name}] page {page_index} ...")
resp = requests.post(MEMBER_LIST_URL, headers=headers, json=payload, timeout=30)
result = resp.json()
if not result.get("Success"):
p(f" [{type_name}] ERROR: {result.get('Messages', result)}")
break
items = result.get("Data", [])
all_data.extend(items)
p(f" [{type_name}] page {page_index} +{len(items)} total {len(all_data)}")
# limit check
if limit > 0 and len(all_data) >= limit:
all_data = all_data[:limit]
break
if len(items) < PAGE_SIZE:
break
page_index += 1
p(f"[{type_name}] DONE {len(all_data)}")
return all_data
def _fetch_one_product(args):
"""获取一个客户的会员卡列表"""
headers, mer_store_id, member = args
member_id = member.get("MemberId") or member.get("Id")
params = {"merStoreId": mer_store_id, "memberId": member_id}
try:
resp = requests.get(MEMBER_PRODUCT_URL, params=params, headers=headers, timeout=15)
result = resp.json()
if result.get("Success"):
prods = result.get("Data", [])
for prod in prods:
prod["_MemName"] = member.get("MemName", "")
prod["_MemMobile"] = member.get("MemMobile", "")
prod["_MemberId"] = member_id
return prods
except Exception:
pass
return []
def expand_card_items(products: list) -> list:
"""
将会员卡的一对多项目展开为明细行
每行: 卡信息 + 单个项目(项目名, 总次数, 剩余次数)
"""
card_items = []
for card in products:
item_names = card.get("BuyProductItemName", "")
item_amounts = card.get("BuyProductItemAmount", "")
item_ex_amounts = card.get("BuyProductItemExAmount", "")
if not item_names:
# 没有项目明细的卡,保留为一行(项目列为空)
row = {
"客户姓名": card.get("_MemName", ""),
"手机号": card.get("_MemMobile", ""),
"客户ID": card.get("_MemberId", ""),
"会员卡ID": card.get("Id", ""),
"套餐名称": card.get("ProductName", ""),
"项目名称": "",
"总次数": "",
"剩余次数": "",
"实付金额": card.get("ActualPrice", ""),
"套餐价格": card.get("ProductPrice", ""),
"支付方式": card.get("PayTypeName", ""),
"购买渠道": card.get("BuyChannelName", ""),
"是否有余量": "" if card.get("IsSurplus") else "",
"是否撤销": "" if card.get("IsRevoke") else "",
"到期时间": card.get("ExpiryTime", ""),
"购买时间": card.get("CreatedAt", ""),
"操作员工": card.get("BonusEmployName", ""),
"绑定车牌": card.get("MemCarList", ""),
"备注": card.get("Remark", ""),
}
card_items.append(row)
continue
names = item_names.split(";") if item_names else []
amounts = item_amounts.split(";") if item_amounts else []
ex_amounts = item_ex_amounts.split(";") if item_ex_amounts else []
for i, name in enumerate(names):
name = name.strip()
if not name:
continue
total = amounts[i].strip() if i < len(amounts) else ""
remain = ex_amounts[i].strip() if i < len(ex_amounts) else ""
row = {
"客户姓名": card.get("_MemName", ""),
"手机号": card.get("_MemMobile", ""),
"客户ID": card.get("_MemberId", ""),
"会员卡ID": card.get("Id", ""),
"套餐名称": card.get("ProductName", ""),
"项目名称": name,
"总次数": total,
"剩余次数": remain,
"实付金额": card.get("ActualPrice", "") if i == 0 else "", # 只在第一行显示卡级别信息
"套餐价格": card.get("ProductPrice", "") if i == 0 else "",
"支付方式": card.get("PayTypeName", "") if i == 0 else "",
"购买渠道": card.get("BuyChannelName", "") if i == 0 else "",
"是否有余量": "" if card.get("IsSurplus") else "" if i == 0 else "",
"是否撤销": "" if card.get("IsRevoke") else "" if i == 0 else "",
"到期时间": card.get("ExpiryTime", "") if i == 0 else "",
"购买时间": card.get("CreatedAt", "") if i == 0 else "",
"操作员工": card.get("BonusEmployName", "") if i == 0 else "",
"绑定车牌": card.get("MemCarList", "") if i == 0 else "",
"备注": card.get("Remark", "") if i == 0 else "",
}
card_items.append(row)
return card_items
def fetch_all_products(headers: dict, members: list, mer_store_id: str, limit: int = 0) -> list:
"""并发获取会员卡并展开为项目明细"""
if limit > 0:
members = members[:limit]
total = len(members)
if total == 0:
return []
p(f"[会员卡] fetching {total} members (workers={MAX_WORKERS}) ...")
all_products = []
task_args = [(headers, mer_store_id, m) for m in members]
done_count = 0
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
futures = {executor.submit(_fetch_one_product, arg): i for i, arg in enumerate(task_args)}
for future in as_completed(futures):
done_count += 1
prods = future.result()
all_products.extend(prods)
if done_count % 20 == 0 or done_count == total:
p(f" {progress_bar(done_count, total, prefix='cards')} got {len(all_products)} cards")
p(f"[会员卡] raw cards: {len(all_products)}")
# 展开为项目明细
card_items = expand_card_items(all_products)
p(f"[会员卡] expanded items: {len(card_items)}")
return card_items
# ============ 字段定义 ============
MEMBER_FIELDS = [
("MemberId", "客户ID"), ("MemName", "客户姓名"), ("MemMobile", "手机号"),
("MemSexType", "性别"), ("MemCardNo", "卡号"), ("MemBirthDay", "生日"),
("MemCardGradeName", "会员等级"), ("BalancePrice", "余额"),
("IntegralAmount", "积分"), ("TotalRechargePrice", "累计充值"),
("TotalConsumPrice", "累计消费"), ("TotalConsumTimes", "消费次数"),
("LastConsumTime", "最近消费时间"), ("ToStoreTimes", "到店次数"),
("MemCarsAmount", "车辆数"), ("AllCarPlateNumber", "车牌号"),
("AllCarModelName", "车型"), ("MemCreatedAt", "注册时间"),
("MemCompany", "公司"), ("MemAddress", "地址"),
("MerStoreName", "所属门店"), ("Remark", "备注"),
]
# 会员卡项目明细 - 字段顺序
CARD_ITEM_COLUMNS = [
"客户姓名", "手机号", "客户ID", "会员卡ID", "套餐名称",
"项目名称", "总次数", "剩余次数",
"实付金额", "套餐价格", "支付方式", "购买渠道",
"是否有余量", "是否撤销", "到期时间", "购买时间",
"操作员工", "绑定车牌", "备注",
]
def format_value(val):
if val is None:
return ""
if isinstance(val, bool):
return "" if val else ""
if isinstance(val, str) and "T" in val and len(val) > 18:
try:
dt = datetime.fromisoformat(val.replace("Z", "+00:00"))
return dt.strftime("%Y-%m-%d %H:%M:%S")
except Exception:
return val
return val
def write_excel(personal_members, company_members, card_items, output_path):
p("[Excel] writing ...")
wb = openpyxl.Workbook()
hdr_font = Font(bold=True, color="FFFFFF", size=11)
hdr_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
hdr_align = Alignment(horizontal="center", vertical="center", wrap_text=True)
cell_align = Alignment(vertical="center", wrap_text=True)
border = Border(left=Side("thin"), right=Side("thin"), top=Side("thin"), bottom=Side("thin"))
def write_member_sheet(ws, fields, data):
for c, (_, name) in enumerate(fields, 1):
cell = ws.cell(1, c, name)
cell.font = hdr_font; cell.fill = hdr_fill; cell.alignment = hdr_align; cell.border = border
for r, item in enumerate(data, 2):
for c, (key, _) in enumerate(fields, 1):
cell = ws.cell(r, c, format_value(item.get(key, "")))
cell.alignment = cell_align; cell.border = border
# auto width
for c in range(1, len(fields) + 1):
col_letter = openpyxl.utils.get_column_letter(c)
mx = 6
for row in ws.iter_rows(min_col=c, max_col=c):
for cell in row:
v = str(cell.value or "")
w = sum(2 if ord(ch) > 127 else 1 for ch in v)
if w > mx:
mx = w
ws.column_dimensions[col_letter].width = min(mx + 4, 50)
def write_card_item_sheet(ws, columns, data):
# header with color coding
item_fill = PatternFill(start_color="ED7D31", end_color="ED7D31", fill_type="solid")
for c, name in enumerate(columns, 1):
cell = ws.cell(1, c, name)
cell.font = hdr_font
# 项目名称/总次数/剩余次数 用橙色高亮
cell.fill = item_fill if name in ("项目名称", "总次数", "剩余次数") else hdr_fill
cell.alignment = hdr_align; cell.border = border
for r, item in enumerate(data, 2):
for c, key in enumerate(columns, 1):
cell = ws.cell(r, c, format_value(item.get(key, "")))
cell.alignment = cell_align; cell.border = border
# auto width
for c in range(1, len(columns) + 1):
col_letter = openpyxl.utils.get_column_letter(c)
mx = 6
for row in ws.iter_rows(min_col=c, max_col=c):
for cell in row:
v = str(cell.value or "")
w = sum(2 if ord(ch) > 127 else 1 for ch in v)
if w > mx:
mx = w
ws.column_dimensions[col_letter].width = min(mx + 4, 50)
ws1 = wb.active; ws1.title = "个人客户"
write_member_sheet(ws1, MEMBER_FIELDS, personal_members)
ws2 = wb.create_sheet("单位客户")
write_member_sheet(ws2, MEMBER_FIELDS, company_members)
ws3 = wb.create_sheet("会员卡项目明细")
write_card_item_sheet(ws3, CARD_ITEM_COLUMNS, card_items)
wb.save(str(output_path))
p(f"[Excel] OK -> {output_path}")
def parse_args():
"""解析命令行参数,支持 --limit N"""
args = sys.argv[1:]
username, password, limit = None, None, 0
i = 0
while i < len(args):
if args[i] == "--limit" and i + 1 < len(args):
limit = int(args[i + 1])
i += 2
elif username is None:
username = args[i]
i += 1
elif password is None:
password = args[i]
i += 1
else:
i += 1
return username, password, limit
def main():
p("=" * 50)
p(" 快富通系统数据导出工具")
p("=" * 50)
username, password, limit = parse_args()
if not username:
username = input("账号 [zhongdexinqcyp]: ").strip() or "zhongdexinqcyp"
if not password:
password = input("密码 [ZDX2018]: ").strip() or "ZDX2018"
limit_msg = f" (limit={limit})" if limit > 0 else ""
p(f"\nparams: user={username} limit={limit}{limit_msg}")
# 1. login
login_data = login(username, password)
qxentity = login_data["qxentity"]
headers = build_headers(login_data)
mer_store_id = qxentity["businessproductunitid"]
# 2. members
p("\n--- members ---")
t0 = time.time()
personal_members = fetch_all_members(headers, qxentity, mem_type=1, limit=limit)
company_members = fetch_all_members(headers, qxentity, mem_type=2, limit=limit)
p(f"[members] {time.time()-t0:.1f}s personal={len(personal_members)} company={len(company_members)}")
# 3. card items
p("\n--- card items ---")
t0 = time.time()
all_members = personal_members + company_members
card_items = fetch_all_products(headers, all_members, mer_store_id, limit=limit)
p(f"[card items] {time.time()-t0:.1f}s")
# 4. excel
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
store_name = qxentity.get("businessproductunitName", "未知门店")
suffix = f"_limit{limit}" if limit > 0 else ""
output_path = OUTPUT_DIR / f"快富通导出_{store_name}_{timestamp}{suffix}.xlsx"
write_excel(personal_members, company_members, card_items, output_path)
# 5. summary
p("\n" + "=" * 50)
p(f" DONE! store: {store_name}")
p(f" personal: {len(personal_members)} company: {len(company_members)} card_items: {len(card_items)}")
p(f" file: {output_path}")
p("=" * 50)
if __name__ == "__main__":
main()
@@ -0,0 +1,26 @@
import requests, time, urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
H = {
'Accept': 'application/json, text/plain, */*',
'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJpc3MiOiJodHRwczovL2F1dGgxLmhrczM2MC5jb20vY29yZSIsImF1ZCI6Imh0dHBzOi8vYXV0aDEuaGtzMzYwLmNvbS9jb3JlL3Jlc291cmNlcyIsImV4cCI6MTc4MDEyODg1MCwibmJmIjoxNzgwMTI1MjUwLCJjbGllbnRfaWQiOiJ6YS5jbGllbnQiLCJjbGllbnRfbG9jYXRpb24iOiJkYXRhY2VudGVyIiwic2NvcGUiOiJ3cml0ZSJ9.fiumtw9xCYWj-euP_LCQdrT9Wd4OsVopuHQpt3Qaae8En4lPA7aaqOfpXVF8gwxtoayfpjATtIaMomkUcnglYqZBCUTC50bc6IHYFgRrYl_7h4g9BCIHwGEswYbvFiQfAB5Q4gLFptzEJ1W2pjHnrNgmum5syQR3fsR5_25OayQ_KI6HWdtR3wReuInl0PQcDJs5jxdeId2ViDuYnl1x7TDFoIIwPov46H4KViUrBKFwr6iaTcNwrpl0thPBZjLJ8StTj50JwL1tRe71LbHkavD3MGsqs9_ulJaFZgyu2UYpl6cO0Let2zk9w-k2echh7P1ajQg7LfO2hEJ-c6RHXg',
'Content-Type': 'application/json;charset=utf-8',
'Origin': 'http://www.kuaixiuge.com',
'User-Agent': 'Mozilla/5.0',
}
U = 'http://saas.hks360.com:84/WXinfoservice/GetAllServices'
gch = '245742202000038'
print(f"=== {gch} ===")
requests.options(U, params={'cid':'24574','gch':gch}, headers=H, verify=False, timeout=10)
time.sleep(0.15)
r = requests.get(U, params={'cid':'24574','gch':gch}, headers=H, verify=False, timeout=15)
print(f"status={r.status_code}")
d = r.json()
rows = (d.get('rows') or []) if isinstance(d, dict) else []
print(f"rows={len(rows)}")
for idx, row in enumerate(rows):
svc = row.get('servicename', '?')
wxxm = [x.get('xmname','?') for x in (row.get('wxxmList') or [])]
parts = [p.get('pname','?') for p in (row.get('wxinfopartsList') or [])]
print(f" [{idx}] svc={svc} | xm={wxxm} | parts={parts}")
@@ -0,0 +1,56 @@
import requests, time, urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
H = {
'Accept': 'application/json, text/plain, */*',
'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJpc3MiOiJodHRwczovL2F1dGgxLmhrczM2MC5jb20vY29yZSIsImF1ZCI6Imh0dHBzOi8vYXV0aDEuaGtzMzYwLmNvbS9jb3JlL3Jlc291cmNlcyIsImV4cCI6MTc4MDEyODg1MCwibmJmIjoxNzgwMTI1MjUwLCJjbGllbnRfaWQiOiJ6YS5jbGllbnQiLCJjbGllbnRfbG9jYXRpb24iOiJkYXRhY2VudGVyIiwic2NvcGUiOiJ3cml0ZSJ9.fiumtw9xCYWj-euP_LCQdrT9Wd4OsVopuHQpt3Qaae8En4lPA7aaqOfpXVF8gwxtoayfpjATtIaMomkUcnglYqZBCUTC50bc6IHYFgRrYl_7h4g9BCIHwGEswYbvFiQfAB5Q4gLFptzEJ1W2pjHnrNgmum5syQR3fsR5_25OayQ_KI6HWdtR3wReuInl0PQcDJs5jxdeId2ViDuYnl1x7TDFoIIwPov46H4KViUrBKFwr6iaTcNwrpl0thPBZjLJ8StTj50JwL1tRe71LbHkavD3MGsqs9_ulJaFZgyu2UYpl6cO0Let2zk9w-k2echh7P1ajQg7LfO2hEJ-c6RHXg',
'Content-Type': 'application/json;charset=utf-8',
'Origin': 'http://www.kuaixiuge.com',
'User-Agent': 'Mozilla/5.0',
}
U = 'http://saas.hks360.com:84/WXinfoservice/GetAllServices'
SVC = ['serviceid','servicename','servicetype','servicenumber','serviceprice','servicehj','timefee','isinquiry','isinterimpurchase']
XM = ['xmdm','xmname','xmprice','hour']
PT = ['pcode','pname','price','count','amount','unit']
OUT = ['serviceid','servicename','servicetype','servicenumber','serviceprice','servicehj','timefee','isinquiry','isinterimpurchase',
'xm_xmdm','xm_xmname','xm_xmprice','xm_hour',
'part_pcode','part_pname','part_price','part_count','part_amount','part_unit']
s = requests.Session()
for gch in ['245742605000037','245742605000039','245742108000042']:
print(f"\n=== {gch} ===")
s.options(U, params={'cid':'24574','gch':gch}, headers=H, verify=False, timeout=10)
time.sleep(0.15)
r = s.get(U, params={'cid':'24574','gch':gch}, headers=H, verify=False, timeout=15)
data = r.json()
rows = (data.get('rows') or []) if isinstance(data, dict) else []
print(f" rows={len(rows)}")
result = []
for row in rows:
si = {k: row.get(k) for k in SVC}
wxxm = row.get('wxxmList') or []
parts = row.get('wxinfopartsList') or []
has = False
if wxxm:
has = True
for x in wxxm:
m = dict(si)
for k in XM: m['xm_'+k] = x.get(k)
result.append(m)
if parts:
has = True
for p in parts:
m = dict(si)
for k in PT: m['part_'+k] = p.get(k)
result.append(m)
if not has:
result.append(si)
print(f" parsed={len(result)} rows")
for idx, d in enumerate(result[:3]):
vals = [str(d.get(k,''))[:30] for k in OUT]
print(f" [{idx}] {vals}")
@@ -0,0 +1,12 @@
import time
exp = 17801288500
nbf = 1780012525000
now = int(time.time() * 1000)
print(f'Token exp: {exp} ms -> {time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime(exp/1000))}')
print(f'Token nbf: {nbf} ms -> {time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime(nbf/1000))}')
print(f'Current time: {now} ms -> {time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime(now/1000))}')
print()
print('--- For Asia/Hong_Kong (UTC+8) ---')
for label, ts in [('Token exp', exp), ('Token nbf', nbf), ('Current', now)]:
local = time.gmtime(ts/1000)
print(f'{label}: {time.strftime("%Y-%m-%d %H:%M:%S", local)} UTC -> {time.strftime("%Y-%m-%d %H:%M:%S", (local[0], local[1], local[2], local[3]+8, local[4], local[5], local[6], local[7], local[8]) if local[3]+8 <= 23 else time.gmtime(ts/1000 + 8*3600))} UTC+8')
@@ -0,0 +1,114 @@
"""验证客户信息翻页是否正常(检查不再重复)"""
import requests, urllib3
from bs4 import BeautifulSoup
urllib3.disable_warnings()
BASE = "http://139.129.162.9"
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Content-Type": "application/x-www-form-urlencoded",
})
r = session.post(f"{BASE}/login.aspx", data={
"ucode": "9864", "uname": "admin", "upwd": "@19790825%ZFq",
"windowSize": "1614", "DeviceVersion": "", "ipAdress": "", "Location": "",
}, timeout=20, verify=False)
page_url = f"{BASE}/carinfo.aspx?clientWidth=1614"
def get_val(soup, name):
el = soup.find("input", {"id": name}) or soup.find("input", {"name": name})
return el.get("value", "") if el else ""
hidden_fields = {
"HiddenCountyear": "", "HiddenCount120": "", "HiddenCount240": "",
"HiddenTtime": "", "HiddenOpenId": "", "HiddenBdtime": "",
"HiddenVipId": "", "HiddenLasttime": "", "HiddenHfstate": "",
"HiddenLastgch": "", "HiddenJyId": "", "HiddenHkscode": "",
"HiddenBrand": "", "HiddenBrandname": "", "HiddenChassisnumber": "",
"HiddenEngineDesc": "", "HiddenEngineStyle": "", "HiddenFamilyname": "",
"HiddenGearbox": "", "HiddenGearboxName": "", "HiddenLyid": "",
"HiddenProductyear": "", "HiddenVehiclename": "", "HiddenVehiclesale": "",
"HiddenVin": "", "HiddenYearpattern": "", "HiddenDrivetype": "",
"HiddenModelbrandlogourl": "", "HiddenModelbrandmfr": "", "HiddenModelid": "",
"HiddenFueltype": "", "HiddenKilowattpower": "", "HiddenListedyear": "",
"HiddenListedmonth": "", "HiddenStopyear": "", "HiddenBodynumdoors": "",
"HiddenTransmissiondescription": "", "HiddenMakename": "",
"HiddenModelbrandid": "", "HiddenMakeid": "", "HiddenIschoosevehicletype": "",
}
# GET初始页
r0 = session.get(page_url, timeout=20, verify=False)
soup0 = BeautifulSoup(r0.text, "html.parser")
vs = get_val(soup0, "__VIEWSTATE")
vs_gen = get_val(soup0, "__VIEWSTATEGENERATOR") or "B80C0CC7"
ev = get_val(soup0, "__EVENTVALIDATION")
# 搜索
data = {
"__EVENTTARGET": "",
"__EVENTARGUMENT": "",
"__VIEWSTATE": vs,
"__VIEWSTATEGENERATOR": vs_gen,
"__VIEWSTATEENCRYPTED": "",
"__EVENTVALIDATION": ev,
"TextTime1": "2015-01-01",
"TextTime2": "",
"TextCname": "",
"txtVin": "",
"Txtcartype": "",
"txtEngineno": "",
"Button3": "搜索",
"AspNetPager1_input": "1",
**hidden_fields,
}
r1 = session.post(page_url, data=data, headers={"Referer": page_url, "Origin": BASE}, timeout=30, verify=False)
soup = BeautifulSoup(r1.text, "html.parser")
# 抓前5页,检查每页第一条客户ID是否不同
print("=== 检查翻页是否正常 ===")
all_ids = []
for pg in range(1, 6):
if pg > 1:
cur_vs = get_val(soup, "__VIEWSTATE")
cur_ev = get_val(soup, "__EVENTVALIDATION")
cur_vs_gen = get_val(soup, "__VIEWSTATEGENERATOR") or vs_gen
pdata = {
"__EVENTTARGET": "AspNetPager1",
"__EVENTARGUMENT": str(pg),
"__VIEWSTATE": cur_vs,
"__VIEWSTATEGENERATOR": cur_vs_gen,
"__VIEWSTATEENCRYPTED": "",
"__EVENTVALIDATION": cur_ev,
"TextTime1": "2015-01-01",
"TextTime2": "",
"TextCname": "",
"txtVin": "",
"Txtcartype": "",
"txtEngineno": "",
"AspNetPager1_input": str(pg - 1),
**hidden_fields,
}
html = session.post(page_url, data=pdata, headers={"Referer": page_url, "Origin": BASE, "Content-Type": "application/x-www-form-urlencoded"}, timeout=30, verify=False).text
soup = BeautifulSoup(html, "html.parser")
table = soup.find("table", class_="table-theme1")
if not table:
print(f"Page {pg}: 无表格!")
continue
rows = [r for r in table.find_all("tr") if r.find("td")]
if rows:
first_cells = [c.text.strip() for c in rows[0].find_all("td")]
cid = first_cells[0] if first_cells else "?"
plate = first_cells[1] if len(first_cells) > 1 else "?"
owner = first_cells[2] if len(first_cells) > 2 else "?"
all_ids.append(cid)
print(f"Page {pg}: ID={cid}, 车牌={plate}, 车主={owner}")
# 检查是否有重复
if len(all_ids) == len(set(all_ids)):
print(f"\n[OK] 5页的客户ID均不重复,翻页正常!")
else:
dups = [x for x in all_ids if all_ids.count(x) > 1]
print(f"\n[FAIL] 发现重复ID: {dups}")
@@ -0,0 +1,114 @@
"""检查翻页过程中VIEWSTATE是否断链"""
import requests, urllib3
from bs4 import BeautifulSoup
urllib3.disable_warnings()
BASE = "http://139.129.162.9"
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Content-Type": "application/x-www-form-urlencoded",
})
r = session.post(f"{BASE}/login.aspx", data={
"ucode": "9864", "uname": "admin", "upwd": "@19790825%ZFq",
"windowSize": "1614", "DeviceVersion": "", "ipAdress": "", "Location": "",
}, timeout=20, verify=False)
page_url = f"{BASE}/carinfo.aspx?clientWidth=1614"
def get_val(soup, name):
el = soup.find("input", {"id": name}) or soup.find("input", {"name": name})
return el.get("value", "") if el else ""
hidden_fields = {k: "" for k in [
"HiddenCountyear", "HiddenCount120", "HiddenCount240",
"HiddenTtime", "HiddenOpenId", "HiddenBdtime", "HiddenVipId",
"HiddenLasttime", "HiddenHfstate", "HiddenLastgch", "HiddenJyId",
"HiddenHkscode", "HiddenBrand", "HiddenBrandname", "HiddenChassisnumber",
"HiddenEngineDesc", "HiddenEngineStyle", "HiddenFamilyname",
"HiddenGearbox", "HiddenGearboxName", "HiddenLyid",
"HiddenProductyear", "HiddenVehiclename", "HiddenVehiclesale",
"HiddenVin", "HiddenYearpattern", "HiddenDrivetype",
"HiddenModelbrandlogourl", "HiddenModelbrandmfr", "HiddenModelid",
"HiddenFueltype", "HiddenKilowattpower", "HiddenListedyear",
"HiddenListedmonth", "HiddenStopyear", "HiddenBodynumdoors",
"HiddenTransmissiondescription", "HiddenMakename",
"HiddenModelbrandid", "HiddenMakeid", "HiddenIschoosevehicletype",
]}
r0 = session.get(page_url, timeout=20, verify=False)
soup0 = BeautifulSoup(r0.text, "html.parser")
vs = get_val(soup0, "__VIEWSTATE")
vs_gen = get_val(soup0, "__VIEWSTATEGENERATOR") or "B80C0CC7"
ev = get_val(soup0, "__EVENTVALIDATION")
# 搜索
data = {
"__EVENTTARGET": "",
"__EVENTARGUMENT": "",
"__VIEWSTATE": vs,
"__VIEWSTATEGENERATOR": vs_gen,
"__VIEWSTATEENCRYPTED": "",
"__EVENTVALIDATION": ev,
"TextTime1": "2015-01-01",
"TextTime2": "", "TextCname": "", "txtVin": "",
"Txtcartype": "", "txtEngineno": "",
"Button3": "搜索",
"AspNetPager1_input": "1",
**hidden_fields,
}
r1 = session.post(page_url, data=data, headers={"Referer": page_url, "Origin": BASE}, timeout=30, verify=False)
soup = BeautifulSoup(r1.text, "html.parser")
# 抓20页,检查每页第一条ID和VIEWSTATE长度
errors = 0
prev_id = None
dups = 0
for pg in range(1, 21):
if pg > 1:
cur_vs = get_val(soup, "__VIEWSTATE")
cur_ev = get_val(soup, "__EVENTVALIDATION")
cur_vs_gen = get_val(soup, "__VIEWSTATEGENERATOR") or vs_gen
if not cur_vs:
print(f"Page {pg}: VIEWSTATE empty!")
errors += 1
continue
pdata = {
"__EVENTTARGET": "AspNetPager1",
"__EVENTARGUMENT": str(pg),
"__VIEWSTATE": cur_vs,
"__VIEWSTATEGENERATOR": cur_vs_gen,
"__VIEWSTATEENCRYPTED": "",
"__EVENTVALIDATION": cur_ev,
"TextTime1": "2015-01-01",
"TextTime2": "", "TextCname": "", "txtVin": "",
"Txtcartype": "", "txtEngineno": "",
"AspNetPager1_input": str(pg - 1),
**hidden_fields,
}
try:
html = session.post(page_url, data=pdata, headers={"Referer": page_url, "Origin": BASE, "Content-Type": "application/x-www-form-urlencoded"}, timeout=30, verify=False).text
except Exception as e:
print(f"Page {pg}: Request failed: {e}")
errors += 1
continue
soup = BeautifulSoup(html, "html.parser")
table = soup.find("table", class_="table-theme1")
rows = [r for r in table.find_all("tr") if r.find("td")] if table else []
if rows:
cells = [c.text.strip() for c in rows[0].find_all("td")]
cid = cells[0] if cells else "?"
plate = cells[1] if len(cells) > 1 else "?"
vs_len = len(get_val(soup, "__VIEWSTATE") or "")
dup_mark = " <-- DUP!" if cid == prev_id else ""
if cid == prev_id:
dups += 1
print(f"Page {pg:3d}: ID={cid:10s} plate={plate:12s} VS_len={vs_len:6d}{dup_mark}")
prev_id = cid
else:
print(f"Page {pg:3d}: No rows found!")
errors += 1
print(f"\nTotal errors: {errors}, duplicates: {dups}")
@@ -0,0 +1,581 @@
"""
快修哥系统数据导出脚本
登录入口: http://139.129.162.9/
字段: ucode=公司代码, uname=用户名, upwd=密码
- 客户信息(2015年起) -> 桌面/快修哥_客户信息.xlsx
- 库存统计 -> 桌面/快修哥_库存统计.xlsx
"""
import requests
import time
import os
import sys
import re
import pandas as pd
from bs4 import BeautifulSoup
from tqdm import tqdm
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 强制无缓冲输出
if hasattr(sys.stdout, 'reconfigure'):
sys.stdout.reconfigure(line_buffering=True)
DESKTOP = os.path.join(os.path.expanduser("~"), "Desktop")
# ── 登录配置 ──────────────────────────────────────────────
BASE = "http://139.129.162.9"
HOST_DOMAIN = "139.129.162.9"
COMPANY_CODE = "25375"
USERNAME = "admin"
PASSWORD = "123456"
TEST_MODE = True # 测试模式:只抓5条数据
TEST_LIMIT = 50
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9",
"Connection": "keep-alive",
})
def log(msg=""):
print(msg, flush=True)
def get_val(soup, name):
el = soup.find("input", {"id": name}) or soup.find("input", {"name": name})
return el.get("value", "") if el else ""
def login():
"""登录快修哥"""
log("正在登录快修哥系统...")
login_url = f"{BASE}/login.aspx"
r = session.get(login_url, timeout=20, verify=False)
if r.status_code != 200:
log(f"获取登录页失败: HTTP {r.status_code}")
return False
soup = BeautifulSoup(r.text, "html.parser")
payload = {
"ucode": COMPANY_CODE,
"uname": USERNAME,
"upwd": PASSWORD,
"windowSize": "1614",
"DeviceVersion": "",
"ipAdress": "",
"Location": "",
}
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"Referer": login_url,
"Origin": BASE,
}
r2 = session.post(login_url, data=payload, headers=headers,
timeout=20, verify=False, allow_redirects=True)
log(f"登录后URL: {r2.url}")
cookies = session.cookies.get_dict()
log(f"Cookie keys: {list(cookies.keys())}")
if "hksdms" in cookies or "ASP.NET_SessionId" in cookies:
log("登录成功!")
return True
if r2.url != login_url and "login" not in r2.url.lower():
log("登录成功(URL跳转)")
return True
log("登录状态不确定,继续尝试...")
return True
def fetch_post(url, data, extra_headers=None):
"""带重试的POST"""
h = {"Content-Type": "application/x-www-form-urlencoded"}
if extra_headers:
h.update(extra_headers)
for attempt in range(5):
try:
r = session.post(url, data=data, headers=h, timeout=30, verify=False)
r.raise_for_status()
return r.text
except Exception as e:
if attempt == 4:
raise
log(f" 请求失败({e}),第{attempt+1}次重试...")
time.sleep(2)
def extract_table(soup, table_class="table-theme1"):
"""提取表格数据为 (headers, rows)"""
table = soup.find("table", class_=table_class) or soup.find("table")
if not table:
return None, None
header_list = []
for th in table.find_all("th"):
header_list.append(th.text.strip())
if not header_list:
first_row = table.find("tr")
if first_row:
header_list = [td.text.strip() for td in first_row.find_all("td")]
rows = []
for row in table.find_all("tr"):
cells = row.find_all("td")
if cells:
rows.append([c.text.strip() for c in cells])
return header_list, rows
def parse_total_pages(soup, first_page_rows):
"""从页面解析总页数"""
total_pages = 1
all_text = soup.get_text()
m = re.search(r'\s*(\d+)\s*条', all_text)
if m:
total_records = int(m.group(1))
rows_per_page = max(first_page_rows, 10)
total_pages = (total_records + rows_per_page - 1) // rows_per_page
log(f"{total_records} 条记录,每页 {rows_per_page} 条,预计 {total_pages}")
pager = soup.find(id="AspNetPager1")
if pager:
nums = []
for a in pager.find_all("a"):
try:
nums.append(int(a.text.strip()))
except:
pass
if nums:
total_pages = max(total_pages, max(nums))
return total_pages
def get_hidden_fields(soup):
"""获取当前页面的 VIEWSTATE 等隐藏字段"""
return {
"vs": get_val(soup, "__VIEWSTATE"),
"vs_gen": get_val(soup, "__VIEWSTATEGENERATOR"),
"ev": get_val(soup, "__EVENTVALIDATION"),
}
# ══════════════════════════════════════════════════════════════
# 1. 客户信息 (从2015年起)
# ══════════════════════════════════════════════════════════════
CUSTOMER_HIDDEN_FIELDS = [
"HiddenCountyear", "HiddenCount120", "HiddenCount240", "HiddenTtime",
"HiddenOpenId", "HiddenBdtime", "HiddenVipId", "HiddenLasttime",
"HiddenHfstate", "HiddenLastgch", "HiddenJyId", "HiddenHkscode",
"HiddenBrand", "HiddenBrandname", "HiddenChassisnumber", "HiddenEngineDesc",
"HiddenEngineStyle", "HiddenFamilyname", "HiddenGearbox", "HiddenGearboxName",
"HiddenLyid", "HiddenProductyear", "HiddenVehiclename", "HiddenVehiclesale",
"HiddenVin", "HiddenYearpattern", "HiddenDrivetype", "HiddenModelbrandlogourl",
"HiddenModelbrandmfr", "HiddenModelid", "HiddenFueltype", "HiddenKilowattpower",
"HiddenListedyear", "HiddenListedmonth", "HiddenStopyear", "HiddenBodynumdoors",
"HiddenTransmissiondescription", "HiddenMakename", "HiddenModelbrandid",
"HiddenMakeid", "HiddenIschoosevehicletype",
]
def make_customer_data(page_no, viewstate, event_val, vs_generator, is_first=False):
from datetime import datetime
d = {
"__EVENTTARGET": "" if is_first else "AspNetPager1",
"__EVENTARGUMENT": "" if is_first else str(page_no),
"__VIEWSTATE": viewstate,
"__VIEWSTATEGENERATOR": vs_generator,
"__VIEWSTATEENCRYPTED": "",
"__EVENTVALIDATION": event_val,
"TextTime1": "2015-01-01",
"TextTime2": datetime.now().strftime("%Y-%m-%d"),
"TextCname": "",
"txtVin": "",
"Txtcartype": "",
"txtEngineno": "",
}
if is_first:
d["Button3"] = "搜索"
# AspNetPager1_input 不需要发送(浏览器 form.submit() 不会带它)
for f in CUSTOMER_HIDDEN_FIELDS:
d[f] = ""
return d
def reset_customer_session(page_url, extra_h):
"""重新 GET 页面 + 搜索,返回 (soup, ok)"""
r0 = session.get(page_url, timeout=20, verify=False)
if "登录" in r0.text and "ucode" in r0.text:
log(" Session过期,重新登录...")
login()
r0 = session.get(page_url, timeout=20, verify=False)
soup = BeautifulSoup(r0.text, "html.parser")
hf = get_hidden_fields(soup)
if not hf["vs"]:
log(" 重置后仍无VIEWSTATE")
return soup, False
html = fetch_post(page_url, make_customer_data(
1, hf["vs"], hf["ev"], hf["vs_gen"], is_first=True
), extra_h)
soup = BeautifulSoup(html, "html.parser")
return soup, True
def export_customer_info():
log("\n========== 导出客户信息(2015年起) ==========")
page_url = f"{BASE}/carinfo.aspx?clientWidth=1614"
ref = f"{BASE}/carinfo.aspx"
extra_h = {"Referer": ref, "Origin": BASE, "Host": HOST_DOMAIN}
data_list = []
header_list = []
seen_ids = set() # 已见的第一列ID,用于去重
# 初始化:GET + 搜索第一页
soup, ok = reset_customer_session(page_url, extra_h)
if not ok:
log("初始化失败")
return None
header_list, rows = extract_table(soup)
if not rows:
log("未找到客户信息表格")
return None
data_list.extend(rows)
for r in rows:
if r and r[0]:
seen_ids.add(r[0])
# 测试模式:限制总条数
effective_limit = TEST_LIMIT if TEST_MODE else float('inf')
if len(data_list) >= effective_limit:
data_list = data_list[:effective_limit]
total_pages = parse_total_pages(soup, len(rows))
log(f"客户信息共 {total_pages} 页,开始爬取...")
if TEST_MODE:
log(f"[测试模式] 目标 {TEST_LIMIT} 条,当前已 {len(data_list)}")
if len(data_list) < effective_limit:
page_idx = 2 # 当前要爬的页码
max_attempts = 5 # 单页最大重试次数
max_resets = 3 # 最大重新初始化次数
reset_count = 0
with tqdm(total=total_pages - 1, desc="客户信息") as pbar:
while page_idx <= total_pages:
# 测试模式:已达目标条数则退出
if len(data_list) >= effective_limit:
if TEST_MODE:
log(f"[测试模式] 已达 {effective_limit} 条,停止翻页")
break
hf = get_hidden_fields(soup)
if not hf["vs"]:
log(f"{page_idx}页VIEWSTATE丢失,重新初始化...")
soup, ok = reset_customer_session(page_url, extra_h)
if not ok:
log(f" 初始化失败,跳过剩余页")
break
# 重置后回到第1页,需要重新翻到目标页
log(f" 重新翻页到第{page_idx}页...")
# 重新搜索
hf = get_hidden_fields(soup)
html = fetch_post(page_url, make_customer_data(
1, hf["vs"], hf["ev"], hf["vs_gen"], is_first=True
), extra_h)
soup = BeautifulSoup(html, "html.parser")
for skip_pg in range(2, page_idx):
hf = get_hidden_fields(soup)
if not hf["vs"]:
log(f" 翻页到{skip_pg}时VIEWSTATE丢失,放弃")
break
html = fetch_post(page_url, make_customer_data(
skip_pg, hf["vs"], hf["ev"], hf["vs_gen"]
), extra_h)
soup = BeautifulSoup(html, "html.parser")
continue
attempt = 0
success = False
while attempt < max_attempts:
attempt += 1
html = fetch_post(page_url, make_customer_data(
page_idx, hf["vs"], hf["ev"], hf["vs_gen"]
), extra_h)
soup = BeautifulSoup(html, "html.parser")
_, page_rows = extract_table(soup)
if not page_rows:
log(f"{page_idx}页无数据(尝试{attempt}/{max_attempts})")
hf = get_hidden_fields(soup)
if not hf["vs"]:
break
continue
# 检查是否重复
first_id = page_rows[0][0] if page_rows else ""
if first_id and first_id in seen_ids:
log(f"{page_idx}页首条ID={first_id}已存在(尝试{attempt}/{max_attempts})")
hf = get_hidden_fields(soup)
if not hf["vs"]:
break
continue
# 成功获取新数据
new_rows = []
for r in page_rows:
rid = r[0] if r else ""
if rid and rid in seen_ids:
continue # 跳过已存在的行
new_rows.append(r)
if rid:
seen_ids.add(rid)
data_list.extend(new_rows)
# 测试模式截断
if TEST_MODE and len(data_list) > effective_limit:
data_list = data_list[:effective_limit]
pbar.update(1)
success = True
break
if not success:
reset_count += 1
if reset_count > max_resets:
log(f"{page_idx}页已重置{reset_count}次仍失败,跳过剩余页")
break
log(f"{page_idx}页连续{max_attempts}次失败,重新初始化session({reset_count}/{max_resets})...")
soup, ok = reset_customer_session(page_url, extra_h)
if not ok:
log(f" 初始化失败,跳过剩余页")
break
# 不增加 page_idx,重试当前页
continue
page_idx += 1
log(f"客户信息共抓取 {len(data_list)} 条(去重后)")
df = pd.DataFrame(data_list, columns=header_list if header_list else None)
df = df.dropna(how="all")
df = df[df.apply(lambda row: any(str(v).strip() for v in row), axis=1)]
# 去掉操作列(编辑、删除)
op_cols = [c for c in df.columns if c in ("编辑", "删除") or str(c).startswith("Unnamed")]
if op_cols:
df = df.drop(columns=op_cols)
out = os.path.join(DESKTOP, "快修哥_客户信息.xlsx")
df.to_excel(out, index=False)
log(f"客户信息已保存: {out}")
return out
# ══════════════════════════════════════════════════════════════
# 2. 库存统计
# ══════════════════════════════════════════════════════════════
def make_stock_data(page_no, viewstate, vs_gen, event_val, is_first=False):
d = {
"__EVENTTARGET": "" if is_first else "AspNetPager1",
"__EVENTARGUMENT": "" if is_first else str(page_no),
"__VIEWSTATE": viewstate,
"__VIEWSTATEGENERATOR": vs_gen,
"__EVENTVALIDATION": event_val,
"__VIEWSTATEENCRYPTED": "",
"TextPjmc": "",
"TextKwh": "",
"TextVehicle": "",
}
if is_first:
d["Button3"] = "搜索"
return d
def reset_stock_session(page_url, extra_h):
"""重新 GET 页面 + 搜索,返回 (soup, ok)"""
r0 = session.get(page_url, timeout=20, verify=False)
if "登录" in r0.text and "ucode" in r0.text:
log(" Session过期,重新登录...")
login()
r0 = session.get(page_url, timeout=20, verify=False)
soup = BeautifulSoup(r0.text, "html.parser")
hf = get_hidden_fields(soup)
if not hf["vs"]:
log(" 重置后仍无VIEWSTATE")
return soup, False
html = fetch_post(page_url, make_stock_data(
1, hf["vs"], hf["vs_gen"], hf["ev"], is_first=True
), extra_h)
soup = BeautifulSoup(html, "html.parser")
return soup, True
def export_stock():
log("\n========== 导出库存统计 ==========")
page_url = f"{BASE}/stockpj.aspx?clientWidth=1614"
ref = f"{BASE}/stockpj.aspx"
extra_h = {"Referer": ref, "Origin": BASE, "Host": HOST_DOMAIN}
data_list = []
header_list = []
seen_ids = set()
soup, ok = reset_stock_session(page_url, extra_h)
if not ok:
log("初始化失败")
return None
header_list, rows = extract_table(soup)
if not rows:
log("未找到库存表格")
return None
data_list.extend(rows)
for r in rows:
if r and r[0]:
seen_ids.add(r[0])
# 测试模式:限制总条数
effective_limit = TEST_LIMIT if TEST_MODE else float('inf')
if len(data_list) >= effective_limit:
data_list = data_list[:effective_limit]
total_pages = parse_total_pages(soup, len(rows))
log(f"库存统计共 {total_pages} 页,开始爬取...")
if TEST_MODE:
log(f"[测试模式] 目标 {TEST_LIMIT} 条,当前已 {len(data_list)}")
if len(data_list) < effective_limit:
page_idx = 2
max_attempts = 5
max_resets = 3
reset_count = 0
with tqdm(total=total_pages - 1, desc="库存统计") as pbar:
while page_idx <= total_pages:
# 测试模式:已达目标条数则退出
if len(data_list) >= effective_limit:
if TEST_MODE:
log(f"[测试模式] 已达 {effective_limit} 条,停止翻页")
break
hf = get_hidden_fields(soup)
if not hf["vs"]:
log(f"{page_idx}页VIEWSTATE丢失,重新初始化...")
soup, ok = reset_stock_session(page_url, extra_h)
if not ok:
break
log(f" 重新翻页到第{page_idx}页...")
hf = get_hidden_fields(soup)
html = fetch_post(page_url, make_stock_data(
1, hf["vs"], hf["vs_gen"], hf["ev"], is_first=True
), extra_h)
soup = BeautifulSoup(html, "html.parser")
for skip_pg in range(2, page_idx):
hf = get_hidden_fields(soup)
if not hf["vs"]:
break
html = fetch_post(page_url, make_stock_data(
skip_pg, hf["vs"], hf["vs_gen"], hf["ev"]
), extra_h)
soup = BeautifulSoup(html, "html.parser")
continue
attempt = 0
success = False
while attempt < max_attempts:
attempt += 1
html = fetch_post(page_url, make_stock_data(
page_idx, hf["vs"], hf["vs_gen"], hf["ev"]
), extra_h)
soup = BeautifulSoup(html, "html.parser")
_, page_rows = extract_table(soup)
if not page_rows:
log(f"{page_idx}页无数据(尝试{attempt}/{max_attempts})")
hf = get_hidden_fields(soup)
if not hf["vs"]:
break
continue
first_id = page_rows[0][0] if page_rows else ""
if first_id and first_id in seen_ids:
log(f"{page_idx}页首条ID={first_id}已存在(尝试{attempt}/{max_attempts})")
hf = get_hidden_fields(soup)
if not hf["vs"]:
break
continue
new_rows = []
for r in page_rows:
rid = r[0] if r else ""
if rid and rid in seen_ids:
continue
new_rows.append(r)
if rid:
seen_ids.add(rid)
data_list.extend(new_rows)
# 测试模式截断
if TEST_MODE and len(data_list) > effective_limit:
data_list = data_list[:effective_limit]
pbar.update(1)
success = True
break
if not success:
reset_count += 1
if reset_count > max_resets:
log(f"{page_idx}页已重置{reset_count}次仍失败,跳过剩余页")
break
log(f"{page_idx}页连续{max_attempts}次失败,重新初始化session({reset_count}/{max_resets})...")
soup, ok = reset_stock_session(page_url, extra_h)
if not ok:
break
continue
page_idx += 1
log(f"库存统计共抓取 {len(data_list)} 条(去重后)")
df = pd.DataFrame(data_list, columns=header_list if header_list else None)
df = df.dropna(how="all")
df = df[df.apply(lambda row: any(str(v).strip() for v in row), axis=1)]
out = os.path.join(DESKTOP, "快修哥_库存统计.xlsx")
df.to_excel(out, index=False)
log(f"库存统计已保存: {out}")
return out
if __name__ == "__main__":
ok = login()
if not ok:
log("登录失败")
sys.exit(1)
customer_path = export_customer_info()
stock_path = None
try:
stock_path = export_stock()
except Exception as e:
log(f"库存导出失败: {e}")
log("\n========== 全部完成 ==========")
if customer_path:
log(f" 客户信息: {customer_path}")
if stock_path:
log(f" 库存统计: {stock_path}")
@@ -0,0 +1,42 @@
import requests
import urllib3
import json
urllib3.disable_warnings()
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJpc3MiOiJodHRwczovL2F1dGgxLmhrczM2MC5jb20vY29yZSIsImF1ZCI6Imh0dHBzOi8vYXV0aDEuaGtzMzYwLmNvbS9jb3JlL3Jlc291cmNlcyIsImV4cCI6MTc4MDEyODg1MCwibmJmIjoxNzgwMTI1MjUwLCJjbGllbnRfaWQiOiJ6YS5jbGllbnQiLCJjbGllbnRfbG9jYXRpb24iOiJkYXRhY2VudGVyIiwic2NvcGUiOiJ3cml0ZSJ9.fiumtw9xCYWj-euP_LCQdrT9Wd4OsVopuHQpt3Qaae8En4lPA7aaqOfpXVF8gwxtoayfpjATtIaMomkUcnglYqZBCUTC50bc6IHYFgRrYl_7h4g9BCIHwGEswYbvFiQfAB5Q4gLFptzEJ1W2pjHnrNgmum5syQR3fsR5_25OayQ_KI6HWdtR3wReuInl0PQcDJs5jxdeId2ViDuYnl1x7TDFoIIwPov46H4KViUrBKFwr6iaTcNwrpl0thPBZjLJ8StTj50JwL1tRe71LbHkavD3MGsqs9_ulJaFZgyu2UYpl6cO0Let2zk9w-k2echh7P1ajQg7LfO2hEJ-c6RHXg',
'Content-Type': 'application/json;charset=utf-8',
'Origin': 'http://www.kuaixiuge.com',
'Referer': 'http://www.kuaixiuge.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0',
}
url = 'http://saas.hks360.com:84/WXinfoservice/GetAllServices'
# 测试几个不同的 gch
test_gch_list = ['10001', '10002', '1', '1001']
for gch in test_gch_list:
params = {'cid': '24574', 'gch': gch}
try:
r = requests.get(url, params=params, headers=headers, verify=False, timeout=15)
print(f'\n--- gch={gch} ---')
print(f'Status: {r.status_code}')
try:
data = r.json()
print(f'JSON keys: {list(data.keys()) if isinstance(data, dict) else type(data)}')
if isinstance(data, dict):
rows = data.get('rows', [])
print(f'rows type: {type(rows)}, len: {len(rows) if rows else 0}')
if rows:
print(f'First row keys: {list(rows[0].keys())}')
print(f'First row wxxmList: {rows[0].get("wxxmList")}')
print(f'First row wxinfopartsList: {rows[0].get("wxinfopartsList")}')
except Exception as je:
print(f'JSON parse error: {je}')
print(f'Raw: {r.text[:500]}')
except Exception as e:
print(f'Request error gch={gch}: {e}')
@@ -0,0 +1,36 @@
import requests
import urllib3
import json
urllib3.disable_warnings()
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJpc3MiOiJodHRwczovL2F1dGgxLmhrczM2MC5jb20vY29yZSIsImF1ZCI6Imh0dHBzOi8vYXV0aDEuaGtzMzYwLmNvbS9jb3JlL3Jlc291cmNlcyIsImV4cCI6MTc4MDEyODg1MCwibmJmIjoxNzgwMTI1MjUwLCJjbGllbnRfaWQiOiJ6YS5jbGllbnQiLCJjbGllbnRfbG9jYXRpb24iOiJkYXRhY2VudGVyIiwic2NvcGUiOiJ3cml0ZSJ9.fiumtw9xCYWj-euP_LCQdrT9Wd4OsVopuHQpt3Qaae8En4lPA7aaqOfpXVF8gwxtoayfpjATtIaMomkUcnglYqZBCUTC50bc6IHYFgRrYl_7h4g9BCIHwGEswYbvFiQfAB5Q4gLFptzEJ1W2pjHnrNgmum5syQR3fsR5_25OayQ_KI6HWdtR3wReuInl0PQcDJs5jxdeId2ViDuYnl1x7TDFoIIwPov46H4KViUrBKFwr6iaTcNwrpl0thPBZjLJ8StTj50JwL1tRe71LbHkavD3MGsqs9_ulJaFZgyu2UYpl6cO0Let2zk9w-k2echh7P1ajQg7LfO2hEJ-c6RHXg',
'Content-Type': 'application/json;charset=utf-8',
'Origin': 'http://www.kuaixiuge.com',
'Referer': 'http://www.kuaixiuge.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0',
}
url = 'http://saas.hks360.com:84/WXinfoservice/GetAllServices'
params = {'cid': '24574', 'gch': '10001'}
r = requests.get(url, params=params, headers=headers, verify=False, timeout=15)
data = r.json()
print(f"success: {data.get('success')}")
print(f"msg: {data.get('msg')}")
print(f"total: {data.get('total')}")
print(f"failure: {data.get('failure')}")
print(f"failureMsg: {data.get('failureMsg')}")
print(f"obj type: {type(data.get('obj'))}")
obj = data.get('obj')
if obj is not None:
if isinstance(obj, list):
print(f"obj len: {len(obj)}")
if obj:
print(f"obj[0] keys: {list(obj[0].keys()) if hasattr(obj[0], 'keys') else obj[0]}")
else:
print(f"obj: {str(obj)[:1000]}")
print(f"rows: {data.get('rows')}")
@@ -0,0 +1,22 @@
import requests
import urllib3
import json
urllib3.disable_warnings()
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJpc3MiOiJodHRwczovL2F1dGgxLmhrczM2MC5jb20vY29yZSIsImF1ZCI6Imh0dHBzOi8vYXV0aDEuaGtzMzYwLmNvbS9jb3JlL3Jlc291cmNlcyIsImV4cCI6MTc4MDEyODg1MCwibmJmIjoxNzgwMTI1MjUwLCJjbGllbnRfaWQiOiJ6YS5jbGllbnQiLCJjbGllbnRfbG9jYXRpb24iOiJkYXRhY2VudGVyIiwic2NvcGUiOiJ3cml0ZSJ9.fiumtw9xCYWj-euP_LCQdrT9Wd4OsVopuHQpt3Qaae8En4lPA7aaqOfpXVF8gwxtoayfpjATtIaMomkUcnglYqZBCUTC50bc6IHYFgRrYl_7h4g9BCIHwGEswYbvFiQfAB5Q4gLFptzEJ1W2pjHnrNgmum5syQR3fsR5_25OayQ_KI6HWdtR3wReuInl0PQcDJs5jxdeId2ViDuYnl1x7TDFoIIwPov46H4KViUrBKFwr6iaTcNwrpl0thPBZjLJ8StTj50JwL1tRe71LbHkavD3MGsqs9_ulJaFZgyu2UYpl6cO0Let2zk9w-k2echh7P1ajQg7LfO2hEJ-c6RHXg',
'Content-Type': 'application/json;charset=utf-8',
'Origin': 'http://www.kuaixiuge.com',
'Referer': 'http://www.kuaixiuge.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0',
}
url = 'http://saas.hks360.com:84/WXinfoservice/GetAllServices'
params = {'cid': '24574', 'gch': '10001'}
r = requests.get(url, params=params, headers=headers, verify=False, timeout=15)
data = r.json()
print(json.dumps(data, ensure_ascii=False, indent=2))
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,202 @@
import requests
from bs4 import BeautifulSoup
import re
import time
import os
import urllib3
from openpyxl import Workbook
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
cookies = {
'ASP.NET_SessionId': 'vorf0ihfffhays4ojjgmolqu',
'Hm_lvt_ab3baaa579f771d051a6b0baad5a8cfe': '1780017347',
'HMACCOUNT': 'A6A0585E8C70051D',
'iswatchme': '0',
'setaddat': '0',
'hksdms': 'username2=admin&truename2=%e6%9d%a8%e7%9b%bc%e8%8f%b2&id=24574&wxusername2=18739061579&zb=false&qx=111-11111111111-11111111111111-0-1111-11111111111111-111111111-1111111111-1111111111111-1111-0-0-0-0-0-0-0-0-0-0&login=1&actname=%e7%ae%a1%e7%90%86%e5%91%98&act=%e7%ae%a1%e7%90%86%e5%91%98&username=admin&truename=%e6%9d%a8%e7%9b%bc%e8%8f%b2&userid=31100&valid=True&wxusername=18739061579&uniqueKey=9c680418-f250-4bbb-9b5c-c88b3bbb04ab&timeunitprice=0.00&allowquickout=False&telqx=1&tel=18739061579&StoreName=%e6%b4%9b%e9%98%b3%e5%be%b7%e5%a8%81Jeep%e4%b8%93%e4%bf%ae%e5%ba%97&attestationTel=18739061579&StoreName2=%e5%be%b7%e5%a8%81%e7%bb%b4%e4%bf%ae%e4%bf%9d%e5%85%bb&vipid=SAAS24574&zonecode=1003&zone=%e5%b1%b1%e4%b8%9c&CustomerID=164902&IsInitialized=1&ScrmModuleValidTime=&isScrmModule=False&isBasicModule=True&isTechnologyModule=True&isPartsManageModule=True&isBusinessImprovementModule=False',
'Morder': 'TextTime1=2016-04-01&TextTime2=2026-05-29&sortstring= order by indate desc ,tid desc &CurrentPage=1',
'SERVERID': '000e421eb0ab0efb9790874bd5c8f758|1780019746|1780017343',
'Hm_lpvt_ab3baaa579f771d051a6b0baad5a8cfe': '1780019753',
}
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Cache-Control': 'max-age=0',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'http://www.kuaixiuge.com',
'Proxy-Connection': 'keep-alive',
'Referer': 'http://www.kuaixiuge.com/MaintenanceOrder.aspx?clientWidth=1647',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0',
}
params = {
'clientWidth': '1647',
}
OUTPUT_DIR = r'D:\Idea Project\F6+宜搭+其它(1)\张阳脚本\文件输出'
COLUMNS = ['工单号', '车牌号', 'VIN码', '客户名', '电话', '车型', '进厂时间', '出厂时间', '类别', '状态', '金额', '工位', '接车人']
session = requests.Session()
session.cookies.update(cookies)
def get_viewstate_and_pager(soup):
viewstate = soup.find('input', {'name': '__VIEWSTATE'})
viewstate_gen = soup.find('input', {'name': '__VIEWSTATEGENERATOR'})
event_validation = soup.find('input', {'name': '__EVENTVALIDATION'})
viewstate_val = viewstate['value'] if viewstate else ''
viewstate_gen_val = viewstate_gen['value'] if viewstate_gen else ''
event_val = event_validation['value'] if event_validation else ''
total_pages = 1
total_records = 0
body_text = soup.get_text()
match = re.search(r'共(\d+)条记录', body_text)
if match:
total_records = int(match.group(1))
total_pages = (total_records + 14) // 15
return viewstate_val, viewstate_gen_val, event_val, total_pages, total_records
def extract_gch_from_row(row):
cells = row.find_all('td')
if not cells:
return None, []
row_data = [cell.get_text(strip=True) for cell in cells]
gch = None
for text in row_data:
match = re.search(r'24574\d{9,}', text)
if match:
gch = match.group(0)
break
return gch, row_data
def init_first_page():
src_path = os.path.join(os.path.dirname(__file__), '快修哥历史维修记录.py')
with open(src_path, 'r', encoding='utf-8') as f:
src = f.read()
def get_str(key, next_key):
s = src.find("'" + key + "': '") + len(key) + 5
e = src.find("',\n '" + next_key + "'", s)
return src[s:e]
vs = get_str('__VIEWSTATE', '__VIEWSTATEGENERATOR')
ev = get_str('__EVENTVALIDATION', 'TextTime1')
post_resp = session.post(
'http://www.kuaixiuge.com/MaintenanceOrder.aspx',
params=params,
headers=headers,
data={
'__EVENTTARGET': '',
'__EVENTARGUMENT': '',
'__LASTFOCUS': '',
'__VIEWSTATE': vs,
'__VIEWSTATEGENERATOR': 'DECE3335',
'__EVENTVALIDATION': ev,
'TextTime1': '2016-04-01',
'TextTime2': '2026-05-29',
'DropDownUser': '0',
'DropDownStatus': '全部',
'TextGch': '',
'TextCname': '',
'txtSenpeopleOrPhone': '',
'DropDownXlxz': '全部',
'Button3': '搜索',
'AspNetPager1_input': '1',
},
verify=False,
)
soup = BeautifulSoup(post_resp.text, 'html.parser')
vs, vsg, ev, total_pages, total_records = get_viewstate_and_pager(soup)
return soup, vs, vsg, ev, total_pages, total_records
soup, viewstate, viewstate_gen, event_validation, total_pages, total_records = init_first_page()
print(f"总页数: {total_pages} (共 {total_records} 条记录)")
all_rows = []
for page in range(1, total_pages + 1):
if page == 1:
print(f"正在获取第 {page} 页...")
else:
print(f"正在获取第 {page} 页...")
time.sleep(1)
data = {
'__EVENTTARGET': 'AspNetPager1',
'__EVENTARGUMENT': str(page),
'__LASTFOCUS': '',
'__VIEWSTATE': viewstate,
'__VIEWSTATEGENERATOR': viewstate_gen,
'__EVENTVALIDATION': event_validation,
'TextTime1': '2016-04-01',
'TextTime2': '2026-05-29',
'DropDownUser': '0',
'DropDownStatus': '全部',
'TextGch': '',
'TextCname': '',
'txtSenpeopleOrPhone': '',
'DropDownXlxz': '全部',
'AspNetPager1_input': str(page),
}
morder = f'TextTime1=2016-04-01&TextTime2=2026-05-29&sortstring= order by indate desc ,tid desc &CurrentPage={page}'
session.cookies.set('Morder', morder)
response = session.post(
'http://www.kuaixiuge.com/MaintenanceOrder.aspx',
params=params,
headers=headers,
data=data,
verify=False,
)
soup = BeautifulSoup(response.text, 'html.parser')
viewstate, viewstate_gen, event_validation, _, _ = get_viewstate_and_pager(soup)
gridview = soup.find('table', {'id': 'GridView1'})
if gridview:
rows = gridview.find_all('tr')[1:]
print(f"{page} 页提取到 {len(rows)} 行数据")
for row in rows:
gch, row_data = extract_gch_from_row(row)
if gch:
all_rows.append(row_data[:13])
else:
print(f" ⚠ 第 {page} 页未找到 GridView1 表格!")
print(f"\n共找到 {len(all_rows)} 条工单")
os.makedirs(OUTPUT_DIR, exist_ok=True)
output_path = os.path.join(OUTPUT_DIR, '快修哥历史维修记录_主列表.xlsx')
wb = Workbook()
ws = wb.active
ws.title = '历史维修记录'
ws.append(COLUMNS)
for row in all_rows:
ws.append(row)
ws.auto_filter.ref = ws.dimensions
for col in ws.columns:
max_len = 0
col_letter = col[0].column_letter
for cell in col:
if cell.value:
max_len = max(max_len, len(str(cell.value)))
ws.column_dimensions[col_letter].width = min(max_len + 4, 40)
for cell in ws['A']:
cell.number_format = '@'
wb.save(output_path)
print(f"数据已保存到 {output_path}")
File diff suppressed because one or more lines are too long
@@ -0,0 +1,194 @@
import requests
import time
import os
import datetime
import json
import urllib3
from openpyxl import Workbook, load_workbook
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
DETAIL_HEADERS = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJpc3MiOiJodHRwczovL2F1dGgxLmhrczM2MC5jb20vY29yZSIsImF1ZCI6Imh0dHBzOi8vYXV0aDEuaGtzMzYwLmNvbS9jb3JlL3Jlc291cmNlcyIsImV4cCI6MTc4MDEyODg1MCwibmJmIjoxNzgwMTI1MjUwLCJjbGllbnRfaWQiOiJ6YS5jbGllbnQiLCJjbGllbnRfbG9jYXRpb24iOiJkYXRhY2VudGVyIiwic2NvcGUiOiJ3cml0ZSJ9.fiumtw9xCYWj-euP_LCQdrT9Wd4OsVopuHQpt3Qaae8En4lPA7aaqOfpXVF8gwxtoayfpjATtIaMomkUcnglYqZBCUTC50bc6IHYFgRrYl_7h4g9BCIHwGEswYbvFiQfAB5Q4gLFptzEJ1W2pjHnrNgmum5syQR3fsR5_25OayQ_KI6HWdtR3wReuInl0PQcDJs5jxdeId2ViDuYnl1x7TDFoIIwPov46H4KViUrBKFwr6iaTcNwrpl0thPBZjLJ8StTj50JwL1tRe71LbHkavD3MGsqs9_ulJaFZgyu2UYpl6cO0Let2zk9w-k2echh7P1ajQg7LfO2hEJ-c6RHXg',
'Content-Type': 'application/json;charset=utf-8',
'Connection': 'keep-alive',
'Origin': 'http://www.kuaixiuge.com',
'Proxy-Connection': 'keep-alive',
'Referer': 'http://www.kuaixiuge.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0',
}
MAX_DETAIL = 99999
OUTPUT_DIR = r'D:\Idea Project\F6+宜搭+其它(1)\张阳脚本\文件输出'
INPUT_FILE = os.path.join(OUTPUT_DIR, '快修哥历史维修记录_合并去重_无明细_20260530_140130.xlsx')
OUTPUT_FILE = os.path.join(OUTPUT_DIR, f'快修哥历史维修记录_含明细_{datetime.datetime.now().strftime("%Y%m%d_%H%M%S")}.xlsx')
PROGRESS_FILE = os.path.join(OUTPUT_DIR, os.path.basename(INPUT_FILE).replace('.xlsx', '_进度.json'))
SERVICE_COLUMNS = [
'serviceid', 'servicename', 'servicetype', 'servicenumber',
'serviceprice', 'servicehj', 'timefee', 'isinquiry', 'isinterimpurchase',
]
WXXM_COLUMNS = [
'xmdm', 'xmname', 'xmprice', 'hour',
]
PARTS_COLUMNS = [
'pcode', 'pname', 'price', 'count', 'amount', 'unit',
]
def fetch_detail(gch, cid='24574'):
params = {'cid': cid, 'gch': gch}
url = 'http://saas.hks360.com:84/WXinfoservice/GetAllServices'
try:
requests.options(url, params=params, headers=DETAIL_HEADERS, verify=False, timeout=10)
time.sleep(0.15)
resp = requests.get(url, params=params, headers=DETAIL_HEADERS, verify=False, timeout=15)
if resp.status_code == 200:
data = resp.json()
rows = (data.get('rows') or []) if isinstance(data, dict) else []
return rows, True
print(f" [X] HTTP {resp.status_code} gch={gch}")
return [], False
except Exception as e:
print(f" [X] 异常 gch={gch}, {e}")
return [], False
def parse_detail_rows(detail_rows):
result = []
for row in detail_rows:
service_info = {k: row.get(k) for k in SERVICE_COLUMNS}
wxxm_list = row.get('wxxmList', []) or []
parts_list = row.get('wxinfopartsList', []) or []
has_any = False
if wxxm_list:
has_any = True
for xm in wxxm_list:
merged = dict(service_info)
for k in WXXM_COLUMNS:
merged['xm_' + k] = xm.get(k)
result.append(merged)
if parts_list:
has_any = True
for part in parts_list:
merged = dict(service_info)
for k in PARTS_COLUMNS:
merged['part_' + k] = part.get(k)
result.append(merged)
if not has_any:
result.append(service_info)
return result
DETAIL_HEADER = (['明细_serviceid', '明细_服务名称', '明细_服务类型', '明细_服务数量',
'明细_服务单价', '明细_服务合计', '明细_工时费', '明细_是否询价', '明细_是否中间采购',
'明细_项目代码', '明细_项目名称', '明细_项目价格', '明细_项目工时',
'明细_配件编码', '明细_配件名称', '明细_配件单价', '明细_配件数量', '明细_配件金额', '明细_配件单位'])
DETAIL_XM_KEYS = ['serviceid', 'servicename', 'servicetype', 'servicenumber',
'serviceprice', 'servicehj', 'timefee', 'isinquiry', 'isinterimpurchase',
'xm_xmdm', 'xm_xmname', 'xm_xmprice', 'xm_hour']
DETAIL_PART_KEYS = ['part_pcode', 'part_pname', 'part_price', 'part_count', 'part_amount', 'part_unit']
DETAIL_OUTPUT_KEYS = DETAIL_XM_KEYS + DETAIL_PART_KEYS
def _save_xlsx(main_data, all_detail_rows, headers_row, output_path):
expanded_rows = []
for main_row, detail_items in zip(main_data, all_detail_rows):
if detail_items:
for d in detail_items:
row_vals = list(main_row) + [d.get(k, '') for k in DETAIL_OUTPUT_KEYS]
expanded_rows.append(row_vals)
else:
expanded_rows.append(list(main_row) + [''] * len(DETAIL_OUTPUT_KEYS))
out_wb = Workbook()
out_ws = out_wb.active
out_ws.title = '历史维修记录(含明细)'
out_ws.append(list(headers_row) + list(DETAIL_HEADER))
for row_vals in expanded_rows:
out_ws.append(row_vals)
for cell in out_ws['A']:
cell.number_format = '@'
out_ws.auto_filter.ref = out_ws.dimensions
out_wb.save(output_path)
print(f"数据已保存至 {output_path} ({len(expanded_rows)} 行)")
print(f"读取输入文件: {INPUT_FILE}")
wb = load_workbook(INPUT_FILE)
ws = wb.active
headers_row = [cell.value for cell in ws[1]]
main_data = []
gch_list = []
for row in ws.iter_rows(min_row=2, values_only=True):
row_list = list(row)
gch_str = str(int(row_list[0])) if isinstance(row_list[0], float) else str(row_list[0])
row_list[0] = gch_str
main_data.append(row_list)
gch_list.append(gch_str)
print(f"{len(gch_list)} 条工单待获取明细,本次取前 {MAX_DETAIL}")
all_detail_rows = [None] * len(gch_list)
fetch_count = min(len(gch_list), MAX_DETAIL)
start_idx = 0
if os.path.exists(PROGRESS_FILE):
with open(PROGRESS_FILE, 'r', encoding='utf-8') as f:
saved = json.load(f)
saved_data = saved.get('detail_data', [])
if len(saved_data) == len(gch_list):
for idx, item in enumerate(saved_data):
if idx < len(all_detail_rows) and item is not None:
all_detail_rows[idx] = item
start_idx = saved.get('next_idx', 0)
else:
print(f"[!] 进度文件与输入不匹配 (进度{len(saved_data)}条, 输入{len(gch_list)}条), 从头开始")
if start_idx >= fetch_count:
print(f"[!] 已完成全部 {fetch_count} 条, 无需重新请求")
else:
print(f"[断点续传] 从第 {start_idx + 1}/{fetch_count} 条继续 (已完成 {start_idx} 条)")
consecutive_fails = 0
SAVE_ON_FAILS = 10
SLEEP_SEC = 0.8
def save_progress():
os.makedirs(OUTPUT_DIR, exist_ok=True)
with open(PROGRESS_FILE, 'w', encoding='utf-8') as f:
json.dump({'next_idx': i + 1, 'detail_data': all_detail_rows}, f, ensure_ascii=False)
for i in range(start_idx, fetch_count):
gch = gch_list[i]
if (i + 1) % 10 == 0 or i == fetch_count - 1:
print(f"正在获取明细 {i + 1}/{fetch_count} ... (连续失败: {consecutive_fails})")
time.sleep(SLEEP_SEC)
raw, ok = fetch_detail(gch)
all_detail_rows[i] = parse_detail_rows(raw)
if not ok:
consecutive_fails += 1
if consecutive_fails >= SAVE_ON_FAILS:
print(f"\n[!!] 连续失败 {consecutive_fails} 次,保存已获取数据后终止 (下次从第 {i - consecutive_fails + 1} 条继续)")
_save_xlsx(main_data, all_detail_rows, headers_row, OUTPUT_FILE)
raise SystemExit(1)
else:
consecutive_fails = 0
save_progress()
print("\n全部完成! 正在最终保存...")
with_detail = sum(1 for d in all_detail_rows if d)
empty_detail = sum(1 for d in all_detail_rows if d is not None and len(d) == 0)
none_detail = sum(1 for d in all_detail_rows if d is None)
total_detail_rows = sum(len(d) for d in all_detail_rows if d)
print(f" 有明细={with_detail}条, 无明细(空)={empty_detail}条, 未请求={none_detail}条, 明细行={total_detail_rows}")
_save_xlsx(main_data, all_detail_rows, headers_row, OUTPUT_FILE)
if os.path.exists(PROGRESS_FILE):
os.remove(PROGRESS_FILE)
print("进度文件已清理")
@@ -0,0 +1,19 @@
import requests
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSIsImtpZCI6ImEzck1VZ01Gdjl0UGNsTGE2eUYzekFrZnF1RSJ9.eyJpc3MiOiJodHRwczovL2F1dGgxLmhrczM2MC5jb20vY29yZSIsImF1ZCI6Imh0dHBzOi8vYXV0aDEuaGtzMzYwLmNvbS9jb3JlL3Jlc291cmNlcyIsImV4cCI6MTc4MDEyNTIyNiwibmJmIjoxNzgwMTIxNjI2LCJjbGllbnRfaWQiOiJ6YS5jbGllbnQiLCJjbGllbnRfbG9jYXRpb24iOiJkYXRhY2VudGVyIiwic2NvcGUiOiJ3cml0ZSJ9.NJYITCX-gPmezXu9l04A_GedlvDQQqyWRJVKK0lF_4y6n9gg7KwAvzttw5T-6xWe5o7-rZA7WdH4IqKvdgi7u5d4Le7cDqA4jvlNZ3_IdSTTbHeMx-jxOyb-hD6b_Q1MwzeEPq2SUjYVR9W1uhzIUG-AI4y-gB-y2L3OXk5hL315EFAWKjttdXpkeDsWU9Vi8m3PUJ_zWUJHKWpwY8duzM9WbF6ugcLJ-3RuD7RZaPcvYH_L-EBt8PkwjCU4thKqG2RAb2-JCnTHz-GiBDJ34i9mHr-17v5MmefbZQtQXKdQf2-p8WZo4Ntx_vBiJH6sh9A0-owsRkoGQZ2vEPkJnw',
'Content-Type': 'application/json;charset=utf-8',
'Origin': 'http://www.kuaixiuge.com',
'Proxy-Connection': 'keep-alive',
'Referer': 'http://www.kuaixiuge.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0',
}
params = {
'cid': '24574',
'gch': '245742505000013',
}
response = requests.get('http://saas.hks360.com:84/WXinfoservice/GetAllServices', params=params, headers=headers, verify=False)
@@ -0,0 +1,715 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "1cdf8cdc",
"metadata": {},
"source": [
"客户信息导出"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "4ffdf71d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'RowsCount': 0,\n",
" 'Data': [{'Id': 'MM9995626323',\n",
" 'MemberId': 'MM9995626323',\n",
" 'MemCarId': None,\n",
" 'MerchantId': 'CR0000002145',\n",
" 'MerStoreId': 'CS0000044172',\n",
" 'MerStoreCode': '0214500001',\n",
" 'MerStoreName': '众得信汽车用品',\n",
" 'TagCode': None,\n",
" 'TagName': None,\n",
" 'MemType': 1,\n",
" 'MemSource': 5,\n",
" 'MemName': '鲍先生',\n",
" 'MemMobile': '13969813963',\n",
" 'MemIDNumber': None,\n",
" 'MemSexType': False,\n",
" 'MemCardNo': '13969813963',\n",
" 'MemBirthDay': None,\n",
" 'MemCardGradeId': 'MG0000025026',\n",
" 'MemCardGradeName': '19周年庆-800',\n",
" 'CardExpiredDate': None,\n",
" 'MemCarsAmount': 1,\n",
" 'BalancePrice': 740.0,\n",
" 'IntegralAmount': 6,\n",
" 'TotalRechargePrice': 800.0,\n",
" 'TotalConsumPrice': 60.0,\n",
" 'TotalConsumTimes': 1,\n",
" 'LastConsumTime': '2025-12-24T11:38:39',\n",
" 'ToStoreTimes': 117,\n",
" 'CreditBillPrice': 0.0,\n",
" 'TotalCreditBillPrice': 0.0,\n",
" 'LastRepayTime': None,\n",
" 'DefRepayTime': None,\n",
" 'MemCreatedAt': '2025-12-24T11:35:32',\n",
" 'MemCompany': None,\n",
" 'MemAddress': None,\n",
" 'MemOpenId': 'otST1wH1I0NmejvUEYzwSdi7Wo4I',\n",
" 'IsSetPassword': 0,\n",
" 'AllCarPlateNumber': '鲁B9LM65',\n",
" 'AllCarModelName': '-',\n",
" 'MemWechatName': None,\n",
" 'MemWechatImageLink': 'https%3A%2F%2Fthirdwx.qlogo.cn%2Fmmopen%2Fvi_32%2FVTvcGww1B9KT5Bibr0sAHjQtSRKt2LqM0RzO4FVXRYkH4CseBlIXS0omNzyshjreRbUF1oyHibmt3afRiaKEVvc3g%2F132',\n",
" 'Enabled': True,\n",
" 'IsDisplay': True,\n",
" 'Remark': None,\n",
" 'MerAppId': 'wxe6801eee9c3e1853',\n",
" 'IId': 0},\n",
" {'Id': 'MM9995629539',\n",
" 'MemberId': 'MM9995629539',\n",
" 'MemCarId': None,\n",
" 'MerchantId': 'CR0000002145',\n",
" 'MerStoreId': 'CS0000044172',\n",
" 'MerStoreCode': '0214500001',\n",
" 'MerStoreName': '众得信汽车用品',\n",
" 'TagCode': None,\n",
" 'TagName': None,\n",
" 'MemType': 1,\n",
" 'MemSource': 0,\n",
" 'MemName': '王先生',\n",
" 'MemMobile': '18000800080',\n",
" 'MemIDNumber': None,\n",
" 'MemSexType': False,\n",
" 'MemCardNo': '18000800080',\n",
" 'MemBirthDay': None,\n",
" 'MemCardGradeId': 'MG0000025026',\n",
" 'MemCardGradeName': '19周年庆-800',\n",
" 'CardExpiredDate': None,\n",
" 'MemCarsAmount': 1,\n",
" 'BalancePrice': 608.0,\n",
" 'IntegralAmount': 16,\n",
" 'TotalRechargePrice': 800.0,\n",
" 'TotalConsumPrice': 192.0,\n",
" 'TotalConsumTimes': 4,\n",
" 'LastConsumTime': '2026-02-23T10:41:03',\n",
" 'ToStoreTimes': 56,\n",
" 'CreditBillPrice': 0.0,\n",
" 'TotalCreditBillPrice': 0.0,\n",
" 'LastRepayTime': None,\n",
" 'DefRepayTime': None,\n",
" 'MemCreatedAt': '2025-12-21T15:19:19',\n",
" 'MemCompany': None,\n",
" 'MemAddress': None,\n",
" 'MemOpenId': 'otST1wHeI4aMavhAkweedxcC56uc',\n",
" 'IsSetPassword': 0,\n",
" 'AllCarPlateNumber': '鲁B0048D',\n",
" 'AllCarModelName': '-',\n",
" 'MemWechatName': None,\n",
" 'MemWechatImageLink': 'https%3A%2F%2Fthirdwx.qlogo.cn%2Fmmopen%2Fvi_32%2FibzEtZY7mqJVibibLibL3Nm9iauvAQYO1icI4YnoyQWrYDHm0dS4TKXH6wklUUl1N6Io7PuKUxEQjFPLybjyialG30UVZ2Mz64x9FbWXCaa37CWvOg%2F132',\n",
" 'Enabled': True,\n",
" 'IsDisplay': True,\n",
" 'Remark': None,\n",
" 'MerAppId': 'wxe6801eee9c3e1853',\n",
" 'IId': 0},\n",
" {'Id': 'MM9995645867',\n",
" 'MemberId': 'MM9995645867',\n",
" 'MemCarId': None,\n",
" 'MerchantId': 'CR0000002145',\n",
" 'MerStoreId': 'CS0000044172',\n",
" 'MerStoreCode': '0214500001',\n",
" 'MerStoreName': '众得信汽车用品',\n",
" 'TagCode': None,\n",
" 'TagName': None,\n",
" 'MemType': 1,\n",
" 'MemSource': 0,\n",
" 'MemName': '王先生',\n",
" 'MemMobile': '13801229158',\n",
" 'MemIDNumber': None,\n",
" 'MemSexType': False,\n",
" 'MemCardNo': '13801229158',\n",
" 'MemBirthDay': None,\n",
" 'MemCardGradeId': 'MG0000000211',\n",
" 'MemCardGradeName': '普通',\n",
" 'CardExpiredDate': None,\n",
" 'MemCarsAmount': 1,\n",
" 'BalancePrice': 0.0,\n",
" 'IntegralAmount': 139,\n",
" 'TotalRechargePrice': 0.0,\n",
" 'TotalConsumPrice': 1398.0,\n",
" 'TotalConsumTimes': 1,\n",
" 'LastConsumTime': '2025-12-10T17:20:22',\n",
" 'ToStoreTimes': 131,\n",
" 'CreditBillPrice': 0.0,\n",
" 'TotalCreditBillPrice': 0.0,\n",
" 'LastRepayTime': None,\n",
" 'DefRepayTime': None,\n",
" 'MemCreatedAt': '2025-12-10T17:18:44',\n",
" 'MemCompany': None,\n",
" 'MemAddress': None,\n",
" 'MemOpenId': None,\n",
" 'IsSetPassword': 0,\n",
" 'AllCarPlateNumber': '京L26920',\n",
" 'AllCarModelName': '-',\n",
" 'MemWechatName': None,\n",
" 'MemWechatImageLink': None,\n",
" 'Enabled': True,\n",
" 'IsDisplay': True,\n",
" 'Remark': None,\n",
" 'MerAppId': 'wxe6801eee9c3e1853',\n",
" 'IId': 0},\n",
" {'Id': 'MM9995649031',\n",
" 'MemberId': 'MM9995649031',\n",
" 'MemCarId': None,\n",
" 'MerchantId': 'CR0000002145',\n",
" 'MerStoreId': 'CS0000044172',\n",
" 'MerStoreCode': '0214500001',\n",
" 'MerStoreName': '众得信汽车用品',\n",
" 'TagCode': None,\n",
" 'TagName': None,\n",
" 'MemType': 1,\n",
" 'MemSource': 0,\n",
" 'MemName': '(慈善总会)张先生',\n",
" 'MemMobile': '13791911122',\n",
" 'MemIDNumber': None,\n",
" 'MemSexType': False,\n",
" 'MemCardNo': '13791911122',\n",
" 'MemBirthDay': None,\n",
" 'MemCardGradeId': 'MG0000025026',\n",
" 'MemCardGradeName': '19周年庆-800',\n",
" 'CardExpiredDate': None,\n",
" 'MemCarsAmount': 1,\n",
" 'BalancePrice': 310.0,\n",
" 'IntegralAmount': 47,\n",
" 'TotalRechargePrice': 800.0,\n",
" 'TotalConsumPrice': 490.0,\n",
" 'TotalConsumTimes': 7,\n",
" 'LastConsumTime': '2026-03-24T10:14:16',\n",
" 'ToStoreTimes': 27,\n",
" 'CreditBillPrice': 0.0,\n",
" 'TotalCreditBillPrice': 0.0,\n",
" 'LastRepayTime': None,\n",
" 'DefRepayTime': None,\n",
" 'MemCreatedAt': '2025-12-08T14:55:02',\n",
" 'MemCompany': None,\n",
" 'MemAddress': None,\n",
" 'MemOpenId': None,\n",
" 'IsSetPassword': 0,\n",
" 'AllCarPlateNumber': '鲁UAJ561',\n",
" 'AllCarModelName': '-',\n",
" 'MemWechatName': None,\n",
" 'MemWechatImageLink': None,\n",
" 'Enabled': True,\n",
" 'IsDisplay': True,\n",
" 'Remark': None,\n",
" 'MerAppId': 'wxe6801eee9c3e1853',\n",
" 'IId': 0},\n",
" {'Id': 'MM9995667455',\n",
" 'MemberId': 'MM9995667455',\n",
" 'MemCarId': None,\n",
" 'MerchantId': 'CR0000002145',\n",
" 'MerStoreId': 'CS0000044172',\n",
" 'MerStoreCode': '0214500001',\n",
" 'MerStoreName': '众得信汽车用品',\n",
" 'TagCode': None,\n",
" 'TagName': None,\n",
" 'MemType': 1,\n",
" 'MemSource': 0,\n",
" 'MemName': '市场龙大冷鲜肉',\n",
" 'MemMobile': '18653254568',\n",
" 'MemIDNumber': None,\n",
" 'MemSexType': False,\n",
" 'MemCardNo': '18653254568',\n",
" 'MemBirthDay': None,\n",
" 'MemCardGradeId': 'MG0000000211',\n",
" 'MemCardGradeName': '普通',\n",
" 'CardExpiredDate': None,\n",
" 'MemCarsAmount': 1,\n",
" 'BalancePrice': 0.0,\n",
" 'IntegralAmount': 103,\n",
" 'TotalRechargePrice': 0.0,\n",
" 'TotalConsumPrice': 1038.0,\n",
" 'TotalConsumTimes': 1,\n",
" 'LastConsumTime': '2025-11-26T16:50:54',\n",
" 'ToStoreTimes': 145,\n",
" 'CreditBillPrice': 0.0,\n",
" 'TotalCreditBillPrice': 0.0,\n",
" 'LastRepayTime': None,\n",
" 'DefRepayTime': None,\n",
" 'MemCreatedAt': '2025-11-26T15:48:22',\n",
" 'MemCompany': None,\n",
" 'MemAddress': None,\n",
" 'MemOpenId': None,\n",
" 'IsSetPassword': 0,\n",
" 'AllCarPlateNumber': '鲁BRF12110',\n",
" 'AllCarModelName': '-',\n",
" 'MemWechatName': None,\n",
" 'MemWechatImageLink': None,\n",
" 'Enabled': True,\n",
" 'IsDisplay': True,\n",
" 'Remark': None,\n",
" 'MerAppId': 'wxe6801eee9c3e1853',\n",
" 'IId': 0},\n",
" {'Id': 'MM9995669799',\n",
" 'MemberId': 'MM9995669799',\n",
" 'MemCarId': None,\n",
" 'MerchantId': 'CR0000002145',\n",
" 'MerStoreId': 'CS0000044172',\n",
" 'MerStoreCode': '0214500001',\n",
" 'MerStoreName': '众得信汽车用品',\n",
" 'TagCode': None,\n",
" 'TagName': None,\n",
" 'MemType': 1,\n",
" 'MemSource': 6,\n",
" 'MemName': '王先生',\n",
" 'MemMobile': '15963200818',\n",
" 'MemIDNumber': None,\n",
" 'MemSexType': False,\n",
" 'MemCardNo': '15963200818',\n",
" 'MemBirthDay': None,\n",
" 'MemCardGradeId': 'MG0000000211',\n",
" 'MemCardGradeName': '普通',\n",
" 'CardExpiredDate': None,\n",
" 'MemCarsAmount': 1,\n",
" 'BalancePrice': 0.0,\n",
" 'IntegralAmount': 91,\n",
" 'TotalRechargePrice': 0.0,\n",
" 'TotalConsumPrice': 918.0,\n",
" 'TotalConsumTimes': 1,\n",
" 'LastConsumTime': '2025-11-24T19:17:03',\n",
" 'ToStoreTimes': 147,\n",
" 'CreditBillPrice': 0.0,\n",
" 'TotalCreditBillPrice': 0.0,\n",
" 'LastRepayTime': None,\n",
" 'DefRepayTime': None,\n",
" 'MemCreatedAt': '2025-11-24T19:15:54',\n",
" 'MemCompany': None,\n",
" 'MemAddress': None,\n",
" 'MemOpenId': None,\n",
" 'IsSetPassword': 0,\n",
" 'AllCarPlateNumber': '鲁B82FD8',\n",
" 'AllCarModelName': '-',\n",
" 'MemWechatName': None,\n",
" 'MemWechatImageLink': None,\n",
" 'Enabled': True,\n",
" 'IsDisplay': True,\n",
" 'Remark': None,\n",
" 'MerAppId': 'wxe6801eee9c3e1853',\n",
" 'IId': 0},\n",
" {'Id': 'MM9995681626',\n",
" 'MemberId': 'MM9995681626',\n",
" 'MemCarId': None,\n",
" 'MerchantId': 'CR0000002145',\n",
" 'MerStoreId': 'CS0000044172',\n",
" 'MerStoreCode': '0214500001',\n",
" 'MerStoreName': '众得信汽车用品',\n",
" 'TagCode': None,\n",
" 'TagName': None,\n",
" 'MemType': 1,\n",
" 'MemSource': 6,\n",
" 'MemName': '丁先生',\n",
" 'MemMobile': '15166655600',\n",
" 'MemIDNumber': None,\n",
" 'MemSexType': False,\n",
" 'MemCardNo': '15166655600',\n",
" 'MemBirthDay': None,\n",
" 'MemCardGradeId': 'MG0000025026',\n",
" 'MemCardGradeName': '19周年庆-800',\n",
" 'CardExpiredDate': None,\n",
" 'MemCarsAmount': 1,\n",
" 'BalancePrice': 50.0,\n",
" 'IntegralAmount': 73,\n",
" 'TotalRechargePrice': 800.0,\n",
" 'TotalConsumPrice': 750.0,\n",
" 'TotalConsumTimes': 12,\n",
" 'LastConsumTime': '2026-04-07T12:53:57',\n",
" 'ToStoreTimes': 13,\n",
" 'CreditBillPrice': 0.0,\n",
" 'TotalCreditBillPrice': 0.0,\n",
" 'LastRepayTime': None,\n",
" 'DefRepayTime': None,\n",
" 'MemCreatedAt': '2025-11-17T09:27:09',\n",
" 'MemCompany': None,\n",
" 'MemAddress': None,\n",
" 'MemOpenId': 'otST1wHo_IkEQuKNFI9bbYTN-RGY',\n",
" 'IsSetPassword': 0,\n",
" 'AllCarPlateNumber': '鲁B5X1Z7',\n",
" 'AllCarModelName': '-',\n",
" 'MemWechatName': None,\n",
" 'MemWechatImageLink': 'https%3A%2F%2Fthirdwx.qlogo.cn%2Fmmopen%2Fvi_32%2FWnoLdrqW83HCxhCA67uP1LmT5R8n1hSx82NeGkMSVhiaLQbCJibfrQxFzJVDiaa8yQjcpqibaibfiboekW9x1icpjpnHz0Ppp4WAFepSSSj0IE0jsA%2F132',\n",
" 'Enabled': True,\n",
" 'IsDisplay': True,\n",
" 'Remark': None,\n",
" 'MerAppId': 'wxe6801eee9c3e1853',\n",
" 'IId': 0},\n",
" {'Id': 'MM9995694249',\n",
" 'MemberId': 'MM9995694249',\n",
" 'MemCarId': None,\n",
" 'MerchantId': 'CR0000002145',\n",
" 'MerStoreId': 'CS0000044172',\n",
" 'MerStoreCode': '0214500001',\n",
" 'MerStoreName': '众得信汽车用品',\n",
" 'TagCode': None,\n",
" 'TagName': None,\n",
" 'MemType': 1,\n",
" 'MemSource': 5,\n",
" 'MemName': '王先生',\n",
" 'MemMobile': '18366205288',\n",
" 'MemIDNumber': None,\n",
" 'MemSexType': False,\n",
" 'MemCardNo': '18366205288',\n",
" 'MemBirthDay': None,\n",
" 'MemCardGradeId': 'MG0000025026',\n",
" 'MemCardGradeName': '19周年庆-800',\n",
" 'CardExpiredDate': None,\n",
" 'MemCarsAmount': 1,\n",
" 'BalancePrice': 540.0,\n",
" 'IntegralAmount': 26,\n",
" 'TotalRechargePrice': 800.0,\n",
" 'TotalConsumPrice': 360.0,\n",
" 'TotalConsumTimes': 5,\n",
" 'LastConsumTime': '2026-04-06T14:38:17',\n",
" 'ToStoreTimes': 14,\n",
" 'CreditBillPrice': 0.0,\n",
" 'TotalCreditBillPrice': 0.0,\n",
" 'LastRepayTime': None,\n",
" 'DefRepayTime': None,\n",
" 'MemCreatedAt': '2025-12-21T14:00:26',\n",
" 'MemCompany': None,\n",
" 'MemAddress': None,\n",
" 'MemOpenId': 'otST1wE09a-st8_vY1uTMPPds0is',\n",
" 'IsSetPassword': 0,\n",
" 'AllCarPlateNumber': '鲁BFS2799',\n",
" 'AllCarModelName': '-',\n",
" 'MemWechatName': None,\n",
" 'MemWechatImageLink': 'https%3A%2F%2Fthirdwx.qlogo.cn%2Fmmopen%2Fvi_32%2FQ0j4TwGTfTJeTu8goEic626iakUMLh41gLRQUDN0C38iaXLkNH8X1GD1ic74uGEAh1HbZOSKlh1ic1uCBRmTibN9QKdg%2F132',\n",
" 'Enabled': True,\n",
" 'IsDisplay': True,\n",
" 'Remark': None,\n",
" 'MerAppId': 'wxe6801eee9c3e1853',\n",
" 'IId': 0},\n",
" {'Id': 'MM9995697970',\n",
" 'MemberId': 'MM9995697970',\n",
" 'MemCarId': None,\n",
" 'MerchantId': 'CR0000002145',\n",
" 'MerStoreId': 'CS0000044172',\n",
" 'MerStoreCode': '0214500001',\n",
" 'MerStoreName': '众得信汽车用品',\n",
" 'TagCode': None,\n",
" 'TagName': None,\n",
" 'MemType': 1,\n",
" 'MemSource': 5,\n",
" 'MemName': '谢女士',\n",
" 'MemMobile': '13305320367',\n",
" 'MemIDNumber': None,\n",
" 'MemSexType': False,\n",
" 'MemCardNo': '13305320367',\n",
" 'MemBirthDay': None,\n",
" 'MemCardGradeId': 'MG0000024724',\n",
" 'MemCardGradeName': '18周年庆-1000',\n",
" 'CardExpiredDate': None,\n",
" 'MemCarsAmount': 2,\n",
" 'BalancePrice': 11.0,\n",
" 'IntegralAmount': 178,\n",
" 'TotalRechargePrice': 1800.0,\n",
" 'TotalConsumPrice': 1839.0,\n",
" 'TotalConsumTimes': 4,\n",
" 'LastConsumTime': '2026-04-10T09:31:04',\n",
" 'ToStoreTimes': 10,\n",
" 'CreditBillPrice': 0.0,\n",
" 'TotalCreditBillPrice': 0.0,\n",
" 'LastRepayTime': None,\n",
" 'DefRepayTime': None,\n",
" 'MemCreatedAt': '2026-01-23T17:30:36',\n",
" 'MemCompany': None,\n",
" 'MemAddress': None,\n",
" 'MemOpenId': None,\n",
" 'IsSetPassword': 0,\n",
" 'AllCarPlateNumber': '鲁AEV999,鲁U60J09',\n",
" 'AllCarModelName': '一汽奥迪A645TFSIqutro运动型,-',\n",
" 'MemWechatName': None,\n",
" 'MemWechatImageLink': None,\n",
" 'Enabled': True,\n",
" 'IsDisplay': True,\n",
" 'Remark': None,\n",
" 'MerAppId': 'wxe6801eee9c3e1853',\n",
" 'IId': 0},\n",
" {'Id': 'MM9995700319',\n",
" 'MemberId': 'MM9995700319',\n",
" 'MemCarId': None,\n",
" 'MerchantId': 'CR0000002145',\n",
" 'MerStoreId': 'CS0000044172',\n",
" 'MerStoreCode': '0214500001',\n",
" 'MerStoreName': '众得信汽车用品',\n",
" 'TagCode': None,\n",
" 'TagName': None,\n",
" 'MemType': 1,\n",
" 'MemSource': 0,\n",
" 'MemName': '刘先生',\n",
" 'MemMobile': '18678998881',\n",
" 'MemIDNumber': None,\n",
" 'MemSexType': False,\n",
" 'MemCardNo': '18678998881',\n",
" 'MemBirthDay': None,\n",
" 'MemCardGradeId': 'MG0000025026',\n",
" 'MemCardGradeName': '19周年庆-800',\n",
" 'CardExpiredDate': None,\n",
" 'MemCarsAmount': 1,\n",
" 'BalancePrice': 704.0,\n",
" 'IntegralAmount': 8,\n",
" 'TotalRechargePrice': 800.0,\n",
" 'TotalConsumPrice': 96.0,\n",
" 'TotalConsumTimes': 2,\n",
" 'LastConsumTime': '2025-11-29T15:12:46',\n",
" 'ToStoreTimes': 142,\n",
" 'CreditBillPrice': 0.0,\n",
" 'TotalCreditBillPrice': 0.0,\n",
" 'LastRepayTime': None,\n",
" 'DefRepayTime': None,\n",
" 'MemCreatedAt': '2025-11-04T12:58:15',\n",
" 'MemCompany': None,\n",
" 'MemAddress': None,\n",
" 'MemOpenId': 'otST1wOkxmGIso8GeCcdC7487IuQ',\n",
" 'IsSetPassword': 0,\n",
" 'AllCarPlateNumber': '鲁BY6663',\n",
" 'AllCarModelName': '进口奔驰E级',\n",
" 'MemWechatName': None,\n",
" 'MemWechatImageLink': 'https%3A%2F%2Fthirdwx.qlogo.cn%2Fmmopen%2Fvi_32%2FQ6vSFVsNGKZgWibQ7Z6WZUg8OZagGI0BtTjlcBuh7JhXcLNicnias5xs6HD7B7BP8HJorw8AsI2hyAnn1PTvrfOah7XLopq9weqCC5XAuP2IMo%2F132',\n",
" 'Enabled': True,\n",
" 'IsDisplay': True,\n",
" 'Remark': None,\n",
" 'MerAppId': 'wxe6801eee9c3e1853',\n",
" 'IId': 0}],\n",
" 'HasErrors': False,\n",
" 'Success': True,\n",
" 'AllMessages': '',\n",
" 'Messages': []}"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import requests\n",
"\n",
"headers = {\n",
" 'accept': 'application/json, text/plain, */*',\n",
" 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n",
" 'appkey': '8e240000-3e12-0016-1e0f-08d58267a484',\n",
" 'audl_user': 'a9980000-3e10-0016-3126-08d46c195899',\n",
" 'authtoken': 'fdf15a45-e109-4268-bc82-d45c212d51dc',\n",
" 'content-type': 'application/json',\n",
" 'openchainsign': 'fPhhJnH7gwXJE9x+WgCsIplLwTRqB63/07K4/EidDLJEyqI9BMZULkYsKGMnr8Zqwka1LDP+EeSNM6gtEfLWCT49UhvumHmcqhaFym+Fccl+qMdm810yU5mQkcyhcJGM',\n",
" 'origin': 'https://fos.lunz.cn',\n",
" 'priority': 'u=1, i',\n",
" 'sec-ch-ua': '\"Microsoft Edge\";v=\"147\", \"Not.A/Brand\";v=\"8\", \"Chromium\";v=\"147\"',\n",
" 'sec-ch-ua-mobile': '?0',\n",
" 'sec-ch-ua-platform': '\"Windows\"',\n",
" 'sec-fetch-dest': 'empty',\n",
" 'sec-fetch-mode': 'cors',\n",
" 'sec-fetch-site': 'same-site',\n",
" 'signstring': 'KDhKfryYtTU3pxQ7dhkCuA==',\n",
" 'ucuserid': '8e240000-3e12-0016-8992-08d46c17a418',\n",
" 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0',\n",
"}\n",
"\n",
"json_data = {\n",
" 'paging': {\n",
" 'pageSize': 10,\n",
" 'pageIndex': 1,\n",
" 'sort': [],\n",
" 'filters': [\n",
" {\n",
" 'field': 'IsDisplay',\n",
" 'op': 'eq',\n",
" 'Term': '1',\n",
" },\n",
" {\n",
" 'field': 'Enabled',\n",
" 'op': 'eq',\n",
" 'Term': '1',\n",
" },\n",
" ],\n",
" },\n",
" 'qxentity': {\n",
" 'roletype': 3,\n",
" 'roleId': '8fb00000-3e0d-0016-d490-08d5826bb3c5',\n",
" 'memberuserid': 'a9980000-3e10-0016-3126-08d46c195899',\n",
" 'userid': '8e240000-3e12-0016-8992-08d46c17a418',\n",
" 'signstring': 'KDhKfryYtTU3pxQ7dhkCuA==',\n",
" 'custcentercode': '02145',\n",
" 'custcenterid': 'CR0000002145',\n",
" 'businessproductunitcode': '0214500001',\n",
" 'businessproductunitid': 'CS0000044172',\n",
" 'businessproductunitName': '众得信汽车用品',\n",
" 'openChainSign': 'fPhhJnH7gwXJE9x+WgCsIplLwTRqB63/07K4/EidDLJEyqI9BMZULkYsKGMnr8Zqwka1LDP+EeSNM6gtEfLWCT49UhvumHmcqhaFym+Fccl+qMdm810yU5mQkcyhcJGM',\n",
" 'openChain': 0,\n",
" 'toke': '00000000-0000-0000-0000-000000000000',\n",
" },\n",
" 'searchValue': '',\n",
" 'memType': 1,\n",
"}\n",
"\n",
"response = requests.post('https://fos-api.lunz.cn/api/member/GetMemberinfoList', headers=headers, json=json_data)\n"
]
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"source": "# 单位客户",
"id": "5bd770cacae23461"
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"source": [
"import requests\n",
"\n",
"headers = {\n",
" 'accept': 'application/json, text/plain, */*',\n",
" 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n",
" 'appkey': '8e240000-3e12-0016-1e0f-08d58267a484',\n",
" 'audl_user': 'a9980000-3e10-0016-3126-08d46c195899',\n",
" 'authtoken': 'fdf15a45-e109-4268-bc82-d45c212d51dc',\n",
" 'content-type': 'application/json',\n",
" 'openchainsign': 'fPhhJnH7gwXJE9x+WgCsIplLwTRqB63/07K4/EidDLJEyqI9BMZULkYsKGMnr8Zqwka1LDP+EeSNM6gtEfLWCT49UhvumHmcqhaFym+Fccl+qMdm810yU5mQkcyhcJGM',\n",
" 'origin': 'https://fos.lunz.cn',\n",
" 'priority': 'u=1, i',\n",
" 'sec-ch-ua': '\"Microsoft Edge\";v=\"147\", \"Not.A/Brand\";v=\"8\", \"Chromium\";v=\"147\"',\n",
" 'sec-ch-ua-mobile': '?0',\n",
" 'sec-ch-ua-platform': '\"Windows\"',\n",
" 'sec-fetch-dest': 'empty',\n",
" 'sec-fetch-mode': 'cors',\n",
" 'sec-fetch-site': 'same-site',\n",
" 'signstring': 'KDhKfryYtTU3pxQ7dhkCuA==',\n",
" 'ucuserid': '8e240000-3e12-0016-8992-08d46c17a418',\n",
" 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0',\n",
"}\n",
"\n",
"json_data = {\n",
" 'paging': {\n",
" 'pageSize': 10,\n",
" 'pageIndex': 1,\n",
" 'sort': [],\n",
" 'filters': [\n",
" {\n",
" 'field': 'IsDisplay',\n",
" 'op': 'eq',\n",
" 'Term': '1',\n",
" },\n",
" {\n",
" 'field': 'Enabled',\n",
" 'op': 'eq',\n",
" 'Term': '1',\n",
" },\n",
" ],\n",
" },\n",
" 'qxentity': {\n",
" 'roletype': 3,\n",
" 'roleId': '8fb00000-3e0d-0016-d490-08d5826bb3c5',\n",
" 'memberuserid': 'a9980000-3e10-0016-3126-08d46c195899',\n",
" 'userid': '8e240000-3e12-0016-8992-08d46c17a418',\n",
" 'signstring': 'KDhKfryYtTU3pxQ7dhkCuA==',\n",
" 'custcentercode': '02145',\n",
" 'custcenterid': 'CR0000002145',\n",
" 'businessproductunitcode': '0214500001',\n",
" 'businessproductunitid': 'CS0000044172',\n",
" 'businessproductunitName': '众得信汽车用品',\n",
" 'openChainSign': 'fPhhJnH7gwXJE9x+WgCsIplLwTRqB63/07K4/EidDLJEyqI9BMZULkYsKGMnr8Zqwka1LDP+EeSNM6gtEfLWCT49UhvumHmcqhaFym+Fccl+qMdm810yU5mQkcyhcJGM',\n",
" 'openChain': 0,\n",
" 'toke': '00000000-0000-0000-0000-000000000000',\n",
" },\n",
" 'searchValue': '',\n",
" 'memType': 2,\n",
"}\n",
"\n",
"response = requests.post('https://fos-api.lunz.cn/api/member/GetMemberinfoList', headers=headers, json=json_data)\n",
"\n",
"# Note: json_data will not be serialized by requests\n",
"# exactly as it was in the original request.\n",
"#data = '{\\n \"paging\": {\\n \"pageSize\": 10,\\n \"pageIndex\": 1,\\n \"sort\": [],\\n \"filters\": [\\n {\\n \"field\": \"IsDisplay\",\\n \"op\": \"eq\",\\n \"Term\": \"1\"\\n },\\n {\\n \"field\": \"Enabled\",\\n \"op\": \"eq\",\\n \"Term\": \"1\"\\n }\\n ]\\n },\\n \"qxentity\": {\\n \"roletype\": 3,\\n \"roleId\": \"8fb00000-3e0d-0016-d490-08d5826bb3c5\",\\n \"memberuserid\": \"a9980000-3e10-0016-3126-08d46c195899\",\\n \"userid\": \"8e240000-3e12-0016-8992-08d46c17a418\",\\n \"signstring\": \"KDhKfryYtTU3pxQ7dhkCuA==\",\\n \"custcentercode\": \"02145\",\\n \"custcenterid\": \"CR0000002145\",\\n \"businessproductunitcode\": \"0214500001\",\\n \"businessproductunitid\": \"CS0000044172\",\\n \"businessproductunitName\": \"众得信汽车用品\",\\n \"openChainSign\": \"fPhhJnH7gwXJE9x+WgCsIplLwTRqB63/07K4/EidDLJEyqI9BMZULkYsKGMnr8Zqwka1LDP+EeSNM6gtEfLWCT49UhvumHmcqhaFym+Fccl+qMdm810yU5mQkcyhcJGM\",\\n \"openChain\": 0,\\n \"toke\": \"00000000-0000-0000-0000-000000000000\"\\n },\\n \"searchValue\": \"\",\\n \"memType\": 2\\n}'.encode()\n",
"#response = requests.post('https://fos-api.lunz.cn/api/member/GetMemberinfoList', headers=headers, data=data)\n"
],
"id": "aeee08e848672701"
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"source": "# 会员卡业务",
"id": "f11a5a42a1062bc7"
},
{
"cell_type": "code",
"execution_count": 5,
"id": "88fdb482",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'Data': [{'Id': 'BR9998813435', 'MerchantId': 'CR0000002145', 'MerStoreId': 'CS0000044172', 'MerStoreCode': '0214500001', 'MerName': '众得信汽车养护中心', 'ShortName': '众得信汽车用品', 'TagCode': None, 'TagName': None, 'MemberId': 'MM9995471853', 'MemCarId': None, 'MemCarList': '鲁VL525V', 'CreatedAt': '2026-04-09T11:33:34', 'UpdatedAt': '2026-04-09T11:33:34', 'MemName': '王女士', 'MemType': 1, 'MemMobile': '13553046731', 'PlateNumber': None, 'ProductId': 'PI0000501623', 'ProductName': '999保养套餐(20周年庆)', 'TerminalType': 'DT0000000388', 'BuyChannel': 'DT0000000345', 'BuyChannelName': '到店购买', 'PayType': 'DT0000000316', 'PayTypeName': '微信', 'ProductCost': 55.0, 'ProductPrice': 999.0, 'ActualPrice': 999.0, 'GiftIntegralAmount': 99, 'ExpiryTime': '2029-04-09T23:59:59', 'IsSurplus': True, 'IsRevoke': False, 'Remark': '', 'BuyProductItemName': '洗车;更换火花塞工时费;倒轮平衡;节气门清洗;活动变速箱油;布雷博刹车油;二合一除碳清洗剂;活动空调滤;活动空滤;保养工时费;活动机滤;睿烁活动机油', 'BuyProductItemAmount': '3.00;1.00;1.00;1.00;1.00;1.00;1.00;1.00;1.00;3.00;3.00;12.00', 'BuyProductItemExAmount': '2.00;1.00;1.00;1.00;1.00;1.00;1.00;0.00;0.00;2.00;2.00;8.00', 'BonusEmployId': 'EP0000024462', 'CreatedById': '0f40c19e-9d49-43a7-86a9-541e7673e3a4', 'BonusEmployName': '丁宓', 'ProductDesc': '', 'IId': 1}], 'HasErrors': False, 'Success': True, 'AllMessages': '', 'Messages': []}\n"
]
}
],
"source": [
"import requests\n",
"\n",
"headers = {\n",
" 'accept': 'application/json, text/plain, */*',\n",
" 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n",
" 'appkey': '8e240000-3e12-0016-1e0f-08d58267a484',\n",
" 'audl_user': 'a9980000-3e10-0016-3126-08d46c195899',\n",
" 'authtoken': '9852c674-d229-45ca-8688-7616b121da29',\n",
" 'openchainsign': 'fPhhJnH7gwXJE9x+WgCsIplLwTRqB63/07K4/EidDLJEyqI9BMZULkYsKGMnr8Zqwka1LDP+EeSNM6gtEfLWCT49UhvumHmcqhaFym+Fccl+qMdm810yU5mQkcyhcJGM',\n",
" 'origin': 'https://fos.lunz.cn',\n",
" 'priority': 'u=1, i',\n",
" 'sec-ch-ua': '\"Microsoft Edge\";v=\"147\", \"Not.A/Brand\";v=\"8\", \"Chromium\";v=\"147\"',\n",
" 'sec-ch-ua-mobile': '?0',\n",
" 'sec-ch-ua-platform': '\"Windows\"',\n",
" 'sec-fetch-dest': 'empty',\n",
" 'sec-fetch-mode': 'cors',\n",
" 'sec-fetch-site': 'same-site',\n",
" 'signstring': 'LFytfFXpoVAHFfjaNXFprQ==',\n",
" 'ucuserid': '8e240000-3e12-0016-8992-08d46c17a418',\n",
" 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0',\n",
"}\n",
"\n",
"params = {\n",
" 'merStoreId': 'CS0000044172',\n",
" 'memberId': 'MM9995471853',\n",
"}\n",
"\n",
"response = requests.get('https://fos-api.lunz.cn/api/memberProduc/GetMemProductByMemId', params=params, headers=headers)\n",
"print(response.json())\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "F6+宜搭+其它",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.11"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
@@ -10,32 +10,41 @@
},
{
"cell_type": "code",
"execution_count": 1,
"id": "initial_id",
"metadata": {
"collapsed": true,
"ExecuteTime": {
"end_time": "2026-04-23T08:20:44.296733100Z",
"start_time": "2026-04-23T08:16:24.431928500Z"
}
},
"collapsed": true
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|██████████| 986/986 [09:09<00:00, 1.79it/s]\n"
]
}
],
"source": [
"import time\n",
"import pandas as pd\n",
"import requests\n",
"from tqdm import tqdm\n",
"\n",
"# Cookie 和 Headers 配置\n",
"import requests\n",
"import requests\n",
"\n",
"cookies = {\n",
" 'Hm_lvt_684c22b31d0037eca5a691cde16370ad': '1773130169,1774314982',\n",
" 'acw_tc': '76b20ff917769316546828414e03f43fa61f25e670b6f93f0480d1876ff447',\n",
" 'e_token': '3734b115f75f41ba8534f6780317e904',\n",
" 'e_token': '078d3c82d4cf4d68a86d147b7797cf98',\n",
" 'weixin_token': 'Y',\n",
" 'e_c': 'MjEwNjQxMDIzMTY2ODczNjc0',\n",
" 'e_i_o_c_n': 'JTIyJUU5JUE5JUIwJUU1JThBJUEwJUU2JUIxJUJEJUU4JUJEJUE2JUU2JTlDJThEJUU1JThBJUExJUU0JUI4JUFEJUU1JUJGJTgzJUU2JUIxJTlGJUU1JThDJTk3JUU1JThDJUJBJUU2JTlGJUIzJUU1JUIyJUI4JUU4JUI3JUFGJUU1JUJBJTk3JTVCKzIwMDU2MzQrJTVEJTIy',\n",
" 'e_i_p_c_n': 'JTIyJUU5JUE5JUIwJUU1JThBJUEwJUU2JUIxJUJEJUU4JUJEJUE2JUU2JTlDJThEJUU1JThBJUExJUU0JUI4JUFEJUU1JUJGJTgzJUU2JUIxJTlGJUU1JThDJTk3JUU1JThDJUJBJUU2JTlGJUIzJUU1JUIyJUI4JUU4JUI3JUFGJUU1JUJBJTk3JUU1JUJBJTk3JUU5JTk1JUJGJTVCKzIwMjg3NTcrJTVEJTIy',\n",
" 'e_c': 'MTMxMDU1MjE0NzM1Mjc5ODg=',\n",
" 'acw_tc': '76b20fb517801201588657844e21ecfb30d9ab85051f45e42c388e7e4400de',\n",
"}\n",
"\n",
"\n",
"headers = {\n",
" 'Accept': 'application/json, text/plain, */*',\n",
" 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n",
@@ -47,15 +56,18 @@
" 'Sec-Fetch-Dest': 'empty',\n",
" 'Sec-Fetch-Mode': 'cors',\n",
" 'Sec-Fetch-Site': 'same-origin',\n",
" 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0',\n",
" 'sec-ch-ua': '\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Microsoft Edge\";v=\"146\"',\n",
" 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0',\n",
" 'sec-ch-ua': '\"Chromium\";v=\"148\", \"Microsoft Edge\";v=\"148\", \"Not/A)Brand\";v=\"99\"',\n",
" 'sec-ch-ua-mobile': '?0',\n",
" 'sec-ch-ua-platform': '\"Windows\"',\n",
" # 'Cookie': 'Hm_lvt_684c22b31d0037eca5a691cde16370ad=1773130169,1774314982; e_token=078d3c82d4cf4d68a86d147b7797cf98; weixin_token=Y; e_c=MTMxMDU1MjE0NzM1Mjc5ODg=; acw_tc=76b20fb517801201588657844e21ecfb30d9ab85051f45e42c388e7e4400de',\n",
"}\n",
"\n",
"\n",
"\n",
"\n",
"all_data = []\n",
"for i in tqdm(range(1, 372)):\n",
"for i in tqdm(range(1, 987)):\n",
" json_data = {\n",
" 'pageStart': i,\n",
" 'pageNums': 10,\n",
@@ -73,18 +85,8 @@
" all_data += data_list\n",
"\n",
"df = pd.DataFrame(all_data)\n",
"df.to_excel(r\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\13719825616驰加车辆信息1.xlsx\", index=False)\n"
],
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|██████████| 371/371 [04:12<00:00, 1.47it/s]\n"
]
}
],
"execution_count": 1
"df.to_excel(r\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\18027080999驰加车辆信息1.xlsx\", index=False)\n"
]
},
{
"cell_type": "markdown",
@@ -96,6 +98,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"id": "51f8f4b21505280a",
"metadata": {
"ExecuteTime": {
@@ -103,6 +106,33 @@
"start_time": "2026-04-23T08:20:44.300734900Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"正在获取第 1 页数据以计算总记录数...\n",
"✅ 获取成功。总记录数: 13861\n",
"📄 预计总页数: 1387 (每页 10 条)\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"正在爬取结算单: 100%|██████████| 1387/1387 [21:56<00:00, 1.05it/s]\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"🎉 数据已成功保存至: D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\18027080999结算单.xlsx\n",
"📊 共保存 13861 条记录 (期望: 13861)\n"
]
}
],
"source": [
"import time\n",
"import requests\n",
@@ -110,15 +140,12 @@
"import pandas as pd\n",
"\n",
"# ================= 配置区域 =================\n",
"# Cookie 和 Headers 配置\n",
"cookies = {\n",
" 'Hm_lvt_684c22b31d0037eca5a691cde16370ad': '1773130169,1774314982',\n",
" 'acw_tc': '76b20ff917769316546828414e03f43fa61f25e670b6f93f0480d1876ff447',\n",
" 'e_token': '3734b115f75f41ba8534f6780317e904',\n",
" 'e_token': '078d3c82d4cf4d68a86d147b7797cf98',\n",
" 'weixin_token': 'Y',\n",
" 'e_c': 'MjEwNjQxMDIzMTY2ODczNjc0',\n",
" 'e_i_o_c_n': 'JTIyJUU5JUE5JUIwJUU1JThBJUEwJUU2JUIxJUJEJUU4JUJEJUE2JUU2JTlDJThEJUU1JThBJUExJUU0JUI4JUFEJUU1JUJGJTgzJUU2JUIxJTlGJUU1JThDJTk3JUU1JThDJUJBJUU2JTlGJUIzJUU1JUIyJUI4JUU4JUI3JUFGJUU1JUJBJTk3JTVCKzIwMDU2MzQrJTVEJTIy',\n",
" 'e_i_p_c_n': 'JTIyJUU5JUE5JUIwJUU1JThBJUEwJUU2JUIxJUJEJUU4JUJEJUE2JUU2JTlDJThEJUU1JThBJUExJUU0JUI4JUFEJUU1JUJGJTgzJUU2JUIxJTlGJUU1JThDJTk3JUU1JThDJUJBJUU2JTlGJUIzJUU1JUIyJUI4JUU4JUI3JUFGJUU1JUJBJTk3JUU1JUJBJTk3JUU5JTk1JUJGJTVCKzIwMjg3NTcrJTVEJTIy',\n",
" 'e_c': 'MTMxMDU1MjE0NzM1Mjc5ODg=',\n",
" 'acw_tc': '76b20fb517801201588657844e21ecfb30d9ab85051f45e42c388e7e4400de',\n",
"}\n",
"\n",
"headers = {\n",
@@ -132,19 +159,19 @@
" 'Sec-Fetch-Dest': 'empty',\n",
" 'Sec-Fetch-Mode': 'cors',\n",
" 'Sec-Fetch-Site': 'same-origin',\n",
" 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0',\n",
" 'sec-ch-ua': '\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Microsoft Edge\";v=\"146\"',\n",
" 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0',\n",
" 'sec-ch-ua': '\"Chromium\";v=\"148\", \"Microsoft Edge\";v=\"148\", \"Not/A)Brand\";v=\"99\"',\n",
" 'sec-ch-ua-mobile': '?0',\n",
" 'sec-ch-ua-platform': '\"Windows\"',\n",
" # 'Cookie': 'acw_tc=76b20f7917743149845914058ebc47559507cd60c8642e0d54dbe45b6da222; Hm_lvt_684c22b31d0037eca5a691cde16370ad=1773130169,1774314982; Hm_lpvt_684c22b31d0037eca5a691cde16370ad=1774314982; HMACCOUNT=A6A0585E8C70051D; e_token=f44423067c184689a19eb9dc564bf33e; weixin_token=Y; e_c=NTQ0MzQ1MDE0MTA3ODA1Mg==; e_i_o_c_n=JTIyJUU1JTlCJTlCJUU0JUJDJTlBJUU1JUI4JTgyJUU4JThEJUEzJUU5JUFBJThGJUU2JUIxJUJEJUU4JUJEJUE2JUU3JUJCJUI0JUU0JUJGJUFFJUU0JUI4JUFEJUU1JUJGJTgzJTVCKzIwMDM5MDYrJTVEJTIy; e_i_p_c_n=JTIyJUU5JUE5JUIwJUU1JThBJUEwJUU2JUIxJUJEJUU4JUJEJUE2JUU2JTlDJThEJUU1JThBJUExJUU0JUI4JUFEJUU1JUJGJTgzJUU1JTlCJTlCJUU0JUJDJTlBJUU1JUI4JTgyJUU2JUEzJTk1JUU2JUE2JTg4JUU1JTlCJUFEJUU1JUJBJTk3JUU1JUJBJTk3JUU0JUI4JUJCJTVCKzIwMjk5MDkrJTVEJTIy',\n",
" # 'Cookie': 'Hm_lvt_684c22b31d0037eca5a691cde16370ad=1773130169,1774314982; e_token=078d3c82d4cf4d68a86d147b7797cf98; weixin_token=Y; e_c=MTMxMDU1MjE0NzM1Mjc5ODg=; acw_tc=76b20fb517801201588657844e21ecfb30d9ab85051f45e42c388e7e4400de',\n",
"}\n",
"\n",
"\n",
"# 业务参数配置\n",
"TARGET_URL = 'https://teds.tyreplus.com.cn/api/aftersales/payment/queryPaymentSettlementListByCondition'\n",
"STORE_CODES = [\"350085\"] # 门店列表\n",
"STORE_CODES = [\"125515\"] # 门店列表\n",
"PAGE_SIZE = 10\n",
"OUTPUT_PATH = r\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\15888527505结算单.xlsx\"\n",
"OUTPUT_PATH = r\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\18027080999结算单.xlsx\"\n",
"MAX_RETRIES = 3\n",
"\n",
"# ================= 功能函数 =================\n",
@@ -262,35 +289,7 @@
" print(\"请检查文件路径是否正确,且文件未被其他程序占用。\")\n",
"else:\n",
" print(\"\\n💤 没有数据可保存。\")"
],
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"正在获取第 1 页数据以计算总记录数...\n",
"✅ 获取成功。总记录数: 8310\n",
"📄 预计总页数: 831 (每页 10 条)\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"正在爬取结算单: 100%|██████████| 831/831 [16:01<00:00, 1.16s/it]\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"🎉 数据已成功保存至: D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\15888527505结算单.xlsx\n",
"📊 共保存 8310 条记录 (期望: 8310)\n"
]
}
],
"execution_count": 2
]
},
{
"cell_type": "markdown",
@@ -302,6 +301,7 @@
},
{
"cell_type": "code",
"execution_count": 1,
"id": "b7e0d7a4da6c9b03",
"metadata": {
"ExecuteTime": {
@@ -309,6 +309,36 @@
"start_time": "2026-04-23T08:36:56.424636200Z"
}
},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "12fa0cd57dfa4ce6800c2d0f318d8a1b",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
" 0%| | 0/13861 [00:00<?, ?it/s]"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"请求异常: HTTPSConnectionPool(host='teds.tyreplus.com.cn', port=443): Max retries exceeded with url: /api/v2/aftersales/payment/queryPaymentSettlementDetail (Caused by ConnectTimeoutError(<HTTPSConnection(host='teds.tyreplus.com.cn', port=443) at 0x16c7296a210>, 'Connection to teds.tyreplus.com.cn timed out. (connect timeout=10)')) (paymentNo: SI2604131850723783)\n",
"HTTP 请求失败,状态码: 504 (paymentNo: SI2505301159182058)\n",
"请求异常: HTTPSConnectionPool(host='teds.tyreplus.com.cn', port=443): Max retries exceeded with url: /api/v2/aftersales/payment/queryPaymentSettlementDetail (Caused by ConnectTimeoutError(<HTTPSConnection(host='teds.tyreplus.com.cn', port=443) at 0x16c7296a5d0>, 'Connection to teds.tyreplus.com.cn timed out. (connect timeout=10)')) (paymentNo: SI2504041651975383)\n",
"请求异常: HTTPSConnectionPool(host='teds.tyreplus.com.cn', port=443): Max retries exceeded with url: /api/v2/aftersales/payment/queryPaymentSettlementDetail (Caused by ConnectTimeoutError(<HTTPSConnection(host='teds.tyreplus.com.cn', port=443) at 0x16c7296a210>, 'Connection to teds.tyreplus.com.cn timed out. (connect timeout=10)')) (paymentNo: SI2502080511238782)\n",
"请求异常: HTTPSConnectionPool(host='teds.tyreplus.com.cn', port=443): Max retries exceeded with url: /api/v2/aftersales/payment/queryPaymentSettlementDetail (Caused by ConnectTimeoutError(<HTTPSConnection(host='teds.tyreplus.com.cn', port=443) at 0x16c7296a5d0>, 'Connection to teds.tyreplus.com.cn timed out. (connect timeout=10)')) (paymentNo: SI2502080509234932)\n",
"请求异常: HTTPSConnectionPool(host='teds.tyreplus.com.cn', port=443): Max retries exceeded with url: /api/v2/aftersales/payment/queryPaymentSettlementDetail (Caused by ConnectTimeoutError(<HTTPSConnection(host='teds.tyreplus.com.cn', port=443) at 0x16c7296b890>, 'Connection to teds.tyreplus.com.cn timed out. (connect timeout=10)')) (paymentNo: SI2502080508232252)\n",
"请求异常: HTTPSConnectionPool(host='teds.tyreplus.com.cn', port=443): Max retries exceeded with url: /api/v2/aftersales/payment/queryPaymentSettlementDetail (Caused by ConnectTimeoutError(<HTTPSConnection(host='teds.tyreplus.com.cn', port=443) at 0x16c7296b890>, 'Connection to teds.tyreplus.com.cn timed out. (connect timeout=10)')) (paymentNo: SI2502080502220502)\n",
"请求异常: HTTPSConnectionPool(host='teds.tyreplus.com.cn', port=443): Max retries exceeded with url: /api/v2/aftersales/payment/queryPaymentSettlementDetail (Caused by ConnectTimeoutError(<HTTPSConnection(host='teds.tyreplus.com.cn', port=443) at 0x16c7f86b390>, 'Connection to teds.tyreplus.com.cn timed out. (connect timeout=10)')) (paymentNo: SI2502080457209621)\n"
]
}
],
"source": [
"import requests\n",
"from tqdm.notebook import tqdm\n",
@@ -317,12 +347,12 @@
"\n",
"cookies = {\n",
" 'Hm_lvt_684c22b31d0037eca5a691cde16370ad': '1773130169,1774314982',\n",
" 'acw_tc': '76b20ff917769316546828414e03f43fa61f25e670b6f93f0480d1876ff447',\n",
" 'e_token': '3734b115f75f41ba8534f6780317e904',\n",
" 'acw_tc': '76b20fe117802768775208769ed2b23577232cf69ace8a477e7b4fc813a8c1',\n",
" 'e_token': 'd998582737bd490eb3ae2372b0793f9b',\n",
" 'weixin_token': 'Y',\n",
" 'e_c': 'MjEwNjQxMDIzMTY2ODczNjc0',\n",
" 'e_i_o_c_n': 'JTIyJUU5JUE5JUIwJUU1JThBJUEwJUU2JUIxJUJEJUU4JUJEJUE2JUU2JTlDJThEJUU1JThBJUExJUU0JUI4JUFEJUU1JUJGJTgzJUU2JUIxJTlGJUU1JThDJTk3JUU1JThDJUJBJUU2JTlGJUIzJUU1JUIyJUI4JUU4JUI3JUFGJUU1JUJBJTk3JTVCKzIwMDU2MzQrJTVEJTIy',\n",
" 'e_i_p_c_n': 'JTIyJUU5JUE5JUIwJUU1JThBJUEwJUU2JUIxJUJEJUU4JUJEJUE2JUU2JTlDJThEJUU1JThBJUExJUU0JUI4JUFEJUU1JUJGJTgzJUU2JUIxJTlGJUU1JThDJTk3JUU1JThDJUJBJUU2JTlGJUIzJUU1JUIyJUI4JUU4JUI3JUFGJUU1JUJBJTk3JUU1JUJBJTk3JUU5JTk1JUJGJTVCKzIwMjg3NTcrJTVEJTIy',\n",
" 'e_c': 'MTMxMDU1MjE0NzM1Mjc5ODg=',\n",
" 'e_i_o_c_n': 'JTIyJUU5JUE5JUIwJUU1JThBJUEwJUU2JUIxJUJEJUU4JUJEJUE2JUU2JTlDJThEJUU1JThBJUExJUU0JUI4JUFEJUU1JUJGJTgzJUU1JTg3JUE0JUU1JUIyJTk3JUU5JTk1JTg3JUU5JTg3JTkxJUU1JTg3JUE0JUU4JUI3JUFGJUU1JUJBJTk3JTVCKzIwMDMwMzQrJTVEJTIy',\n",
" 'e_i_p_c_n': 'JTIyJUU5JUE5JUIwJUU1JThBJUEwJUU2JUIxJUJEJUU4JUJEJUE2JUU2JTlDJThEJUU1JThBJUExJUU0JUI4JUFEJUU1JUJGJTgzJUU1JTg3JUE0JUU1JUIyJTk3JUU5JTk1JTg3JUU5JTg3JTkxJUU1JTg3JUE0JUU4JUI3JUFGJUU1JUJBJTk3JUU1JUJBJTk3JUU0JUI4JUJCJTVCKzIwMjQ0OTUrJTVEJTIy',\n",
"}\n",
"\n",
"headers = {\n",
@@ -336,14 +366,14 @@
" 'Sec-Fetch-Dest': 'empty',\n",
" 'Sec-Fetch-Mode': 'cors',\n",
" 'Sec-Fetch-Site': 'same-origin',\n",
" 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0',\n",
" 'sec-ch-ua': '\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Microsoft Edge\";v=\"146\"',\n",
" 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0',\n",
" 'sec-ch-ua': '\"Chromium\";v=\"148\", \"Microsoft Edge\";v=\"148\", \"Not/A)Brand\";v=\"99\"',\n",
" 'sec-ch-ua-mobile': '?0',\n",
" 'sec-ch-ua-platform': '\"Windows\"',\n",
" # 'Cookie': 'acw_tc=76b20f7917743149845914058ebc47559507cd60c8642e0d54dbe45b6da222; Hm_lvt_684c22b31d0037eca5a691cde16370ad=1773130169,1774314982; Hm_lpvt_684c22b31d0037eca5a691cde16370ad=1774314982; HMACCOUNT=A6A0585E8C70051D; e_token=f44423067c184689a19eb9dc564bf33e; weixin_token=Y; e_c=NTQ0MzQ1MDE0MTA3ODA1Mg==; e_i_o_c_n=JTIyJUU1JTlCJTlCJUU0JUJDJTlBJUU1JUI4JTgyJUU4JThEJUEzJUU5JUFBJThGJUU2JUIxJUJEJUU4JUJEJUE2JUU3JUJCJUI0JUU0JUJGJUFFJUU0JUI4JUFEJUU1JUJGJTgzJTVCKzIwMDM5MDYrJTVEJTIy; e_i_p_c_n=JTIyJUU5JUE5JUIwJUU1JThBJUEwJUU2JUIxJUJEJUU4JUJEJUE2JUU2JTlDJThEJUU1JThBJUExJUU0JUI4JUFEJUU1JUJGJTgzJUU1JTlCJTlCJUU0JUJDJTlBJUU1JUI4JTgyJUU2JUEzJTk1JUU2JUE2JTg4JUU1JTlCJUFEJUU1JUJBJTk3JUU1JUJBJTk3JUU0JUI4JUJCJTVCKzIwMjk5MDkrJTVEJTIy',\n",
" # 'Cookie': 'Hm_lvt_684c22b31d0037eca5a691cde16370ad=1773130169,1774314982; acw_tc=76b20fe117802768775208769ed2b23577232cf69ace8a477e7b4fc813a8c1; e_token=d998582737bd490eb3ae2372b0793f9b; weixin_token=Y; e_c=MTMxMDU1MjE0NzM1Mjc5ODg=; e_i_o_c_n=JTIyJUU5JUE5JUIwJUU1JThBJUEwJUU2JUIxJUJEJUU4JUJEJUE2JUU2JTlDJThEJUU1JThBJUExJUU0JUI4JUFEJUU1JUJGJTgzJUU1JTg3JUE0JUU1JUIyJTk3JUU5JTk1JTg3JUU5JTg3JTkxJUU1JTg3JUE0JUU4JUI3JUFGJUU1JUJBJTk3JTVCKzIwMDMwMzQrJTVEJTIy; e_i_p_c_n=JTIyJUU5JUE5JUIwJUU1JThBJUEwJUU2JUIxJUJEJUU4JUJEJUE2JUU2JTlDJThEJUU1JThBJUExJUU0JUI4JUFEJUU1JUJGJTgzJUU1JTg3JUE0JUU1JUIyJTk3JUU5JTk1JTg3JUU5JTg3JTkxJUU1JTg3JUE0JUU4JUI3JUFGJUU1JUJBJTk3JUU1JUJBJTk3JUU0JUI4JUJCJTVCKzIwMjQ0OTUrJTVEJTIy',\n",
"}\n",
"\n",
"df = pd.read_excel(fr\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\15888527505结算单.xlsx\", sheet_name='Sheet1')\n",
"df = pd.read_excel(fr\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\18027080999结算单.xlsx\", sheet_name='Sheet1')\n",
"\n",
"all_data_list = []\n",
"all_service_data = []\n",
@@ -426,54 +456,212 @@
" index=False)\n",
"df3.to_excel(r\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\1历史维修记录明细-支付方式1.xlsx\",\n",
" index=False)\n"
],
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bdef2047419fb838",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
" 0%| | 0/8310 [00:00<?, ?it/s]"
],
"application/vnd.jupyter.widget-view+json": {
"model_id": "97447ec8af08478fa6c858910f2fb294",
"version_major": 2,
"version_minor": 0,
"model_id": "f91b10f941ab425bb47b2b50c721ccd3"
}
"version_minor": 0
},
"text/plain": [
"处理进度: 0%| | 0/13861 [00:00<?, ?条/s]"
]
},
"metadata": {},
"output_type": "display_data",
"jetTransient": {
"display_id": null
}
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"HTTP 请求失败,状态码: 502 (paymentNo: SI2511041053790586)\n",
"请求异常: HTTPSConnectionPool(host='teds.tyreplus.com.cn', port=443): Max retries exceeded with url: /api/v2/aftersales/payment/queryPaymentSettlementDetail (Caused by ConnectTimeoutError(<HTTPSConnection(host='teds.tyreplus.com.cn', port=443) at 0x194ab953c50>, 'Connection to teds.tyreplus.com.cn timed out. (connect timeout=10)')) (paymentNo: SI2511011255731172)\n",
"请求异常: HTTPSConnectionPool(host='teds.tyreplus.com.cn', port=443): Max retries exceeded with url: /api/v2/aftersales/payment/queryPaymentSettlementDetail (Caused by ConnectTimeoutError(<HTTPSConnection(host='teds.tyreplus.com.cn', port=443) at 0x194ab9539d0>, 'Connection to teds.tyreplus.com.cn timed out. (connect timeout=10)')) (paymentNo: SI2504160329323760)\n"
]
"output_type": "display_data"
}
],
"execution_count": 3
},
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"source": [
"import requests\n",
"from tqdm.notebook import tqdm\n",
"import pandas as pd\n",
"import time\n",
"from concurrent.futures import ThreadPoolExecutor, as_completed\n",
"from threading import Lock\n",
"\n",
"# 全局锁,用于保护共享数据的写入\n",
"lock = Lock()\n",
"\n",
"cookies = {\n",
" 'Hm_lvt_684c22b31d0037eca5a691cde16370ad': '1773130169,1774314982',\n",
" 'e_token': '078d3c82d4cf4d68a86d147b7797cf98',\n",
" 'weixin_token': 'Y',\n",
" 'e_c': 'MTMxMDU1MjE0NzM1Mjc5ODg=',\n",
" 'acw_tc': '76b20fb517801201588657844e21ecfb30d9ab85051f45e42c388e7e4400de',\n",
"}\n",
"\n",
"headers = {\n",
" 'Accept': 'application/json, text/plain, */*',\n",
" 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n",
" 'Connection': 'keep-alive',\n",
" 'Content-Type': 'application/json;charset=UTF-8',\n",
" 'Irisclient': 'PC-web#tedspc',\n",
" 'Origin': 'https://teds.tyreplus.com.cn',\n",
" 'Referer': 'https://teds.tyreplus.com.cn/tedspc/index.html',\n",
" 'Sec-Fetch-Dest': 'empty',\n",
" 'Sec-Fetch-Mode': 'cors',\n",
" 'Sec-Fetch-Site': 'same-origin',\n",
" 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0',\n",
" 'sec-ch-ua': '\"Chromium\";v=\"148\", \"Microsoft Edge\";v=\"148\", \"Not/A)Brand\";v=\"99\"',\n",
" 'sec-ch-ua-mobile': '?0',\n",
" 'sec-ch-ua-platform': '\"Windows\"',\n",
"}\n",
"\n",
"df = pd.read_excel(fr\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\18027080999结算单.xlsx\", sheet_name='Sheet1')\n",
"\n",
"all_data_list = []\n",
"all_service_data = []\n",
"all_order_data = []\n",
"all_payment_data = []\n",
"\n",
"def fetch_payment_data(row_data, pbar=None):\n",
" \"\"\"\n",
" 处理单个 paymentNo 的请求\n",
" \"\"\"\n",
" id = row_data[\"paymentNo\"]\n",
" json_data = {\n",
" 'paymentNo': id,\n",
" }\n",
" \n",
" retry_count = 0\n",
" success = False\n",
" result = {\n",
" 'all_data': None,\n",
" 'services': [],\n",
" 'orders': [],\n",
" 'payments': []\n",
" }\n",
" \n",
" while retry_count < 5 and not success:\n",
" try:\n",
" response = requests.post(\n",
" 'https://teds.tyreplus.com.cn/api/v2/aftersales/payment/queryPaymentSettlementDetail',\n",
" cookies=cookies,\n",
" headers=headers,\n",
" json=json_data,\n",
" timeout=10\n",
" )\n",
" \n",
" if response.status_code == 200:\n",
" resp_json = response.json()\n",
" if resp_json.get('code') == 'success' and 'obj' in resp_json:\n",
" all_data = resp_json['obj']\n",
" service_list = all_data.get('orderServiceList', [])\n",
" order_list = all_data.get('orderSkuList', [])\n",
" paymentMethodList = all_data.get('paymentMethodList', [])\n",
" \n",
" # 添加 paymentNo 字段\n",
" all_data['paymentNo'] = id\n",
" result['all_data'] = all_data\n",
" \n",
" for service in service_list:\n",
" service['paymentNo'] = id\n",
" result['services'].append(service)\n",
" \n",
" for order in order_list:\n",
" order['paymentNo'] = id\n",
" result['orders'].append(order)\n",
" \n",
" for payment in paymentMethodList:\n",
" payment['paymentNo'] = id\n",
" result['payments'].append(payment)\n",
" \n",
" success = True\n",
" else:\n",
" print(f\"\\n请求返回非成功状态: {resp_json.get('message', '未知错误')} (paymentNo: {id})\")\n",
" else:\n",
" print(f\"\\nHTTP 请求失败,状态码: {response.status_code} (paymentNo: {id})\")\n",
" \n",
" except Exception as e:\n",
" print(f\"\\n请求异常: {e} (paymentNo: {id})\")\n",
" \n",
" if not success:\n",
" retry_count += 1\n",
" if retry_count < 5:\n",
" time.sleep(2)\n",
" else:\n",
" print(f\"\\n已达到最大重试次数,跳过 paymentNo: {id}\")\n",
" \n",
" # 更新进度条\n",
" if pbar:\n",
" pbar.update(1)\n",
" \n",
" return result\n",
"\n",
"# 使用线程池,最大并发数为3\n",
"max_workers = 3\n",
"\n",
"# 创建进度条\n",
"with tqdm(total=len(df), desc=\"处理进度\", unit=\"条\", ncols=100) as pbar:\n",
" with ThreadPoolExecutor(max_workers=max_workers) as executor:\n",
" # 提交所有任务\n",
" future_to_row = {\n",
" executor.submit(fetch_payment_data, row, pbar): row \n",
" for _, row in df.iterrows()\n",
" }\n",
" \n",
" # 处理完成的任务\n",
" for future in as_completed(future_to_row):\n",
" row = future_to_row[future]\n",
" try:\n",
" result = future.result()\n",
" \n",
" # 使用锁保护共享数据的写入\n",
" with lock:\n",
" if result['all_data']:\n",
" all_data_list.append(result['all_data'])\n",
" \n",
" if result['services']:\n",
" all_service_data.extend(result['services'])\n",
" \n",
" if result['orders']:\n",
" all_order_data.extend(result['orders'])\n",
" \n",
" if result['payments']:\n",
" all_payment_data.extend(result['payments'])\n",
" \n",
" except Exception as e:\n",
" print(f\"\\n处理 paymentNo {row['paymentNo']} 时发生异常: {e}\")\n",
"\n",
"# 创建DataFrame\n",
"print(\"\\n正在保存数据...\")\n",
"df_result = pd.DataFrame(all_data_list)\n",
"df1 = pd.DataFrame(all_service_data)\n",
"df2 = pd.DataFrame(all_order_data)\n",
"df3 = pd.DataFrame(all_payment_data)\n",
"\n",
"# 保存到Excel\n",
"df.to_excel(r\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\1历史维修记录明细1.xlsx\", index=False)\n",
"df1.to_excel(r\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\1历史维修记录明细-服务明细1.xlsx\",\n",
" index=False)\n",
"df2.to_excel(r\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\1历史维修记录明细-产品明细1.xlsx\",\n",
" index=False)\n",
"df3.to_excel(r\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\1历史维修记录明细-支付方式1.xlsx\",\n",
" index=False)"
],
"id": "bdef2047419fb838"
"with tqdm(total=4, desc=\"保存文件\", unit=\"个\") as pbar:\n",
" df_result.to_excel(r\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\1历史维修记录明细1.xlsx\", index=False)\n",
" pbar.update(1)\n",
" pbar.set_postfix_str(\"主数据\")\n",
" \n",
" df1.to_excel(r\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\1历史维修记录明细-服务明细1.xlsx\", index=False)\n",
" pbar.update(1)\n",
" pbar.set_postfix_str(\"服务明细\")\n",
" \n",
" df2.to_excel(r\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\1历史维修记录明细-产品明细1.xlsx\", index=False)\n",
" pbar.update(1)\n",
" pbar.set_postfix_str(\"产品明细\")\n",
" \n",
" df3.to_excel(r\"D:\\Idea Project\\F6+宜搭+其它(1)\\张阳脚本\\文件输出\\1历史维修记录明细-支付方式1.xlsx\", index=False)\n",
" pbar.update(1)\n",
" pbar.set_postfix_str(\"支付方式\")\n",
"\n",
"print(f\"\\n✅ 处理完成!\")\n",
"print(f\"📊 主数据: {len(df_result)} 条\")\n",
"print(f\"📊 服务明细: {len(df1)} 条\")\n",
"print(f\"📊 产品明细: {len(df2)} 条\")\n",
"print(f\"📊 支付方式: {len(df3)} 条\")"
]
}
],
"metadata": {