简介:

我们在写python程序的时候,很多时候都有bug,都是自己写的,自己造的孽,又的时候报错又是一堆,不知道是那部分出错了。

我这初学者水平,就是打print,看哪部分执行了,哪部分没执行,由此来看问题大概在什么地方。

其实python有更好的处理方案,logging模块。

从Python2.3起,Python的标准库加入了logging模块.logging模块给运行中的应用提供了一个标准的信息输出接口.典型的logging机制实现是把要输出的数据简单地写到一个txt文件中去.写log文件的方式是一种常见的打log的方式,而logging模块提供的更多,它可以把输出信息输出到所有类文件的对象中去,甚至TCP和UDP的sockets,email服务器,Unix的syslog系统,NT系列的事件log系统,内存的buffer和HTTP服务器,当然还有”真正的”文件中去.

Logging库被设计成模块的方式,它提供了以下几个子模块:loggers,handlers,filters和formatters.Loggers把应用需要直接调用的接口暴露出来.Handlers把log记录发到相应的目的地.Filters决定哪些记录需要发给handler.Formatters定义了log记录的输出格式.

Logger对象扮演了三重角色.首先,它暴露给应用几个方法以便应用可以在运行时写log.其次,Logger对象按照log信息的严重程度或者根据filter对象来决定如何处理log信息(默认的过滤功能).最后,logger还负责把log信息传送给相关的loghandlers.

Logger中最长使用的方法分成两部分中:configuration和message sending.

用于Configuration的方法:

setLevel(level)
    addFilter(filter)
    removeFilter(filter)
    addHandler(handler)
    removeHandler(handler)
    
    
setLevel()方法定义了一个logger处理的最底严重程度(比如说中/高/底三种,我定义为中,那么只有严重程度为中或者高的log才会被处理).debug级别是内置的最低级别,critical是最高级别.举例来说,如果严重级别设为info级,logger仅仅处理info,warning,error和critical级的log,而debug级别的则忽略掉.

根据logger对象的设置,以下的方法被用来写log:

debug(log_message, [*args[, **kwargs]])
    info(log_message, [*args[, **kwargs]])
    warning(log_message, [*args[, **kwargs]])
    error(log_message, [*args[, **kwargs]])
    critical(log_message, [*args[, **kwargs]])
    exception(message[, *args])
    log(log_level, log_message, [*args[, **kwargs]])

Handler对象负责分配合适的log信息(基于log信息的严重程度)到handler指定的目的地.Logger对象可以用addHandler()方法添加零个或多个handler对象到它自身.一个常见的场景是,一个应用可能希望把所有的log信息都发送到一个log文件中去,所有的error级别以上的log信息都发送到stdout,所有critical的log信息通过email发送.这个场景里要求三个不同handler处理,每个handler负责把特定的log信息发送到特定的地方.

标准库里面包括以下的handlers:

StreamHandler       (流式,控制台模式?)
    FileHandler          (文件式)
    RotatingFileHandler    (自动覆盖文件)
    TimedRotatingFileHandler  (按时间自动覆盖文件)
    SocketHandler
    DatagramHandler
    SysLogHandler
    NTEventLogHandler
    SMTPHandler            (SMTP邮件处理)
    MemoryHandler
    HTTPHandler

一:基本使用

1.无脑测试

import logging
import sys # 获取logger实例,如果参数为空则返回root logger
logger = logging.getLogger("AppName") # 指定logger输出格式
formatter = logging.Formatter('%(asctime)s %(levelname)-8s: (%(name)s)%(pathname)s %(message)s') # 文件日志
file_handler = logging.FileHandler("test.log")
file_handler.setFormatter(formatter) # 可以通过setFormatter指定输出格式 # 控制台日志
console_handler = logging.StreamHandler(sys.stdout)
console_handler.formatter = formatter # 也可以直接给formatter赋值 # 为logger添加的日志处理器
logger.addHandler(file_handler)
logger.addHandler(console_handler) # 指定日志的最低输出级别,默认为WARN级别
logger.setLevel(logging.DEBUG) # 输出不同级别的log
logger.debug('this is debug info')
logger.info('this is information')
logger.warning('this is warning message')
logger.error('this is error message')
logger.fatal('this is fatal message, it is same as logger.critical')
logger.critical('this is critical message')
# 记录异常信息 try:
1 / 0
except:
logger.exception('except:') # 移除文件日志处理器,那么log文件就不记录这些日志了
logger.removeHandler(file_handler)
logger.debug('this is debug info----2')
#修改日志输出级别
logger.setLevel(logging.ERROR)
logger.info('this is information----2')
logger.warning('this is warning message----2')
logger.error('this is error message----2')
logger.fatal('this is fatal message, it is same as logger.critical----2')
logger.critical('this is critical message----2')

