diff --git a/Day61-65/.gitkeep b/Day61-65/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Day66-75/66.网络爬虫和相关工具.md b/Day61-65/61.网络爬虫和相关工具.md similarity index 100% rename from Day66-75/66.网络爬虫和相关工具.md rename to Day61-65/61.网络爬虫和相关工具.md diff --git a/Day61-65/61.预备知识.md b/Day61-65/61.预备知识.md deleted file mode 100644 index 4a95122..0000000 --- a/Day61-65/61.预备知识.md +++ /dev/null @@ -1,140 +0,0 @@ -## 预备知识 - -### 并发编程 - -所谓并发编程就是让程序中有多个部分能够并发或同时执行,并发编程带来的好处不言而喻,其中最为关键的两点是提升了执行效率和改善了用户体验。下面简单阐述一下Python中实现并发编程的三种方式: - -1. 多线程:Python中通过`threading`模块的`Thread`类并辅以`Lock`、`Condition`、`Event`、`Semaphore`和`Barrier`等类来支持多线程编程。Python解释器通过GIL(全局解释器锁)来防止多个线程同时执行本地字节码,这个锁对于CPython(Python解释器的官方实现)是必须的,因为CPython的内存管理并不是线程安全的。因为GIL的存在,Python的多线程并不能利用CPU的多核特性。 - -2. 多进程:使用多进程可以有效的解决GIL的问题,Python中的`multiprocessing`模块提供了`Process`类来实现多进程,其他的辅助类跟`threading`模块中的类类似,由于进程间的内存是相互隔离的(操作系统对进程的保护),进程间通信(共享数据)必须使用管道、套接字等方式,这一点从编程的角度来讲是比较麻烦的,为此,Python的`multiprocessing`模块提供了一个名为`Queue`的类,它基于管道和锁机制提供了多个进程共享的队列。 - - ```Python - """ - 用下面的命令运行程序并查看执行时间,例如: - time python3 example06.py - real 0m20.657s - user 1m17.749s - sys 0m0.158s - 使用多进程后实际执行时间为20.657秒,而用户时间1分17.749秒约为实际执行时间的4倍 - 这就证明我们的程序通过多进程使用了CPU的多核特性,而且这台计算机配置了4核的CPU - """ - import concurrent.futures - import math - - PRIMES = [ - 1116281, - 1297337, - 104395303, - 472882027, - 533000389, - 817504243, - 982451653, - 112272535095293, - 112582705942171, - 112272535095293, - 115280095190773, - 115797848077099, - 1099726899285419 - ] * 5 - - - def is_prime(num): - """判断素数""" - assert num > 0 - for i in range(2, int(math.sqrt(num)) + 1): - if num % i == 0: - return False - return num != 1 - - - def main(): - """主函数""" - with concurrent.futures.ProcessPoolExecutor() as executor: - for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)): - print('%d is prime: %s' % (number, prime)) - - - if __name__ == '__main__': - main() - ``` - -3. 异步编程(异步I/O):所谓异步编程是通过调度程序从任务队列中挑选任务,调度程序以交叉的形式执行这些任务,我们并不能保证任务将以某种顺序去执行,因为执行顺序取决于队列中的一项任务是否愿意将CPU处理时间让位给另一项任务。异步编程通常通过多任务协作处理的方式来实现,由于执行时间和顺序的不确定,因此需要通过钩子函数(回调函数)或者`Future`对象来获取任务执行的结果。目前我们使用的Python 3通过`asyncio`模块以及`await`和`async`关键字(Python 3.5中引入,Python 3.7中正式成为关键字)提供了对异步I/O的支持。 - - ```Python - import asyncio - - - async def fetch(host): - """从指定的站点抓取信息(协程函数)""" - print(f'Start fetching {host}\n') - # 跟服务器建立连接 - reader, writer = await asyncio.open_connection(host, 80) - # 构造请求行和请求头 - writer.write(b'GET / HTTP/1.1\r\n') - writer.write(f'Host: {host}\r\n'.encode()) - writer.write(b'\r\n') - # 清空缓存区(发送请求) - await writer.drain() - # 接收服务器的响应(读取响应行和响应头) - line = await reader.readline() - while line != b'\r\n': - print(line.decode().rstrip()) - line = await reader.readline() - print('\n') - writer.close() - - - def main(): - """主函数""" - urls = ('www.sohu.com', 'www.douban.com', 'www.163.com') - # 获取系统默认的事件循环 - loop = asyncio.get_event_loop() - # 用生成式语法构造一个包含多个协程对象的列表 - tasks = [fetch(url) for url in urls] - # 通过asyncio模块的wait函数将协程列表包装成Task(Future子类)并等待其执行完成 - # 通过事件循环的run_until_complete方法运行任务直到Future完成并返回它的结果 - loop.run_until_complete(asyncio.wait(tasks)) - loop.close() - - - if __name__ == '__main__': - main() - ``` - - > 说明:目前大多数网站都要求基于HTTPS通信,因此上面例子中的网络请求不一定能收到正常的响应,也就是说响应状态码不一定是200,有可能是3xx或者4xx。当然我们这里的重点不在于获得网站响应的内容,而是帮助大家理解`asyncio`模块以及`async`和`await`两个关键字的使用。 - -我们对三种方式的使用场景做一个简单的总结。 - -以下情况需要使用多线程: - -1. 程序需要维护许多共享的状态(尤其是可变状态),Python中的列表、字典、集合都是线程安全的,所以使用线程而不是进程维护共享状态的代价相对较小。 -2. 程序会花费大量时间在I/O操作上,没有太多并行计算的需求且不需占用太多的内存。 - -以下情况需要使用多进程: - -1. 程序执行计算密集型任务(如:字节码操作、数据处理、科学计算)。 -2. 程序的输入可以并行的分成块,并且可以将运算结果合并。 -3. 程序在内存使用方面没有任何限制且不强依赖于I/O操作(如:读写文件、套接字等)。 - -最后,如果程序不需要真正的并发性或并行性,而是更多的依赖于异步处理和回调时,异步I/O就是一种很好的选择。另一方面,当程序中有大量的等待与休眠时,也应该考虑使用异步I/O。 - -> 扩展:关于进程,还需要做一些补充说明。首先,为了控制进程的执行,操作系统内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程使之继续执行,这种行为被称为进程切换(也叫调度)。进程切换是比较耗费资源的操作,因为在进行切换时首先要保存当前进程的上下文(内核再次唤醒该进程时所需要的状态,包括:程序计数器、状态寄存器、数据栈等),然后还要恢复准备执行的进程的上下文。正在执行的进程由于期待的某些事件未发生,如请求系统资源失败、等待某个操作完成、新数据尚未到达等原因会主动由运行状态变为阻塞状态,当进程进入阻塞状态,是不占用CPU资源的。这些知识对于理解到底选择哪种方式进行并发编程也是很重要的。 - -### I/O模式和事件驱动 - -对于一次I/O操作(以读操作为例),数据会先被拷贝到操作系统内核的缓冲区中,然后从操作系统内核的缓冲区拷贝到应用程序的缓冲区(这种方式称为标准I/O或缓存I/O,大多数文件系统的默认I/O都是这种方式),最后交给进程。所以说,当一个读操作发生时(写操作与之类似),它会经历两个阶段:(1)等待数据准备就绪;(2)将数据从内核拷贝到进程中。 - -由于存在这两个阶段,因此产生了以下几种I/O模式: - -1. 阻塞 I/O(blocking I/O):进程发起读操作,如果内核数据尚未就绪,进程会阻塞等待数据直到内核数据就绪并拷贝到进程的内存中。 -2. 非阻塞 I/O(non-blocking I/O):进程发起读操作,如果内核数据尚未就绪,进程不阻塞而是收到内核返回的错误信息,进程收到错误信息可以再次发起读操作,一旦内核数据准备就绪,就立即将数据拷贝到了用户内存中,然后返回。 -3. 多路I/O复用( I/O multiplexing):监听多个I/O对象,当I/O对象有变化(数据就绪)的时候就通知用户进程。多路I/O复用的优势并不在于单个I/O操作能处理得更快,而是在于能处理更多的I/O操作。 -4. 异步 I/O(asynchronous I/O):进程发起读操作后就可以去做别的事情了,内核收到异步读操作后会立即返回,所以用户进程不阻塞,当内核数据准备就绪时,内核发送一个信号给用户进程,告诉它读操作完成了。 - -通常,我们编写一个处理用户请求的服务器程序时,有以下三种方式可供选择: - -1. 每收到一个请求,创建一个新的进程,来处理该请求; -2. 每收到一个请求,创建一个新的线程,来处理该请求; -3. 每收到一个请求,放入一个事件列表,让主进程通过非阻塞I/O方式来处理请求 - -第1种方式实现比较简单,但由于创建进程开销比较大,会导致服务器性能比较差;第2种方式,由于要涉及到线程的同步,有可能会面临竞争、死锁等问题;第3种方式,就是所谓事件驱动的方式,它利用了多路I/O复用和异步I/O的优点,虽然代码逻辑比前面两种都复杂,但能达到最好的性能,这也是目前大多数网络服务器采用的方式。 diff --git a/Day61-65/62.Tornado入门.md b/Day61-65/62.Tornado入门.md deleted file mode 100644 index e1249f8..0000000 --- a/Day61-65/62.Tornado入门.md +++ /dev/null @@ -1,377 +0,0 @@ -## Tornado入门 - -### Tornado概述 - -Python的Web框架种类繁多(比Python语言的关键字还要多),但在众多优秀的Web框架中,Tornado框架最适合用来开发需要处理长连接和应对高并发的Web应用。Tornado框架在设计之初就考虑到性能问题,通过对非阻塞I/O和epoll(Linux 2.5.44内核引入的一种多路I/O复用方式,旨在实现高性能网络服务,在BSD和macOS中是kqueue)的运用,Tornado可以处理大量的并发连接,更轻松的应对C10K(万级并发)问题,是非常理想的实时通信Web框架。 - -> 扩展:基于线程的Web服务器产品(如:Apache)会维护一个线程池来处理用户请求,当用户请求到达时就为该请求分配一个线程,如果线程池中没有空闲线程了,那么可以通过创建新的线程来应付新的请求,但前提是系统尚有空闲的内存空间,显然这种方式很容易将服务器的空闲内存耗尽(大多数Linux发行版本中,默认的线程栈大小为8M)。想象一下,如果我们要开发一个社交类应用,这类应用中,通常需要显示实时更新的消息、对象状态的变化和各种类型的通知,那也就意味着客户端需要保持请求连接来接收服务器的各种响应,在这种情况下,服务器上的工作线程很容易被耗尽,这也就意味着新的请求很有可能无法得到响应。 - -Tornado框架源于FriendFeed网站,在FriendFeed网站被Facebook收购之后得以开源,正式发布的日期是2009年9月10日。Tornado能让你能够快速开发高速的Web应用,如果你想编写一个可扩展的社交应用、实时分析引擎,或RESTful API,那么Tornado框架就是很好的选择。Tornado其实不仅仅是一个Web开发的框架,它还是一个高性能的事件驱动网络访问引擎,内置了高性能的HTTP服务器和客户端(支持同步和异步请求),同时还对WebSocket提供了完美的支持。 - -了解和学习Tornado最好的资料就是它的官方文档,在[tornadoweb.org](http://www.tornadoweb.org)上面有很多不错的例子,你也可以在Github上找到Tornado的源代码和历史版本。 - -### 5分钟上手Tornado - -1. 创建并激活虚拟环境。 - - ```Shell - mkdir hello-tornado - cd hello-tornado - python3 -m venv venv - source venv/bin/activate - ``` - -2. 安装Tornado。 - - ```Shell - pip install tornado - ``` - -3. 编写Web应用。 - - ```Python - """ - example01.py - """ - import tornado.ioloop - import tornado.web - - - class MainHandler(tornado.web.RequestHandler): - - def get(self): - self.write('
{{hint}}
- - - -``` - -模板页userinfo.html。 - -```HTML - - - - - -{{hint}}
- -- 退出聊天室 -
- - - - -``` - diff --git a/Day66-75/69.并发下载.md b/Day61-65/64.并发下载.md similarity index 100% rename from Day66-75/69.并发下载.md rename to Day61-65/64.并发下载.md diff --git a/Day66-75/70.解析动态内容.md b/Day61-65/65.解析动态内容.md similarity index 100% rename from Day66-75/70.解析动态内容.md rename to Day61-65/65.解析动态内容.md diff --git a/Day61-65/65.项目实战.md b/Day61-65/65.项目实战.md deleted file mode 100644 index dbbae84..0000000 --- a/Day61-65/65.项目实战.md +++ /dev/null @@ -1,2 +0,0 @@ -## 项目实战 - diff --git a/Day66-75/75.常见反爬策略及应对方案.md b/Day61-65/75.常见反爬策略及应对方案.md similarity index 100% rename from Day66-75/75.常见反爬策略及应对方案.md rename to Day61-65/75.常见反爬策略及应对方案.md diff --git a/Day61-65/code/.gitkeep b/Day61-65/code/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/Day66-75/code/asyncio01.py b/Day61-65/code/asyncio01.py similarity index 100% rename from Day66-75/code/asyncio01.py rename to Day61-65/code/asyncio01.py diff --git a/Day66-75/code/asyncio02.py b/Day61-65/code/asyncio02.py similarity index 100% rename from Day66-75/code/asyncio02.py rename to Day61-65/code/asyncio02.py diff --git a/Day66-75/code/coroutine01.py b/Day61-65/code/coroutine01.py similarity index 100% rename from Day66-75/code/coroutine01.py rename to Day61-65/code/coroutine01.py diff --git a/Day66-75/code/coroutine02.py b/Day61-65/code/coroutine02.py similarity index 100% rename from Day66-75/code/coroutine02.py rename to Day61-65/code/coroutine02.py diff --git a/Day61-65/code/project_of_tornado/service/__init__.py b/Day61-65/code/douban/douban/__init__.py similarity index 100% rename from Day61-65/code/project_of_tornado/service/__init__.py rename to Day61-65/code/douban/douban/__init__.py diff --git a/Day66-75/code/douban/douban/items.py b/Day61-65/code/douban/douban/items.py similarity index 100% rename from Day66-75/code/douban/douban/items.py rename to Day61-65/code/douban/douban/items.py diff --git a/Day66-75/code/douban/douban/middlewares.py b/Day61-65/code/douban/douban/middlewares.py similarity index 100% rename from Day66-75/code/douban/douban/middlewares.py rename to Day61-65/code/douban/douban/middlewares.py diff --git a/Day66-75/code/douban/douban/pipelines.py b/Day61-65/code/douban/douban/pipelines.py similarity index 100% rename from Day66-75/code/douban/douban/pipelines.py rename to Day61-65/code/douban/douban/pipelines.py diff --git a/Day66-75/code/douban/douban/settings.py b/Day61-65/code/douban/douban/settings.py similarity index 100% rename from Day66-75/code/douban/douban/settings.py rename to Day61-65/code/douban/douban/settings.py diff --git a/Day66-75/code/douban/douban/spiders/__init__.py b/Day61-65/code/douban/douban/spiders/__init__.py similarity index 100% rename from Day66-75/code/douban/douban/spiders/__init__.py rename to Day61-65/code/douban/douban/spiders/__init__.py diff --git a/Day66-75/code/douban/douban/spiders/movie.py b/Day61-65/code/douban/douban/spiders/movie.py similarity index 100% rename from Day66-75/code/douban/douban/spiders/movie.py rename to Day61-65/code/douban/douban/spiders/movie.py diff --git a/Day66-75/code/douban/scrapy.cfg b/Day61-65/code/douban/scrapy.cfg similarity index 100% rename from Day66-75/code/douban/scrapy.cfg rename to Day61-65/code/douban/scrapy.cfg diff --git a/Day66-75/code/example01.py b/Day61-65/code/example01.py similarity index 100% rename from Day66-75/code/example01.py rename to Day61-65/code/example01.py diff --git a/Day66-75/code/example02.py b/Day61-65/code/example02.py similarity index 100% rename from Day66-75/code/example02.py rename to Day61-65/code/example02.py diff --git a/Day66-75/code/example03.py b/Day61-65/code/example03.py similarity index 100% rename from Day66-75/code/example03.py rename to Day61-65/code/example03.py diff --git a/Day66-75/code/example04.py b/Day61-65/code/example04.py similarity index 100% rename from Day66-75/code/example04.py rename to Day61-65/code/example04.py diff --git a/Day66-75/code/example05.py b/Day61-65/code/example05.py similarity index 100% rename from Day66-75/code/example05.py rename to Day61-65/code/example05.py diff --git a/Day66-75/code/example06.py b/Day61-65/code/example06.py similarity index 100% rename from Day66-75/code/example06.py rename to Day61-65/code/example06.py diff --git a/Day66-75/code/example07.py b/Day61-65/code/example07.py similarity index 100% rename from Day66-75/code/example07.py rename to Day61-65/code/example07.py diff --git a/Day66-75/code/example08.py b/Day61-65/code/example08.py similarity index 100% rename from Day66-75/code/example08.py rename to Day61-65/code/example08.py diff --git a/Day66-75/code/example09.py b/Day61-65/code/example09.py similarity index 100% rename from Day66-75/code/example09.py rename to Day61-65/code/example09.py diff --git a/Day66-75/code/example10.py b/Day61-65/code/example10.py similarity index 100% rename from Day66-75/code/example10.py rename to Day61-65/code/example10.py diff --git a/Day66-75/code/example10a.py b/Day61-65/code/example10a.py similarity index 100% rename from Day66-75/code/example10a.py rename to Day61-65/code/example10a.py diff --git a/Day66-75/code/example11.py b/Day61-65/code/example11.py similarity index 100% rename from Day66-75/code/example11.py rename to Day61-65/code/example11.py diff --git a/Day66-75/code/example11a.py b/Day61-65/code/example11a.py similarity index 100% rename from Day66-75/code/example11a.py rename to Day61-65/code/example11a.py diff --git a/Day66-75/code/example12.py b/Day61-65/code/example12.py similarity index 100% rename from Day66-75/code/example12.py rename to Day61-65/code/example12.py diff --git a/Day66-75/code/generator01.py b/Day61-65/code/generator01.py similarity index 100% rename from Day66-75/code/generator01.py rename to Day61-65/code/generator01.py diff --git a/Day66-75/code/generator02.py b/Day61-65/code/generator02.py similarity index 100% rename from Day66-75/code/generator02.py rename to Day61-65/code/generator02.py diff --git a/Day66-75/code/guido.jpg b/Day61-65/code/guido.jpg similarity index 100% rename from Day66-75/code/guido.jpg rename to Day61-65/code/guido.jpg diff --git a/Day61-65/code/hello-tornado/chat_handlers.py b/Day61-65/code/hello-tornado/chat_handlers.py deleted file mode 100644 index 528d8a1..0000000 --- a/Day61-65/code/hello-tornado/chat_handlers.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -handlers.py - 用户登录和聊天的处理器 -""" -import tornado.web -import tornado.websocket - -nicknames = set() -connections = {} - - -class LoginHandler(tornado.web.RequestHandler): - - def get(self): - self.render('login.html', hint='') - - def post(self): - nickname = self.get_argument('nickname') - if nickname in nicknames: - self.render('login.html', hint='昵称已被使用,请更换昵称') - self.set_secure_cookie('nickname', nickname) - self.render('chat.html') - - -class ChatHandler(tornado.websocket.WebSocketHandler): - - def open(self): - nickname = self.get_secure_cookie('nickname').decode() - nicknames.add(nickname) - for conn in connections.values(): - conn.write_message(f'~~~{nickname}进入了聊天室~~~') - connections[nickname] = self - - def on_message(self, message): - nickname = self.get_secure_cookie('nickname').decode() - for conn in connections.values(): - if conn is not self: - conn.write_message(f'{nickname}说:{message}') - - def on_close(self): - nickname = self.get_secure_cookie('nickname').decode() - del connections[nickname] - nicknames.remove(nickname) - for conn in connections.values(): - conn.write_message(f'~~~{nickname}离开了聊天室~~~') diff --git a/Day61-65/code/hello-tornado/chat_server.py b/Day61-65/code/hello-tornado/chat_server.py deleted file mode 100644 index 4985acd..0000000 --- a/Day61-65/code/hello-tornado/chat_server.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -chat_server.py - 聊天服务器 -""" -import os - -import tornado.web -import tornado.ioloop - -from chat_handlers import LoginHandler, ChatHandler - - -def main(): - app = tornado.web.Application( - handlers=[(r'/login', LoginHandler), (r'/chat', ChatHandler)], - template_path=os.path.join(os.path.dirname(__file__), 'templates'), - static_path=os.path.join(os.path.dirname(__file__), 'static'), - cookie_secret='MWM2MzEyOWFlOWRiOWM2MGMzZThhYTk0ZDNlMDA0OTU=', - ) - app.listen(8888) - tornado.ioloop.IOLoop.current().start() - - -if __name__ == '__main__': - main() diff --git a/Day61-65/code/hello-tornado/example01.py b/Day61-65/code/hello-tornado/example01.py deleted file mode 100644 index 6005256..0000000 --- a/Day61-65/code/hello-tornado/example01.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -example01.py - 五分钟上手Tornado -""" -import tornado.ioloop -import tornado.web - -from tornado.options import define, options, parse_command_line - -# 定义默认端口 -define('port', default=8000, type=int) - - -class MainHandler(tornado.web.RequestHandler): - """自定义请求处理器""" - - def get(self): - # 向客户端(浏览器)写入内容 - self.write('- 退出聊天室 -
- - - - diff --git a/Day61-65/code/hello-tornado/templates/login.html b/Day61-65/code/hello-tornado/templates/login.html deleted file mode 100644 index 69d6c55..0000000 --- a/Day61-65/code/hello-tornado/templates/login.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - -{{hint}}
- -{{news['title']}}
-图表组件使用的是 百度图表echarts。
-Amaze UI 有许多不同的表格可用。
-Amaze UI 有许多不同的表格可用。
-"+n.replace("%icon",'
",this.viewport=document.createElement("div"),this.viewport.className="ath-viewport",this.options.modal&&(this.viewport.className+=" ath-modal"),this.options.mandatory&&(this.viewport.className+=" ath-mandatory"),this.viewport.style.position="absolute",this.element=document.createElement("div"),this.element.className="ath-container ath-"+s.OS+" ath-"+s.OS+(s.OSVersion+"").substr(0,1)+" ath-"+(s.isTablet?"tablet":"phone"),this.element.style.cssText="-webkit-transition-property:-webkit-transform,opacity;-webkit-transition-duration:0s;-webkit-transition-timing-function:ease-out;transition-property:transform,opacity;transition-duration:0s;transition-timing-function:ease-out;",this.element.style.webkitTransform="translate3d(0,-"+window.innerHeight+"px,0)",this.element.style.transform="translate3d(0,-"+window.innerHeight+"px,0)",this.options.icon&&this.applicationIcon&&(this.element.className+=" ath-icon",this.img=document.createElement("img"),this.img.className="ath-application-icon",this.img.addEventListener("load",this,!1),this.img.addEventListener("error",this,!1),this.img.src=this.applicationIcon.href,this.element.appendChild(this.img)),this.element.innerHTML+=n,this.viewport.style.left="-99999em",this.viewport.appendChild(this.element),this.container.appendChild(this.viewport),this.img?this.doLog("Add to homescreen: not displaying callout because waiting for img to load"):this._delayedShow()},_delayedShow:function(t){setTimeout(this._show.bind(this),1e3*this.options.startDelay+500)},_show:function(){var t=this;this.updateViewport(),window.addEventListener("resize",this,!1),window.addEventListener("scroll",this,!1),window.addEventListener("orientationchange",this,!1),this.options.modal&&document.addEventListener("touchmove",this,!0),this.options.mandatory||setTimeout(function(){t.element.addEventListener("click",t,!0)},1e3),setTimeout(function(){t.element.style.webkitTransitionDuration="1.2s",t.element.style.transitionDuration="1.2s",t.element.style.webkitTransform="translate3d(0,0,0)",t.element.style.transform="translate3d(0,0,0)"},0),this.options.lifespan&&(this.removeTimer=setTimeout(this.remove.bind(this),1e3*this.options.lifespan)),this.options.onShow&&this.options.onShow.call(this)},remove:function(){clearTimeout(this.removeTimer),this.img&&(this.img.removeEventListener("load",this,!1),this.img.removeEventListener("error",this,!1)),window.removeEventListener("resize",this,!1),window.removeEventListener("scroll",this,!1),window.removeEventListener("orientationchange",this,!1),document.removeEventListener("touchmove",this,!0),this.element.removeEventListener("click",this,!0),this.element.addEventListener("transitionend",this,!1),this.element.addEventListener("webkitTransitionEnd",this,!1),this.element.addEventListener("MSTransitionEnd",this,!1),this.element.style.webkitTransitionDuration="0.3s",this.element.style.opacity="0"},_removeElements:function(){this.element.removeEventListener("transitionend",this,!1),this.element.removeEventListener("webkitTransitionEnd",this,!1),this.element.removeEventListener("MSTransitionEnd",this,!1),this.container.removeChild(this.viewport),this.shown=!1,this.options.onRemove&&this.options.onRemove.call(this)},updateViewport:function(){if(this.shown){this.viewport.style.width=window.innerWidth+"px",this.viewport.style.height=window.innerHeight+"px",this.viewport.style.left=window.scrollX+"px",this.viewport.style.top=window.scrollY+"px";var t=document.documentElement.clientWidth;this.orientation=t>document.documentElement.clientHeight?"landscape":"portrait";var e="ios"==s.OS?"portrait"==this.orientation?screen.width:screen.height:screen.width;this.scale=screen.width>t?1:e/window.innerWidth,this.element.style.fontSize=this.options.fontSize/this.scale+"px"}},resize:function(){clearTimeout(this.resizeTimer),this.resizeTimer=setTimeout(this.updateViewport.bind(this),100)},updateSession:function(){s.hasLocalStorage!==!1&&localStorage&&localStorage.setItem(this.options.appID,JSON.stringify(this.session))},clearSession:function(){this.session=v,this.updateSession()},getItem:function(t){try{if(!localStorage)throw new Error("localStorage is not defined");return localStorage.getItem(t)}catch(e){s.hasLocalStorage=!1}},optOut:function(){this.session.optedout=!0,this.updateSession()},optIn:function(){this.session.optedout=!1,this.updateSession()},clearDisplayCount:function(){this.session.displayCount=0,this.updateSession()},_preventDefault:function(t){t.preventDefault(),t.stopPropagation()}},s.VERSION="3.2.2",t.exports=r.addToHomescreen=s},function(t,e,i){"use strict";var n=i(1),s=i(2),o=function(t,e){var i=this;this.options=n.extend({},o.DEFAULTS,e),this.$element=n(t),this.$element.addClass("am-fade am-in").on("click.alert.amui",".am-close",function(){i.close()})};o.DEFAULTS={removeElement:!0},o.prototype.close=function(){function t(){e.trigger("closed.alert.amui").remove()}var e=this.$element;e.trigger("close.alert.amui").removeClass("am-in"),s.support.transition&&e.hasClass("am-fade")?e.one(s.support.transition.end,t).emulateTransitionEnd(200):t()},s.plugin("alert",o),n(document).on("click.alert.amui.data-api","[data-am-alert]",function(t){var e=n(t.target);e.is(".am-close")&&n(this).alert("close")}),t.exports=o},function(t,e,i){"use strict";var n=i(1),s=i(2),o=function(t,e){this.$element=n(t),this.options=n.extend({},o.DEFAULTS,e),this.isLoading=!1,this.hasSpinner=!1};o.DEFAULTS={loadingText:"loading...",disabledClassName:"am-disabled",activeClassName:"am-active",spinner:void 0},o.prototype.setState=function(t,e){var i=this.$element,o="disabled",a=i.data(),r=this.options,l=i.is("input")?"val":"html",c="am-btn-"+t+" "+r.disabledClassName;t+="Text",r.resetText||(r.resetText=i[l]()),s.support.animation&&r.spinner&&"html"===l&&!this.hasSpinner&&(r.loadingText=''+r.loadingText,this.hasSpinner=!0),e=e||(void 0===a[t]?r[t]:a[t]),i[l](e),setTimeout(n.proxy(function(){"loadingText"===t?(i.addClass(c).attr(o,o),this.isLoading=!0):this.isLoading&&(i.removeClass(c).removeAttr(o),this.isLoading=!1)},this),0)},o.prototype.toggle=function(){var t=!0,e=this.$element,i=this.$element.parent('[class*="am-btn-group"]'),n=o.DEFAULTS.activeClassName;if(i.length){var s=this.$element.find("input");"radio"==s.prop("type")&&(s.prop("checked")&&e.hasClass(n)?t=!1:i.find("."+n).removeClass(n)),t&&s.prop("checked",!e.hasClass(n)).trigger("change")}t&&(e.toggleClass(n),e.hasClass(n)||e.blur())},s.plugin("button",o,{dataOptions:"data-am-loading",methodCall:function(t,e){"toggle"===t[0]?e.toggle():"string"==typeof t[0]&&e.setState.apply(e,t)}}),n(document).on("click.button.amui.data-api","[data-am-button]",function(t){t.preventDefault();var e=n(t.target);e.hasClass("am-btn")||(e=e.closest(".am-btn")),e.button("toggle")}),s.ready(function(t){n("[data-am-loading]",t).button(),n("[data-am-button]",t).find("input:checked").each(function(){n(this).parent("label").addClass(o.DEFAULTS.activeClassName)})}),t.exports=s.button=o},function(t,e,i){"use strict";function n(t){return this.each(function(){var e=s(this),i=e.data("amui.collapse"),n=s.extend({},a.DEFAULTS,o.utils.options(e.attr("data-am-collapse")),"object"==typeof t&&t);!i&&n.toggle&&"open"===t&&(t=!t),i||e.data("amui.collapse",i=new a(this,n)),"string"==typeof t&&i[t]()})}var s=i(1),o=i(2),a=function(t,e){this.$element=s(t),this.options=s.extend({},a.DEFAULTS,e),this.transitioning=null,this.options.parent&&(this.$parent=s(this.options.parent)),this.options.toggle&&this.toggle()};a.DEFAULTS={toggle:!0},a.prototype.open=function(){if(!this.transitioning&&!this.$element.hasClass("am-in")){var t=s.Event("open.collapse.amui");if(this.$element.trigger(t),!t.isDefaultPrevented()){var e=this.$parent&&this.$parent.find("> .am-panel > .am-in");if(e&&e.length){var i=e.data("amui.collapse");if(i&&i.transitioning)return;n.call(e,"close"),i||e.data("amui.collapse",null)}this.$element.removeClass("am-collapse").addClass("am-collapsing").height(0),this.transitioning=1;var a=function(){this.$element.removeClass("am-collapsing").addClass("am-collapse am-in").height("").trigger("opened.collapse.amui"),this.transitioning=0};if(!o.support.transition)return a.call(this);var r=this.$element[0].scrollHeight;this.$element.one(o.support.transition.end,s.proxy(a,this)).emulateTransitionEnd(300).css({height:r})}}},a.prototype.close=function(){if(!this.transitioning&&this.$element.hasClass("am-in")){var t=s.Event("close.collapse.amui");if(this.$element.trigger(t),!t.isDefaultPrevented()){this.$element.height(this.$element.height()).redraw(),this.$element.addClass("am-collapsing").removeClass("am-collapse am-in"),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.trigger("closed.collapse.amui").removeClass("am-collapsing").addClass("am-collapse")};return o.support.transition?void this.$element.height(0).one(o.support.transition.end,s.proxy(e,this)).emulateTransitionEnd(300):e.call(this)}}},a.prototype.toggle=function(){this[this.$element.hasClass("am-in")?"close":"open"]()},s.fn.collapse=n,s(document).on("click.collapse.amui.data-api","[data-am-collapse]",function(t){var e,i=s(this),a=o.utils.options(i.attr("data-am-collapse")),r=a.target||t.preventDefault()||(e=i.attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,""),l=s(r),c=l.data("amui.collapse"),u=c?"toggle":a,h=a.parent,d=h&&s(h);c&&c.transitioning||(d&&d.find("[data-am-collapse]").not(i).addClass("am-collapsed"),i[l.hasClass("am-in")?"addClass":"removeClass"]("am-collapsed")),n.call(l,u)}),t.exports=o.collapse=a},function(t,e,i){"use strict";var n=i(1),s=i(2),o=n(document),a=function(t,e){if(this.$element=n(t),this.options=n.extend({},a.DEFAULTS,e),this.format=r.parseFormat(this.options.format),this.$element.data("date",this.options.date),this.language=this.getLocale(this.options.locale),this.theme=this.options.theme,this.$picker=n(r.template).appendTo("body").on({click:n.proxy(this.click,this)}),this.isInput=this.$element.is("input"),this.component=!!this.$element.is(".am-datepicker-date")&&this.$element.find(".am-datepicker-add-on"),this.isInput?this.$element.on({"click.datepicker.amui":n.proxy(this.open,this),"keyup.datepicker.amui":n.proxy(this.update,this)}):this.component?this.component.on("click.datepicker.amui",n.proxy(this.open,this)):this.$element.on("click.datepicker.amui",n.proxy(this.open,this)),this.minViewMode=this.options.minViewMode,"string"==typeof this.minViewMode)switch(this.minViewMode){case"months":this.minViewMode=1;break;case"years":this.minViewMode=2;break;default:this.minViewMode=0}if(this.viewMode=this.options.viewMode,"string"==typeof this.viewMode)switch(this.viewMode){case"months":this.viewMode=1;break;case"years":this.viewMode=2;break;default:this.viewMode=0}this.startViewMode=this.viewMode,this.weekStart=(this.options.weekStart||a.locales[this.language].weekStart||0)%7,this.weekEnd=(this.weekStart+6)%7,this.onRender=this.options.onRender,this.setTheme(),this.fillDow(),this.fillMonths(),this.update(),this.showMode()};a.DEFAULTS={locale:"zh_CN",format:"yyyy-mm-dd",weekStart:void 0,viewMode:0,minViewMode:0,date:"",theme:"",autoClose:1,onRender:function(t){return""}},a.prototype.open=function(t){this.$picker.show(),this.height=this.component?this.component.outerHeight():this.$element.outerHeight(),this.place(),n(window).on("resize.datepicker.amui",n.proxy(this.place,this)),t&&(t.stopPropagation(),t.preventDefault());var e=this;o.on("mousedown.datapicker.amui touchstart.datepicker.amui",function(t){0===n(t.target).closest(".am-datepicker").length&&e.close()}),this.$element.trigger({type:"open.datepicker.amui",date:this.date})},a.prototype.close=function(){this.$picker.hide(),n(window).off("resize.datepicker.amui",this.place),this.viewMode=this.startViewMode,this.showMode(),this.isInput||n(document).off("mousedown.datapicker.amui touchstart.datepicker.amui",this.close),this.$element.trigger({type:"close.datepicker.amui",date:this.date})},a.prototype.set=function(){var t,e=r.formatDate(this.date,this.format);this.isInput?t=this.$element.attr("value",e):(this.component&&(t=this.$element.find("input").attr("value",e)),this.$element.data("date",e)),t&&t.trigger("change")},a.prototype.setValue=function(t){"string"==typeof t?this.date=r.parseDate(t,this.format):this.date=new Date(t),this.set(),this.viewDate=new Date(this.date.getFullYear(),this.date.getMonth(),1,0,0,0,0),this.fill()},a.prototype.place=function(){var t=this.component?this.component.offset():this.$element.offset(),e=this.component?this.component.width():this.$element.width(),i=t.top+this.height,n=t.left,s=o.width()-t.left-e,a=this.isOutView();if(this.$picker.removeClass("am-datepicker-right"),this.$picker.removeClass("am-datepicker-up"),o.width()>640){if(a.outRight)return this.$picker.addClass("am-datepicker-right"),void this.$picker.css({top:i,left:"auto",right:s});a.outBottom&&(this.$picker.addClass("am-datepicker-up"),i=t.top-this.$picker.outerHeight(!0))}else n=0;this.$picker.css({top:i,left:n})},a.prototype.update=function(t){this.date=r.parseDate("string"==typeof t?t:this.isInput?this.$element.prop("value"):this.$element.data("date"),this.format),this.viewDate=new Date(this.date.getFullYear(),this.date.getMonth(),1,0,0,0,0),this.fill()},a.prototype.fillDow=function(){for(var t=this.weekStart,e=" ')+"