上传代码

This commit is contained in:
xxy
2020-05-02 15:05:21 +08:00
parent c8c80fa719
commit ed34c67d08
733 changed files with 61899 additions and 0 deletions

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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";
}
}

View File

@ -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 基础验证
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,3 @@
spring:
profiles:
include: [common-dev]

View File

@ -0,0 +1,3 @@
spring:
profiles:
include: [common-test]

View File

@ -0,0 +1,13 @@
server:
port: 8081
spring:
profiles:
active: dev
admin:
username: admin
password: admin123456

View 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>

View 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>

View File

@ -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>

View File

@ -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>

View 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
}

View 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;}

View 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);
}
}
}

File diff suppressed because one or more lines are too long

View 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");
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View 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}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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
}
}
]
}

View File

@ -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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Some files were not shown because too many files have changed in this diff Show More