2.结果

控制台有输出,文件有记录,在改变了日志等级以后,会有部分信息被隐藏。

这不正式我们需要的么?

以前自己打print,调试完了还要再去注释掉或者删除。

用这个就好了。

写代码调试的时候,用logging.debug

调试完了,把logging配置为warning,或者error。debug就不输出了。

二:配置

1.logging自身的方法设置

上面的无脑测试,就是通过logging自身的方法进行的设置,只是不方便统一调用

2.通过文件加载配置(fileConfig)

通过配置文件,加载配置,据说版本比较老,部分配置参数不支持,推荐用字典加载。

3.通过字典加载配置(dictConfig)

推荐用这种方式加载配置信息。

logging的字典配置信息主要分5个部分:基本设置、日志内容格式、过滤器、处理器和管理器。

三:字典加载配置

1.无脑测试

# !/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time : 2018-06-26 9:10
# @Author : Jackadam
# @Email :jackadam@sina.com
# @File : logging_conf.py
# @Software: PyCharm import logging.config, logging, os BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DEBUG = True # 标记是否在开发环境
# 给过滤器使用的判断
class RequireDebugTrue(logging.Filter):
# 实现filter方法
def filter(self, record):
return DEBUG LOGGING = {
# 基本设置
'version': 1, # 日志级别
'disable_existing_loggers': False, # 是否禁用现有的记录器 # 日志格式集合
'formatters': {
# 标准输出格式
'standard': {
# [具体时间][线程名:线程ID][日志名字:日志级别名称(日志级别ID)] [输出的模块:输出的函数]:日志内容
'format': '[%(asctime)s][%(threadName)s:%(thread)d][%(name)s:%(levelname)s(%(lineno)d)][%(module)s:%(funcName)s]:%(message)s'
}
}, # 过滤器
'filters': {
'require_debug_true': {
'()': RequireDebugTrue,
}
}, # 处理器集合
'handlers': {
# 输出到控制台
'console': {
'level': 'DEBUG', # 输出信息的最低级别
'class': 'logging.StreamHandler',
'formatter': 'standard', # 使用standard格式
'filters': ['require_debug_true', ], # 仅当 DEBUG = True 该处理器才生效
},
# 输出到文件
'log': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler',
'formatter': 'standard',
'filename': os.path.join(BASE_DIR, 'debug.log'), # 输出位置
'maxBytes': 1024 * 1024 * 5, # 文件大小 5M
'backupCount': 5, # 备份份数
'encoding': 'utf8', # 文件编码
},
}, # 日志管理器集合
'loggers': {
# 管理器
'default': {
'handlers': ['console', 'log'],
'level': 'DEBUG',
'propagate': True, # 是否传递给父记录器
},
# 管理器
'shoujitiku': {
'handlers': ['console', 'log'],
'level': 'DEBUG',
'propagate': True, # 是否传递给父记录器
}, }
} def log_main():
# 加载前面的标准配置
logging.config.dictConfig(LOGGING) # 获取loggers其中的一个日志管理器
logger = logging.getLogger("shoujitiku")
return logger loger = log_main()
loger.debug('hello')

2.调用方式

函数log_main():

代码中定义了一个字典LOGGING,

加载配置就用

logging.config.dictConfig(LOGGING)

然后使用其中一个日志管理器叫做shoujitiku

logger=logging.getLogger('shoujitiku')

然后返回logger

3.配置文件中的调用

logging.getLogger('shoujitiku'),首先获取日志管理器集合当中的一个管理器shoujitiku,其中handlers,包括了两个处理器,一个是控制台输出console,用方法'class': 'logging.StreamHandler',,一个是日志文件循环输出log,用方法'class': 'logging.handlers.RotatingFileHandler',

