上传代码
40
novel-crawl/pom.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>novel</artifactId>
|
||||
<groupId>com.java2nb</groupId>
|
||||
<version>3.0.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>novel-crawl</artifactId>
|
||||
|
||||
<name>novel-crawl</name>
|
||||
<description>小说精品屋 爬虫管理程序</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.java2nb</groupId>
|
||||
<artifactId>novel-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
@ -0,0 +1,25 @@
|
||||
package com.java2nb.novel;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.web.servlet.ServletComponentScan;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableCaching
|
||||
@EnableScheduling
|
||||
@ServletComponentScan
|
||||
@MapperScan(basePackages = {"com.java2nb.novel.mapper"})
|
||||
public class CrawlNovelApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(CrawlNovelApplication.class);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package com.java2nb.novel.controller;
|
||||
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.java2nb.novel.core.bean.ResultBean;
|
||||
import com.java2nb.novel.core.utils.BeanUtil;
|
||||
import com.java2nb.novel.entity.CrawlSource;
|
||||
import com.java2nb.novel.service.CrawlService;
|
||||
import com.java2nb.novel.vo.CrawlSourceVO;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("crawl")
|
||||
@RequiredArgsConstructor
|
||||
public class CrawlController {
|
||||
|
||||
private final CrawlService crawlService;
|
||||
|
||||
|
||||
/**
|
||||
* 新增爬虫源
|
||||
* */
|
||||
@PostMapping("addCrawlSource")
|
||||
public ResultBean addCrawlSource(CrawlSource source){
|
||||
crawlService.addCrawlSource(source);
|
||||
|
||||
return ResultBean.ok();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 爬虫源分页列表查询
|
||||
* */
|
||||
@PostMapping("listCrawlByPage")
|
||||
public ResultBean listCrawlByPage(@RequestParam(value = "curr", defaultValue = "1") int page, @RequestParam(value = "limit", defaultValue = "10") int pageSize){
|
||||
|
||||
return ResultBean.ok(new PageInfo<>(BeanUtil.copyList(crawlService.listCrawlByPage(page,pageSize), CrawlSourceVO.class)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启或停止爬虫
|
||||
* */
|
||||
@PostMapping("openOrCloseCrawl")
|
||||
public ResultBean openOrCloseCrawl(Integer sourceId,Byte sourceStatus){
|
||||
|
||||
crawlService.openOrCloseCrawl(sourceId,sourceStatus);
|
||||
|
||||
return ResultBean.ok();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package com.java2nb.novel.controller;
|
||||
|
||||
import com.java2nb.novel.entity.Book;
|
||||
import com.java2nb.novel.entity.BookContent;
|
||||
import com.java2nb.novel.entity.BookIndex;
|
||||
import com.java2nb.novel.entity.News;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author 11797
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Controller
|
||||
public class PageController {
|
||||
|
||||
|
||||
|
||||
@RequestMapping("{url}.html")
|
||||
public String module(@PathVariable("url") String url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
@RequestMapping("{module}/{url}.html")
|
||||
public String module2(@PathVariable("module") String module, @PathVariable("url") String url) {
|
||||
return module + "/" + url;
|
||||
}
|
||||
|
||||
@RequestMapping("{module}/{classify}/{url}.html")
|
||||
public String module3(@PathVariable("module") String module, @PathVariable("classify") String classify, @PathVariable("url") String url) {
|
||||
return module + "/" + classify + "/" + url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 首页
|
||||
* */
|
||||
@RequestMapping(path = {"/", "/index", "/index.html"})
|
||||
public String index() {
|
||||
return "crawl/crawlSource_list";
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package com.java2nb.novel.core.config;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
/**
|
||||
* SpringSecurity配置
|
||||
* @author Administrator
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Value("${admin.username}")
|
||||
private String username;
|
||||
|
||||
@Value("${admin.password}")
|
||||
private String password;
|
||||
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
super.configure(web);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
|
||||
User.UserBuilder builder = User.builder().passwordEncoder(passwordEncoder()::encode);
|
||||
auth.inMemoryAuthentication().withUser(builder.username(username).password(password).roles("ADMIN").build());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.csrf().disable()//禁用了 csrf 功能
|
||||
.authorizeRequests()//限定签名成功的请求
|
||||
.antMatchers("/**").hasRole("ADMIN")
|
||||
.anyRequest().permitAll()//其他没有限定的请求,允许访问
|
||||
.and().anonymous()//对于没有配置权限的其他请求允许匿名访问
|
||||
.and().formLogin()//使用 spring security 默认登录页面
|
||||
.and().httpBasic();//启用http 基础验证
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package com.java2nb.novel.core.listener;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.java2nb.novel.core.crawl.CrawlParser;
|
||||
import com.java2nb.novel.core.crawl.RuleBean;
|
||||
import com.java2nb.novel.entity.Book;
|
||||
import com.java2nb.novel.entity.BookContent;
|
||||
import com.java2nb.novel.entity.BookIndex;
|
||||
import com.java2nb.novel.entity.CrawlSource;
|
||||
import com.java2nb.novel.service.BookService;
|
||||
import com.java2nb.novel.service.CrawlService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
import javax.servlet.annotation.WebListener;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
*/
|
||||
@WebListener
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class StarterListener implements ServletContextListener {
|
||||
|
||||
private final BookService bookService;
|
||||
|
||||
private final CrawlService crawlService;
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
log.info("程序启动,开始执行自动更新线程。。。");
|
||||
new Thread(() -> {
|
||||
while (true) {
|
||||
try {
|
||||
//1.查询最新目录更新时间在一个月之内的前100条需要更新的数据
|
||||
Date currentDate = new Date();
|
||||
Date startDate = DateUtils.addDays(currentDate, -30);
|
||||
List<Book> bookList = bookService.queryNeedUpdateBook(startDate, 100);
|
||||
for (Book needUpdateBook : bookList) {
|
||||
try {
|
||||
//查询爬虫源规则
|
||||
CrawlSource source = crawlService.queryCrawlSource(needUpdateBook.getCrawlSourceId());
|
||||
RuleBean ruleBean = new ObjectMapper().readValue(source.getCrawlRule(), RuleBean.class);
|
||||
//解析小说基本信息
|
||||
Book book = CrawlParser.parseBook(ruleBean, needUpdateBook.getCrawlBookId());
|
||||
//这里只做老书更新
|
||||
book.setCrawlLastTime(currentDate);
|
||||
book.setId(needUpdateBook.getId());
|
||||
//查询已存在的章节
|
||||
Map<Integer, BookIndex> existBookIndexMap = bookService.queryExistBookIndexMap(needUpdateBook.getId());
|
||||
//解析章节目录
|
||||
Map<Integer, List> indexAndContentList = CrawlParser.parseBookIndexAndContent(needUpdateBook.getCrawlBookId(),book, ruleBean, existBookIndexMap);
|
||||
bookService.updateBookAndIndexAndContent(book, (List<BookIndex>) indexAndContentList.get(CrawlParser.BOOK_INDEX_LIST_KEY), (List<BookContent>) indexAndContentList.get(CrawlParser.BOOK_CONTENT_LIST_KEY));
|
||||
}catch (Exception e){
|
||||
log.error(e.getMessage(), e);
|
||||
//解析异常中断,更新一下小说的最后解析时间
|
||||
bookService.updateCrawlLastTime(needUpdateBook.getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Thread.sleep(1000 * 60 * 10);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package com.java2nb.novel.core.schedule;
|
||||
|
||||
|
||||
import com.java2nb.novel.core.cache.CacheKey;
|
||||
import com.java2nb.novel.core.cache.CacheService;
|
||||
import com.java2nb.novel.core.utils.ThreadUtil;
|
||||
import com.java2nb.novel.entity.CrawlSource;
|
||||
import com.java2nb.novel.service.CrawlService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 爬虫线程监控器,监控执行完成的爬虫源,并修改状态
|
||||
*
|
||||
* @author Administrator
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class CrawlThreadMonitor {
|
||||
|
||||
private final CacheService cacheService;
|
||||
|
||||
private final CrawlService crawlService;
|
||||
|
||||
@Scheduled(fixedRate = 1000 * 60 * 5)
|
||||
public void monitor() {
|
||||
|
||||
//查询需要监控的正在运行的爬虫源
|
||||
List<CrawlSource> sources = crawlService.queryCrawlSourceByStatus((byte) 1);
|
||||
|
||||
for (CrawlSource source : sources) {
|
||||
Set<Long> runningCrawlThreadIds = (Set<Long>) cacheService.getObject(CacheKey.RUNNING_CRAWL_THREAD_KEY_PREFIX + source.getId());
|
||||
boolean sourceStop = true;
|
||||
if (runningCrawlThreadIds != null) {
|
||||
for (Long threadId : runningCrawlThreadIds) {
|
||||
Thread thread = ThreadUtil.findThread(threadId);
|
||||
|
||||
if (thread != null && thread.isAlive()) {
|
||||
//有活跃线程,说明该爬虫源正在运行,数据库中状态正确,不需要修改
|
||||
sourceStop = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceStop) {
|
||||
crawlService.updateCrawlSourceStatus(source.getId(), (byte) 0);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package com.java2nb.novel.mapper;
|
||||
|
||||
import com.java2nb.novel.entity.BookIndex;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
*/
|
||||
public interface CrawlBookIndexMapper extends BookIndexMapper {
|
||||
|
||||
|
||||
/**
|
||||
* 查询最后的章节
|
||||
* */
|
||||
BookIndex queryLastIndex(@Param("bookId") Long bookId);
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.java2nb.novel.mapper;
|
||||
|
||||
import com.java2nb.novel.entity.Book;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
*/
|
||||
public interface CrawlBookMapper extends BookMapper {
|
||||
|
||||
/**
|
||||
* 查询需要更新的小说数据
|
||||
* @param startDate 最新更新时间的起始时间
|
||||
* @param limit 查询条数
|
||||
* @return 小说集合
|
||||
* */
|
||||
List<Book> queryNeedUpdateBook(@Param("startDate") Date startDate, @Param("limit") int limit);
|
||||
|
||||
/**
|
||||
* 查询小说总字数
|
||||
* @param bookId 小说ID
|
||||
* @return 小说总字数
|
||||
* */
|
||||
Integer queryTotalWordCount(@Param("bookId") Long bookId);
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package com.java2nb.novel.service;
|
||||
|
||||
import com.java2nb.novel.entity.Book;
|
||||
import com.java2nb.novel.entity.BookContent;
|
||||
import com.java2nb.novel.entity.BookIndex;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
*/
|
||||
public interface BookService {
|
||||
|
||||
|
||||
/**
|
||||
* 根据小说名和作者名查询是否存在
|
||||
* @param bookName 小说名
|
||||
* @param authorName 作者名
|
||||
* @return 是否存在该小说名和作者名的小说
|
||||
*/
|
||||
boolean queryIsExistByBookNameAndAuthorName(String bookName, String authorName);
|
||||
|
||||
/**
|
||||
* 更新书籍的爬虫属性
|
||||
* @param sourceId 爬虫源ID
|
||||
* @param bookId 源站小说ID
|
||||
* */
|
||||
void updateCrawlProperties(Integer sourceId, String bookId);
|
||||
|
||||
/**
|
||||
* 通过分类ID查询分类名
|
||||
* @param catId 分类ID
|
||||
* @return 分类名
|
||||
* */
|
||||
String queryCatNameByCatId(int catId);
|
||||
|
||||
/**
|
||||
* 保存小说表,目录表,内容表数据
|
||||
* @param book 小说数据
|
||||
* @param bookIndexList 目录集合
|
||||
* @param bookContentList 内容集合
|
||||
* */
|
||||
void saveBookAndIndexAndContent(Book book, List<BookIndex> bookIndexList, List<BookContent> bookContentList);
|
||||
|
||||
/**
|
||||
* 查询需要更新的小说数据
|
||||
*
|
||||
* @param startDate 最新更新时间的起始时间
|
||||
* @param limit 查询条数
|
||||
* @return 小说集合
|
||||
* */
|
||||
List<Book> queryNeedUpdateBook(Date startDate, int limit);
|
||||
|
||||
/**
|
||||
* 查询已存在的章节
|
||||
* @param bookId 小说ID
|
||||
* @return 章节号和章节数据对映射map
|
||||
* */
|
||||
Map<Integer,BookIndex> queryExistBookIndexMap(Long bookId);
|
||||
|
||||
/**
|
||||
* 更新小说表,目录表,内容表数据
|
||||
* @param book 小说数据
|
||||
* @param bookIndexList 目录集合
|
||||
* @param bookContentList 内容集合
|
||||
* */
|
||||
void updateBookAndIndexAndContent(Book book, List<BookIndex> bookIndexList, List<BookContent> bookContentList);
|
||||
|
||||
/**
|
||||
* 更新一下最后一次的抓取时间
|
||||
* @param bookId 小说ID
|
||||
* */
|
||||
void updateCrawlLastTime(Long bookId);
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package com.java2nb.novel.service;
|
||||
|
||||
import com.java2nb.novel.core.crawl.RuleBean;
|
||||
import com.java2nb.novel.entity.CrawlSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
*/
|
||||
public interface CrawlService {
|
||||
|
||||
/**
|
||||
* 新增爬虫源
|
||||
* @param source 爬虫源提交的数据对象
|
||||
* */
|
||||
void addCrawlSource(CrawlSource source);
|
||||
|
||||
|
||||
/**
|
||||
* 爬虫源分页列表
|
||||
* @param page 当前页码
|
||||
* @param pageSize 分页大小
|
||||
*@return 爬虫源集合
|
||||
* */
|
||||
List<CrawlSource> listCrawlByPage(int page, int pageSize);
|
||||
|
||||
/**
|
||||
* 开启或停止爬虫
|
||||
* @param sourceId 爬虫源ID
|
||||
* @param sourceStatus 状态,0关闭,1开启
|
||||
* */
|
||||
void openOrCloseCrawl(Integer sourceId, Byte sourceStatus);
|
||||
|
||||
/**
|
||||
* 更新爬虫状态
|
||||
* @param sourceId 爬虫源ID
|
||||
* @param sourceStatus 状态,0关闭,1开启
|
||||
* */
|
||||
void updateCrawlSourceStatus(Integer sourceId, Byte sourceStatus);
|
||||
|
||||
/**
|
||||
* 根据爬虫状态查询爬虫源集合
|
||||
* @param sourceStatus 状态,0关闭,1开启
|
||||
* @return 返回爬虫源集合
|
||||
* */
|
||||
List<CrawlSource> queryCrawlSourceByStatus(Byte sourceStatus);
|
||||
|
||||
/**
|
||||
* 根据分类ID和规则解析分类列表
|
||||
* @param catId 分类ID
|
||||
* @param ruleBean 规则对象
|
||||
* @param sourceId
|
||||
*/
|
||||
void parseBookList(int catId, RuleBean ruleBean, Integer sourceId);
|
||||
|
||||
|
||||
/**
|
||||
* 查询爬虫源
|
||||
* @param sourceId 源ID
|
||||
* @return 源信息
|
||||
* */
|
||||
CrawlSource queryCrawlSource(Integer sourceId);
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
package com.java2nb.novel.service.impl;
|
||||
|
||||
import com.java2nb.novel.core.utils.IdWorker;
|
||||
import com.java2nb.novel.entity.Book;
|
||||
import com.java2nb.novel.entity.BookContent;
|
||||
import com.java2nb.novel.entity.BookIndex;
|
||||
import com.java2nb.novel.mapper.*;
|
||||
import com.java2nb.novel.service.BookService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.mybatis.dynamic.sql.render.RenderingStrategies;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.java2nb.novel.mapper.BookDynamicSqlSupport.crawlBookId;
|
||||
import static com.java2nb.novel.mapper.BookDynamicSqlSupport.crawlSourceId;
|
||||
import static com.java2nb.novel.mapper.CrawlSourceDynamicSqlSupport.id;
|
||||
import static org.mybatis.dynamic.sql.SqlBuilder.*;
|
||||
import static org.mybatis.dynamic.sql.select.SelectDSL.select;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class BookServiceImpl implements BookService {
|
||||
|
||||
private final CrawlBookMapper bookMapper;
|
||||
|
||||
private final BookCategoryMapper bookCategoryMapper;
|
||||
|
||||
private final CrawlBookIndexMapper bookIndexMapper;
|
||||
|
||||
private final BookContentMapper bookContentMapper;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean queryIsExistByBookNameAndAuthorName(String bookName, String authorName) {
|
||||
|
||||
return bookMapper.count(countFrom(BookDynamicSqlSupport.book).where(BookDynamicSqlSupport.bookName, isEqualTo(bookName))
|
||||
.and(BookDynamicSqlSupport.authorName, isEqualTo(authorName))
|
||||
.build()
|
||||
.render(RenderingStrategies.MYBATIS3))>0;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCrawlProperties(Integer sourceId, String bookId) {
|
||||
bookMapper.update(update(BookDynamicSqlSupport.book)
|
||||
.set(crawlSourceId)
|
||||
.equalTo(sourceId)
|
||||
.set(crawlBookId)
|
||||
.equalTo(bookId)
|
||||
.build()
|
||||
.render(RenderingStrategies.MYBATIS3));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String queryCatNameByCatId(int catId) {
|
||||
return bookCategoryMapper.selectMany(select(BookCategoryDynamicSqlSupport.name)
|
||||
.from(BookCategoryDynamicSqlSupport.bookCategory)
|
||||
.where(id, isEqualTo(catId))
|
||||
.build()
|
||||
.render(RenderingStrategies.MYBATIS3)).get(0).getName();
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void saveBookAndIndexAndContent(Book book, List<BookIndex> bookIndexList, List<BookContent> bookContentList) {
|
||||
if(!queryIsExistByBookNameAndAuthorName(book.getBookName(),book.getAuthorName())) {
|
||||
|
||||
if(bookIndexList.size()>0) {
|
||||
|
||||
if (book.getId() == null) {
|
||||
book.setId(new IdWorker().nextId());
|
||||
}
|
||||
|
||||
//保存小说主表
|
||||
|
||||
bookMapper.insertSelective(book);
|
||||
|
||||
//批量保存目录和内容
|
||||
bookIndexMapper.insertMultiple(bookIndexList);
|
||||
bookContentMapper.insertMultiple(bookContentList);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Book> queryNeedUpdateBook(Date startDate, int limit) {
|
||||
return bookMapper.queryNeedUpdateBook(startDate, limit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, BookIndex> queryExistBookIndexMap(Long bookId) {
|
||||
List<BookIndex> bookIndexs = bookIndexMapper.selectMany(select(BookIndexDynamicSqlSupport.id,BookIndexDynamicSqlSupport.indexNum,BookIndexDynamicSqlSupport.indexName)
|
||||
.from(BookIndexDynamicSqlSupport.bookIndex)
|
||||
.where(BookIndexDynamicSqlSupport.bookId,isEqualTo(bookId))
|
||||
.build()
|
||||
.render(RenderingStrategies.MYBATIS3));
|
||||
if (bookIndexs.size() > 0) {
|
||||
return bookIndexs.stream().collect(Collectors.toMap(BookIndex::getIndexNum, Function.identity()));
|
||||
}
|
||||
return new HashMap<>(0);
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void updateBookAndIndexAndContent(Book book, List<BookIndex> bookIndexList, List<BookContent> bookContentList) {
|
||||
Date currentDate = new Date();
|
||||
for (int i = 0; i < bookIndexList.size(); i++) {
|
||||
BookIndex bookIndex = bookIndexList.get(i);
|
||||
BookContent bookContent = bookContentList.get(i);
|
||||
|
||||
//插入或更新目录
|
||||
Integer wordCount = bookContent.getContent().length();
|
||||
bookIndex.setWordCount(wordCount);
|
||||
bookIndex.setUpdateTime(currentDate);
|
||||
|
||||
if(bookIndex.getId() == null) {
|
||||
//插入
|
||||
bookIndex.setBookId(book.getId());
|
||||
Long indexId = new IdWorker().nextId();
|
||||
bookIndex.setId(indexId);
|
||||
bookIndex.setCreateTime(currentDate);
|
||||
bookIndexMapper.insertSelective(bookIndex);
|
||||
}else{
|
||||
//更新
|
||||
bookIndexMapper.updateByPrimaryKeySelective(bookIndex);
|
||||
}
|
||||
|
||||
if(bookContent.getIndexId() == null) {
|
||||
//插入
|
||||
bookContent.setIndexId(bookIndex.getId());
|
||||
bookContentMapper.insertSelective(bookContent);
|
||||
}else{
|
||||
//更新
|
||||
|
||||
bookContentMapper.update(update(BookContentDynamicSqlSupport.bookContent)
|
||||
.set(BookContentDynamicSqlSupport.content)
|
||||
.equalTo(bookContent.getContent())
|
||||
.where(BookContentDynamicSqlSupport.indexId,isEqualTo(bookContent.getIndexId()))
|
||||
.build()
|
||||
.render(RenderingStrategies.MYBATIS3));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//更新小说主表
|
||||
book.setWordCount(queryTotalWordCount(book.getId()));
|
||||
BookIndex lastIndex = queryLastIndex(book.getId());
|
||||
book.setLastIndexId(lastIndex.getId());
|
||||
book.setLastIndexName(lastIndex.getIndexName());
|
||||
book.setLastIndexUpdateTime(lastIndex.getUpdateTime());
|
||||
book.setUpdateTime(currentDate);
|
||||
book.setBookName(null);
|
||||
book.setAuthorName(null);
|
||||
bookMapper.updateByPrimaryKeySelective(book);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCrawlLastTime(Long bookId) {
|
||||
Book book = new Book();
|
||||
book.setId(bookId);
|
||||
book.setCrawlLastTime(new Date());
|
||||
bookMapper.updateByPrimaryKeySelective(book);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询最后的章节
|
||||
* */
|
||||
private BookIndex queryLastIndex(Long bookId) {
|
||||
return bookIndexMapper.queryLastIndex(bookId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询小说总字数
|
||||
* */
|
||||
private Integer queryTotalWordCount(Long bookId) {
|
||||
|
||||
return bookMapper.queryTotalWordCount(bookId);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,245 @@
|
||||
package com.java2nb.novel.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.java2nb.novel.core.cache.CacheKey;
|
||||
import com.java2nb.novel.core.cache.CacheService;
|
||||
import com.java2nb.novel.core.crawl.CrawlParser;
|
||||
import com.java2nb.novel.core.crawl.RuleBean;
|
||||
import com.java2nb.novel.core.utils.IdWorker;
|
||||
import com.java2nb.novel.core.utils.SpringUtil;
|
||||
import com.java2nb.novel.core.utils.ThreadUtil;
|
||||
import com.java2nb.novel.entity.Book;
|
||||
import com.java2nb.novel.entity.BookContent;
|
||||
import com.java2nb.novel.entity.BookIndex;
|
||||
import com.java2nb.novel.entity.CrawlSource;
|
||||
import com.java2nb.novel.mapper.*;
|
||||
import com.java2nb.novel.service.BookService;
|
||||
import com.java2nb.novel.service.CrawlService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.mybatis.dynamic.sql.render.RenderingStrategies;
|
||||
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.java2nb.novel.core.utils.HttpUtil.getByHttpClient;
|
||||
import static com.java2nb.novel.mapper.BookDynamicSqlSupport.crawlBookId;
|
||||
import static com.java2nb.novel.mapper.BookDynamicSqlSupport.crawlSourceId;
|
||||
import static com.java2nb.novel.mapper.CrawlSourceDynamicSqlSupport.*;
|
||||
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo;
|
||||
import static org.mybatis.dynamic.sql.SqlBuilder.update;
|
||||
import static org.mybatis.dynamic.sql.select.SelectDSL.select;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class CrawlServiceImpl implements CrawlService {
|
||||
|
||||
|
||||
private final CrawlSourceMapper crawlSourceMapper;
|
||||
|
||||
private final BookService bookService;
|
||||
|
||||
|
||||
private final CacheService cacheService;
|
||||
|
||||
|
||||
@Override
|
||||
public void addCrawlSource(CrawlSource source) {
|
||||
Date currentDate = new Date();
|
||||
source.setCreateTime(currentDate);
|
||||
source.setUpdateTime(currentDate);
|
||||
crawlSourceMapper.insertSelective(source);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CrawlSource> listCrawlByPage(int page, int pageSize) {
|
||||
PageHelper.startPage(page, pageSize);
|
||||
SelectStatementProvider render = select(id, sourceName, sourceStatus, createTime, updateTime)
|
||||
.from(crawlSource)
|
||||
.orderBy(updateTime)
|
||||
.build()
|
||||
.render(RenderingStrategies.MYBATIS3);
|
||||
return crawlSourceMapper.selectMany(render);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void openOrCloseCrawl(Integer sourceId, Byte sourceStatus) {
|
||||
|
||||
//判断是开启还是关闭,如果是关闭,则修改数据库状态后获取该爬虫正在运行的线程集合并全部停止
|
||||
//如果是开启,先查询数据库中状态,判断该爬虫源是否还在运行,如果在运行,则忽略,
|
||||
// 如果没有则修改数据库状态,并启动线程爬取小说数据加入到runningCrawlThread中
|
||||
if (sourceStatus == (byte) 0) {
|
||||
//关闭,直接修改数据库状态,并直接修改数据库状态后获取该爬虫正在运行的线程集合全部停止
|
||||
SpringUtil.getBean(CrawlService.class).updateCrawlSourceStatus(sourceId, sourceStatus);
|
||||
Set<Long> runningCrawlThreadId = (Set<Long>) cacheService.getObject(CacheKey.RUNNING_CRAWL_THREAD_KEY_PREFIX + sourceId);
|
||||
if (runningCrawlThreadId != null) {
|
||||
for (Long ThreadId : runningCrawlThreadId) {
|
||||
Thread thread = ThreadUtil.findThread(ThreadId);
|
||||
if (thread != null && thread.isAlive()) {
|
||||
thread.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
//开启
|
||||
//查询爬虫源状态和规则
|
||||
CrawlSource source = queryCrawlSource(sourceId);
|
||||
Byte realSourceStatus = source.getSourceStatus();
|
||||
|
||||
if (realSourceStatus == (byte) 0) {
|
||||
//该爬虫源已经停止运行了,修改数据库状态,并启动线程爬取小说数据加入到runningCrawlThread中
|
||||
SpringUtil.getBean(CrawlService.class).updateCrawlSourceStatus(sourceId, sourceStatus);
|
||||
RuleBean ruleBean = new ObjectMapper().readValue(source.getCrawlRule(), RuleBean.class);
|
||||
|
||||
Set<Long> threadIds = new HashSet<>();
|
||||
//按分类开始爬虫解析任务
|
||||
for (int i = 1; i < 8; i++) {
|
||||
final int catId = i;
|
||||
Thread thread = new Thread(() -> {
|
||||
|
||||
parseBookList(catId, ruleBean, sourceId);
|
||||
|
||||
});
|
||||
thread.start();
|
||||
//thread加入到监控缓存中
|
||||
threadIds.add(thread.getId());
|
||||
|
||||
}
|
||||
cacheService.setObject(CacheKey.RUNNING_CRAWL_THREAD_KEY_PREFIX + sourceId, threadIds);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public CrawlSource queryCrawlSource(Integer sourceId) {
|
||||
SelectStatementProvider render = select(CrawlSourceDynamicSqlSupport.sourceStatus, CrawlSourceDynamicSqlSupport.crawlRule)
|
||||
.from(crawlSource)
|
||||
.where(id, isEqualTo(sourceId))
|
||||
.build()
|
||||
.render(RenderingStrategies.MYBATIS3);
|
||||
return crawlSourceMapper.selectMany(render).get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析分类列表
|
||||
*/
|
||||
@Override
|
||||
public void parseBookList(int catId, RuleBean ruleBean, Integer sourceId) {
|
||||
|
||||
//当前页码1
|
||||
int page = 1;
|
||||
int totalPage = page;
|
||||
|
||||
while (page <= totalPage) {
|
||||
|
||||
try {
|
||||
//拼接分类URL
|
||||
String catBookListUrl = ruleBean.getBookListUrl()
|
||||
.replace("{catId}", ruleBean.getCatIdRule().get("catId" + catId))
|
||||
.replace("{page}", page + "");
|
||||
|
||||
String bookListHtml = getByHttpClient(catBookListUrl);
|
||||
if (bookListHtml != null) {
|
||||
Pattern bookIdPatten = Pattern.compile(ruleBean.getBookIdPatten());
|
||||
Matcher bookIdMatcher = bookIdPatten.matcher(bookListHtml);
|
||||
boolean isFindBookId = bookIdMatcher.find();
|
||||
while (isFindBookId) {
|
||||
try {
|
||||
String bookId = bookIdMatcher.group(1);
|
||||
Book book = CrawlParser.parseBook(ruleBean, bookId);
|
||||
//这里只做新书入库,查询是否存在这本书
|
||||
boolean isExist = bookService.queryIsExistByBookNameAndAuthorName(book.getBookName(), book.getAuthorName());
|
||||
//如果该小说不存在,则可以解析入库,但是标记该小说正在入库,30分钟之后才允许再次入库
|
||||
if (!isExist && StringUtils.isBlank(cacheService.get(CacheKey.NEW_BOOK_IN_SAVE + book.getBookName() + "-" + book.getAuthorName()))) {
|
||||
//没有该书,可以入库
|
||||
cacheService.set(CacheKey.NEW_BOOK_IN_SAVE + book.getBookName() + "-" + book.getAuthorName(), "true", 60 * 30);
|
||||
book.setCatId(catId);
|
||||
//根据分类ID查询分类
|
||||
book.setCatName(bookService.queryCatNameByCatId(catId));
|
||||
if (catId == 7) {
|
||||
//女频
|
||||
book.setWorkDirection((byte) 1);
|
||||
} else {
|
||||
//男频
|
||||
book.setWorkDirection((byte) 0);
|
||||
}
|
||||
book.setCrawlBookId(bookId);
|
||||
book.setCrawlSourceId(sourceId);
|
||||
book.setCrawlLastTime(new Date());
|
||||
book.setId(new IdWorker().nextId());
|
||||
//解析章节目录
|
||||
Map<Integer, List> indexAndContentList = CrawlParser.parseBookIndexAndContent(bookId,book, ruleBean, new HashMap<>(0));
|
||||
|
||||
bookService.saveBookAndIndexAndContent(book, (List<BookIndex>) indexAndContentList.get(CrawlParser.BOOK_INDEX_LIST_KEY), (List<BookContent>) indexAndContentList.get(CrawlParser.BOOK_CONTENT_LIST_KEY));
|
||||
|
||||
} else {
|
||||
//只更新书籍的爬虫相关字段
|
||||
bookService.updateCrawlProperties(sourceId, bookId);
|
||||
}
|
||||
}catch (Exception e){
|
||||
log.error(e.getMessage(),e);
|
||||
}
|
||||
|
||||
|
||||
isFindBookId = bookIdMatcher.find();
|
||||
}
|
||||
|
||||
Pattern totalPagePatten = Pattern.compile(ruleBean.getTotalPagePatten());
|
||||
Matcher totalPageMatcher = totalPagePatten.matcher(bookListHtml);
|
||||
boolean isFindTotalPage = totalPageMatcher.find();
|
||||
if (isFindTotalPage) {
|
||||
|
||||
totalPage = Integer.parseInt(totalPageMatcher.group(1));
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}catch (Exception e){
|
||||
log.error(e.getMessage(),e);
|
||||
}
|
||||
|
||||
page += 1;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCrawlSourceStatus(Integer sourceId, Byte sourceStatus) {
|
||||
CrawlSource source = new CrawlSource();
|
||||
source.setId(sourceId);
|
||||
source.setSourceStatus(sourceStatus);
|
||||
crawlSourceMapper.updateByPrimaryKeySelective(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CrawlSource> queryCrawlSourceByStatus(Byte sourceStatus) {
|
||||
SelectStatementProvider render = select(CrawlSourceDynamicSqlSupport.id, CrawlSourceDynamicSqlSupport.sourceStatus, CrawlSourceDynamicSqlSupport.crawlRule)
|
||||
.from(crawlSource)
|
||||
.where(CrawlSourceDynamicSqlSupport.sourceStatus, isEqualTo(sourceStatus))
|
||||
.build()
|
||||
.render(RenderingStrategies.MYBATIS3);
|
||||
return crawlSourceMapper.selectMany(render);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.java2nb.novel.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.java2nb.novel.entity.CrawlSource;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.annotation.Generated;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
*/
|
||||
@Data
|
||||
public class CrawlSourceVO extends CrawlSource{
|
||||
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm")
|
||||
private Date createTime;
|
||||
|
||||
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm")
|
||||
private Date updateTime;
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString();
|
||||
}
|
||||
}
|
3
novel-crawl/src/main/resources/application-dev.yml
Normal file
@ -0,0 +1,3 @@
|
||||
spring:
|
||||
profiles:
|
||||
include: [common-dev]
|
3
novel-crawl/src/main/resources/application-test.yml
Normal file
@ -0,0 +1,3 @@
|
||||
spring:
|
||||
profiles:
|
||||
include: [common-test]
|
13
novel-crawl/src/main/resources/application.yml
Normal file
@ -0,0 +1,13 @@
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
spring:
|
||||
profiles:
|
||||
active: dev
|
||||
|
||||
|
||||
admin:
|
||||
username: admin
|
||||
password: admin123456
|
||||
|
||||
|
30
novel-crawl/src/main/resources/ehcache.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="ehcache.xsd">
|
||||
<!--timeToIdleSeconds 当缓存闲置n秒后销毁 -->
|
||||
<!--timeToLiveSeconds 当缓存存活n秒后销毁 -->
|
||||
<!-- 缓存配置 name:缓存名称。 maxElementsInMemory:缓存最大个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。
|
||||
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
|
||||
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
|
||||
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。 diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
|
||||
maxElementsOnDisk:硬盘最大缓存个数。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk
|
||||
store persists between restarts of the Virtual Machine. The default value
|
||||
is false. diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是
|
||||
LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 maxEntriesLocalHeap="1000"
|
||||
: 堆内存中最大缓存对象数,0没有限制(必须设置) maxEntriesLocalDisk="1000" : 硬盘最大缓存个数。 -->
|
||||
<!-- 磁盘缓存位置 -->
|
||||
<diskStore path="user.dir/cachedata" />
|
||||
|
||||
<!-- 默认缓存 -->
|
||||
<defaultCache maxElementsInMemory="10000" eternal="false"
|
||||
timeToIdleSeconds="120" timeToLiveSeconds="120"
|
||||
maxElementsOnDisk="10000000" diskExpiryThreadIntervalSeconds="120"
|
||||
memoryStoreEvictionPolicy="LRU">
|
||||
|
||||
<persistence strategy="localTempSwap" />
|
||||
</defaultCache>
|
||||
|
||||
|
||||
<cache name="util_cache" maxEntriesLocalHeap="0" eternal="true"
|
||||
overflowToDisk="true" diskPersistent="true" />
|
||||
|
||||
</ehcache>
|
64
novel-crawl/src/main/resources/logback-boot.xml
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<!-- 彩色日志依赖的渲染类 -->
|
||||
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
|
||||
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
|
||||
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
|
||||
<!-- 彩色日志格式 -->
|
||||
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
|
||||
|
||||
<!-- %m输出的信息,%p日志级别,%t线程名,%d日期,%c类的全名,%i索引【从数字0开始递增】,,, -->
|
||||
<!-- appender是configuration的子节点,是负责写日志的组件。 -->
|
||||
<!-- ConsoleAppender:把日志输出到控制台 -->
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<!--
|
||||
<pattern>%d %p (%file:%line\)- %m%n</pattern>
|
||||
-->
|
||||
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||
<!-- 控制台也要使用UTF-8,不要使用GBK,否则会中文乱码 -->
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
|
||||
<!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是demo.log -->
|
||||
<!-- 2.如果日期没有发生变化,但是当前日志的文件大小超过1KB时,对当前日志进行分割 重命名 -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
|
||||
<File>logs/novel-crawl.log</File>
|
||||
<!-- rollingPolicy:当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名。 -->
|
||||
<!-- TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动 -->
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!-- 活动文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
|
||||
<!-- 文件名:logs/demo.2017-12-05.0.log -->
|
||||
<fileNamePattern>logs/debug.%d.%i.log</fileNamePattern>
|
||||
<!-- 每产生一个日志文件,该日志文件的保存期限为30天 -->
|
||||
<maxHistory>30</maxHistory>
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<!-- maxFileSize:这是活动文件的大小,默认值是10MB,测试时可改成1KB看效果 -->
|
||||
<maxFileSize>10MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<!-- pattern节点,用来设置日志的输入格式 -->
|
||||
<pattern>
|
||||
%d %p (%file:%line\)- %m%n
|
||||
</pattern>
|
||||
<!-- 记录日志的编码:此处设置字符集 - -->
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
<!-- 控制台输出日志级别 -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="FILE" />
|
||||
</root>
|
||||
<!-- 指定项目中某个包,当有日志操作行为时的日志记录级别 -->
|
||||
<!-- com.maijinjie.springboot 为根包,也就是只要是发生在这个根包下面的所有日志操作行为的权限都是DEBUG -->
|
||||
<!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE -->
|
||||
<logger name="com.java2nb" level="DEBUG">
|
||||
<appender-ref ref="STDOUT" />
|
||||
<appender-ref ref="FILE" />
|
||||
</logger>
|
||||
</configuration>
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.java2nb.novel.mapper.CrawlBookIndexMapper">
|
||||
|
||||
<select id="queryLastIndex" parameterType="long" resultType="com.java2nb.novel.entity.BookIndex">
|
||||
|
||||
|
||||
select id,index_name,update_time
|
||||
from book_index where book_id = #{bookId}
|
||||
order by index_num DESC limit 1
|
||||
|
||||
</select>
|
||||
|
||||
|
||||
|
||||
</mapper>
|
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="com.java2nb.novel.mapper.CrawlBookMapper">
|
||||
|
||||
<select id="queryNeedUpdateBook" resultType="com.java2nb.novel.entity.Book">
|
||||
|
||||
select id,crawl_source_id,crawl_book_id,crawl_last_time
|
||||
from book where last_index_update_time > #{startDate}
|
||||
order by crawl_last_time
|
||||
limit ${limit}
|
||||
|
||||
|
||||
</select>
|
||||
|
||||
<select id="queryTotalWordCount" parameterType="long" resultType="int">
|
||||
|
||||
select sum(t2.word_count) from book t1 inner join book_index t2
|
||||
on t1.id = t2.book_id and t1.id = #{bookId}
|
||||
</select>
|
||||
|
||||
|
||||
</mapper>
|
235
novel-crawl/src/main/resources/static/css/base.css
Normal file
@ -0,0 +1,235 @@
|
||||
@charset "utf-8";
|
||||
body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, fieldset, input, p, a, blockquote, th { margin: 0; padding: 0 }
|
||||
h1, h2, h3, h4, h5, h6 { font-size: 14px }
|
||||
ol, ul, li { list-style: none outside none }
|
||||
table { border-collapse: collapsse; border-spacing: 0 }
|
||||
fieldset, img { border: 0 none }
|
||||
/*html { background: ##f5f5f5 }*/
|
||||
body { background: #f5f5f5; color: #333; font: 12px/1.5 PingFangSC-Regular,HelveticaNeue-Light,'Helvetica Neue Light','Microsoft YaHei',sans-serif,"宋体"; text-align: left }
|
||||
input::-moz-focus-inner {
|
||||
border:none;
|
||||
padding:0
|
||||
}
|
||||
a img { border: none }
|
||||
a { outline: none; color: #333; text-decoration: none }
|
||||
a:hover, .topBar a:hover, .red, .record_list li:hover .read_link a { color: #f70 }
|
||||
.red1 { color: #ff4040 }
|
||||
.unlink { text-decoration: underline }
|
||||
.blue { color: #5fc3f3 }
|
||||
.green { color: #360 }
|
||||
.black { color: #000 }
|
||||
.black3 { color: #333 }
|
||||
.black6 { color: #666 }
|
||||
.black9 { color: #999 }
|
||||
.ccc { color: #ccc }
|
||||
.orange { color: #f60 }
|
||||
.font12 { font-size: 12px!important }
|
||||
.font14 { font-size: 14px!important }
|
||||
.font16 { font-size: 16px!important }
|
||||
.font18 { font-size: 18px!important }
|
||||
.font20 { font-size: 20px!important }
|
||||
.font26 { font-size: 26px!important }
|
||||
.ellipsis {overflow: hidden; text-overflow: ellipsis; white-space: nowrap; word-break: keep-all }
|
||||
textarea { resize: none; outline: none; border: 1px solid #CCC; font: 12px/1.8 "microsoft yahei", Arial; padding-left: 5px }
|
||||
input { outline: none; border: none; /* padding-left: 5px; font-size: 13px;*/ font-family: "microsoft yahei", Arial; *background:none
|
||||
}
|
||||
i, em, cite { font-style: normal }
|
||||
.layui-inline, input, label { vertical-align: middle }
|
||||
button, input, optgroup, select, textarea { color: inherit; font: inherit; margin: 0; outline: 0 }
|
||||
button, select { text-transform: none }
|
||||
/*select { -webkit-appearance: none; border: none }*/
|
||||
input { line-height: normal }
|
||||
input[type=checkbox], input[type=radio] { box-sizing: border-box; padding: 0 }
|
||||
input[type=number]::-webkit-inner-spin-button, input[type=number]::-webkit-outer-spin-button { height:auto }
|
||||
input[type=search] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box }
|
||||
input[type=search]::-webkit-search-cancel-button, input[type=search]::-webkit-search-decoration { -webkit-appearance:none }
|
||||
input[type="submit"], input[type="reset"], input[type="button"], button { -webkit-appearance: none }
|
||||
:-moz-placeholder { color: #999 }
|
||||
::-moz-placeholder { color: #999 }
|
||||
input:-ms-input-placeholder, textarea:-ms-input-placeholder { color: #999 }
|
||||
input::-webkit-input-placeholder, textarea::-webkit-input-placeholder { color: #999 }
|
||||
.cf { zoom: 1 }
|
||||
.cf:before, .cf:after { content: ""; display: table; display: block; font-size: 0; height: 0; line-height: 0; clear: both; visibility: hidden }
|
||||
.cf:after { clear: both }
|
||||
.clear { clear: both }
|
||||
.tl { text-align: left }
|
||||
.tc { text-align: center }
|
||||
.tr { text-align: right }
|
||||
.fl { float: left }
|
||||
.fr { float: right }
|
||||
.block { display: block }
|
||||
.none, .hidden { display: none }
|
||||
/*base end*/
|
||||
.channelWrap { background: #fff; border-radius: 6px; padding: 20px; margin-bottom: 20px }
|
||||
.channelWrap.channelBanner { padding-bottom: 14px }
|
||||
.wrap_left { width: 750px }
|
||||
.wrap_right { width: 250px }
|
||||
.wrap_inner { padding: 20px; border-radius: 6px; background: #fff; }
|
||||
.wrap_bg { border-radius: 6px; background: #fff; }
|
||||
.pad20 { padding: 20px }
|
||||
.pad20_nobt { padding: 20px 20px 0 }
|
||||
.topBar { width: 100%; background: #fbfaf8; border-bottom: 1px solid #eae6e2; height: 35px; line-height: 35px }
|
||||
.box_center { width: 1020px; margin: 0 auto }
|
||||
.top_l { float: left }
|
||||
.top_r { float: right }
|
||||
.topBar .line { display: inline-block; padding: 0 12px; color: #e5d9da }
|
||||
.topBar a { display: inline-block; color: #8C8C8C }
|
||||
.topBar a.on { color: #333 }
|
||||
.topMain { height: 92px; background: #fff; overflow: hidden }
|
||||
.logo { width: 198px; float: left; padding: 23px 130px 0 0; display: block }
|
||||
.logo img { width: auto; height: 48px }
|
||||
.searchBar { width: 342px; margin-top: 27px; overflow: hidden }
|
||||
.searchBar .search/*, .searchBar .hotword*/ { width: 342px; overflow: hidden }
|
||||
.searchBar .s_int { width: 250px; padding: 0 14px 0 18px; height: 36px; line-height: 36px\9; vertical-align: middle; border: 1px solid #f80; border-right: none; color: #333; float: left; border-radius: 20px 0 0 20px; font-size: 14px; /*background: #fff;*/ background: 0 0 }
|
||||
/*.searchBar .s_btn { width: 78px; height: 38px; line-height: 38px; background: #f65167; color: #fff; font-size: 16px; text-align: center; float: left; cursor: pointer; padding: 0 }
|
||||
.searchBar .s_btn:hover { background:#E23249 }*/
|
||||
.searchBar .search_btn { float: left;
|
||||
width: 58px;
|
||||
height: 38px;
|
||||
text-align: center;
|
||||
border-radius: 0 20px 20px 0;
|
||||
background-color: #f80; cursor: pointer; }
|
||||
.searchBar .search_btn .icon { width: 18px; height: 18px; display: block; margin: 9px auto 0; background: url(../images/search.png) no-repeat; background-size:cover; filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/images/search.png', sizingMethod='scale'); }
|
||||
|
||||
/*.hotword { padding-top: 3px }
|
||||
.hotword a, .hotword span { color: #999; margin: 0 6px 0 5px }
|
||||
.hotword a:hover { color: #666 }*/
|
||||
.bookShelf { margin-top: 27px; padding-left: 20px; overflow: hidden }
|
||||
.bookShelf .sj_link { height: 38px; line-height: 38px; padding-left: 30px; font-size: 15px; color: #404040; background: url(../images/icon_sj.png) no-repeat 6px 50%; float: left }
|
||||
.bookShelf .user_link { height: 38px; line-height: 38px; padding-left: 20px; font-size: 15px; color: #404040; float: right }
|
||||
.bookShelf .user_head { width: 26px; height: 26px; border-radius: 50%; float: left; margin: 6px 5px 0 0 }
|
||||
.bookShelf .user_name { max-width: 100px; display: inline-block }
|
||||
.bookShelf .line { float: left; color: #ccc }
|
||||
/*.bookShelf img { position: absolute; top: 17px; left: 17px; z-index: 10 }*/
|
||||
.mainNav { width: 100%; height: 48px; background: #f80; margin-bottom: 20px }
|
||||
.mainNav .nav li { float: left }
|
||||
.mainNav .nav li a { float: left; height: 44px; line-height: 48px; color: #fff; font-size: 16px; margin: 0 34px; border-bottom: 2px solid #f80; transition: color .3s,background-color .3s,border .3s }
|
||||
.mainNav .nav li.on a, .mainNav .nav li a:hover { border-bottom: 2px solid rgba(255,255,255,.8) }
|
||||
.footer { padding: 0 0 20px; /*margin-top: 20px; background: #fbfaf8; border-top: 1px solid #e0e0e0; */text-align: center; font-size: 12px }
|
||||
.copyright ul li { color: #999; line-height: 26px }
|
||||
.copyright .menu { padding: 2px 0 6px; font-size: 12px }
|
||||
.copyright .line { display: inline-block; padding: 0 12px; color: #e5d9da }
|
||||
.copyright p { margin-top: 10px; color: #999 }
|
||||
.code_bar img { margin-left: 66px }
|
||||
.rBar { float: right; width: 268px }
|
||||
.btn_gray, .btn_red, .btn_ora, .btn_ora_white, .btn_red1 { border-radius: 20px; font-size: 15px; display: inline-block; text-align: center; cursor: pointer; /*padding: 0 34px; height: 34px; line-height: 34px;*/ padding: 11px 36px; line-height: 1; }
|
||||
.btn_gray { border: 1px solid #dedede; background: #fafafa; }
|
||||
.btn_red, .btn_ora { border: 1px solid #f80; background: #f80; color: #fff }
|
||||
.btn_red1 { border: 1px solid #ff4040; background: #ff4040; color: #fff }
|
||||
.btn_ora_white { border: 1px solid #f80; color: #f80 }
|
||||
.btn_ora_white:hover { background: #fefaf6 }
|
||||
.btn_link { padding: 2px 6px; background: #f80; color: #fff; border-radius: 2px }
|
||||
.btn_gray:hover { background: #f0f0f0; color: #333 }
|
||||
.btn_ora:hover, .btn_red:hover, .btn_link:hover { background: #f70; color: #fff }
|
||||
.btn_red1:hover { background: #fc2525; color: #fff }
|
||||
.pay_Checkout .btn_red, .btn_big {
|
||||
font-size: 16px;
|
||||
padding: 15px 0;
|
||||
border-radius: 4px;
|
||||
width: 196px; }
|
||||
i.vip { width: 26px; height: 14px; text-align: center; line-height: 14px; font-size: 11px; color: #fff; background: #fe8034; border-radius: 2px; margin: 13px 0 0 3px; display: inline-block; transform: scale(0.88); }
|
||||
i.vip_b { width: 36px; height: 22px; text-align: center; line-height: 22px; font-size: 15px; color: #fff; background: #f70; border-radius: 4px; margin-left: 5px; display: inline-block; vertical-align: 3px }
|
||||
.pageBox { text-align: center; padding: 20px 0 }
|
||||
.pageBox a, .pageBox span { display: inline-block; color: #999; padding: 6px 10px; margin: 0 5px; border-radius: 4px; font-size: 14px; line-height: 1 }
|
||||
.pageBox .current, .pageBox a:hover { background: #f80; color: #fff }
|
||||
.top_nearread { display: inline-block; position: relative; margin-right: 10px; float:left }
|
||||
.top_nearread .nearread { padding: 0 9px }
|
||||
.top_nearread .nearread.on { border-left: 1px solid #d9d9d9; border-right: 1px solid #d9d9d9; background: #FFF; padding: 0 8px; height: 36px; position: relative; z-index: 8 }
|
||||
.icon_down { display: inline-block; vertical-align: middle; margin: 2px 0 0 5px; width: 0px; height: 0px; overflow: hidden; border-width: 4px; border-style: solid dashed dashed; border-color: #7f7f7f transparent transparent; }
|
||||
.book_record { width: 382px; position: absolute; top: 0; right: 0; z-index: 9 }
|
||||
.record_box { width: 380px; background: #fff; margin-top:35px; border: 1px solid #d9d9d9 }
|
||||
.book_record .sp { width:77px; height:6px; background:#fff; position:absolute; top:32px; right:1px }
|
||||
.record_title { padding: 14px 10px }
|
||||
.record_title a { border: 1px solid #dedede; background: #fafafa; border-radius: 2px; font-size: 12px; padding: 6px 12px; line-height: 1; margin-right: 14px }
|
||||
.record_title a.on { border: 1px solid #f65167; background: #f65167; color: #fff }
|
||||
.record_box .all { display: block; height: 28px; line-height: 28px; text-align: center; background: #f6f6f6 }
|
||||
.record_list ul { margin-bottom: 10px }
|
||||
.record_list li { clear: both; padding: 10px; line-height: 1; overflow: hidden }
|
||||
.record_list li:hover { background: #f6f6f6 }
|
||||
.record_list li .cover { width: 50px; height: 63px; background: #f6f6f6 }
|
||||
.record_list li .cover img { width: 100%; height: 100%; }
|
||||
.record_list a { display: inline; color: #333 }
|
||||
.record_list .book_intro { width: 300px; height: 65px; padding-left: 10px; position: relative; }
|
||||
.record_list .book_intro p { height: 20px; line-height: 20px; overflow: hidden; color: #999; }
|
||||
.record_list .book_intro .p1 { font-size: 14px; }
|
||||
.record_list .book_intro .p2 { margin: 2px 0; white-space: nowrap; text-overflow: ellipsis }
|
||||
.record_list .book_intro .p3 { }
|
||||
.record_list .book_intro i.vip { margin:0 0 0 3px }
|
||||
.record_list .read_link a { color: #fff }
|
||||
.manBody {}
|
||||
.manBody .mainNav { background:#3e3d43 }
|
||||
.manBody .searchBar .s_int { border: 1px solid #878689; border-right:none; background-position:8px -22px }
|
||||
.manBody .mainNav .nav li.on a, .manBody .mainNav .nav li a:hover { background:#313035 }
|
||||
.nav_sub { margin-bottom: 16px }
|
||||
.nav_sub a { padding: 0 6px }
|
||||
|
||||
.copyright .menu a { color: #666; font-size: 12px }
|
||||
.copyright .menu a:hover, .bookShelf .sj_link:hover { color: #f70 }
|
||||
|
||||
.rightList .more, .more_bar { margin: 1px 0; height: 34px; line-height: 34px; border-radius: 1px; background-color: #f7f7f7; text-align: center }
|
||||
.rightList .more a, .more_bar a { display: block; color: #666 }
|
||||
.header, .footer { min-width: 1020px }
|
||||
|
||||
/*base*/
|
||||
.noborder { border: 0!important }
|
||||
.nomargin { margin: 0!important }
|
||||
.ml { margin-left: 12px }
|
||||
.mr { margin-right: 12px }
|
||||
.ml5 { margin-left: 5px }
|
||||
.ml10 { margin-left: 10px }
|
||||
.ml15 { margin-left: 15px }
|
||||
.ml20 { margin-left: 20px }
|
||||
.mr5 { margin-right: 5px }
|
||||
.mr10 { margin-right: 10px }
|
||||
.mr15 { margin-right: 15px }
|
||||
.mr20 { margin-right: 20px }
|
||||
.mt5 { margin-top: 5px }
|
||||
.mt10 { margin-top: 10px }
|
||||
.mt15 { margin-top: 15px }
|
||||
.mt20 { margin-top: 20px }
|
||||
.mb5 { margin-bottom: 5px }
|
||||
.mb10 { margin-bottom: 10px }
|
||||
.mb15 { margin-bottom: 15px }
|
||||
.mb20 { margin-bottom: 20px }
|
||||
.mb50 { margin-bottom: 50px }
|
||||
.pointer { cursor: pointer }
|
||||
.notindent { text-indent: inherit!important }
|
||||
.vm { vertical-align: middle!important }
|
||||
.border_t { border-top: 1px solid #eee }
|
||||
.border_b { border-bottom: 1px solid #eee }
|
||||
.border_l { border-left: 1px solid #eee }
|
||||
.border_r { border-right: 1px solid #eee }
|
||||
.layui-laypage-curr{
|
||||
background: #f80;
|
||||
}
|
||||
.layui-laypage-curr em {
|
||||
color: #fff;
|
||||
}
|
||||
.layui-disabled, .layui-disabled:hover {
|
||||
color: #d2d2d2 !important;
|
||||
cursor: not-allowed !important
|
||||
}
|
||||
|
||||
#noFeedbackNote {
|
||||
line-height: 400px;
|
||||
text-align: center;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
#txtDescription {
|
||||
/*width: 900px;*/
|
||||
height: 288px;
|
||||
margin: 20px auto 20px;
|
||||
padding: 10px;
|
||||
|
||||
|
||||
/*新增样式*/
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #eee;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.userBox {
|
||||
margin: 0 auto
|
||||
}
|
137
novel-crawl/src/main/resources/static/css/user.css
Normal file
@ -0,0 +1,137 @@
|
||||
@charset "utf-8";
|
||||
.updateTable .style a { color:#999 }
|
||||
.updateTable .author a { color:#999; cursor:text }
|
||||
.bind, .updateTable .style a:hover { color:#f65167 }
|
||||
.userBox { /*width: 998px; border: 1px solid #eaeaea;*/ margin:0 auto 50px; background: #fff; border-radius: 6px }
|
||||
.channelViewhistory .userBox { margin: 0 auto }
|
||||
.user_l { width:350px; float:left; padding:50px 50px }
|
||||
.user_l h3 { font-size:23px; font-weight:normal; line-height:1; text-align: center }
|
||||
.user_l #LabErr { color:#ff4040; display:block; height:40px; line-height:40px; text-align: center; font-size: 14px }
|
||||
.user_l .log_list { width:350px }
|
||||
.user_l .s_input { margin-bottom:25px; font-size:14px }
|
||||
.s_input { width:348px; height:30px; line-height:38px\9; vertical-align:middle; border:1px solid #ddd; border-radius:2px }
|
||||
.icon_name, .icon_key, .icon_code { width:312px; padding-left:36px; background:url(../images/icon_user.png) no-repeat 13px 13px }
|
||||
.icon_key { background-position: 13px -51px }
|
||||
.icon_code { background-position: 13px -117px; width:200px; float:left }
|
||||
.code_pic { height:38px; float:right }
|
||||
.btn_phone { height:40px; width:100px; float:right; cursor:pointer; padding:0; text-align:center; border-radius:2px; background:#dfdfdf }
|
||||
.log_code { *padding-bottom:25px }
|
||||
.user_l .btn_red { width:100%; font-size:19px; padding:12px }
|
||||
.autologin { color:#999; line-height:1; margin-bottom:18px }
|
||||
.autologin em { vertical-align:2px; margin-left:4px }
|
||||
.user_r { width:259px; margin:80px 0; padding:20px 70px; border-left:1px dotted #e3e3e3; float:right; text-align:center }
|
||||
.user_r .tit { font-size:16px; line-height:1; padding: 6px 0 25px }
|
||||
.user_r .btn_ora { padding:10px 34px }
|
||||
.fast_login { padding:60px 0 0 }
|
||||
.fast_list { text-align:center; padding:0.5rem }
|
||||
.fast_list li { display:inline-block; *display:inline; zoom:1 }
|
||||
.fast_list li .img { width:48px; height:48px; margin:20px 0 5px }
|
||||
.fast_list li a:hover { opacity:0.8; filter: alpha(opacity=80); -moz-opacity: 0.8 }
|
||||
.fast_list li span { display:block }
|
||||
.fast_list .login_qq { margin:0 42px }
|
||||
.fast_list .login_wb a { color:#f55c5b }
|
||||
.fast_list .login_qq a { color:#51b7ff }
|
||||
.fast_list .login_wx a { color:#66d65e }
|
||||
.fast_tit { position:relative; overflow:hidden }
|
||||
.fast_tit .lines { position:absolute; top:50%; left:0; width:100%; height:1px; line-height:1; background:#eaeaea }
|
||||
.fast_tit .title { background:#fff; font-size:16px; padding:3px 14px; position:relative; display:inline-block; z-index:999 }
|
||||
/*userinfo*/
|
||||
.my_l { width:198px; float:left; font-size: 13px; padding-top: 20px; }
|
||||
.my_l li a { display:block; height:42px; line-height:42px; padding-left:62px; border-left:4px solid #fff; background:url(../images/icon_user.png) no-repeat; margin-bottom:5px; color: #666 }
|
||||
.my_l li .on { background-color:#fafafa; border-left:2px solid #f80; color:#000; border-radius: 0 2px 2px 0 }
|
||||
.my_l .link_1 { background-position:32px -188px }
|
||||
.my_l .link_2 { background-position:32px -230px }
|
||||
.my_l .link_3 { background-position:32px -272px }
|
||||
.my_l .link_4 { background-position:32px -314px }
|
||||
.my_l .link_5 { background-position:32px -356px }
|
||||
.my_l .link_6 { background-position:32px -397px }
|
||||
.my_l .link_7 { background-position:32px -440px }
|
||||
.my_l .link_8 { background-position:32px -481px }
|
||||
.my_r { width:739px; padding:0 30px 30px; float:right; border-left:1px solid #efefef; min-height:470px }
|
||||
.my_info { padding:30px 0 5px }
|
||||
.user_big_head { /*width:110px; height:110px; padding:4px; border:1px solid #eaeaea;*/ margin-right:30px; float:left; width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%; }
|
||||
.my_r .my_name { font-size:18px; line-height:1; padding:5px 0 12px 0 }
|
||||
.my_r .s_input { width:318px; padding:0 10px }
|
||||
.my_list li { line-height:28px }
|
||||
.my_list li i, .my_list li em.red { margin-right:6px }
|
||||
.my_list .binded { color:#999; margin-left:6px }
|
||||
.my_list .btn_link { margin-left:12px }
|
||||
.mytab_list li { line-height:30px; padding:10px 0; font-size:14px }
|
||||
.mytab_list li .tit { width:70px; color: #aaa; text-align:right; display:inline-block; margin-right:18px }
|
||||
.mytab_list .user_img { width:48px; height:48px; vertical-align:middle; border-radius:50% }
|
||||
.my_bookshelf .title { padding:20px 0 15px; line-height:30px }
|
||||
.my_bookshelf h4 { font-size:14px; color:#666 }
|
||||
.my_bookshelf h2 { font-size:18px; font-weight:normal }
|
||||
.updateTable { width: 739px; color: #999 }
|
||||
.updateTable table { width: 100%; margin-bottom:14px }
|
||||
.updateTable th, .updateTable td { height: 40px; line-height: 40px; vertical-align: middle; padding-left: 6px; font-weight:normal; text-align:left }
|
||||
.updateTable th { background:#f9f9f9; color:#333; border-top:1px solid #eee }
|
||||
.updateTable td { height:40px; line-height:40px }
|
||||
.updateTable .style { width:80px; padding-left:10px }
|
||||
.updateTable .name { width: 178px; padding-right: 10px }
|
||||
.updateTable .name a, .updateTable .chapter a { max-width: 168px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
|
||||
.updateTable .chapter { padding-right: 5px }
|
||||
.updateTable .chapter a { max-width:220px; float: left }
|
||||
.updateTable .author { width: 72px; text-align: left }
|
||||
.updateTable .goread { width: 80px; text-align:center }
|
||||
.updateTable .time { width: 86px }
|
||||
.updateTable .word { width: 64px; padding-right:10px; text-align: right }
|
||||
.updateTable .rank { width: 30px; padding-right:10px; text-align: center }
|
||||
.updateTable .name a, .updateTable .chapter a, .updateTable .author a { height: 40px; line-height: 40px; display: inline-block; overflow: hidden }
|
||||
.updateTable tr:nth-child(2n) td { background:#fafafa }
|
||||
.dataTable { width: 739px }
|
||||
.dataTable table { width: 100%; margin-bottom:14px; border-collapse:collapse }
|
||||
.dataTable th, .dataTable td { height: 40px; line-height: 40px; vertical-align: middle; padding:0 10px; font-weight:normal; text-align:center; border:1px solid #eaeaea }
|
||||
.dataTable th { background:#f8f8f8 }
|
||||
.nodate { border-top: 1px solid #eaeaea; padding:60px 0 }
|
||||
.viewhistoryBox { /*padding: 0 30px 30px; */ padding: 0 20px 10px }
|
||||
.viewhistoryBox .updateTable { width:100% }
|
||||
/*.btn_gray, .btn_red, .btn_ora { font-size:14px; padding:8px 28px }*/
|
||||
.book_tit { height: 48px; line-height:48px; margin: 0 14px; border-bottom: 1px solid #eaeaea; overflow:hidden }
|
||||
.book_tit .fl { font-size:14px; color:#999 }
|
||||
.book_tit .fl h3 { font-size:18px; color:#333; font-weight:normal; margin-right:5px; display:inline }
|
||||
.book_tit .fr { font-size:14px }
|
||||
|
||||
.commentBar, .feedback_list { border-top:1px solid #eee; margin-bottom:15px }
|
||||
/*.comment_list { padding: 16px 0; border-bottom: 1px solid #eee }
|
||||
.comment_list .user_head { width:54px; height:54px; border-radius:50%; float: left; margin-right: 14px }
|
||||
.comment_list .li_1 { overflow: hidden }
|
||||
.comment_list .user_name { color: #ed4259 }
|
||||
.comment_list .li_2 { padding:3px 0; color:#999 }
|
||||
.comment_list .li_3, .comment_list .li_4 { margin-left:68px }
|
||||
.comment_list .reply { padding-left: 12px }
|
||||
.comment_list .num { color: #ed4259; margin: 0 3px }
|
||||
.comment_list .li_4 { line-height:34px; padding-top:8px; margin-top:15px; border-top:1px solid #eaeaea }
|
||||
.comment_list .li_4 .more { background:#f7f7f7; border-radius:2px; color:#ed4259; text-align:center }*/
|
||||
.no_contet { padding:190px 0 40px; text-align:center; color:#999; border-top:1px solid #eee }
|
||||
.no_comment { background:url(../images/no_comment.png) no-repeat center 80px }
|
||||
|
||||
.comment_list { padding: 20px 0; border-bottom: 1px solid #eee }
|
||||
.comment_list:last-child { border: none }
|
||||
.comment_list .user_heads { /*width: 54px; height: 54px; float: left;*/ position:relative; margin-right: 20px }
|
||||
.comment_list .user_head { width: 50px; height: 50px; border-radius: 50%; background: #f6f6f6 }
|
||||
.comment_list .user_heads span { display: block; margin: 0; position: absolute; left: 12px; bottom: 0 }
|
||||
.comment_list ul { /*width: 640px;*/ width: 660px; }
|
||||
.comment_list .li_0 { font-family: "宋体" }
|
||||
.comment_list .li_0 strong { font-size: 14px; color: #f00 }
|
||||
.comment_list .li_1 { overflow: hidden }
|
||||
.comment_list .user_name { color: #ed4259 }
|
||||
.comment_list .li_2 { padding: 6px 0 }
|
||||
.comment_list .li_3 { color: #999 }
|
||||
.comment_list .reply { padding-left: 12px }
|
||||
.comment_list .num { color: #ed4259; margin: 0 3px }
|
||||
.comment_list .li_4 { line-height: 34px; padding-top: 8px; margin-top: 15px; border-top: 1px solid #eaeaea }
|
||||
.pl_bar li { display: block }
|
||||
.pl_bar .name { color: #666; padding-top: 2px; font-size: 14px }
|
||||
.pl_bar .dec { font-size: 14px; line-height: 1.8; padding: 12px 0 }
|
||||
.pl_bar .other { line-height: 24px; color: #999; font-size: 13px }
|
||||
.pl_bar .other a { display: inline-block; color: #999 }
|
||||
.pl_bar .reply { padding-left: 22px; background: url(../images/icon_reply.png) no-repeat 0 2px }
|
||||
/*.no_comment { padding: 70px 14px 115px; color: #CCCCCC; text-align: center; font-size: 14px; }*/
|
||||
.reply_bar {
|
||||
background: #f9f9f9;
|
||||
border: 1px solid #eee; border-radius: 6px;
|
||||
padding: 10px;
|
||||
line-height: 1.8;}
|
153
novel-crawl/src/main/resources/static/javascript/header.js
Normal file
@ -0,0 +1,153 @@
|
||||
var $C = function (objName) {
|
||||
if (typeof (document.getElementById(objName)) != "object")
|
||||
{ return null; }
|
||||
else
|
||||
{ return document.getElementById(objName); }
|
||||
}
|
||||
jQuery.cookie = function (name, value, options) {
|
||||
if (typeof value != 'undefined') {
|
||||
options = options || {};
|
||||
if (value === null) {
|
||||
value = '';
|
||||
options.expires = -1;
|
||||
}
|
||||
var expires = '';
|
||||
if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
|
||||
var date;
|
||||
if (typeof options.expires == 'number') {
|
||||
date = new Date();
|
||||
date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
|
||||
} else {
|
||||
date = options.expires;
|
||||
}
|
||||
expires = '; expires=' + date.toUTCString();
|
||||
}
|
||||
var path = options.path ? '; path=' + options.path : '';
|
||||
var domain = options.domain ? '; domain=' + options.domain : '';
|
||||
var secure = options.secure ? '; secure' : '';
|
||||
document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
|
||||
} else {
|
||||
var cookieValue = null;
|
||||
if (document.cookie && document.cookie != '') {
|
||||
var cookies = document.cookie.split(';');
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = jQuery.trim(cookies[i]);
|
||||
if (cookie.substring(0, name.length + 1) == (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
};
|
||||
$(function () {
|
||||
|
||||
|
||||
$(".rightList li").mouseover(function () {
|
||||
//$($(this).parent()).children().each(function () {
|
||||
// $(this).removeClass("on");
|
||||
//});
|
||||
//$(this).addClass("on");
|
||||
});
|
||||
$(".rightList_nobor li").mouseover(function () {
|
||||
$($(this).parent()).children().each(function () {
|
||||
$(this).addClass("on");
|
||||
});
|
||||
});
|
||||
|
||||
$("#headerUserHistoryBtn").mouseover(function () {
|
||||
HeaderShowUtil.headerShowHistory();
|
||||
});
|
||||
$("#headerUserHistory").mouseleave(function () {
|
||||
HeaderShowUtil.headerHideHistory();
|
||||
});
|
||||
});
|
||||
function getNote() {
|
||||
}
|
||||
function goPage(cpage) {
|
||||
location.href = '?page=' + cpage;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function isWeiXin() {
|
||||
var ua = window.navigator.userAgent.toLowerCase();
|
||||
if (ua.indexOf("micromessenger") > 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var HeaderShowUtil = {
|
||||
headerShowHistory: function (obj) {
|
||||
if ($("#headerUserHistory").html().length < 10) {
|
||||
var rStr = '<div class="record_box">';
|
||||
rStr += ' <div class="record_title" id="hdShowTitle"><a href="javascript:void(0);" class="record_tit1 on" onclick="javascript:HeaderShowUtil.headerShowHistoryLog(this);">最近阅读</a><a href="javascript:void(0);" class="record_tit2" onclick="javascript:HeaderShowUtil.headerShowFavLog(this);">我的书架</a></div>';
|
||||
rStr += ' <div class="record_list record_list1" id="hdShowHistory">';
|
||||
rStr += ' <ul>';
|
||||
rStr += ' </ul>';
|
||||
rStr += ' <a class="all" href="/" >查看全部</a>';
|
||||
rStr += ' </div>';
|
||||
rStr += ' <div class="record_list record_list2" style="display:none" id="hsShowFav">';
|
||||
rStr += ' <ul>';
|
||||
rStr += ' </ul>';
|
||||
rStr += ' <a class="all" href="/" >查看全部</a>';
|
||||
rStr += ' </div>';
|
||||
rStr += ' <p class="sp"></p>';
|
||||
rStr += ' </div>';
|
||||
$("#headerUserHistory").html(rStr);
|
||||
}
|
||||
$("#headerUserHistory").show();
|
||||
$("#headerUserHistoryBtn").addClass("on");
|
||||
HeaderShowUtil.headerShowHistoryLog();
|
||||
},
|
||||
headerHideHistory: function () {
|
||||
$("#headerUserHistory").hide();
|
||||
$("#headerUserHistoryBtn").removeClass("on");
|
||||
},
|
||||
headerShowHistoryLog: function (obj) {
|
||||
if (obj != undefined) {
|
||||
$("#hdShowTitle a").removeClass("on");
|
||||
$(obj).addClass("on");
|
||||
$("#hdShowHistory").show();
|
||||
$("#hsShowFav").hide();
|
||||
}
|
||||
var cookieHistory = jQuery.cookie("wapviewhistory");
|
||||
if (cookieHistory != undefined && cookieHistory.length > 0) {
|
||||
var bList, bIdList;
|
||||
var bIdArray = new Array();
|
||||
var cookieList = cookieHistory.split(',');
|
||||
for (var i = 0; i < cookieList.length && i < 3; i++) {
|
||||
var str = cookieList[i];
|
||||
if (str.indexOf('|') > 0) {
|
||||
bList = str.split('|');
|
||||
if (bList.length == 3) {
|
||||
bIdList += ',' + bList[0].replace("b", "");
|
||||
bIdArray[bList[0].replace("b", "")] = bList[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
$("#hdShowHistory ul").html("<li>暂无看书历史</li>");
|
||||
}
|
||||
},
|
||||
headerShowFavLog: function (obj) {
|
||||
$("#hdShowTitle a").removeClass("on");
|
||||
$(obj).addClass("on");
|
||||
$("#hsShowFav").show();
|
||||
$("#hdShowHistory").hide();
|
||||
var rStr = '';
|
||||
var uname = jQuery.cookie("waplogname");
|
||||
if (uname != undefined && uname != "") {
|
||||
}
|
||||
else {
|
||||
rStr = '<li><a href="/user/login.html">请先登录</a></li>';
|
||||
$("#hsShowFav ul").html(rStr);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
2
novel-crawl/src/main/resources/static/javascript/jquery-1.8.0.min.js
vendored
Normal file
77
novel-crawl/src/main/resources/static/javascript/user.js
Normal file
@ -0,0 +1,77 @@
|
||||
var UserUtil = {
|
||||
msgStyle: 'background-color:#333; color:#fff; text-align:center; border:none; font-size:20px; padding:10px;',
|
||||
GetFavoritesNew: function () {
|
||||
var bIdList = "";
|
||||
$(".book_list").each(function () {
|
||||
bIdList += "," + $(this).attr("vals");
|
||||
});
|
||||
if (bIdList != "") {
|
||||
}
|
||||
},
|
||||
GetHistory: function () {
|
||||
var bIdList = "";
|
||||
$(".book_list").each(function () {
|
||||
bIdList += "," + $(this).attr("vals");
|
||||
});
|
||||
if (bIdList != "") {
|
||||
}
|
||||
},
|
||||
GetChapterInfo: function () {
|
||||
var cIdList = "";
|
||||
$(".showCName").each(function () {
|
||||
cIdList += "," + $(this).attr("vals");
|
||||
});
|
||||
if (cIdList != "") {
|
||||
}
|
||||
},
|
||||
SignDay: function () {
|
||||
if (!signed) {
|
||||
signed = true;
|
||||
}
|
||||
},
|
||||
SignDayStatus: function () {
|
||||
},
|
||||
RegSendSms: function () {
|
||||
var mob = $("#txtUName").val();
|
||||
var cCode = $("#TxtChkCode").val();
|
||||
if (mob != "" && cCode != "") {
|
||||
$("#btnSendSms").attr("disabled", "disabled");
|
||||
$("#txtUName").attr("readonly", "true");
|
||||
}
|
||||
else {
|
||||
layer.open({
|
||||
content: '手机号码和验证码必须填写',
|
||||
style: UserUtil.msgStyle,
|
||||
time: 2
|
||||
});
|
||||
}
|
||||
},
|
||||
GetPassSendSms: function () {
|
||||
var mob = $("#txtMobile").val();
|
||||
var cCode = $("#TxtChkCode").val();
|
||||
if (mob != "" && cCode != "") {
|
||||
$("#btnSendSms").attr("disabled", "disabled");
|
||||
$("#txtMobile").attr("readonly", "true");
|
||||
}
|
||||
else {
|
||||
layer.open({
|
||||
content: '手机号码和验证码必须填写',
|
||||
style: UserUtil.msgStyle,
|
||||
time: 2
|
||||
});
|
||||
}
|
||||
},
|
||||
RegSmsWait: function () {
|
||||
if (secondStep > 0) {
|
||||
$("#btnSendSms").val("重新发送(" + secondStep + ")");
|
||||
secondStep--;
|
||||
setTimeout("UserUtil.RegSmsWait()", 1000);
|
||||
}
|
||||
else {
|
||||
secondStep = 180;
|
||||
$("#btnSendSms").val("重新获取验证码");
|
||||
$("#btnSendSms").removeAttr("disabled");
|
||||
$("#txtUName").removeAttr("readonly");
|
||||
}
|
||||
}
|
||||
}
|
5018
novel-crawl/src/main/resources/static/layui/css/layui.css
Normal file
@ -0,0 +1,2 @@
|
||||
/** layui-v2.4.5 MIT License By https://www.layui.com */
|
||||
html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #e2e2e2;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:32px;line-height:32px;border-bottom:1px solid #e2e2e2}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 5px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none}
|
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 11 KiB |
@ -0,0 +1,919 @@
|
||||
/** layui-v2.4.5 MIT License By https://www.layui.com */
|
||||
.layui-layer-imgbar, .layui-layer-imgtit a, .layui-layer-tab .layui-layer-title span, .layui-layer-title {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap
|
||||
}
|
||||
|
||||
html #layuicss-layer {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 1989px
|
||||
}
|
||||
|
||||
.layui-layer, .layui-layer-shade {
|
||||
position: fixed;
|
||||
_position: absolute;
|
||||
pointer-events: auto
|
||||
}
|
||||
|
||||
.layui-layer-shade {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
_height: expression(document.body.offsetHeight+"px")
|
||||
}
|
||||
|
||||
.layui-layer {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
top: 150px;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #fff;
|
||||
-webkit-background-clip: content;
|
||||
border-radius: 2px;
|
||||
box-shadow: 1px 1px 50px rgba(0, 0, 0, .3)
|
||||
}
|
||||
|
||||
.layui-layer-close {
|
||||
position: absolute
|
||||
}
|
||||
|
||||
.layui-layer-content {
|
||||
position: relative
|
||||
}
|
||||
|
||||
.layui-layer-border {
|
||||
border: 1px solid #B2B2B2;
|
||||
border: 1px solid rgba(0, 0, 0, .1);
|
||||
box-shadow: 1px 1px 5px rgba(0, 0, 0, .2)
|
||||
}
|
||||
|
||||
.layui-layer-load {
|
||||
background: url(loading-1.gif) center center no-repeat #eee
|
||||
}
|
||||
|
||||
.layui-layer-ico {
|
||||
background: url(icon.png) no-repeat
|
||||
}
|
||||
|
||||
.layui-layer-btn a, .layui-layer-dialog .layui-layer-ico, .layui-layer-setwin a {
|
||||
display: inline-block;
|
||||
*display: inline;
|
||||
*zoom: 1;
|
||||
vertical-align: top
|
||||
}
|
||||
|
||||
.layui-layer-move {
|
||||
display: none;
|
||||
position: fixed;
|
||||
*position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: move;
|
||||
opacity: 0;
|
||||
filter: alpha(opacity=0);
|
||||
background-color: #fff;
|
||||
z-index: 2147483647
|
||||
}
|
||||
|
||||
.layui-layer-resize {
|
||||
position: absolute;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
cursor: se-resize
|
||||
}
|
||||
|
||||
.layer-anim {
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
-webkit-animation-duration: .3s;
|
||||
animation-duration: .3s
|
||||
}
|
||||
|
||||
@-webkit-keyframes layer-bounceIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(.5);
|
||||
transform: scale(.5)
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes layer-bounceIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(.5);
|
||||
-ms-transform: scale(.5);
|
||||
transform: scale(.5)
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(1);
|
||||
-ms-transform: scale(1);
|
||||
transform: scale(1)
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-00 {
|
||||
-webkit-animation-name: layer-bounceIn;
|
||||
animation-name: layer-bounceIn
|
||||
}
|
||||
|
||||
@-webkit-keyframes layer-zoomInDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(.1) translateY(-2000px);
|
||||
transform: scale(.1) translateY(-2000px);
|
||||
-webkit-animation-timing-function: ease-in-out;
|
||||
animation-timing-function: ease-in-out
|
||||
}
|
||||
60% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(.475) translateY(60px);
|
||||
transform: scale(.475) translateY(60px);
|
||||
-webkit-animation-timing-function: ease-out;
|
||||
animation-timing-function: ease-out
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes layer-zoomInDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(.1) translateY(-2000px);
|
||||
-ms-transform: scale(.1) translateY(-2000px);
|
||||
transform: scale(.1) translateY(-2000px);
|
||||
-webkit-animation-timing-function: ease-in-out;
|
||||
animation-timing-function: ease-in-out
|
||||
}
|
||||
60% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(.475) translateY(60px);
|
||||
-ms-transform: scale(.475) translateY(60px);
|
||||
transform: scale(.475) translateY(60px);
|
||||
-webkit-animation-timing-function: ease-out;
|
||||
animation-timing-function: ease-out
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-01 {
|
||||
-webkit-animation-name: layer-zoomInDown;
|
||||
animation-name: layer-zoomInDown
|
||||
}
|
||||
|
||||
@-webkit-keyframes layer-fadeInUpBig {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(2000px);
|
||||
transform: translateY(2000px)
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0);
|
||||
transform: translateY(0)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes layer-fadeInUpBig {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateY(2000px);
|
||||
-ms-transform: translateY(2000px);
|
||||
transform: translateY(2000px)
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateY(0);
|
||||
-ms-transform: translateY(0);
|
||||
transform: translateY(0)
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-02 {
|
||||
-webkit-animation-name: layer-fadeInUpBig;
|
||||
animation-name: layer-fadeInUpBig
|
||||
}
|
||||
|
||||
@-webkit-keyframes layer-zoomInLeft {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(.1) translateX(-2000px);
|
||||
transform: scale(.1) translateX(-2000px);
|
||||
-webkit-animation-timing-function: ease-in-out;
|
||||
animation-timing-function: ease-in-out
|
||||
}
|
||||
60% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(.475) translateX(48px);
|
||||
transform: scale(.475) translateX(48px);
|
||||
-webkit-animation-timing-function: ease-out;
|
||||
animation-timing-function: ease-out
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes layer-zoomInLeft {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(.1) translateX(-2000px);
|
||||
-ms-transform: scale(.1) translateX(-2000px);
|
||||
transform: scale(.1) translateX(-2000px);
|
||||
-webkit-animation-timing-function: ease-in-out;
|
||||
animation-timing-function: ease-in-out
|
||||
}
|
||||
60% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(.475) translateX(48px);
|
||||
-ms-transform: scale(.475) translateX(48px);
|
||||
transform: scale(.475) translateX(48px);
|
||||
-webkit-animation-timing-function: ease-out;
|
||||
animation-timing-function: ease-out
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-03 {
|
||||
-webkit-animation-name: layer-zoomInLeft;
|
||||
animation-name: layer-zoomInLeft
|
||||
}
|
||||
|
||||
@-webkit-keyframes layer-rollIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateX(-100%) rotate(-120deg);
|
||||
transform: translateX(-100%) rotate(-120deg)
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateX(0) rotate(0);
|
||||
transform: translateX(0) rotate(0)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes layer-rollIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
-webkit-transform: translateX(-100%) rotate(-120deg);
|
||||
-ms-transform: translateX(-100%) rotate(-120deg);
|
||||
transform: translateX(-100%) rotate(-120deg)
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: translateX(0) rotate(0);
|
||||
-ms-transform: translateX(0) rotate(0);
|
||||
transform: translateX(0) rotate(0)
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-04 {
|
||||
-webkit-animation-name: layer-rollIn;
|
||||
animation-name: layer-rollIn
|
||||
}
|
||||
|
||||
@keyframes layer-fadeIn {
|
||||
0% {
|
||||
opacity: 0
|
||||
}
|
||||
100% {
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-05 {
|
||||
-webkit-animation-name: layer-fadeIn;
|
||||
animation-name: layer-fadeIn
|
||||
}
|
||||
|
||||
@-webkit-keyframes layer-shake {
|
||||
0%, 100% {
|
||||
-webkit-transform: translateX(0);
|
||||
transform: translateX(0)
|
||||
}
|
||||
10%, 30%, 50%, 70%, 90% {
|
||||
-webkit-transform: translateX(-10px);
|
||||
transform: translateX(-10px)
|
||||
}
|
||||
20%, 40%, 60%, 80% {
|
||||
-webkit-transform: translateX(10px);
|
||||
transform: translateX(10px)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes layer-shake {
|
||||
0%, 100% {
|
||||
-webkit-transform: translateX(0);
|
||||
-ms-transform: translateX(0);
|
||||
transform: translateX(0)
|
||||
}
|
||||
10%, 30%, 50%, 70%, 90% {
|
||||
-webkit-transform: translateX(-10px);
|
||||
-ms-transform: translateX(-10px);
|
||||
transform: translateX(-10px)
|
||||
}
|
||||
20%, 40%, 60%, 80% {
|
||||
-webkit-transform: translateX(10px);
|
||||
-ms-transform: translateX(10px);
|
||||
transform: translateX(10px)
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-06 {
|
||||
-webkit-animation-name: layer-shake;
|
||||
animation-name: layer-shake
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0
|
||||
}
|
||||
100% {
|
||||
opacity: 1
|
||||
}
|
||||
}
|
||||
|
||||
.layui-layer-title {
|
||||
padding: 0 80px 0 20px;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
background-color: #F8F8F8;
|
||||
border-radius: 2px 2px 0 0
|
||||
}
|
||||
|
||||
.layui-layer-setwin {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
*right: 0;
|
||||
top: 15px;
|
||||
font-size: 0;
|
||||
line-height: initial
|
||||
}
|
||||
|
||||
.layui-layer-setwin a {
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
_overflow: hidden
|
||||
}
|
||||
|
||||
.layui-layer-setwin .layui-layer-min cite {
|
||||
position: absolute;
|
||||
width: 14px;
|
||||
height: 2px;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
margin-top: -1px;
|
||||
background-color: #2E2D3C;
|
||||
cursor: pointer;
|
||||
_overflow: hidden
|
||||
}
|
||||
|
||||
.layui-layer-setwin .layui-layer-min:hover cite {
|
||||
background-color: #2D93CA
|
||||
}
|
||||
|
||||
.layui-layer-setwin .layui-layer-max {
|
||||
background-position: -32px -40px
|
||||
}
|
||||
|
||||
.layui-layer-setwin .layui-layer-max:hover {
|
||||
background-position: -16px -40px
|
||||
}
|
||||
|
||||
.layui-layer-setwin .layui-layer-maxmin {
|
||||
background-position: -65px -40px
|
||||
}
|
||||
|
||||
.layui-layer-setwin .layui-layer-maxmin:hover {
|
||||
background-position: -49px -40px
|
||||
}
|
||||
|
||||
.layui-layer-setwin .layui-layer-close1 {
|
||||
background-position: 1px -40px;
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.layui-layer-setwin .layui-layer-close1:hover {
|
||||
opacity: .7
|
||||
}
|
||||
|
||||
.layui-layer-setwin .layui-layer-close2 {
|
||||
position: absolute;
|
||||
right: -28px;
|
||||
top: -28px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-left: 0;
|
||||
background-position: -149px -31px;
|
||||
*right: -18px;
|
||||
_display: none
|
||||
}
|
||||
|
||||
.layui-layer-setwin .layui-layer-close2:hover {
|
||||
background-position: -180px -31px
|
||||
}
|
||||
|
||||
.layui-layer-btn {
|
||||
text-align: right;
|
||||
padding: 0 15px 12px;
|
||||
pointer-events: auto;
|
||||
user-select: none;
|
||||
-webkit-user-select: none
|
||||
}
|
||||
|
||||
.layui-layer-btn a {
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
margin: 5px 5px 0;
|
||||
padding: 0 15px;
|
||||
border: 1px solid #dedede;
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
border-radius: 2px;
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
.layui-layer-btn a:hover {
|
||||
opacity: .9;
|
||||
text-decoration: none
|
||||
}
|
||||
|
||||
.layui-layer-btn a:active {
|
||||
opacity: .8
|
||||
}
|
||||
|
||||
.layui-layer-btn .layui-layer-btn0 {
|
||||
border-color: #f70;
|
||||
background-color: #f70;
|
||||
color: #fff
|
||||
}
|
||||
|
||||
.layui-layer-btn-l {
|
||||
text-align: left
|
||||
}
|
||||
|
||||
.layui-layer-btn-c {
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.layui-layer-dialog {
|
||||
min-width: 260px
|
||||
}
|
||||
|
||||
.layui-layer-dialog .layui-layer-content {
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
line-height: 24px;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto
|
||||
}
|
||||
|
||||
.layui-layer-dialog .layui-layer-content .layui-layer-ico {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 15px;
|
||||
_left: -40px;
|
||||
width: 30px;
|
||||
height: 30px
|
||||
}
|
||||
|
||||
.layui-layer-ico1 {
|
||||
background-position: -30px 0
|
||||
}
|
||||
|
||||
.layui-layer-ico2 {
|
||||
background-position: -60px 0
|
||||
}
|
||||
|
||||
.layui-layer-ico3 {
|
||||
background-position: -90px 0
|
||||
}
|
||||
|
||||
.layui-layer-ico4 {
|
||||
background-position: -120px 0
|
||||
}
|
||||
|
||||
.layui-layer-ico5 {
|
||||
background-position: -150px 0
|
||||
}
|
||||
|
||||
.layui-layer-ico6 {
|
||||
background-position: -180px 0
|
||||
}
|
||||
|
||||
.layui-layer-rim {
|
||||
border: 6px solid #8D8D8D;
|
||||
border: 6px solid rgba(0, 0, 0, .3);
|
||||
border-radius: 5px;
|
||||
box-shadow: none
|
||||
}
|
||||
|
||||
.layui-layer-msg {
|
||||
min-width: 180px;
|
||||
border: 1px solid #D3D4D3;
|
||||
box-shadow: none
|
||||
}
|
||||
|
||||
.layui-layer-hui {
|
||||
min-width: 100px;
|
||||
background-color: #000;
|
||||
filter: alpha(opacity=60);
|
||||
background-color: rgba(0, 0, 0, .6);
|
||||
color: #fff;
|
||||
border: none
|
||||
}
|
||||
|
||||
.layui-layer-hui .layui-layer-content {
|
||||
padding: 12px 25px;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.layui-layer-dialog .layui-layer-padding {
|
||||
padding: 20px 20px 20px 55px;
|
||||
text-align: left
|
||||
}
|
||||
|
||||
.layui-layer-page .layui-layer-content {
|
||||
position: relative;
|
||||
overflow: auto
|
||||
}
|
||||
|
||||
.layui-layer-iframe .layui-layer-btn, .layui-layer-page .layui-layer-btn {
|
||||
padding-top: 10px
|
||||
}
|
||||
|
||||
.layui-layer-nobg {
|
||||
background: 0 0
|
||||
}
|
||||
|
||||
.layui-layer-iframe iframe {
|
||||
display: block;
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.layui-layer-loading {
|
||||
border-radius: 100%;
|
||||
background: 0 0;
|
||||
box-shadow: none;
|
||||
border: none
|
||||
}
|
||||
|
||||
.layui-layer-loading .layui-layer-content {
|
||||
width: 60px;
|
||||
height: 24px;
|
||||
background: url(loading-0.gif) no-repeat
|
||||
}
|
||||
|
||||
.layui-layer-loading .layui-layer-loading1 {
|
||||
width: 37px;
|
||||
height: 37px;
|
||||
background: url(loading-1.gif) no-repeat
|
||||
}
|
||||
|
||||
.layui-layer-ico16, .layui-layer-loading .layui-layer-loading2 {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: url(loading-2.gif) no-repeat
|
||||
}
|
||||
|
||||
.layui-layer-tips {
|
||||
background: 0 0;
|
||||
box-shadow: none;
|
||||
border: none
|
||||
}
|
||||
|
||||
.layui-layer-tips .layui-layer-content {
|
||||
position: relative;
|
||||
line-height: 22px;
|
||||
min-width: 12px;
|
||||
padding: 8px 15px;
|
||||
font-size: 12px;
|
||||
_float: left;
|
||||
border-radius: 2px;
|
||||
box-shadow: 1px 1px 3px rgba(0, 0, 0, .2);
|
||||
background-color: #000;
|
||||
color: #fff
|
||||
}
|
||||
|
||||
.layui-layer-tips .layui-layer-close {
|
||||
right: -2px;
|
||||
top: -1px
|
||||
}
|
||||
|
||||
.layui-layer-tips i.layui-layer-TipsG {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-width: 8px;
|
||||
border-color: transparent;
|
||||
border-style: dashed;
|
||||
*overflow: hidden
|
||||
}
|
||||
|
||||
.layui-layer-tips i.layui-layer-TipsB, .layui-layer-tips i.layui-layer-TipsT {
|
||||
left: 5px;
|
||||
border-right-style: solid;
|
||||
border-right-color: #000
|
||||
}
|
||||
|
||||
.layui-layer-tips i.layui-layer-TipsT {
|
||||
bottom: -8px
|
||||
}
|
||||
|
||||
.layui-layer-tips i.layui-layer-TipsB {
|
||||
top: -8px
|
||||
}
|
||||
|
||||
.layui-layer-tips i.layui-layer-TipsL, .layui-layer-tips i.layui-layer-TipsR {
|
||||
top: 5px;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #000
|
||||
}
|
||||
|
||||
.layui-layer-tips i.layui-layer-TipsR {
|
||||
left: -8px
|
||||
}
|
||||
|
||||
.layui-layer-tips i.layui-layer-TipsL {
|
||||
right: -8px
|
||||
}
|
||||
|
||||
.layui-layer-lan[type=dialog] {
|
||||
min-width: 280px
|
||||
}
|
||||
|
||||
.layui-layer-lan .layui-layer-title {
|
||||
background: #4476A7;
|
||||
color: #fff;
|
||||
border: none
|
||||
}
|
||||
|
||||
.layui-layer-lan .layui-layer-btn {
|
||||
padding: 5px 10px 10px;
|
||||
text-align: right;
|
||||
border-top: 1px solid #E9E7E7
|
||||
}
|
||||
|
||||
.layui-layer-lan .layui-layer-btn a {
|
||||
background: #fff;
|
||||
border-color: #E9E7E7;
|
||||
color: #333
|
||||
}
|
||||
|
||||
.layui-layer-lan .layui-layer-btn .layui-layer-btn1 {
|
||||
background: #C9C5C5
|
||||
}
|
||||
|
||||
.layui-layer-molv .layui-layer-title {
|
||||
background: #009f95;
|
||||
color: #fff;
|
||||
border: none
|
||||
}
|
||||
|
||||
.layui-layer-molv .layui-layer-btn a {
|
||||
background: #009f95;
|
||||
border-color: #009f95
|
||||
}
|
||||
|
||||
.layui-layer-molv .layui-layer-btn .layui-layer-btn1 {
|
||||
background: #92B8B1
|
||||
}
|
||||
|
||||
.layui-layer-iconext {
|
||||
background: url(icon-ext.png) no-repeat
|
||||
}
|
||||
|
||||
.layui-layer-prompt .layui-layer-input {
|
||||
display: block;
|
||||
width: 230px;
|
||||
height: 36px;
|
||||
margin: 0 auto;
|
||||
line-height: 30px;
|
||||
padding-left: 10px;
|
||||
border: 1px solid #e6e6e6;
|
||||
color: #333
|
||||
}
|
||||
|
||||
.layui-layer-prompt textarea.layui-layer-input {
|
||||
width: 300px;
|
||||
height: 100px;
|
||||
line-height: 20px;
|
||||
padding: 6px 10px
|
||||
}
|
||||
|
||||
.layui-layer-prompt .layui-layer-content {
|
||||
padding: 20px
|
||||
}
|
||||
|
||||
.layui-layer-prompt .layui-layer-btn {
|
||||
padding-top: 0
|
||||
}
|
||||
|
||||
.layui-layer-tab {
|
||||
box-shadow: 1px 1px 50px rgba(0, 0, 0, .4)
|
||||
}
|
||||
|
||||
.layui-layer-tab .layui-layer-title {
|
||||
padding-left: 0;
|
||||
overflow: visible
|
||||
}
|
||||
|
||||
.layui-layer-tab .layui-layer-title span {
|
||||
position: relative;
|
||||
float: left;
|
||||
min-width: 80px;
|
||||
max-width: 260px;
|
||||
padding: 0 20px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
.layui-layer-tab .layui-layer-title span.layui-this {
|
||||
height: 43px;
|
||||
border-left: 1px solid #eee;
|
||||
border-right: 1px solid #eee;
|
||||
background-color: #fff;
|
||||
z-index: 10
|
||||
}
|
||||
|
||||
.layui-layer-tab .layui-layer-title span:first-child {
|
||||
border-left: none
|
||||
}
|
||||
|
||||
.layui-layer-tabmain {
|
||||
line-height: 24px;
|
||||
clear: both
|
||||
}
|
||||
|
||||
.layui-layer-tabmain .layui-layer-tabli {
|
||||
display: none
|
||||
}
|
||||
|
||||
.layui-layer-tabmain .layui-layer-tabli.layui-this {
|
||||
display: block
|
||||
}
|
||||
|
||||
.layui-layer-photos {
|
||||
-webkit-animation-duration: .8s;
|
||||
animation-duration: .8s
|
||||
}
|
||||
|
||||
.layui-layer-photos .layui-layer-content {
|
||||
overflow: hidden;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.layui-layer-photos .layui-layer-phimg img {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
*display: inline;
|
||||
*zoom: 1;
|
||||
vertical-align: top
|
||||
}
|
||||
|
||||
.layui-layer-imgbar, .layui-layer-imguide {
|
||||
display: none
|
||||
}
|
||||
|
||||
.layui-layer-imgnext, .layui-layer-imgprev {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 27px;
|
||||
_width: 44px;
|
||||
height: 44px;
|
||||
margin-top: -22px;
|
||||
outline: 0;
|
||||
blr: expression(this.onFocus=this.blur())
|
||||
}
|
||||
|
||||
.layui-layer-imgprev {
|
||||
left: 10px;
|
||||
background-position: -5px -5px;
|
||||
_background-position: -70px -5px
|
||||
}
|
||||
|
||||
.layui-layer-imgprev:hover {
|
||||
background-position: -33px -5px;
|
||||
_background-position: -120px -5px
|
||||
}
|
||||
|
||||
.layui-layer-imgnext {
|
||||
right: 10px;
|
||||
_right: 8px;
|
||||
background-position: -5px -50px;
|
||||
_background-position: -70px -50px
|
||||
}
|
||||
|
||||
.layui-layer-imgnext:hover {
|
||||
background-position: -33px -50px;
|
||||
_background-position: -120px -50px
|
||||
}
|
||||
|
||||
.layui-layer-imgbar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
background-color: rgba(0, 0, 0, .8);
|
||||
background-color: #000 \9;
|
||||
filter: Alpha(opacity=80);
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
font-size: 0
|
||||
}
|
||||
|
||||
.layui-layer-imgtit * {
|
||||
display: inline-block;
|
||||
*display: inline;
|
||||
*zoom: 1;
|
||||
vertical-align: top;
|
||||
font-size: 12px
|
||||
}
|
||||
|
||||
.layui-layer-imgtit a {
|
||||
max-width: 65%;
|
||||
overflow: hidden;
|
||||
color: #fff
|
||||
}
|
||||
|
||||
.layui-layer-imgtit a:hover {
|
||||
color: #fff;
|
||||
text-decoration: underline
|
||||
}
|
||||
|
||||
.layui-layer-imgtit em {
|
||||
padding-left: 10px;
|
||||
font-style: normal
|
||||
}
|
||||
|
||||
@-webkit-keyframes layer-bounceOut {
|
||||
100% {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(.7);
|
||||
transform: scale(.7)
|
||||
}
|
||||
30% {
|
||||
-webkit-transform: scale(1.05);
|
||||
transform: scale(1.05)
|
||||
}
|
||||
0% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes layer-bounceOut {
|
||||
100% {
|
||||
opacity: 0;
|
||||
-webkit-transform: scale(.7);
|
||||
-ms-transform: scale(.7);
|
||||
transform: scale(.7)
|
||||
}
|
||||
30% {
|
||||
-webkit-transform: scale(1.05);
|
||||
-ms-transform: scale(1.05);
|
||||
transform: scale(1.05)
|
||||
}
|
||||
0% {
|
||||
-webkit-transform: scale(1);
|
||||
-ms-transform: scale(1);
|
||||
transform: scale(1)
|
||||
}
|
||||
}
|
||||
|
||||
.layer-anim-close {
|
||||
-webkit-animation-name: layer-bounceOut;
|
||||
animation-name: layer-bounceOut;
|
||||
-webkit-animation-fill-mode: both;
|
||||
animation-fill-mode: both;
|
||||
-webkit-animation-duration: .2s;
|
||||
animation-duration: .2s
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1100px) {
|
||||
.layui-layer-iframe {
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 701 B |
After Width: | Height: | Size: 1.7 KiB |
@ -0,0 +1,96 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>聊天记录</title>
|
||||
|
||||
<link rel="stylesheet" href="http://local.res.layui.com/layui/src/css/layui.css">
|
||||
<style>
|
||||
body .layim-chat-main{height: auto;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="layim-chat-main">
|
||||
<ul id="LAY_view"></ul>
|
||||
</div>
|
||||
|
||||
<div id="LAY_page" style="margin: 0 10px;"></div>
|
||||
|
||||
|
||||
<textarea title="消息模版" id="LAY_tpl" style="display:none;">
|
||||
{{# layui.each(d.data, function(index, item){
|
||||
if(item.id == parent.layui.layim.cache().mine.id){ }}
|
||||
<li class="layim-chat-mine"><div class="layim-chat-user"><img src="{{ item.avatar }}"><cite><i>{{ layui.data.date(item.timestamp) }}</i>{{ item.username }}</cite></div><div class="layim-chat-text">{{ layui.layim.content(item.content) }}</div></li>
|
||||
{{# } else { }}
|
||||
<li><div class="layim-chat-user"><img src="{{ item.avatar }}"><cite>{{ item.username }}<i>{{ layui.data.date(item.timestamp) }}</i></cite></div><div class="layim-chat-text">{{ layui.layim.content(item.content) }}</div></li>
|
||||
{{# }
|
||||
}); }}
|
||||
</textarea>
|
||||
|
||||
<!--
|
||||
上述模版采用了 laytpl 语法,不了解的同学可以去看下文档:http://www.layui.com/doc/modules/laytpl.html
|
||||
|
||||
-->
|
||||
|
||||
|
||||
<script src="http://local.res.layui.com/layui/src/layui.js"></script>
|
||||
<script>
|
||||
layui.use(['layim', 'laypage'], function(){
|
||||
var layim = layui.layim
|
||||
,layer = layui.layer
|
||||
,laytpl = layui.laytpl
|
||||
,$ = layui.jquery
|
||||
,laypage = layui.laypage;
|
||||
|
||||
//聊天记录的分页此处不做演示,你可以采用laypage,不了解的同学见文档:http://www.layui.com/doc/modules/laypage.html
|
||||
|
||||
|
||||
//开始请求聊天记录
|
||||
var param = location.search //获得URL参数。该窗口url会携带会话id和type,他们是你请求聊天记录的重要凭据
|
||||
|
||||
//实际使用时,下述的res一般是通过Ajax获得,而此处仅仅只是演示数据格式
|
||||
,res = {
|
||||
code: 0
|
||||
,msg: ''
|
||||
,data: [{
|
||||
username: '纸飞机'
|
||||
,id: 100000
|
||||
,avatar: 'http://tva3.sinaimg.cn/crop.0.0.512.512.180/8693225ajw8f2rt20ptykj20e80e8weu.jpg'
|
||||
,timestamp: 1480897882000
|
||||
,content: 'face[抱抱] face[心] 你好啊小美女'
|
||||
}, {
|
||||
username: 'Z_子晴'
|
||||
,id: 108101
|
||||
,avatar: 'http://tva3.sinaimg.cn/crop.0.0.512.512.180/8693225ajw8f2rt20ptykj20e80e8weu.jpg'
|
||||
,timestamp: 1480897892000
|
||||
,content: '你没发错吧?face[微笑]'
|
||||
},{
|
||||
username: 'Z_子晴'
|
||||
,id: 108101
|
||||
,avatar: 'http://tva3.sinaimg.cn/crop.0.0.512.512.180/8693225ajw8f2rt20ptykj20e80e8weu.jpg'
|
||||
,timestamp: 1480897898000
|
||||
,content: '你是谁呀亲。。我爱的是贤心!我爱的是贤心!我爱的是贤心!重要的事情要说三遍~'
|
||||
},{
|
||||
username: 'Z_子晴'
|
||||
,id: 108101
|
||||
,avatar: 'http://tva3.sinaimg.cn/crop.0.0.512.512.180/8693225ajw8f2rt20ptykj20e80e8weu.jpg'
|
||||
,timestamp: 1480897908000
|
||||
,content: '注意:这些都是模拟数据,实际使用时,需将其中的模拟接口改为你的项目真实接口。\n该模版文件所在目录(相对于layui.js):\n/css/modules/layim/html/chatlog.html'
|
||||
}]
|
||||
}
|
||||
|
||||
//console.log(param)
|
||||
|
||||
var html = laytpl(LAY_tpl.value).render({
|
||||
data: res.data
|
||||
});
|
||||
$('#LAY_view').html(html);
|
||||
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,38 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>发现</title>
|
||||
|
||||
<link rel="stylesheet" href="http://local.res.layui.com/layui/src/css/layui.css">
|
||||
<style>
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div style="margin: 15px;">
|
||||
<blockquote class="layui-elem-quote">此为自定义的【查找】页面,因需求不一,所以官方暂不提供该模版结构与样式,实际使用时,可移至该文件到你的项目中,对页面自行把控。
|
||||
<br>文件所在目录(相对于layui.js):/css/modules/layim/html/find.html</blockquote>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script src="http://local.res.layui.com/layui/src/layui.js"></script>
|
||||
<script>
|
||||
layui.use(['layim', 'laypage'], function(){
|
||||
var layim = layui.layim
|
||||
,layer = layui.layer
|
||||
,laytpl = layui.laytpl
|
||||
,$ = layui.jquery
|
||||
,laypage = layui.laypage;
|
||||
|
||||
//一些添加好友请求之类的交互参见文档
|
||||
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,87 @@
|
||||
{
|
||||
"code": 0,
|
||||
"pages": 1,
|
||||
"data": [
|
||||
{
|
||||
"id": 76,
|
||||
"content": "申请添加你为好友",
|
||||
"uid": 168,
|
||||
"from": 166488,
|
||||
"from_group": 0,
|
||||
"type": 1,
|
||||
"remark": "有问题要问",
|
||||
"href": null,
|
||||
"read": 1,
|
||||
"time": "刚刚",
|
||||
"user": {
|
||||
"id": 166488,
|
||||
"avatar": "http://q.qlogo.cn/qqapp/101235792/B704597964F9BD0DB648292D1B09F7E8/100",
|
||||
"username": "李彦宏",
|
||||
"sign": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 75,
|
||||
"content": "申请添加你为好友",
|
||||
"uid": 168,
|
||||
"from": 347592,
|
||||
"from_group": 0,
|
||||
"type": 1,
|
||||
"remark": "你好啊!",
|
||||
"href": null,
|
||||
"read": 1,
|
||||
"time": "刚刚",
|
||||
"user": {
|
||||
"id": 347592,
|
||||
"avatar": "http://q.qlogo.cn/qqapp/101235792/B78751375E0531675B1272AD994BA875/100",
|
||||
"username": "麻花疼",
|
||||
"sign": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 62,
|
||||
"content": "雷军 拒绝了你的好友申请",
|
||||
"uid": 168,
|
||||
"from": null,
|
||||
"from_group": null,
|
||||
"type": 1,
|
||||
"remark": null,
|
||||
"href": null,
|
||||
"read": 1,
|
||||
"time": "10天前",
|
||||
"user": {
|
||||
"id": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 60,
|
||||
"content": "马小云 已经同意你的好友申请",
|
||||
"uid": 168,
|
||||
"from": null,
|
||||
"from_group": null,
|
||||
"type": 1,
|
||||
"remark": null,
|
||||
"href": null,
|
||||
"read": 1,
|
||||
"time": "10天前",
|
||||
"user": {
|
||||
"id": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 61,
|
||||
"content": "贤心 已经同意你的好友申请",
|
||||
"uid": 168,
|
||||
"from": null,
|
||||
"from_group": null,
|
||||
"type": 1,
|
||||
"remark": null,
|
||||
"href": null,
|
||||
"read": 1,
|
||||
"time": "10天前",
|
||||
"user": {
|
||||
"id": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,208 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>消息盒子</title>
|
||||
|
||||
<link rel="stylesheet" href="../../../layui.css?v=1">
|
||||
<style>
|
||||
.layim-msgbox{margin: 15px;}
|
||||
.layim-msgbox li{position: relative; margin-bottom: 10px; padding: 0 130px 10px 60px; padding-bottom: 10px; line-height: 22px; border-bottom: 1px dotted #e2e2e2;}
|
||||
.layim-msgbox .layim-msgbox-tips{margin: 0; padding: 10px 0; border: none; text-align: center; color: #999;}
|
||||
.layim-msgbox .layim-msgbox-system{padding: 0 10px 10px 10px;}
|
||||
.layim-msgbox li p span{padding-left: 5px; color: #999;}
|
||||
.layim-msgbox li p em{font-style: normal; color: #FF5722;}
|
||||
|
||||
.layim-msgbox-avatar{position: absolute; left: 0; top: 0; width: 50px; height: 50px;}
|
||||
.layim-msgbox-user{padding-top: 5px;}
|
||||
.layim-msgbox-content{margin-top: 3px;}
|
||||
.layim-msgbox .layui-btn-small{padding: 0 15px; margin-left: 5px;}
|
||||
.layim-msgbox-btn{position: absolute; right: 0; top: 12px; color: #999;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<ul class="layim-msgbox" id="LAY_view"></ul>
|
||||
|
||||
<div style="margin: 0 15px;">
|
||||
<blockquote class="layui-elem-quote">注意:这些都是模拟数据,实际使用时,需将其中的模拟接口改为你的项目真实接口。
|
||||
<br>该模版文件所在目录(相对于layui.js):/css/modules/layim/html/msgbox.html</blockquote>
|
||||
</div>
|
||||
|
||||
<textarea title="消息模版" id="LAY_tpl" style="display:none;">
|
||||
{{# layui.each(d.data, function(index, item){
|
||||
if(item.from){ }}
|
||||
<li data-uid="{{ item.from }}" data-fromGroup="{{ item.from_group }}">
|
||||
<a href="/u/{{ item.from }}/" >
|
||||
<img src="{{ item.user.avatar }}" class="layui-circle layim-msgbox-avatar">
|
||||
</a>
|
||||
<p class="layim-msgbox-user">
|
||||
<a href="/u/{{ item.from }}/" >{{ item.user.username||'' }}</a>
|
||||
<span>{{ item.time }}</span>
|
||||
</p>
|
||||
<p class="layim-msgbox-content">
|
||||
{{ item.content }}
|
||||
<span>{{ item.remark ? '附言: '+item.remark : '' }}</span>
|
||||
</p>
|
||||
<p class="layim-msgbox-btn">
|
||||
<button class="layui-btn layui-btn-small" data-type="agree">同意</button>
|
||||
<button class="layui-btn layui-btn-small layui-btn-primary" data-type="refuse">拒绝</button>
|
||||
</p>
|
||||
</li>
|
||||
{{# } else { }}
|
||||
<li class="layim-msgbox-system">
|
||||
<p><em>系统:</em>{{ item.content }}<span>{{ item.time }}</span></p>
|
||||
</li>
|
||||
{{# }
|
||||
}); }}
|
||||
</textarea>
|
||||
|
||||
<!--
|
||||
上述模版采用了 laytpl 语法,不了解的同学可以去看下文档:http://www.layui.com/doc/modules/laytpl.html
|
||||
-->
|
||||
|
||||
|
||||
<script src="../../../../layui.js?v=1"></script>
|
||||
<script>
|
||||
layui.use(['layim', 'flow'], function(){
|
||||
var layim = layui.layim
|
||||
,layer = layui.layer
|
||||
,laytpl = layui.laytpl
|
||||
,$ = layui.jquery
|
||||
,flow = layui.flow;
|
||||
|
||||
var cache = {}; //用于临时记录请求到的数据
|
||||
|
||||
//请求消息
|
||||
var renderMsg = function(page, callback){
|
||||
|
||||
//实际部署时,请将下述 getmsg.json 改为你的接口地址
|
||||
|
||||
$.get('getmsg.json', {
|
||||
page: page || 1
|
||||
}, function(res){
|
||||
if(res.code != 0){
|
||||
return layer.msg(res.msg);
|
||||
}
|
||||
|
||||
//记录来源用户信息
|
||||
layui.each(res.data, function(index, item){
|
||||
cache[item.from] = item.user;
|
||||
});
|
||||
|
||||
callback && callback(res.data, res.pages);
|
||||
});
|
||||
};
|
||||
|
||||
//消息信息流
|
||||
flow.load({
|
||||
elem: '#LAY_view' //流加载容器
|
||||
,isAuto: false
|
||||
,end: '<li class="layim-msgbox-tips">暂无更多新消息</li>'
|
||||
,done: function(page, next){ //加载下一页
|
||||
renderMsg(page, function(data, pages){
|
||||
var html = laytpl(LAY_tpl.value).render({
|
||||
data: data
|
||||
,page: page
|
||||
});
|
||||
next(html, page < pages);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
//打开页面即把消息标记为已读
|
||||
/*
|
||||
$.post('/message/read', {
|
||||
type: 1
|
||||
});
|
||||
*/
|
||||
|
||||
//操作
|
||||
var active = {
|
||||
//同意
|
||||
agree: function(othis){
|
||||
var li = othis.parents('li')
|
||||
,uid = li.data('uid')
|
||||
,from_group = li.data('fromGroup')
|
||||
,user = cache[uid];
|
||||
|
||||
//选择分组
|
||||
parent.layui.layim.setFriendGroup({
|
||||
type: 'friend'
|
||||
,username: user.username
|
||||
,avatar: user.avatar
|
||||
,group: parent.layui.layim.cache().friend //获取好友分组数据
|
||||
,submit: function(group, index){
|
||||
|
||||
//将好友追加到主面板
|
||||
parent.layui.layim.addList({
|
||||
type: 'friend'
|
||||
,avatar: user.avatar //好友头像
|
||||
,username: user.username //好友昵称
|
||||
,groupid: group //所在的分组id
|
||||
,id: uid //好友ID
|
||||
,sign: user.sign //好友签名
|
||||
});
|
||||
parent.layer.close(index);
|
||||
othis.parent().html('已同意');
|
||||
|
||||
|
||||
//实际部署时,请开启下述注释,并改成你的接口地址
|
||||
/*
|
||||
$.post('/im/agreeFriend', {
|
||||
uid: uid //对方用户ID
|
||||
,from_group: from_group //对方设定的好友分组
|
||||
,group: group //我设定的好友分组
|
||||
}, function(res){
|
||||
if(res.code != 0){
|
||||
return layer.msg(res.msg);
|
||||
}
|
||||
|
||||
//将好友追加到主面板
|
||||
parent.layui.layim.addList({
|
||||
type: 'friend'
|
||||
,avatar: user.avatar //好友头像
|
||||
,username: user.username //好友昵称
|
||||
,groupid: group //所在的分组id
|
||||
,id: uid //好友ID
|
||||
,sign: user.sign //好友签名
|
||||
});
|
||||
parent.layer.close(index);
|
||||
othis.parent().html('已同意');
|
||||
});
|
||||
*/
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//拒绝
|
||||
,refuse: function(othis){
|
||||
var li = othis.parents('li')
|
||||
,uid = li.data('uid');
|
||||
|
||||
layer.confirm('确定拒绝吗?', function(index){
|
||||
$.post('/im/refuseFriend', {
|
||||
uid: uid //对方用户ID
|
||||
}, function(res){
|
||||
if(res.code != 0){
|
||||
return layer.msg(res.msg);
|
||||
}
|
||||
layer.close(index);
|
||||
othis.parent().html('<em>已拒绝</em>');
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$('body').on('click', '.layui-btn', function(){
|
||||
var othis = $(this), type = othis.data('type');
|
||||
active[type] ? active[type].call(this, othis) : '';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 3.2 KiB |
BIN
novel-crawl/src/main/resources/static/layui/font/iconfont.eot
Normal file
473
novel-crawl/src/main/resources/static/layui/font/iconfont.svg
Normal file
After Width: | Height: | Size: 274 KiB |
BIN
novel-crawl/src/main/resources/static/layui/font/iconfont.ttf
Normal file
BIN
novel-crawl/src/main/resources/static/layui/font/iconfont.woff
Normal file
BIN
novel-crawl/src/main/resources/static/layui/images/face/0.gif
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/1.gif
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/10.gif
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/11.gif
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/12.gif
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/13.gif
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/14.gif
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/15.gif
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/16.gif
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/17.gif
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/18.gif
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/19.gif
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/2.gif
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/20.gif
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/21.gif
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/22.gif
Normal file
After Width: | Height: | Size: 9.6 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/23.gif
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/24.gif
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/25.gif
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/26.gif
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/27.gif
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/28.gif
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/29.gif
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/3.gif
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/30.gif
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/31.gif
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/32.gif
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/33.gif
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/34.gif
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/35.gif
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/36.gif
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/37.gif
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/38.gif
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/39.gif
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/4.gif
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/40.gif
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/41.gif
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/42.gif
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/43.gif
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/44.gif
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/45.gif
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/46.gif
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/47.gif
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/48.gif
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/49.gif
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/5.gif
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
novel-crawl/src/main/resources/static/layui/images/face/50.gif
Normal file
After Width: | Height: | Size: 5.7 KiB |