查询手机号.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. import json
  2. from datetime import datetime, timezone, timedelta
  3. import tkinter as tk
  4. from tkinter import ttk, messagebox, filedialog
  5. import pandas as pd
  6. import os
  7. from threading import Thread
  8. def parse_records_from_text(json_text):
  9. """
  10. 从粘贴的JSON文本中解析记录数据
  11. """
  12. try:
  13. # 1. 解析外层JSON
  14. if isinstance(json_text, dict):
  15. data = json_text
  16. else:
  17. data = json.loads(json_text)
  18. # 获取records字段
  19. records_str = data.get('data', {}).get('records', '')
  20. if not records_str:
  21. return {'error': 'records字段为空', 'records': []}
  22. # 解析内层records JSON
  23. records_data = json.loads(records_str)
  24. record_map = records_data.get('recordMap', {})
  25. if not record_map:
  26. return {'error': 'recordMap为空', 'records': []}
  27. # 获取fieldMap
  28. formula_info = records_data.get('formulaInfo', {})
  29. field_map = formula_info.get('fieldMap', {})
  30. # 解析所有记录
  31. results = []
  32. for record_key, record in record_map.items():
  33. # 提取手机号
  34. phone = None
  35. if 'fld0af1E3B' in record:
  36. phone_data = record['fld0af1E3B'].get('value', {})
  37. if phone_data and isinstance(phone_data, dict):
  38. phone = phone_data.get('fullPhoneNum')
  39. # 提取批量数据编码
  40. batch_code = None
  41. if 'fldbQDk5b7' in record:
  42. values = record['fldbQDk5b7'].get('value', [])
  43. if values and len(values) > 0:
  44. batch_code = values[0].get('number')
  45. # 提取有顾问跟进
  46. has_advisor = None
  47. if 'fldSiIb9nZ' in field_map:
  48. field_value = field_map['fldSiIb9nZ'].get(record_key, {}).get('value')
  49. if field_value:
  50. try:
  51. field_data = json.loads(field_value)
  52. if field_data.get('data') and len(field_data['data']) > 0:
  53. has_advisor = field_data['data'][0]
  54. except:
  55. pass
  56. # 提取批量创建时间
  57. batch_create_time = None
  58. if 'fldlzQO4tZ' in field_map:
  59. field_value = field_map['fldlzQO4tZ'].get(record_key, {}).get('value')
  60. if field_value:
  61. try:
  62. field_data = json.loads(field_value)
  63. if field_data.get('data') and len(field_data['data']) > 0:
  64. timestamp_data = field_data['data'][0]
  65. if isinstance(timestamp_data, dict) and 'value' in timestamp_data:
  66. timestamp_ms = timestamp_data['value']
  67. if timestamp_ms and isinstance(timestamp_ms, (int, float)):
  68. if timestamp_ms > 1000000000000:
  69. timestamp_sec = timestamp_ms / 1000
  70. else:
  71. timestamp_sec = timestamp_ms
  72. dt = datetime.fromtimestamp(timestamp_sec, tz=timezone(timedelta(hours=8)))
  73. batch_create_time = dt.strftime('%Y-%m-%d %H:%M:%S')
  74. except:
  75. pass
  76. # 提取批量创建人员
  77. batch_creator = None
  78. if 'fldm8odlu2' in field_map:
  79. field_value = field_map['fldm8odlu2'].get(record_key, {}).get('value')
  80. if field_value:
  81. try:
  82. field_data = json.loads(field_value)
  83. if field_data.get('data') and len(field_data['data']) > 0:
  84. batch_creator = field_data['data'][0]
  85. except:
  86. pass
  87. # 提取创建人姓名(从recordMeta)
  88. created_user = None
  89. if 'recordMeta' in records_data:
  90. meta = records_data['recordMeta'].get(record_key, {}).get('recMeta', {})
  91. if 'createdUser' in meta:
  92. # 这里存储的是用户ID,可以尝试从userMap获取姓名
  93. user_map = records_data.get('userMap', {})
  94. user_info = user_map.get(meta['createdUser'], {})
  95. if user_info:
  96. created_user = user_info.get('name') or user_info.get('enName')
  97. if not created_user:
  98. created_user = meta['createdUser']
  99. results.append({
  100. '手机号': phone or '无数据',
  101. '批量数据编码': batch_code or '无数据',
  102. '有顾问跟进': has_advisor or '未知',
  103. '批量创建时间': batch_create_time or '无数据',
  104. '批量创建人员': batch_creator or '无数据',
  105. '创建人': created_user or '无数据'
  106. })
  107. # 映射显示名称
  108. advisor_map = {
  109. 'optXaXhoOh': '有顾问跟进',
  110. 'optJdspBAs': '无顾问跟进'
  111. }
  112. for record in results:
  113. if record['有顾问跟进'] in advisor_map:
  114. record['有顾问跟进'] = advisor_map[record['有顾问跟进']]
  115. return {'error': None, 'records': results, 'count': len(results)}
  116. except json.JSONDecodeError as e:
  117. return {'error': f'JSON解析错误: {str(e)}', 'records': []}
  118. except Exception as e:
  119. return {'error': f'处理错误: {str(e)}', 'records': []}
  120. def parse_and_display():
  121. """解析粘贴的JSON数据并显示在表格中"""
  122. json_text = text_input.get("1.0", tk.END).strip()
  123. if not json_text:
  124. messagebox.showwarning("提示", "请粘贴JSON数据!")
  125. return
  126. # 禁用按钮
  127. btn_parse.config(state='disabled')
  128. btn_parse.config(text='解析中...')
  129. btn_export.config(state='disabled')
  130. # 清空表格
  131. tree.delete(*tree.get_children())
  132. # 更新状态
  133. label_status.config(text="正在解析数据...", foreground='blue')
  134. root.update()
  135. def do_parse():
  136. try:
  137. result = parse_records_from_text(json_text)
  138. if result.get('error'):
  139. root.after(0, lambda: messagebox.showerror("错误", f"解析失败: {result['error']}"))
  140. root.after(0, lambda: label_status.config(text="解析失败", foreground='red'))
  141. else:
  142. records = result.get('records', [])
  143. count = result.get('count', 0)
  144. # 显示在表格中
  145. for record in records:
  146. root.after(0, lambda r=record:
  147. tree.insert('', 'end', values=(
  148. r['手机号'],
  149. r['批量数据编码'],
  150. r['有顾问跟进'],
  151. r['批量创建时间'],
  152. r['批量创建人员']
  153. )))
  154. root.update()
  155. # 保存结果到内存,供导出使用
  156. global last_results
  157. last_results = records
  158. # 更新状态
  159. root.after(0, lambda: label_status.config(
  160. text=f"解析完成!共 {count} 条记录",
  161. foreground='green'))
  162. root.after(0, lambda: btn_export.config(state='normal'))
  163. if count == 0:
  164. root.after(0, lambda: messagebox.showinfo("提示", "未解析到任何记录"))
  165. except Exception as e:
  166. root.after(0, lambda: messagebox.showerror("错误", f"解析失败: {str(e)}"))
  167. root.after(0, lambda: label_status.config(text="解析失败", foreground='red'))
  168. finally:
  169. root.after(0, lambda: btn_parse.config(state='normal'))
  170. root.after(0, lambda: btn_parse.config(text='解析数据'))
  171. Thread(target=do_parse, daemon=True).start()
  172. def export_to_excel():
  173. """导出结果到Excel"""
  174. if not tree.get_children():
  175. messagebox.showwarning("提示", "没有数据可导出,请先解析数据!")
  176. return
  177. # 获取表格数据
  178. data = []
  179. for item in tree.get_children():
  180. values = tree.item(item)['values']
  181. data.append({
  182. '手机号': values[0],
  183. '批量数据编码': values[1],
  184. '有顾问跟进': values[2],
  185. '批量创建时间': values[3],
  186. '批量创建人员': values[4]
  187. })
  188. if not data:
  189. messagebox.showwarning("提示", "没有数据可导出!")
  190. return
  191. # 选择保存位置
  192. file_path = filedialog.asksaveasfilename(
  193. defaultextension=".xlsx",
  194. filetypes=[("Excel文件", "*.xlsx"), ("CSV文件", "*.csv")],
  195. title="保存数据",
  196. initialfile=f"飞书数据_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
  197. )
  198. if not file_path:
  199. return
  200. try:
  201. df = pd.DataFrame(data)
  202. df.to_excel(file_path, index=False, engine='openpyxl')
  203. messagebox.showinfo("成功", f"数据已导出到:\n{file_path}")
  204. label_status.config(text=f"已导出到:{os.path.basename(file_path)}", foreground='green')
  205. except Exception as e:
  206. messagebox.showerror("错误", f"导出失败: {str(e)}")
  207. def clear_all():
  208. """清空所有"""
  209. text_input.delete("1.0", tk.END)
  210. tree.delete(*tree.get_children())
  211. label_status.config(text="已清空", foreground='gray')
  212. btn_export.config(state='disabled')
  213. global last_results
  214. last_results = []
  215. def load_example():
  216. """加载示例数据"""
  217. example_text = """{"code":0,"msg":"","data":{"records":"{\\"tableID\\":\\"tblXQQeIwMqSFSME\\",\\"recordMap\\":{\\"recvtyUtg2C0Xl\\":{\\"fld0af1E3B\\":{\\"value\\":{\\"fullPhoneNum\\":\\"18616756877\\"}},\\"fldbQDk5b7\\":{\\"value\\":[{\\"number\\":\\"SHPLSJ202608281532312\\"}]}},\\"recvtyUtg2Z6yf\\":{\\"fld0af1E3B\\":{\\"value\\":{\\"fullPhoneNum\\":\\"13918255747\\"}},\\"fldbQDk5b7\\":{\\"value\\":[{\\"number\\":\\"SHPLSJ202608281532385\\"}]}}},\\"formulaInfo\\":{\\"fieldMap\\":{\\"fldSiIb9nZ\\":{\\"recvtyUtg2C0Xl\\":{\\"value\\":\\"{\\\\\\"bus_type\\\\\\":[3],\\\\\\"data\\\\\\":[\\\\\\"optJdspBAs\\\\\\"]}\\"},\\"recvtyUtg2Z6yf\\":{\\"value\\":\\"{\\\\\\"bus_type\\\\\\":[3],\\\\\\"data\\\\\\":[\\\\\\"optJdspBAs\\\\\\"]}\\"}},\\"fldm8odlu2\\":{\\"recvtyUtg2C0Xl\\":{\\"value\\":\\"{\\\\\\"bus_type\\\\\\":[201],\\\\\\"data\\\\\\":[\\\\\\"/\\\\\\"]}\\"},\\"recvtyUtg2Z6yf\\":{\\"value\\":\\"{\\\\\\"bus_type\\\\\\":[201],\\\\\\"data\\\\\\":[\\\\\\"/\\\\\\"]}\\"}},\\"fldlzQO4tZ\\":{\\"recvtyUtg2C0Xl\\":{\\"value\\":\\"{\\\\\\"bus_type\\\\\\":[5],\\\\\\"data\\\\\\":[null]}\\"},\\"recvtyUtg2Z6yf\\":{\\"value\\":\\"{\\\\\\"bus_type\\\\\\":[5],\\\\\\"data\\\\\\":[{\\\\\\"value\\\\\\":1787082110000}]}\\"}}}},\\"recordMeta\\":{\\"recvtyUtg2C0Xl\\":{\\"recMeta\\":{\\"createdUser\\":\\"7646228072081099729\\"}},\\"recvtyUtg2Z6yf\\":{\\"recMeta\\":{\\"createdUser\\":\\"7646228072081099729\\"}}}}}"}}"""
  218. text_input.delete("1.0", tk.END)
  219. text_input.insert("1.0", example_text)
  220. label_status.config(text="已加载示例数据,点击「解析数据」查看结果", foreground='blue')
  221. # 创建GUI窗口
  222. root = tk.Tk()
  223. root.title("飞书JSON数据解析工具")
  224. root.geometry("950x700")
  225. root.resizable(False, False)
  226. # 设置样式
  227. style = ttk.Style()
  228. style.theme_use('clam')
  229. # 顶部:输入区域
  230. frame_top = ttk.LabelFrame(root, text="粘贴飞书API返回的JSON数据", padding="10")
  231. frame_top.pack(fill='x', padx=10, pady=5)
  232. text_input = tk.Text(frame_top, font=('Consolas', 9), height=10, wrap='none')
  233. text_input.pack(fill='x', expand=True)
  234. # 添加水平滚动条
  235. scrollbar_h = ttk.Scrollbar(frame_top, orient='horizontal', command=text_input.xview)
  236. text_input.configure(xscrollcommand=scrollbar_h.set)
  237. scrollbar_h.pack(fill='x')
  238. # 提示和按钮区域
  239. hint_frame = ttk.Frame(frame_top)
  240. hint_frame.pack(fill='x', pady=(5, 0))
  241. ttk.Label(hint_frame, text="提示:粘贴完整的API返回JSON(包含data.records字段)",
  242. font=('微软雅黑', 9), foreground='gray').pack(side='left')
  243. btn_frame = ttk.Frame(frame_top)
  244. btn_frame.pack(fill='x', pady=(5, 0))
  245. btn_parse = ttk.Button(btn_frame, text="解析数据", command=parse_and_display, width=12)
  246. btn_parse.pack(side='left', padx=(0, 10))
  247. btn_example = ttk.Button(btn_frame, text="加载示例", command=load_example, width=10)
  248. btn_example.pack(side='left', padx=(0, 10))
  249. btn_clear = ttk.Button(btn_frame, text="清空", command=clear_all, width=10)
  250. btn_clear.pack(side='left', padx=(0, 10))
  251. btn_export = ttk.Button(btn_frame, text="导出Excel", command=export_to_excel, width=12, state='disabled')
  252. btn_export.pack(side='left')
  253. # 中间:结果显示表格
  254. frame_table = ttk.LabelFrame(root, text="解析结果", padding="10")
  255. frame_table.pack(fill='both', expand=True, padx=10, pady=5)
  256. # 创建Treeview
  257. columns = ('手机号', '批量数据编码', '有顾问跟进', '批量创建时间', '批量创建人员')
  258. tree = ttk.Treeview(frame_table, columns=columns, show='headings', height=15)
  259. # 设置列标题和宽度
  260. tree.heading('手机号', text='手机号')
  261. tree.heading('批量数据编码', text='批量数据编码')
  262. tree.heading('有顾问跟进', text='有顾问跟进')
  263. tree.heading('批量创建时间', text='批量创建时间')
  264. tree.heading('批量创建人员', text='批量创建人员')
  265. tree.column('手机号', width=150, anchor='center')
  266. tree.column('批量数据编码', width=200, anchor='center')
  267. tree.column('有顾问跟进', width=150, anchor='center')
  268. tree.column('批量创建时间', width=180, anchor='center')
  269. tree.column('批量创建人员', width=180, anchor='center')
  270. # 添加滚动条
  271. scrollbar = ttk.Scrollbar(frame_table, orient='vertical', command=tree.yview)
  272. tree.configure(yscrollcommand=scrollbar.set)
  273. tree.pack(side='left', fill='both', expand=True)
  274. scrollbar.pack(side='right', fill='y')
  275. # 底部:状态栏
  276. frame_bottom = ttk.Frame(root, padding="5")
  277. frame_bottom.pack(fill='x')
  278. label_status = ttk.Label(frame_bottom, text="请粘贴JSON数据,点击「解析数据」", font=('微软雅黑', 9), foreground='gray')
  279. label_status.pack(side='left')
  280. # 存储最近一次查询结果(用于导出)
  281. last_results = []
  282. # 运行主循环
  283. root.mainloop()