每个处理器当中都有'level': 'DEBUG', # 输出信息的最低级别 'class': 'logging.StreamHandler', # 使用什么方法 'formatter': 'standard', # 使用standard格式

4.class

  • StreamHandler instances send error messages to streams (file-like objects).   控制台输出
  • FileHandler instances send error messages to disk files.                                  文件记录
  • RotatingFileHandler instances send error messages to disk files, with support for maximum log file sizes and log file rotation.            滚动文件记录
  • TimedRotatingFileHandler instances send error messages to disk files, rotating the log file at certain timed intervals.                       滚动时间记录
  • SocketHandler instances send error messages to TCP/IP sockets.           TCP端口?不懂,没用。
  • DatagramHandler instances send error messages to UDP sockets.          UDP端口?不懂,没用。
  • SMTPHandler instances send error messages to a designated email address.         SMTP发邮件

5.formatter

Formatter对象定义了log信息的结构和内容,构造时需要带两个参数:

  • 一个是格式化的模板fmt,默认会包含最基本的levelmessage信息
  • 一个是格式化的时间样式datefmt,默认为 2003-07-08 16:49:45,896 (%Y-%m-%d %H:%M:%S)

fmt中允许使用的变量可以参考下表。

  • %(name)s Logger的名字
  • %(levelno)s 数字形式的日志级别
  • %(levelname)s 文本形式的日志级别
  • %(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
  • %(filename)s 调用日志输出函数的模块的文件名
  • %(module)s 调用日志输出函数的模块名|
  • %(funcName)s 调用日志输出函数的函数名|
  • %(lineno)d 调用日志输出函数的语句所在的代码行
  • %(created)f 当前时间,用UNIX标准的表示时间的浮点数表示|
  • %(relativeCreated)d 输出日志信息时的,自Logger创建以来的毫秒数|
  • %(asctime)s 字符串形式的当前时间。默认格式是“2003-07-08 16:49:45,896”。逗号后面的是毫秒
  • %(thread)d 线程ID。可能没有
  • %(threadName)s 线程名。可能没有
  • %(process)d 进程ID。可能没有
  • %(message)s 用户输出的消息

6.过滤器

一个日志管理器可以包含多个处理器。有时在某些环境或场景下,希望不使用某些处理器。

以console那个处理器为例。若用py2exe等打包Python脚本,不要添加console处理器。

打包之后的程序会把控制台的消息也输出一个log日志(而且还弹窗提示)。此时我要通过过滤器判断是否需要输出到控制台该处理器。

过滤器同样放在一个过滤器集合中,如下代码:

  1. #过滤器
  2. 'filters':{
  3. 'require_debug_true': {
  4. '()': RequireDebugTrue,
  5. }
  6. },

其中键名是过滤器的名称,键值是过滤器的内容。

过滤器内容只需要设置一个属性,该属性值是继承了logging.Filter的类。如下代码:

  1. DEBUG = True #标记是否在开发环境
  2. #给过滤器使用的判断
  3. class RequireDebugTrue(logging.Filter):
  4. #实现filter方法
  5. def filter(self, record):
  6. return DEBUG

继承该类需要实现filter方法,返回一个布尔值。

在开发环境,我设置DEBUG为True;在客户端,我设置DEBUG为False。从而控制是否需要使用某些处理器。

当然,该值你也可以想办法通过一下判断动态设置。

貌似是设置一个类,里面有默认方法filter,返回一个布尔值。再过滤器中引用这个类就可以了。

四:SMTPHandler

1.处理器

# 发个邮件
'email':{
'level':'DEBUG',
'class':'logging.handlers.SMTPHandler',
'formatter': 'standard',
'mailhost':'smtp.163.com', #SMTP地址
'fromaddr':'jackadam@163.com', #发件人邮箱
'toaddrs':'jackadam@sina.com', #收件人邮箱,可以['a@163.com','a@sina.com','a@google.com']
'subject':'log info', #邮件标题
'credentials':['jackadam','**********'] #邮箱登陆信息,用户名 ***是密码
},

五:TimedRotatingFileHandler

1.处理器

'log_debug': {
'level': 'DEBUG',
'class': 'logging.handlers.TimedRotatingFileHandler',
'formatter': 'standard',
'filename': os.path.join(BASE_DIR, 'TESTdebug.log'), # 输出位置
'when': 'S', #分割单位
'interval': 1,#单位长度
'backupCount': 10, # 备份份数
'encoding': 'utf8', # 文件编码
},

2.常用参数

    • when:是一个字符串,用于描述滚动周期的基本单位,字符串的值及意义如下:
      “S”: Seconds 秒
      “M”: Minutes 分钟
      “H”: Hours 小时
      “D”: Days 天
      “W”: Week day (0=Monday)  周几
      “midnight”: Roll over at midnight  午夜
    • interval: 滚动周期,单位有when指定,比如:when=’D’,interval=1,表示每天产生一个日志文件;
    • backupCount: 表示日志文件的保留个数;

3.注意事项

除了上述参数之外,TimedRotatingFileHandler还有两个比较重要的成员变量,它们分别是suffix和extMatch。

suffix是指日志文件名的后缀,suffix中通常带有格式化的时间字符串,filename和suffix由“.”连接构成文件名(例如:filename=“runtime”, suffix=“%Y-%m-%d.log”,生成的文件名为runtime.2015-07-06.log)。

extMatch是一个编译好的正则表达式,用于匹配日志文件名的后缀,它必须和suffix是匹配的,如果suffix和extMatch匹配不上的话,过期的日志是不会被删除的。

比如,suffix=“%Y-%m-%d.log”, extMatch的只应该是re.compile(r”^\d{4}-\d{2}-\d{2}.log$”)。

默认情况下,在TimedRotatingFileHandler对象初始化时,suffxi和extMatch会根据when的值进行初始化:
‘S’: suffix=”%Y-%m-%d_%H-%M-%S”, extMatch=r”\^d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}”;
‘M’:suffix=”%Y-%m-%d_%H-%M”,extMatch=r”^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}”;
‘H’:suffix=”%Y-%m-%d_%H”,extMatch=r”^\d{4}-\d{2}-\d{2}_\d{2}”;
‘D’:suffxi=”%Y-%m-%d”,extMatch=r”^\d{4}-\d{2}-\d{2}”;
‘MIDNIGHT’:”%Y-%m-%d”,extMatch=r”^\d{4}-\d{2}-\d{2}”;
‘W’:”%Y-%m-%d”,extMatch=r”^\d{4}-\d{2}-\d{2}”;
如果对日志文件名没有特殊要求的话,可以不用设置suffix和extMatch,如果需要,一定要让它们匹配上。

