前段时间练习过的一个小项目,今天再看看,记录一下~
开发工具:PyCharm
Python内置模块:sys、os、base64、json、collections
第三方模块:PyQt5、requests、pandas、Pillow(PIL)、phone、pyecharts
PyQt5模块:实现项目窗体设计 pyecharts模块:绘制分布饼图
说明:
res文件夹:是资源文件夹,里面包含三个文件夹。datafile文件夹保存的是联系人的信息表和联系人分布饼图;img文件夹是保存图片的;ui文件夹保存的是使用Qt Designer工具设计的各种窗体ui文件。Qt Designer的配置参考https://blog.csdn.net/wang_hugh/article/details/88775868addpage.py、editpage.py、gridlayout.py、mainpage.py文件都是由对应的.ui文件使用Pyuic工具转换而来的。其中addpage.ui是添加联系人信息界面;editpage.ui是编辑联系人信息界面;gridlayout.ui是联系人信息展示界面;mainpage.ui是主窗体设计文件card_gray.jpg文件:是名片识别的灰度图card_main.py文件:程序主文件key.txt文件:申请汉王云名片识别接口的keypinyintool.py文件:判断汉字首字母模块文件通过Qt Designer工具设计四个界面,效果如下:
①AI智能联系人管理主页面
② 添加联系人信息页面
③编辑联系人信息界面
④展示联系人信息界面
创建card_main.py文件,初始化页面主页并显示,代码如下:(显示Qt Designer工具设计的界面基本都是这个写法)
#主窗体页面 import sys from PyQt5.QtWidgets import QWidget, QApplication import mainpage class parentWindow(QWidget,mainpage.Ui_Form): # 初始化方法 def __init__(self): # 找到父类主窗体页面 super(parentWindow,self).__init__() # 初始化页面方法 self.setupUi(self) if __name__=='__main__': # 每一个 PyQt5应用都必须创建一个应用对象 app=QApplication(sys.argv) # 初始化页面 window=parentWindow() # 显示主窗体 window.show() # 项目结束调用 sys.exit(app.exec_())点击主窗体的“添加”按钮,显示添加联系人界面。在card_main.py文件中新建childWindow类,初始化页面并建立OPEN()方法,用于显示添加联系人界面,然后在主方法中初始化childWindow类,并且为“添加”按钮添加事件,代码如下:
#主窗体页面 import sys from PyQt5.QtWidgets import QWidget, QApplication import addpage import mainpage class parentWindow(QWidget,mainpage.Ui_Form): # 初始化方法 def __init__(self): # 找到父类主窗体页面 super(parentWindow,self).__init__() # 初始化页面方法 self.setupUi(self) #添加联系人页面 class childWindow(QWidget,addpage.Ui_Form): def __init__(self): # 找到父类 添加联系人页面 super(childWindow,self).__init__() # 初始化页面 self.setupUi(self) #显示添加联系人页面 def OPEN(self): #显示页面 self.show() if __name__=='__main__': # 每一个 PyQt5应用都必须创建一个应用对象 app=QApplication(sys.argv) # 初始化页面 window=parentWindow() # 显示主窗体 window.show() child = childWindow() # 添加页面 window.pushButton_2.clicked.connect(child.OPEN) # 添加按钮事件 # 项目结束调用 sys.exit(app.exec_())运行效果如图:
智能停车场车牌识别系统(一)也有这一部分的实现,写法基本类似。
该项目需要创建一个用于保存联系人信息的表,主要用到pandas模块和os模块。关键代码如下:
import os import pandas as pd cdir=os.getcwd() path=cdir+'/res/datafile/' #建立名片信息表 if not os.path.exists(path): # 建立文件夹 os.makedirs(path) # 姓名 公司 电话 手机 邮件 地址 城市 分类 cardfile=pd.DataFrame(columns=['name','comp','tel','mobile','email','addr','city','type']) # 生成.xlsx文件 cardfile.to_excel(path+'名片信息表.xlsx',sheet_name='data',index=None)运行效果如图:
汉王云名片接口 API申请地址:http://developer.hanvon.com/。进入官网,点击右上角进行登录或注册
登录成功之后,依次点击【开发中心】→【应用管理】,点击右上角的【创建应用】
里面填写的内容参考如下:
再进入到【开发中心】→【应用管理】,点击右边的【Key管理】,进入之后点击【生成服务器key】,点击之后直接点里面的【生成】按钮,不用填写里面的 服务器IP地址白名单。
完成之后会生成Key,界面如下:
将申请的key写到项目根目录下的key.txt文件中。
在childWindow类中创建openfile()方法,该方法是点击添加名片界面里面的“选择名片”按钮触发的方法。先通过QFileDialog.getOpenFileName()方法打开选择文件对话框,选择要识别的名片图片,再调用recg()方法识别名片图片,返回识别结果。如果返回的结果中’code’为0的话,说明返回正确信息,再调用dcontent()方法把信息显示到对应的文本框里面。openfile()方法代码如下:
# 选择名片的按钮执行方法 def openfile(self): # 启动选择文件对话框,查找jpg以及png图片 self.download_path = QFileDialog.getOpenFileName(self, "选择要识别的名片图片", "./res/img", "Image Files(*.jpg *.png)") if not self.download_path[0].strip(): # 判断是否选择图片 # 消息对话框 information 提问对话框 question 警告对话框 warning # 严重错误对话框 critical 关于对话框 about QMessageBox.information(self, '提示信息', '没有选择名片图片') else: pixmap = QPixmap(self.download_path[0]) # self.download_path[0]为图片路径,pixmap解析图片 # print(pixmap) self.label.setPixmap(pixmap) # 设置图片 self.label.setScaledContents(True) # 让图片自适应大小 try: content = self.recg() # 识别名片图片,返回识别结果 except: QMessageBox.information(self, '提示信息', '识别错误,请重新选择图片!') cjson = json.loads(content) print(cjson) if cjson['code'] == '0': # 判断是否正确返回内容 self.dcontent(1, cjson) # 名称 self.dcontent(2, cjson) # 公司 self.dcontent(3, cjson) # 电话 self.dcontent(4, cjson) # 手机 self.dcontent(5, cjson) # 邮件 self.dcontent(6, cjson) # 地址 else: QMessageBox.information(self, '提示信息', '信息码-' + cjson[ 'code'] + ' 请去官网http://developer.hanvon.com/api/toAPIinfo.do?id=2&num= 查看原因')在childWindow类中创建recg()方法,这个方法主要用于处理对选择的图片进行图片识别,返回联系人信息。汉王云官网会有代码示例,网址为:http://developer.hanvon.com/api/toAPIinfo.do?id=2,里面有很多内容,还可以在线体验。该项目的请求接口选择的是多语言带坐标。代码如下:
#识别名片图片 def recg(self): with open('key.txt','r') as file: key=file.readline() #读取写到key.txt文件中的您申请的key #print(key) url='http://api.hanvon.com/rt/ws/v1/ocr/bcard/recg?key=%s&code=cf22e3bb-d41c-47e0-aa44-a92984f5829d' % key img=Image.open(self.download_path[0]) img2=img.convert('L') _w=img2.width _h=img2.height img2=img2.resize((int(_w),int(_h)),Image.ANTIALIAS) img2.save('card_gray.jpg') base64img=base64.b64encode(open('card_gray.jpg','rb').read()).decode() data={"lang":'auto',"color":'gray',"image":base64img} headers={"Content-Type":"application/octet-stream"} resp=requests.post(url,data=json.dumps(data),headers=headers) return resp.textcard_main.py整体代码如下:
#主窗体页面 import base64 import json import sys import os import pandas as pd import requests from PIL import Image # qt5模块 from PyQt5.QtWidgets import * from PyQt5.QtGui import * import addpage import mainpage cdir=os.getcwd() path=cdir+'/res/datafile/' #建立名片信息表 if not os.path.exists(path): # 建立文件夹 os.makedirs(path) # 姓名 公司 电话 手机 邮件 地址 城市 分类 cardfile=pd.DataFrame(columns=['name','comp','tel','mobile','email','addr','city','type']) # 生成.xlsx文件 cardfile.to_excel(path+'名片信息表.xlsx',sheet_name='data',index=None) class parentWindow(QWidget,mainpage.Ui_Form): # 初始化方法 def __init__(self): # 找到父类主窗体页面 super(parentWindow,self).__init__() # 初始化页面方法 self.setupUi(self) #添加联系人页面 class childWindow(QWidget,addpage.Ui_Form): def __init__(self): # 找到父类 添加联系人页面 super(childWindow,self).__init__() # 初始化页面 self.setupUi(self) self.pushButton.clicked.connect(self.openfile) # 给选择名片按钮添加事件 # 显示添加联系人页面 def OPEN(self): self.label.setPixmap(QPixmap("")) # 移除控件上图片 # 移除输入框内容 self.lineEdit_1.setText("") self.lineEdit_2.setText("") self.lineEdit_3.setText("") self.lineEdit_4.setText("") self.lineEdit_5.setText("") self.lineEdit_6.setText("") # 显示页面 self.show() # 选择名片的按钮执行方法 def openfile(self): # 启动选择文件对话框,查找jpg以及png图片 self.download_path = QFileDialog.getOpenFileName(self, "选择要识别的名片图片", "./res/img", "Image Files(*.jpg *.png)") if not self.download_path[0].strip(): # 判断是否选择图片 # 消息对话框 information 提问对话框 question 警告对话框 warning # 严重错误对话框 critical 关于对话框 about QMessageBox.information(self, '提示信息', '没有选择名片图片') else: pixmap = QPixmap(self.download_path[0]) # self.download_path[0]为图片路径,pixmap解析图片 # print(pixmap) self.label.setPixmap(pixmap) # 设置图片 self.label.setScaledContents(True) # 让图片自适应大小 try: content = self.recg() # 识别名片图片,返回识别结果 except: QMessageBox.information(self, '提示信息', '识别错误,请重新选择图片!') cjson = json.loads(content) print(cjson) if cjson['code'] == '0': # 判断是否正确返回内容 self.dcontent(1, cjson) # 名称 self.dcontent(2, cjson) # 公司 self.dcontent(3, cjson) # 电话 self.dcontent(4, cjson) # 手机 self.dcontent(5, cjson) # 邮件 self.dcontent(6, cjson) # 地址 else: QMessageBox.information(self, '提示信息', '信息码-' + cjson[ 'code'] + ' 请去官网http://developer.hanvon.com/api/toAPIinfo.do?id=2&num= 查看原因') # 识别名片图片 def recg(self): with open('key.txt', 'r') as file: key = file.readline() # 读取写到key.txt文件中的您申请的key # print(key) url = 'http://api.hanvon.com/rt/ws/v1/ocr/bcard/recg?key=%s&code=cf22e3bb-d41c-47e0-aa44-a92984f5829d' % key img = Image.open(self.download_path[0]) img2 = img.convert('L') _w = img2.width _h = img2.height img2 = img2.resize((int(_w), int(_h)), Image.ANTIALIAS) img2.save('card_gray.jpg') base64img = base64.b64encode(open('card_gray.jpg', 'rb').read()).decode() data = {"lang": 'auto', "color": 'gray', "image": base64img} headers = {"Content-Type": "application/octet-stream"} resp = requests.post(url, data=json.dumps(data), headers=headers) return resp.text # 设置识别显示的内容 def dcontent(self, k, count): try: if k == 1: self.lineEdit_1.setText(count['name'][0]) elif k == 2: self.lineEdit_2.setText(count['comp'][0]) elif k == 3: self.lineEdit_3.setText(count['tel'][0]) elif k == 4: self.lineEdit_4.setText(count['mobile'][0]) elif k == 5: self.lineEdit_5.setText(count['email'][0]) elif k == 6: self.lineEdit_6.setText(count['addr'][0]) except: pass if __name__=='__main__': # 每一个 PyQt5应用都必须创建一个应用对象 app=QApplication(sys.argv) # 初始化页面 window=parentWindow() # 显示主窗体 window.show() child = childWindow() # 添加页面 window.pushButton_2.clicked.connect(child.OPEN) # 添加按钮事件 # 项目结束调用 sys.exit(app.exec_())运行效果如图: 名片识别成功后,返回的内容如下:
{'code': '0', 'result': None, 'rotatedAngle': '0.0', 'name': ['赵云帆', '466', '224', '796', '313'], 'title': ['总经理', '549', '362', '708', '402'], 'tel': ['0109959179', '374', '460', '642', '485'], 'mobile': [], 'fax': ['0109959176', '372', '497', '642', '523'], 'email': ['zhaoyunfan@hanwang.com', '373', '537', '869', '565'], 'comp': ['汉王科技股份有限公司', '254', '58', '864', '117'], 'dept': [], 'degree': [], 'addr': ['中关村软件园汉王科技', '372', '572', '739', '598'], 'post': [], 'mbox': [], 'htel': [], 'web': [], 'im': [], 'numOther': [], 'other': [], 'extTel': []}上面已经实现了对名片的识别,识别之后需要保存。对信息的保存,主要是保存名片信息到文档中。添加完名片信息之后,点击“保存”按钮,会把名片信息保存到 名片信息表.xlsx 文件中。在childWindow类中创建keep()方法,首先获取输入框内容,根据手机号判断所属区域;接着判断姓名不能为空,根据姓名获取首字母拼音(后面要实现根据首字母进行搜索);最后将名片信息添加到 名片信息表.xlsx 文件中。keep()方法代码如下:
# 保存名片信息到文档 def keep(self): pi_table = pd.read_excel(path + '名片信息表.xlsx', sheet_name='data') # 获取输入框内容 name = self.lineEdit_1.text() comp = self.lineEdit_2.text() tel = self.lineEdit_3.text() mobile = self.lineEdit_4.text() email = self.lineEdit_5.text() addr = self.lineEdit_6.text() # 判断电话是否为空 if mobile.strip(): info = phone.Phone().find(int(mobile)) # 根据电话号判断区域 # print(info) if info == None: city = '其他' else: city = info['province'] else: city = '其他' # 判断姓名是否为空 if name.strip(): type = pinyintool.getPinyin(name[0]) # 获取首字母拼音 # 添加数据 data = pi_table.append({'name': name, 'comp': comp, 'tel': tel, 'mobile': mobile, 'email': email, 'addr': addr, 'city': city, 'type': type, }, ignore_index=True) # 更新xlsx文件 DataFrame(data).to_excel(path + '名片信息表.xlsx', sheet_name='data', index=False) window.dataall() # 主窗体显示全部数据 self.close() # 关闭添加页面 else: QMessageBox.information(self, '提示信息', '姓名不能为空')pinyintool.py代码如下:(通过姓名获取首字母大写)
def single_get_first(unicode1): str1 = unicode1.encode('gbk') try: ord(str1) return str1 except: asc = str1[0] * 256 + str1[1] - 65536 if asc >= -20319 and asc <= -20284: return 'A' if asc >= -20283 and asc <= -19776: return 'B' if asc >= -19775 and asc <= -19219: return 'C' if asc >= -19218 and asc <= -18711: return 'D' if asc >= -18710 and asc <= -18527: return 'E' if asc >= -18526 and asc <= -18240: return 'F' if asc >= -18239 and asc <= -17923: return 'G' if asc >= -17922 and asc <= -17418: return 'H' if asc >= -17417 and asc <= -16475: return 'J' if asc >= -16474 and asc <= -16213: return 'K' if asc >= -16212 and asc <= -15641: return 'L' if asc >= -15640 and asc <= -15166: return 'M' if asc >= -15165 and asc <= -14923: return 'N' if asc >= -14922 and asc <= -14915: return 'O' if asc >= -14914 and asc <= -14631: return 'P' if asc >= -14630 and asc <= -14150: return 'Q' if asc >= -14149 and asc <= -14091: return 'R' if asc >= -14090 and asc <= -13319: return 'S' if asc >= -13318 and asc <= -12839: return 'T' if asc >= -12838 and asc <= -12557: return 'W' if asc >= -12556 and asc <= -11848: return 'X' if asc >= -11847 and asc <= -11056: return 'Y' if asc >= -11055 and asc <= -10247: return 'Z' return '' def getPinyin(string): if string == None: return None if not '\u4e00' <= string <= '\u9fff': return None lst = list(string) charLst = [] for l in lst: charLst.append(single_get_first(l)) return ''.join(charLst)在childWindow类中的 _ _ init _ _( self ) 方法中要给“保存”按钮添加keep事件:
self.pushButton_2.clicked.connect(self.keep) # 给保存按钮添加事件把上面识别的名片信息保存之后,内容如下:
上面内容已经把名片信息保存到了文件中,根据名片信息表的内容与设计的展示名片信息的列表控件,实现主窗体显示联系人信息的功能。首先新建griditem类,主要用于初始化列表样式页面,然后在页面初始化类parentWindow中创建dataall()方法,用于读取联系人信息,并显示到页面上。在dataall()方法里面,每次先循环删除管理器中的组件,然后读取文件内容,循环显示到页面中,每行三个。card_main.py整体代码如下:
# qt5模块 from PyQt5.QtWidgets import * from PyQt5.QtGui import * # 自定义模块 import gridlayout import mainpage import addpage import pinyintool # 内置模块 import sys import requests, base64, json import os # 第三方模块 import pandas as pd from pandas import DataFrame from PIL import Image import phone cdir=os.getcwd() path=cdir+'/res/datafile/' #建立名片信息表 if not os.path.exists(path): # 建立文件夹 os.makedirs(path) # 姓名 公司 电话 手机 邮件 地址 城市 分类 cardfile=pd.DataFrame(columns=['name','comp','tel','mobile','email','addr','city','type']) # 生成.xlsx文件 cardfile.to_excel(path+'名片信息表.xlsx',sheet_name='data',index=None) #主窗体列表样式 class griditem(QWidget,gridlayout.Ui_Form): def __init__(self): #初始化方法 super(griditem,self).__init__() #找到父类主窗体页面 self.setupUi(self) #初始化页面方法 #主窗体页面 class parentWindow(QWidget,mainpage.Ui_Form): # 初始化方法 def __init__(self): # 找到父类主窗体页面 super(parentWindow,self).__init__() # 初始化页面方法 self.setupUi(self) self.dataall() # 显示全部数据 def dataall(self): # 每次先循环删除管理器的组件 while self.gridLayout.count(): item = self.gridLayout.takeAt(0) # 获取第一个组件 widget = item.widget() widget.deleteLater() # 删除组件 i = -1 pi_table = pd.read_excel(path + '名片信息表.xlsx', sheet_name='data') # 读取文件内容 cardArray = pi_table.values # 获取所有数据 for n in range(len(cardArray)): x = n % 3 # x确定每行显示的个数0,1,2,每行三个 if x == 0: # 当x为0的时候设置换行即行数+1 i += 1 item = griditem() # 创建 主窗体列表样式 类 的实例 item.label_1.setText('姓名:' + str(cardArray[n][0])) item.label_2.setText('公司:' + str(cardArray[n][1])) item.label_3.setText('电话:' + str(cardArray[n][2])) item.label_4.setText('手机:' + str(cardArray[n][3])) item.label_5.setText('邮箱:' + str(cardArray[n][4])) item.label_6.setText('地址:' + str(cardArray[n][5])) # 设置名称 为获取项目行数 item.pushButton.setObjectName(str(pi_table.index.tolist()[n])) item.pushButton_1.setObjectName(str(pi_table.index.tolist()[n])) # 为按钮绑定事件 #item.pushButton.clicked.connect(self.edit) # 编辑 #item.pushButton_1.clicked.connect(self.deletedata) # 删除 self.gridLayout.addWidget(item, i, x) # 动态添加控件到gridLayout self.scrollAreaWidgetContents.setMinimumHeight(i * 200) # 设置上下滑动控件可以滑动 self.scrollAreaWidgetContents.setLayout(self.gridLayout) # 设置gridLayout到滑动控件中 #添加联系人页面 class childWindow(QWidget,addpage.Ui_Form): def __init__(self): # 找到父类 添加联系人页面 super(childWindow,self).__init__() # 初始化页面 self.setupUi(self) self.pushButton.clicked.connect(self.openfile) # 给选择名片按钮添加事件 self.pushButton_2.clicked.connect(self.keep) # 给保存按钮添加事件 # 显示添加联系人页面 def OPEN(self): self.label.setPixmap(QPixmap("")) # 移除控件上图片 # 移除输入框内容 self.lineEdit_1.setText("") self.lineEdit_2.setText("") self.lineEdit_3.setText("") self.lineEdit_4.setText("") self.lineEdit_5.setText("") self.lineEdit_6.setText("") # 显示页面 self.show() # 选择名片的按钮执行方法 def openfile(self): # 启动选择文件对话框,查找jpg以及png图片 self.download_path = QFileDialog.getOpenFileName(self, "选择要识别的名片图片", "./res/img", "Image Files(*.jpg *.png)") if not self.download_path[0].strip(): # 判断是否选择图片 # 消息对话框 information 提问对话框 question 警告对话框 warning # 严重错误对话框 critical 关于对话框 about QMessageBox.information(self, '提示信息', '没有选择名片图片') else: pixmap = QPixmap(self.download_path[0]) # self.download_path[0]为图片路径,pixmap解析图片 # print(pixmap) self.label.setPixmap(pixmap) # 设置图片 self.label.setScaledContents(True) # 让图片自适应大小 try: content = self.recg() # 识别名片图片,返回识别结果 except: QMessageBox.information(self, '提示信息', '识别错误,请重新选择图片!') cjson = json.loads(content) print(cjson) if cjson['code'] == '0': # 判断是否正确返回内容 self.dcontent(1, cjson) # 名称 self.dcontent(2, cjson) # 公司 self.dcontent(3, cjson) # 电话 self.dcontent(4, cjson) # 手机 self.dcontent(5, cjson) # 邮件 self.dcontent(6, cjson) # 地址 else: QMessageBox.information(self, '提示信息', '信息码-' + cjson[ 'code'] + ' 请去官网http://developer.hanvon.com/api/toAPIinfo.do?id=2&num= 查看原因') # 识别名片图片 def recg(self): with open('key.txt', 'r') as file: key = file.readline() # 读取写到key.txt文件中的您申请的key # print(key) url = 'http://api.hanvon.com/rt/ws/v1/ocr/bcard/recg?key=%s&code=cf22e3bb-d41c-47e0-aa44-a92984f5829d' % key img = Image.open(self.download_path[0]) img2 = img.convert('L') _w = img2.width _h = img2.height img2 = img2.resize((int(_w), int(_h)), Image.ANTIALIAS) img2.save('card_gray.jpg') base64img = base64.b64encode(open('card_gray.jpg', 'rb').read()).decode() data = {"lang": 'auto', "color": 'gray', "image": base64img} headers = {"Content-Type": "application/octet-stream"} resp = requests.post(url, data=json.dumps(data), headers=headers) return resp.text # 设置识别显示的内容 def dcontent(self, k, count): try: if k == 1: self.lineEdit_1.setText(count['name'][0]) elif k == 2: self.lineEdit_2.setText(count['comp'][0]) elif k == 3: self.lineEdit_3.setText(count['tel'][0]) elif k == 4: self.lineEdit_4.setText(count['mobile'][0]) elif k == 5: self.lineEdit_5.setText(count['email'][0]) elif k == 6: self.lineEdit_6.setText(count['addr'][0]) except: pass # 保存名片信息到文档 def keep(self): pi_table = pd.read_excel(path + '名片信息表.xlsx', sheet_name='data') # 获取输入框内容 name = self.lineEdit_1.text() comp = self.lineEdit_2.text() tel = self.lineEdit_3.text() mobile = self.lineEdit_4.text() email = self.lineEdit_5.text() addr = self.lineEdit_6.text() # 判断电话是否为空 if mobile.strip(): info = phone.Phone().find(int(mobile)) # 根据电话号判断区域 # print(info) if info == None: city = '其他' else: city = info['province'] else: city = '其他' # 判断姓名是否为空 if name.strip(): type = pinyintool.getPinyin(name[0]) # 获取首字母拼音 # 添加数据 data = pi_table.append({'name': name, 'comp': comp, 'tel': tel, 'mobile': mobile, 'email': email, 'addr': addr, 'city': city, 'type': type, }, ignore_index=True) # 更新xlsx文件 DataFrame(data).to_excel(path + '名片信息表.xlsx', sheet_name='data', index=False) window.dataall() # 主窗体显示全部数据 self.close() # 关闭添加页面 else: QMessageBox.information(self, '提示信息', '姓名不能为空') if __name__=='__main__': # 每一个 PyQt5应用都必须创建一个应用对象 app=QApplication(sys.argv) # 初始化页面 window=parentWindow() # 显示主窗体 window.show() child = childWindow() # 添加页面 window.pushButton_2.clicked.connect(child.OPEN) # 添加按钮事件 # 项目结束调用 sys.exit(app.exec_())运行效果如图:
再识别其他名片之后,点击“保存”按钮,主窗体会显示刚才识别的名片信息:
此时该项目的核心内容已经实现了,感觉篇幅有些长了。其他内容在下一篇实现! 链接:AI智能联系人管理系统(二) 转载请注明链接出处,谢谢! 自己完成的一个小项目,记录一下吧。 有什么问题或者需要源代码的,可以评论。我看到的就会回复!!!