貌似这是老版本的信息,我在python3.6中没能实现。

六:RotatingFileHandler

1.处理器

'log': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler',
'formatter': 'standard',
'filename': os.path.join(BASE_DIR, 'TESTdebug.log'), # 输出位置
'maxBytes': 1024 * 1024 * 5, # 文件大小 5M
'backupCount': 5, # 备份份数
'encoding': 'utf8', # 文件编码
},

这个是根据文件大小进行滚动记录的。

会自动添加数字后缀。

七:优先级

我们使用getLogger方法时,读取的是日志管理器集合,由日志管理器集合来决定使用哪个或哪几个日志管理器。

而我们在日志管理器集合和处理器都定义了level,那么以级别高的为准,级别低的就不输出了。

八:总结

1.定义日志格式集合,或单个

2.定义处理器,并确定使用什么格式

3.定义日志管理器集合,决定使用什么处理器,控制台 文件 邮件,任意组合。

4.再任意文件中引入

loger = log_main()
loger.debug('hello')

九:新用法--定时循环的任务,发送一个循环的记录到指定邮箱。

1.定义一个文件处理器:

        'log_email': {
'level': 'INFO',
'class': 'logging.FileHandler',
'formatter': 'email',
'filename': os.path.join(BASE_DIR, 'email.log'), # 输出位置
'encoding': 'utf8', # 文件编码
},

2.加入日志管理器集合

3.定义一个函数来进行读取email.log文件,并把内容返回回去,然后删除这个email.log

import os

def Smtp_log():
file=os.getcwd() + '/log/email.log'
fp=open(file,'r+',encoding='utf-8')
text = fp.readlines()
resoult=''
for i in text:
print(i)
resoult=resoult+i
os.remove(file)
return resoult

4.在任意文件中引入

loger.warning(SMTP_log.Smtp_log())

4.一个问题

由于原来发邮件的日志等级高,所以warning的内容也记录在info debug级的日志中。

现在warning发送的是一段时间的日志,造成了info debug日志的重复,虽然有时间记录,可以用来区分,还是不方便,且经常造成软件崩溃。

目前的做法时,另外实例化一个logging

专门用来处理发邮件。

相关代码如下:

def log_main():
# 加载前面的标准配置
logging.config.dictConfig(LOGGING)
# 获取loggers其中的一个日志管理器
logger = logging.getLogger("default")
return logger def loger_email():
# 加载前面的标准配置
logging.config.dictConfig(LOGGING)
# 获取loggers其中的一个日志管理器
logger = logging.getLogger("email")
return logger def Smtp_log():
file=os.getcwd() + '/log/email.log'
fp=open(file,'r+',encoding='utf-8')
text = fp.readlines()
resoult=''
for i in text:
print(i)
resoult=resoult+i
os.remove(file)
return resoult
LOGGING = {
# 基本设置
'version': 1, # 日志级别
'disable_existing_loggers': False, # 是否禁用现有的记录器
'addLevelName': '5, "EMAIL"', # 日志格式集合
'formatters': {
# 标准输出格式
'standard': {
# [具体时间][线程名:线程ID][日志名字:日志级别名称(日志级别ID)] [输出的模块:输出的函数]:日志内容
'format': '[%(asctime)s]:%(levelname)s(%(lineno)d)][%(module)s:%(funcName)s]:%(message)s'
},
'email': {
# [具体时间][线程名:线程ID][日志名字:日志级别名称(日志级别ID)] [输出的模块:输出的函数]:日志内容
'format': '[%(asctime)s]:%(message)s'
}
}, # 过滤器
'filters': {
'require_debug_true': {
'()': RequireDebugTrue,
}
}, # 处理器集合
'handlers': {
# 输出到控制台
'console': {
'level': 'INFO', # 输出信息的最低级别
'class': 'logging.StreamHandler',
'formatter': 'standard', # 使用standard格式
'filters': ['require_debug_true', ], # 仅当 DEBUG = True 该处理器才生效
},
# 输出到文件 'log_debug': {
'level': 'INFO',
'class': 'logging.handlers.TimedRotatingFileHandler',
'formatter': 'standard',
'filename': os.path.join(BASE_DIR, 'debug.log'), # 输出位置
'when': 'midnight',
'interval': 1,
'backupCount': 31, # 备份份数
'encoding': 'utf8', # 文件编码
},
'log_info': {
'level': 'WARNING',
'class': 'logging.handlers.TimedRotatingFileHandler',
'formatter': 'standard',
'filename': os.path.join(BASE_DIR, 'info.log'), # 输出位置
'when': 'midnight',
'interval': 1,
'backupCount': 31, # 备份份数
'encoding': 'utf8', # 文件编码
},
'log_email': {
'level': 'INFO',
'class': 'logging.FileHandler',
'formatter': 'email',
'filename': os.path.join(BASE_DIR, 'email.log'), # 输出位置
'encoding': 'utf8', # 文件编码
},
'email': {
'level': 'DEBUG',
'class': 'logging.handlers.SMTPHandler',
'formatter': 'email',
'mailhost': 'smtp.163.com', # SMTP地址
'fromaddr': 'jackadam@163.com', # 发件人邮箱
'toaddrs': 'jackadam@sina.com', # 收件人邮箱,可以['a@163.com','a@sina.com','a@google.com']
'subject': '答题机报告', # 邮件标题
'credentials': ['jackadam', '********'] # 邮箱登陆信息,用户名 ***是密码
},
}, # 日志管理器集合
'loggers': {
# 管理器
'default': {
'handlers': ['console', 'log_debug', 'log_info', 'log_email'],
'level': 'DEBUG',
'propagate': True, # 是否传递给父记录器
},
'email': {
'handlers': ['email'],
'level': 'DEBUG',
'propagate': True, # 是否传递给父记录器
},
}
}

python logging模块,升级print调试到logging。的更多相关文章

  1. python开发模块基础:异常处理&hashlib&logging&configparser

    一,异常处理 # 异常处理代码 try: f = open('file', 'w') except ValueError: print('请输入一个数字') except Exception as e ...

  2. Python模块之hashlib模块、logging模块

    一.hashlib模块 hashlib模块介绍:hashlib这个模块提供了摘要算法,例如 MD5.hsa1 摘要算法又称为哈希算法,它是通过一个函数,把任意长度的数据转换为一个长度固定的数据串,这个 ...

  3. Python之路(第十七篇)logging模块

    一.logging模块 (一).日志相关概念 日志是一种可以追踪某些软件运行时所发生事件的方法.软件开发人员可以向他们的代码中调用日志记录相关的方法来表明发生了某些事情.一个事件可以用一个可包含可选变 ...

  4. python logging模块使用流程

    #!/usr/local/bin/python # -*- coding:utf-8 -*- import logging logging.debug('debug message') logging ...

  5. python模块: hashlib模块, configparse模块, logging模块,collections模块

    一. hashlib模块 Python的hashlib提供了常见的摘要算法,如MD5,SHA1等等. 摘要算法又称哈希算法.散列算法.它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用 ...

  6. Python入门之logging模块

    本章目录: 一.logging模块简介 二.logging模块的使用 三.通过JSON或者YMAL文件配置logging模块 ===================================== ...

  7. python logging模块使用教程

    简单使用 #!/usr/local/bin/python # -*- coding:utf-8 -*- import logging logging.debug('debug message') lo ...

  8. 13 python logging模块

    原文:http://www.cnblogs.com/dahu-daqing/p/7040764.html 1 logging模块简介 logging模块是Python内置的标准模块,主要用于输出运行日 ...

  9. python 常用模块 time random os模块 sys模块 json & pickle shelve模块 xml模块 configparser hashlib subprocess logging re正则

    python 常用模块 time random os模块 sys模块 json & pickle shelve模块 xml模块 configparser hashlib  subprocess ...

随机推荐

  1. eclipse中选中一个单词 其他相同的也被选中 怎么设置

    转自:https://zhidao.baidu.com/question/72621094.html 打开Window——Preferences 选择Java——Editor——Mark Occure ...

  2. mac显示影藏文件

    http://blog.csdn.net/xiaoyuanzhiying/article/details/46694577 然后finder强制退出,然后重启.

  3. CPU、OpenGL/DirectorX、显卡驱动和GPU之间的关系

  4. PHP中如何命令行

    PHP中如何命令行 一.总结 一句话总结:配置php系统环境,然后命令行中运行 php -f 文件名即可 配置php系统环境 php_-f_文件名 例如: 1.三种运行php的方式? 运行文件_-f ...

  5. Spring Boot设置值:分别用@ConfigurationProperties和@Value给属性设值及其区别

    @ConfigurationProperties给属性映射值编写JavaBean/** 将配置文件application.properties中配置的每一个属性值映射到当前类的属性中:* @Confi ...

  6. 雷林鹏分享:XML 用途

    XML 用途 XML 应用于 Web 开发的许多方面,常用于简化数据的存储和共享. XML 把数据从 HTML 分离 如果您需要在 HTML 文档中显示动态数据,那么每当数据改变时将花费大量的时间来编 ...

  7. 恶意代码分析-使用apataDNS+inetsim模拟网络环境

    准备工作 虚拟机安装: Win7 Ubuntu apateDNS 密码:wplo inetsim 密码:ghla 客户端Win7需要做的工作 安装apateDNS 服务器端Ubuntu需要做的工作 下 ...

  8. 20170923xlVBA_UpdateClientDetailSQL_Dictionary

    Sub UpdateClientDetailWGQ() Dim Wb As Workbook Dim Sht As Worksheet Dim Rng As Range Dim Arr As Vari ...

  9. MVC实战之排球计分(一)—— 需求分析与数据库设计

    此系列博客目的是制作一款排球计分程序.这系列博客讲讲述此软件的 各个功能的设计与实现. 一.需求分析: 这个程序是排球计分程序,其业务非常简单,具体如下: 1.本程序可以选择用户身份,通过不同角度记录 ...

  10. 非常不错的地区三级联动,js简单易懂。封装起来了

    首先需要引入area.js,然后配置并初始化插件: 例: <!-- 绑定银行卡开始 --> <script src="js/area.js"></sc ...