小说发布更新中。。。。

This commit is contained in:
xiongxiaoyang 2019-11-19 18:54:48 +08:00
parent 21102d7861
commit 9577a806d4
187 changed files with 17094 additions and 736 deletions

View File

@ -3,6 +3,9 @@ package com.java2nb.books.controller;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.java2nb.books.domain.BookContentDO;
import com.java2nb.books.domain.BookIndexDO;
import com.java2nb.books.vo.BookIndexVO;
import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@ -22,9 +25,9 @@ import com.java2nb.common.utils.PageBean;
import com.java2nb.common.utils.Query; import com.java2nb.common.utils.Query;
import com.java2nb.common.utils.R; import com.java2nb.common.utils.R;
import javax.jws.WebParam;
/** /**
*
*
* @author xiongxy * @author xiongxy
* @email 1179705413@qq.com * @email 1179705413@qq.com
* @date 2019-11-13 09:27:04 * @date 2019-11-13 09:27:04
@ -64,7 +67,7 @@ public class BookController {
@ApiOperation(value = "修改页面", notes = "修改页面") @ApiOperation(value = "修改页面", notes = "修改页面")
@GetMapping("/edit/{id}") @GetMapping("/edit/{id}")
String edit(@PathVariable("id") Long id, Model model) { String edit(@PathVariable("id") Long id, Model model) {
BookDO book = bookService.get(id); BookDO book = bookService.get(id);
model.addAttribute("book", book); model.addAttribute("book", book);
return "books/book/edit"; return "books/book/edit";
} }
@ -72,7 +75,7 @@ public class BookController {
@ApiOperation(value = "查看页面", notes = "查看页面") @ApiOperation(value = "查看页面", notes = "查看页面")
@GetMapping("/detail/{id}") @GetMapping("/detail/{id}")
String detail(@PathVariable("id") Long id, Model model) { String detail(@PathVariable("id") Long id, Model model) {
BookDO book = bookService.get(id); BookDO book = bookService.get(id);
model.addAttribute("book", book); model.addAttribute("book", book);
return "books/book/detail"; return "books/book/detail";
} }
@ -83,7 +86,7 @@ public class BookController {
@ApiOperation(value = "新增", notes = "新增") @ApiOperation(value = "新增", notes = "新增")
@ResponseBody @ResponseBody
@PostMapping("/save") @PostMapping("/save")
public R save( BookDO book) { public R save(BookDO book) {
if (bookService.save(book) > 0) { if (bookService.save(book) > 0) {
return R.ok(); return R.ok();
} }
@ -96,8 +99,8 @@ public class BookController {
@ApiOperation(value = "修改", notes = "修改") @ApiOperation(value = "修改", notes = "修改")
@ResponseBody @ResponseBody
@RequestMapping("/update") @RequestMapping("/update")
public R update( BookDO book) { public R update(BookDO book) {
bookService.update(book); bookService.update(book);
return R.ok(); return R.ok();
} }
@ -107,7 +110,7 @@ public class BookController {
@ApiOperation(value = "删除", notes = "删除") @ApiOperation(value = "删除", notes = "删除")
@PostMapping("/remove") @PostMapping("/remove")
@ResponseBody @ResponseBody
public R remove( Long id) { public R remove(Long id) {
if (bookService.remove(id) > 0) { if (bookService.remove(id) > 0) {
return R.ok(); return R.ok();
} }
@ -121,8 +124,69 @@ public class BookController {
@PostMapping("/batchRemove") @PostMapping("/batchRemove")
@ResponseBody @ResponseBody
public R remove(@RequestParam("ids[]") Long[] ids) { public R remove(@RequestParam("ids[]") Long[] ids) {
bookService.batchRemove(ids); bookService.batchRemove(ids);
return R.ok(); return R.ok();
} }
@ApiOperation(value = "新增章节页面", notes = "新增章节页面")
@GetMapping("/index/add")
String indexAdd(Long bookId, Model model) {
model.addAttribute("bookId",bookId);
return "books/bookIndex/add";
}
/**
* 保存章节
*/
@ApiOperation(value = "新增章节", notes = "新增章节")
@ResponseBody
@PostMapping("/index/save")
public R indexSave(BookIndexDO bookIndex, BookContentDO bookContent) {
if (bookService.saveIndexAndContent(bookIndex,bookContent) > 0) {
return R.ok();
}
return R.error();
}
@GetMapping("/index")
String BookIndex() {
return "books/bookIndex/bookIndex";
}
@ApiOperation(value = "获取章节列表", notes = "获取章节列表")
@ResponseBody
@GetMapping("/index/list")
public R indexList(@RequestParam Map<String, Object> params) {
//查询列表数据
Query query = new Query(params);
List<BookIndexVO> bookIndexList = bookService.indexVOList(query);
int total = bookService.indexVOCount(query);
PageBean pageBean = new PageBean(bookIndexList, total);
return R.ok().put("data", pageBean);
}
/**
* 删除
*/
@ApiOperation(value = "删除", notes = "删除")
@PostMapping("/index/remove")
@ResponseBody
public R indexRemove( Long id) {
if (bookService.indexRemove(id) > 0) {
return R.ok();
}
return R.error();
}
/**
* 删除
*/
@ApiOperation(value = "批量删除", notes = "批量删除")
@PostMapping("/index/batchRemove")
@ResponseBody
public R indexRemove(@RequestParam("ids[]") Long[] ids) {
bookService.batchIndexRemove(ids);
return R.ok();
}
} }

View File

@ -2,10 +2,12 @@ package com.java2nb.books.dao;
import com.java2nb.books.domain.BookDO; import com.java2nb.books.domain.BookDO;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/** /**
* *
@ -29,4 +31,6 @@ public interface BookDao {
int remove(Long id); int remove(Long id);
int batchRemove(Long[] ids); int batchRemove(Long[] ids);
void uptUpdateTime( @Param("id") Long bookId, @Param("updateTime") Date date);
} }

View File

@ -5,7 +5,10 @@ import com.java2nb.books.domain.BookIndexDO;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.java2nb.books.vo.BookIndexVO;
import com.java2nb.common.utils.Query;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/** /**
* *
@ -31,4 +34,10 @@ public interface BookIndexDao {
int batchRemove(Long[] ids); int batchRemove(Long[] ids);
void insertBatch(List<BookIndexDO> newBookIndexList); void insertBatch(List<BookIndexDO> newBookIndexList);
Integer queryMaxIndexNum(@Param("bookId") Long bookId);
List<BookIndexVO> listVO(Query query);
int countVO(Query query);
} }

View File

@ -1,6 +1,10 @@
package com.java2nb.books.service; package com.java2nb.books.service;
import com.java2nb.books.domain.BookContentDO;
import com.java2nb.books.domain.BookDO; import com.java2nb.books.domain.BookDO;
import com.java2nb.books.domain.BookIndexDO;
import com.java2nb.books.vo.BookIndexVO;
import com.java2nb.common.utils.Query;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -27,4 +31,18 @@ public interface BookService {
int remove(Long id); int remove(Long id);
int batchRemove(Long[] ids); int batchRemove(Long[] ids);
/**
* 保存章节
*/
int saveIndexAndContent(BookIndexDO bookIndex, BookContentDO bookContent);
List<BookIndexVO> indexVOList(Query query);
int indexVOCount(Query query);
int indexRemove(Long id);
int batchIndexRemove(Long[] ids);
} }

View File

@ -1,21 +1,38 @@
package com.java2nb.books.service.impl; package com.java2nb.books.service.impl;
import com.java2nb.books.dao.BookContentDao;
import com.java2nb.books.dao.BookIndexDao;
import com.java2nb.books.domain.BookContentDO;
import com.java2nb.books.domain.BookIndexDO;
import com.java2nb.books.util.StringUtil;
import com.java2nb.books.vo.BookIndexVO;
import com.java2nb.common.utils.Query;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.java2nb.books.dao.BookDao; import com.java2nb.books.dao.BookDao;
import com.java2nb.books.domain.BookDO; import com.java2nb.books.domain.BookDO;
import com.java2nb.books.service.BookService; import com.java2nb.books.service.BookService;
import org.springframework.transaction.annotation.Transactional;
@Service @Service
public class BookServiceImpl implements BookService { public class BookServiceImpl implements BookService {
@Autowired @Autowired
private BookDao bookDao; private BookDao bookDao;
@Autowired
private BookIndexDao bookIndexDao;
@Autowired
private BookContentDao bookContentDao;
@Override @Override
public BookDO get(Long id){ public BookDO get(Long id){
@ -24,6 +41,11 @@ public class BookServiceImpl implements BookService {
@Override @Override
public List<BookDO> list(Map<String, Object> map){ public List<BookDO> list(Map<String, Object> map){
String sort = (String) map.get("sort");
if(StringUtils.isNotBlank(sort)){
map.put("sort",StringUtil.humpToLine(sort));
}
return bookDao.list(map); return bookDao.list(map);
} }
@ -34,11 +56,16 @@ public class BookServiceImpl implements BookService {
@Override @Override
public int save(BookDO book){ public int save(BookDO book){
book.setVisitCount(0l);
if(book.getUpdateTime() == null){
book.setUpdateTime(new Date());
}
return bookDao.save(book); return bookDao.save(book);
} }
@Override @Override
public int update(BookDO book){ public int update(BookDO book){
return bookDao.update(book); return bookDao.update(book);
} }
@ -51,5 +78,43 @@ public class BookServiceImpl implements BookService {
public int batchRemove(Long[] ids){ public int batchRemove(Long[] ids){
return bookDao.batchRemove(ids); return bookDao.batchRemove(ids);
} }
@Override
@Transactional
public int saveIndexAndContent(BookIndexDO bookIndex, BookContentDO bookContent) {
Integer maxBookNum = bookIndexDao.queryMaxIndexNum(bookIndex.getBookId());
int nextIndexNum = 0;
if(maxBookNum != null){
nextIndexNum = maxBookNum + 1;
}
bookIndex.setIndexNum(nextIndexNum);
bookContent.setBookId(bookIndex.getBookId());
bookContent.setIndexNum(nextIndexNum);
bookDao.uptUpdateTime(bookIndex.getBookId(),new Date());
bookIndexDao.save(bookIndex);
bookContentDao.save(bookContent);
return 1;
}
@Override
public List<BookIndexVO> indexVOList(Query query) {
return bookIndexDao.listVO(query);
}
@Override
public int indexVOCount(Query query) {
return bookIndexDao.countVO(query);
}
@Override
public int indexRemove(Long id) {
return bookIndexDao.remove(id);
}
@Override
public int batchIndexRemove(Long[] ids) {
return bookIndexDao.batchRemove(ids);
}
} }

View File

@ -0,0 +1,38 @@
package com.java2nb.books.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class StringUtil {
private static Pattern linePattern = Pattern.compile("_(\\w)");
/**
* 下划线转驼峰
*/
public static String lineToHump(String str) {
str = str.toLowerCase();
Matcher matcher = linePattern.matcher(str);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
}
matcher.appendTail(sb);
return sb.toString();
}
private static Pattern humpPattern = Pattern.compile("[A-Z]");
/**
* 驼峰转下划线
*/
public static String humpToLine(String str) {
Matcher matcher = humpPattern.matcher(str);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
}
matcher.appendTail(sb);
return sb.toString();
}
}

View File

@ -0,0 +1,12 @@
package com.java2nb.books.vo;
import com.java2nb.books.domain.BookIndexDO;
import lombok.Data;
@Data
public class BookIndexVO extends BookIndexDO {
private String bookName;
}

View File

@ -35,9 +35,9 @@ spring:
datasource: datasource:
type: com.alibaba.druid.pool.DruidDataSource type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/books?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai url: jdbc:mysql://47.106.243.172:3306/books_inner?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root username: books_inner
password: test123456 password: books_inner!8888
#password: #password:
initialSize: 1 initialSize: 1
minIdle: 3 minIdle: 3

View File

@ -9,7 +9,7 @@
<select id="list" resultType="com.java2nb.books.domain.BookIndexDO"> <select id="list" resultType="com.java2nb.books.domain.BookIndexDO">
select `id`,`book_id`,`index_num`,`index_name` from book_index select `id`,`book_id`,`index_num`,`index_name` from book_index
<where> <where>
<if test="id != null and id != ''"> and id = #{id} </if> <if test="id != null and id != ''"> and id = #{id} </if>
<if test="bookId != null and bookId != ''"> and book_id = #{bookId} </if> <if test="bookId != null and bookId != ''"> and book_id = #{bookId} </if>
<if test="indexNum != null and indexNum != ''"> and index_num = #{indexNum} </if> <if test="indexNum != null and indexNum != ''"> and index_num = #{indexNum} </if>
@ -27,17 +27,54 @@
limit #{offset}, #{limit} limit #{offset}, #{limit}
</if> </if>
</select> </select>
<select id="count" resultType="int"> <select id="count" resultType="int">
select count(*) from book_index select count(*) from book_index
<where> <where>
<if test="id != null and id != ''"> and id = #{id} </if> <if test="id != null and id != ''"> and id = #{id} </if>
<if test="bookId != null and bookId != ''"> and book_id = #{bookId} </if> <if test="bookId != null and bookId != ''"> and book_id = #{bookId} </if>
<if test="indexNum != null and indexNum != ''"> and index_num = #{indexNum} </if> <if test="indexNum != null and indexNum != ''"> and index_num = #{indexNum} </if>
<if test="indexName != null and indexName != ''"> and index_name = #{indexName} </if> <if test="indexName != null and indexName != ''"> and index_name = #{indexName} </if>
</where> </where>
</select> </select>
<select id="listVO" resultType="com.java2nb.books.vo.BookIndexVO">
select t1.`id`,t1.`book_id`,t1.`index_num`,t1.`index_name`,t2.book_name bookName from book_index t1 inner join book t2
on t1.book_id = t2.id
<where>
<if test="id != null and id != ''">and t1.id = #{id}</if>
<if test="bookId != null and bookId != ''">and t1.book_id = #{bookId}</if>
<if test="indexNum != null and indexNum != ''">and t1.index_num = #{indexNum}</if>
<if test="indexName != null and indexName != ''">and t1.index_name = #{indexName}</if>
<if test="bookName != null and bookName != ''">and t2.book_name = #{bookName}</if>
<if test="author != null and author != ''">and t2.author = #{author}</if>
</where>
<choose>
<when test="sort != null and sort.trim() != ''">
order by t1.${sort} ${order}
</when>
<otherwise>
order by t1.book_id asc
</otherwise>
</choose>
<if test="offset != null and limit != null">
limit #{offset}, #{limit}
</if>
</select>
<select id="countVO" resultType="int">
select count(*) from book_index t1 inner join book t2
on t1.book_id = t2.id
<where>
<if test="id != null and id != ''">and t1.id = #{id}</if>
<if test="bookId != null and bookId != ''">and t1.book_id = #{bookId}</if>
<if test="indexNum != null and indexNum != ''">and t1.index_num = #{indexNum}</if>
<if test="indexName != null and indexName != ''">and t1.index_name = #{indexName}</if>
<if test="bookName != null and bookName != ''">and t2.book_name = #{bookName}</if>
<if test="author != null and author != ''">and t2.author = #{author}</if>
</where>
</select>
<insert id="save" parameterType="com.java2nb.books.domain.BookIndexDO" useGeneratedKeys="true" keyProperty="id"> <insert id="save" parameterType="com.java2nb.books.domain.BookIndexDO" useGeneratedKeys="true" keyProperty="id">
insert into book_index insert into book_index
( (
@ -54,9 +91,9 @@
#{indexName} #{indexName}
) )
</insert> </insert>
<update id="update" parameterType="com.java2nb.books.domain.BookIndexDO"> <update id="update" parameterType="com.java2nb.books.domain.BookIndexDO">
update book_index update book_index
<set> <set>
<if test="bookId != null">`book_id` = #{bookId}, </if> <if test="bookId != null">`book_id` = #{bookId}, </if>
<if test="indexNum != null">`index_num` = #{indexNum}, </if> <if test="indexNum != null">`index_num` = #{indexNum}, </if>
@ -64,13 +101,13 @@
</set> </set>
where id = #{id} where id = #{id}
</update> </update>
<delete id="remove"> <delete id="remove">
delete from book_index where id = #{value} delete from book_index where id = #{value}
</delete> </delete>
<delete id="batchRemove"> <delete id="batchRemove">
delete from book_index where id in delete from book_index where id in
<foreach item="id" collection="array" open="(" separator="," close=")"> <foreach item="id" collection="array" open="(" separator="," close=")">
#{id} #{id}
</foreach> </foreach>
@ -89,4 +126,8 @@
</foreach> </foreach>
</insert> </insert>
<select id="queryMaxIndexNum" parameterType="java.lang.Long" resultType="java.lang.Integer">
select max(index_num) from book_index where book_id = #{bookId,jdbcType=VARCHAR} ;
</select>
</mapper> </mapper>

View File

@ -3,58 +3,68 @@
<mapper namespace="com.java2nb.books.dao.BookDao"> <mapper namespace="com.java2nb.books.dao.BookDao">
<select id="get" resultType="com.java2nb.books.domain.BookDO"> <select id="get" resultType="com.java2nb.books.domain.BookDO">
select `id`,`catId`,`pic_url`,`book_name`,`author`,`book_desc`,`score`,`book_status`,`visit_count`,`update_time`,`soft_cat`,`soft_tag` from book where id = #{value} select `id`,`catId`,`pic_url`,`book_name`,`author`,`book_desc`,`score`,`book_status`,`visit_count`,`update_time`,`soft_cat`,`soft_tag` from book where id = #{value}
</select> </select>
<select id="list" resultType="com.java2nb.books.domain.BookDO"> <select id="list" resultType="com.java2nb.books.domain.BookDO">
select `id`,`catId`,`pic_url`,`book_name`,`author`,`book_desc`,`score`,`book_status`,`visit_count`,`update_time`,`soft_cat`,`soft_tag` from book select
<where> `id`,`catId`,`pic_url`,`book_name`,`author`,`book_desc`,`score`,`book_status`,`visit_count`,`update_time`,`soft_cat`,`soft_tag`
<if test="id != null and id != ''"> and id = #{id} </if> from book
<if test="catid != null and catid != ''"> and catId = #{catid} </if> <where>
<if test="picUrl != null and picUrl != ''"> and pic_url = #{picUrl} </if> <if test="id != null and id != ''">and id = #{id}</if>
<if test="bookName != null and bookName != ''"> and book_name = #{bookName} </if> <choose>
<if test="author != null and author != ''"> and author = #{author} </if> <when test="catid != null and catid != ''">
<if test="bookDesc != null and bookDesc != ''"> and book_desc = #{bookDesc} </if> and catId = #{catid}
<if test="score != null and score != ''"> and score = #{score} </if> </when>
<if test="bookStatus != null and bookStatus != ''"> and book_status = #{bookStatus} </if> <otherwise>
<if test="visitCount != null and visitCount != ''"> and visit_count = #{visitCount} </if> and catId <![CDATA[ < ]]> 8
<if test="updateTime != null and updateTime != ''"> and update_time = #{updateTime} </if> </otherwise>
<if test="softCat != null and softCat != ''"> and soft_cat = #{softCat} </if> </choose>
<if test="softTag != null and softTag != ''"> and soft_tag = #{softTag} </if> <if test="catid != null and catid != ''"></if>
</where> <if test="picUrl != null and picUrl != ''">and pic_url = #{picUrl}</if>
<if test="bookName != null and bookName != ''">and book_name = #{bookName}</if>
<if test="author != null and author != ''">and author = #{author}</if>
<if test="bookDesc != null and bookDesc != ''">and book_desc = #{bookDesc}</if>
<if test="score != null and score != ''">and score = #{score}</if>
<if test="bookStatus != null and bookStatus != ''">and book_status = #{bookStatus}</if>
<if test="visitCount != null and visitCount != ''">and visit_count = #{visitCount}</if>
<if test="updateTime != null and updateTime != ''">and update_time = #{updateTime}</if>
<if test="softCat != null and softCat != ''">and soft_cat = #{softCat}</if>
<if test="softTag != null and softTag != ''">and soft_tag = #{softTag}</if>
</where>
<choose> <choose>
<when test="sort != null and sort.trim() != ''"> <when test="sort != null and sort.trim() != ''">
order by ${sort} ${order} order by ${sort} ${order}
</when> </when>
<otherwise> <otherwise>
order by id desc order by id desc
</otherwise> </otherwise>
</choose> </choose>
<if test="offset != null and limit != null"> <if test="offset != null and limit != null">
limit #{offset}, #{limit} limit #{offset}, #{limit}
</if> </if>
</select> </select>
<select id="count" resultType="int"> <select id="count" resultType="int">
select count(*) from book select count(*) from book
<where> <where>
<if test="id != null and id != ''"> and id = #{id} </if> <if test="id != null and id != ''">and id = #{id}</if>
<if test="catid != null and catid != ''"> and catId = #{catid} </if> <if test="catid != null and catid != ''">and catId = #{catid}</if>
<if test="picUrl != null and picUrl != ''"> and pic_url = #{picUrl} </if> <if test="picUrl != null and picUrl != ''">and pic_url = #{picUrl}</if>
<if test="bookName != null and bookName != ''"> and book_name = #{bookName} </if> <if test="bookName != null and bookName != ''">and book_name = #{bookName}</if>
<if test="author != null and author != ''"> and author = #{author} </if> <if test="author != null and author != ''">and author = #{author}</if>
<if test="bookDesc != null and bookDesc != ''"> and book_desc = #{bookDesc} </if> <if test="bookDesc != null and bookDesc != ''">and book_desc = #{bookDesc}</if>
<if test="score != null and score != ''"> and score = #{score} </if> <if test="score != null and score != ''">and score = #{score}</if>
<if test="bookStatus != null and bookStatus != ''"> and book_status = #{bookStatus} </if> <if test="bookStatus != null and bookStatus != ''">and book_status = #{bookStatus}</if>
<if test="visitCount != null and visitCount != ''"> and visit_count = #{visitCount} </if> <if test="visitCount != null and visitCount != ''">and visit_count = #{visitCount}</if>
<if test="updateTime != null and updateTime != ''"> and update_time = #{updateTime} </if> <if test="updateTime != null and updateTime != ''">and update_time = #{updateTime}</if>
<if test="softCat != null and softCat != ''"> and soft_cat = #{softCat} </if> <if test="softCat != null and softCat != ''">and soft_cat = #{softCat}</if>
<if test="softTag != null and softTag != ''"> and soft_tag = #{softTag} </if> <if test="softTag != null and softTag != ''">and soft_tag = #{softTag}</if>
</where> </where>
</select> </select>
<insert id="save" parameterType="com.java2nb.books.domain.BookDO" useGeneratedKeys="true" keyProperty="id"> <insert id="save" parameterType="com.java2nb.books.domain.BookDO" useGeneratedKeys="true" keyProperty="id">
insert into book insert into book
( (
`id`, `id`,
@ -86,34 +96,38 @@
#{softTag} #{softTag}
) )
</insert> </insert>
<update id="update" parameterType="com.java2nb.books.domain.BookDO"> <update id="update" parameterType="com.java2nb.books.domain.BookDO">
update book update book
<set> <set>
<if test="catid != null">`catId` = #{catid}, </if> <if test="catid != null">`catId` = #{catid},</if>
<if test="picUrl != null">`pic_url` = #{picUrl}, </if> <if test="picUrl != null">`pic_url` = #{picUrl},</if>
<if test="bookName != null">`book_name` = #{bookName}, </if> <if test="bookName != null">`book_name` = #{bookName},</if>
<if test="author != null">`author` = #{author}, </if> <if test="author != null">`author` = #{author},</if>
<if test="bookDesc != null">`book_desc` = #{bookDesc}, </if> <if test="bookDesc != null">`book_desc` = #{bookDesc},</if>
<if test="score != null">`score` = #{score}, </if> <if test="score != null">`score` = #{score},</if>
<if test="bookStatus != null">`book_status` = #{bookStatus}, </if> <if test="bookStatus != null">`book_status` = #{bookStatus},</if>
<if test="visitCount != null">`visit_count` = #{visitCount}, </if> <if test="visitCount != null">`visit_count` = #{visitCount},</if>
<if test="updateTime != null">`update_time` = #{updateTime}, </if> <if test="updateTime != null">`update_time` = #{updateTime},</if>
<if test="softCat != null">`soft_cat` = #{softCat}, </if> <if test="softCat != null">`soft_cat` = #{softCat},</if>
<if test="softTag != null">`soft_tag` = #{softTag}</if> <if test="softTag != null">`soft_tag` = #{softTag}</if>
</set> </set>
where id = #{id} where id = #{id}
</update> </update>
<delete id="remove"> <delete id="remove">
delete from book where id = #{value} delete from book where id = #{value}
</delete> </delete>
<delete id="batchRemove"> <delete id="batchRemove">
delete from book where id in delete from book where id in
<foreach item="id" collection="array" open="(" separator="," close=")"> <foreach item="id" collection="array" open="(" separator="," close=")">
#{id} #{id}
</foreach> </foreach>
</delete> </delete>
<update id="uptUpdateTime">
update book set update_time = #{updateTime} where id = #{id}
</update>
</mapper> </mapper>

View File

@ -1,49 +1,89 @@
$().ready(function() { $().ready(function () {
validateRule(); validateRule();
}); });
$.validator.setDefaults({ $.validator.setDefaults({
submitHandler : function() { submitHandler: function () {
save(); save();
} }
}); });
function save() { function save() {
$.ajax({ $.ajax({
cache : true, cache: true,
type : "POST", type: "POST",
url : "/books/book/save", url: "/books/book/save",
data : $('#signupForm').serialize(),// 你的formid data: $('#signupForm').serialize(),// 你的formid
async : false, async: false,
error : function(request) { error: function (request) {
parent.layer.alert("Connection error"); parent.layer.alert("Connection error");
}, },
success : function(data) { success: function (data) {
if (data.code == 0) { if (data.code == 0) {
parent.layer.msg("操作成功"); parent.layer.msg("操作成功");
parent.reLoad(); parent.reLoad();
var index = parent.layer.getFrameIndex(window.name); // 获取窗口索引 var index = parent.layer.getFrameIndex(window.name); // 获取窗口索引
parent.layer.close(index); parent.layer.close(index);
} else { } else {
parent.layer.alert(data.msg) parent.layer.alert(data.msg)
} }
} }
}); });
} }
function validateRule() { function validateRule() {
var icon = "<i class='fa fa-times-circle'></i> "; var icon = "<i class='fa fa-times-circle'></i> ";
$("#signupForm").validate({ $("#signupForm").validate({
rules : { ignore: "",
name : { rules: {
required : true catid: {
} required: true
}, },
messages : { picUrl: {
name : { required: true
required : icon + "请输入姓名" },
} bookName: {
} required: true
}) },
author: {
required: true
},
bookDesc: {
required: true
},
score: {
required: true
},
bookStatus: {
required: true
}
},
messages: {
catid: {
required: icon + "请选择分类"
},
picUrl: {
required: icon + "请选择封面"
},
bookName: {
required: icon + "请填写书名"
},
author: {
required: icon + "请填写作者"
},
bookDesc: {
required: icon + "请填写简介"
},
score: {
required: icon + "请填写评分"
},
bookStatus: {
required: icon + "请填写更新状态"
}
}
})
} }

View File

@ -1,214 +1,240 @@
var prefix = "/books/book" var prefix = "/books/book"
$(function() { $(function () {
load(); load();
}); });
function load() { function load() {
$('#exampleTable') $('#exampleTable')
.bootstrapTable( .bootstrapTable(
{ {
method : 'get', // 服务器数据的请求方式 get or post method: 'get', // 服务器数据的请求方式 get or post
url : prefix + "/list", // 服务器数据的加载地址 url: prefix + "/list", // 服务器数据的加载地址
// showRefresh : true, // showRefresh : true,
// showToggle : true, // showToggle : true,
// showColumns : true, // showColumns : true,
iconSize : 'outline', iconSize: 'outline',
toolbar : '#exampleToolbar', toolbar: '#exampleToolbar',
striped : true, // 设置为true会有隔行变色效果 striped: true, // 设置为true会有隔行变色效果
dataType : "json", // 服务器返回的数据类型 dataType: "json", // 服务器返回的数据类型
pagination : true, // 设置为true会在底部显示分页条 pagination: true, // 设置为true会在底部显示分页条
// queryParamsType : "limit", // queryParamsType : "limit",
// //设置为limit则会发送符合RESTFull格式的参数 // //设置为limit则会发送符合RESTFull格式的参数
singleSelect : false, // 设置为true将禁止多选 singleSelect: false, // 设置为true将禁止多选
// contentType : "application/x-www-form-urlencoded", // contentType : "application/x-www-form-urlencoded",
// //发送到服务器的数据编码类型 // //发送到服务器的数据编码类型
pageSize : 10, // 如果设置了分页每页数据条数 pageSize: 10, // 如果设置了分页每页数据条数
pageNumber : 1, // 如果设置了分布首页页码 pageNumber: 1, // 如果设置了分布首页页码
//search : true, // 是否显示搜索框 //search : true, // 是否显示搜索框
showColumns : false, // 是否显示内容下拉框选择显示的列 showColumns: false, // 是否显示内容下拉框选择显示的列
sidePagination : "server", // 设置在哪里进行分页可选值为"client" 或者 "server" sidePagination: "server", // 设置在哪里进行分页可选值为"client" 或者 "server"
queryParams : function(params) { queryParams: function (params) {
//说明传入后台的参数包括offset开始索引limit步长sort排序列orderdesc或者,以及所有列的键值对 //说明传入后台的参数包括offset开始索引limit步长sort排序列orderdesc或者,以及所有列的键值对
var queryParams = getFormJson("searchForm"); var queryParams = getFormJson("searchForm");
queryParams.limit = params.limit; queryParams.limit = params.limit;
queryParams.offset = params.offset; queryParams.offset = params.offset;
return queryParams; queryParams.sort = params.sort;
}, queryParams.order = params.order;
// //请求服务器数据时你可以通过重写参数的方式添加一些额外的参数例如 toolbar 中的参数 如果 return queryParams;
// queryParamsType = 'limit' ,返回参数必须包含 },
// limit, offset, search, sort, order 否则, 需要包含: // //请求服务器数据时你可以通过重写参数的方式添加一些额外的参数例如 toolbar 中的参数 如果
// pageSize, pageNumber, searchText, sortName, // queryParamsType = 'limit' ,返回参数必须包含
// sortOrder. // limit, offset, search, sort, order 否则, 需要包含:
// 返回false将会终止请求 // pageSize, pageNumber, searchText, sortName,
responseHandler: function (rs) { // sortOrder.
// 返回false将会终止请求
responseHandler: function (rs) {
if (rs.code == 0) { if (rs.code == 0) {
return rs.data; return rs.data;
} else { } else {
parent.layer.alert(rs.msg) parent.layer.alert(rs.msg)
return {total: 0, rows: []}; return {total: 0, rows: []};
} }
}, },
columns : [ columns: [
{ {
checkbox : true checkbox: true
}, },
{ {
field : 'id', title: '序号',
title : '' formatter: function () {
}, return arguments[2] + 1;
{ }
field : 'catid', },
title : '' {
}, field: 'catid',
{ title: '分类',
field : 'picUrl', formatter: function (value, row, index) {
title : '' return formatDict("novel_category",value);
}, }
{ },
field : 'bookName', {
title : '' field: 'picUrl',
}, title: '封面',
{ formatter: function (value, row, index) {
field : 'author', return "<img width='100' height='100' src='"+value+"'>";
title : '' }
}, },
{ {
field : 'bookDesc', field: 'bookName',
title : '' title: '书名'
}, },
{ {
field : 'score', field: 'author',
title : '' title: '作者'
}, },
{ {
field : 'bookStatus', field: 'bookDesc',
title : '' width: '300px',
}, title: '简介'
{ },
field : 'visitCount', {
title : '' field: 'score',
}, title: '评分'
{ , sortable: true
field : 'updateTime', },
title : '' {
}, field: 'bookStatus',
{ title: '更新状态'
field : 'softCat', , sortable: true
title : '' },
}, {
{ field: 'visitCount',
field : 'softTag', title: '访问次数'
title : '' , sortable: true
}, },
{ {
title : '操作', field: 'updateTime',
field : 'id', title: '更新时间'
align : 'center', , sortable: true
formatter : function(value, row, index) { },
var d = '<a class="btn btn-primary btn-sm" href="#" mce_href="#" title="详情" onclick="detail(\'' {
+ row.id title: '操作',
+ '\')"><i class="fa fa-file"></i></a> '; field: 'id',
var e = '<a class="btn btn-primary btn-sm" href="#" mce_href="#" title="编辑" onclick="edit(\'' align: 'center',
+ row.id formatter: function (value, row, index) {
+ '\')"><i class="fa fa-edit"></i></a> '; var d = '<a class="btn" style="color: #0a6aa1" href="#" mce_href="#" title="详情" onclick="detail(\''
var r = '<a class="btn btn-warning btn-sm" href="#" title="删除" mce_href="#" onclick="remove(\'' + row.id
+ row.id + '\')">详情</a> <br/>';
+ '\')"><i class="fa fa-remove"></i></a> '; /*var e = '<a class="btn" style="color: #0a6aa1" href="#" mce_href="#" title="编辑" onclick="edit(\''
return d + e + r ; + row.id
} + '\')">编辑</a><br/> ';*/
} ] var r = '<a class="btn" style="color: #0a6aa1" href="#" title="删除" mce_href="#" onclick="remove(\''
}); + row.id
+ '\')">删除</a><br/> ';
var p = '<a class="btn" style="color: #0a6aa1" href="#" mce_href="#" title="章节发布" onclick="addIndex(\''
+ row.id
+ '\')">章节发布</a> ';
return d + r + p;
}
}]
});
} }
function reLoad() { function reLoad() {
$('#exampleTable').bootstrapTable('refresh'); $('#exampleTable').bootstrapTable('refresh');
} }
function add() { function add() {
layer.open({ layer.open({
type : 2, type: 2,
title : '增加', title: '增加',
maxmin : true, maxmin: true,
shadeClose : false, // 点击遮罩关闭层 shadeClose: false, // 点击遮罩关闭层
area : [ '800px', '520px' ], area: ['800px', '520px'],
content : prefix + '/add' // iframe的url content: prefix + '/add' // iframe的url
}); });
} }
function addIndex(bookId) {
layer.open({
type: 2,
title: '发布章节',
maxmin: true,
shadeClose: false, // 点击遮罩关闭层
area: ['800px', '520px'],
content: prefix + '/index/add?bookId='+bookId // iframe的url
});
}
function detail(id) { function detail(id) {
layer.open({ layer.open({
type : 2, type: 2,
title : '详情', title: '详情',
maxmin : true, maxmin: true,
shadeClose : false, // 点击遮罩关闭层 shadeClose: false, // 点击遮罩关闭层
area : [ '800px', '520px' ], area: ['800px', '520px'],
content : prefix + '/detail/' + id // iframe的url content: prefix + '/detail/' + id // iframe的url
}); });
} }
function edit(id) { function edit(id) {
layer.open({ layer.open({
type : 2, type: 2,
title : '编辑', title: '编辑',
maxmin : true, maxmin: true,
shadeClose : false, // 点击遮罩关闭层 shadeClose: false, // 点击遮罩关闭层
area : [ '800px', '520px' ], area: ['800px', '520px'],
content : prefix + '/edit/' + id // iframe的url content: prefix + '/edit/' + id // iframe的url
}); });
} }
function remove(id) { function remove(id) {
layer.confirm('确定要删除选中的记录', { layer.confirm('确定要删除选中的记录', {
btn : [ '确定', '取消' ] btn: ['确定', '取消']
}, function() { }, function () {
$.ajax({ $.ajax({
url : prefix+"/remove", url: prefix + "/remove",
type : "post", type: "post",
data : { data: {
'id' : id 'id': id
}, },
success : function(r) { success: function (r) {
if (r.code==0) { if (r.code == 0) {
layer.msg(r.msg); layer.msg(r.msg);
reLoad(); reLoad();
}else{ } else {
layer.msg(r.msg); layer.msg(r.msg);
} }
} }
}); });
}) })
} }
function resetPwd(id) { function resetPwd(id) {
} }
function batchRemove() {
var rows = $('#exampleTable').bootstrapTable('getSelections'); // 返回所有选择的行当没有选择的记录时返回一个空数组
if (rows.length == 0) {
layer.msg("请选择要删除的数据");
return;
}
layer.confirm("确认要删除选中的'" + rows.length + "'条数据吗?", {
btn : [ '确定', '取消' ]
// 按钮
}, function() {
var ids = new Array();
// 遍历所有选择的行数据取每条数据对应的ID
$.each(rows, function(i, row) {
ids[i] = row['id'];
});
$.ajax({
type : 'POST',
data : {
"ids" : ids
},
url : prefix + '/batchRemove',
success : function(r) {
if (r.code == 0) {
layer.msg(r.msg);
reLoad();
} else {
layer.msg(r.msg);
}
}
});
}, function() {
}); function batchRemove() {
var rows = $('#exampleTable').bootstrapTable('getSelections'); // 返回所有选择的行当没有选择的记录时返回一个空数组
if (rows.length == 0) {
layer.msg("请选择要删除的数据");
return;
}
layer.confirm("确认要删除选中的'" + rows.length + "'条数据吗?", {
btn: ['确定', '取消']
// 按钮
}, function () {
var ids = new Array();
// 遍历所有选择的行数据取每条数据对应的ID
$.each(rows, function (i, row) {
ids[i] = row['id'];
});
$.ajax({
type: 'POST',
data: {
"ids": ids
},
url: prefix + '/batchRemove',
success: function (r) {
if (r.code == 0) {
layer.msg(r.msg);
reLoad();
} else {
layer.msg(r.msg);
}
}
});
}, function () {
});
} }

View File

@ -1,3 +1,36 @@
var E = window.wangEditor;
var editor2 = new E('#contentEditor');
// 自定义菜单配置
editor2.customConfig.menus = [
'head', // 标题
'bold', // 粗体
'fontSize', // 字号
'fontName', // 字体
'italic', // 斜体
'underline', // 下划线
'strikeThrough', // 删除线
'foreColor', // 文字颜色
//'backColor', // 背景颜色
//'link', // 插入链接
'list', // 列表
'justify', // 对齐方式
'quote', // 引用
'emoticon', // 表情
'image', // 插入图片
//'table', // 表格
//'video', // 插入视频
//'code', // 插入代码
'undo', // 撤销
'redo' // 重复
];
editor2.customConfig.onchange = function (html) {
// html 即变化之后的内容
$("#content").val(html);
}
editor2.create();
$().ready(function() { $().ready(function() {
validateRule(); validateRule();
}); });
@ -11,7 +44,7 @@ function save() {
$.ajax({ $.ajax({
cache : true, cache : true,
type : "POST", type : "POST",
url : "/books/bookIndex/save", url : "/books/book/index/save",
data : $('#signupForm').serialize(),// 你的formid data : $('#signupForm').serialize(),// 你的formid
async : false, async : false,
error : function(request) { error : function(request) {
@ -33,16 +66,25 @@ function save() {
} }
function validateRule() { function validateRule() {
var icon = "<i class='fa fa-times-circle'></i> "; var icon = "<i class='fa fa-times-circle'></i> ";
$("#signupForm").validate({ $("#signupForm").validate({
ignore: "",
rules : { rules : {
name : { indexName : {
required : true
},
content : {
required : true required : true
} }
}, },
messages : { messages : {
name : { indexName : {
required : icon + "请输入姓名" required : icon + "请输入章节名"
},
content : {
required : icon + "请输入章节内容"
} }
} }
}) })

View File

@ -1,182 +1,185 @@
var prefix = "/books/book/index"
var prefix = "/books/bookIndex" $(function () {
$(function() { load();
load();
}); });
function load() { function load() {
$('#exampleTable') $('#exampleTable')
.bootstrapTable( .bootstrapTable(
{ {
method : 'get', // 服务器数据的请求方式 get or post method: 'get', // 服务器数据的请求方式 get or post
url : prefix + "/list", // 服务器数据的加载地址 url: prefix + "/list", // 服务器数据的加载地址
// showRefresh : true, // showRefresh : true,
// showToggle : true, // showToggle : true,
// showColumns : true, // showColumns : true,
iconSize : 'outline', iconSize: 'outline',
toolbar : '#exampleToolbar', toolbar: '#exampleToolbar',
striped : true, // 设置为true会有隔行变色效果 striped: true, // 设置为true会有隔行变色效果
dataType : "json", // 服务器返回的数据类型 dataType: "json", // 服务器返回的数据类型
pagination : true, // 设置为true会在底部显示分页条 pagination: true, // 设置为true会在底部显示分页条
// queryParamsType : "limit", // queryParamsType : "limit",
// //设置为limit则会发送符合RESTFull格式的参数 // //设置为limit则会发送符合RESTFull格式的参数
singleSelect : false, // 设置为true将禁止多选 singleSelect: false, // 设置为true将禁止多选
// contentType : "application/x-www-form-urlencoded", // contentType : "application/x-www-form-urlencoded",
// //发送到服务器的数据编码类型 // //发送到服务器的数据编码类型
pageSize : 10, // 如果设置了分页每页数据条数 pageSize: 10, // 如果设置了分页每页数据条数
pageNumber : 1, // 如果设置了分布首页页码 pageNumber: 1, // 如果设置了分布首页页码
//search : true, // 是否显示搜索框 //search : true, // 是否显示搜索框
showColumns : false, // 是否显示内容下拉框选择显示的列 showColumns: false, // 是否显示内容下拉框选择显示的列
sidePagination : "server", // 设置在哪里进行分页可选值为"client" 或者 "server" sidePagination: "server", // 设置在哪里进行分页可选值为"client" 或者 "server"
queryParams : function(params) { queryParams: function (params) {
//说明传入后台的参数包括offset开始索引limit步长sort排序列orderdesc或者,以及所有列的键值对 //说明传入后台的参数包括offset开始索引limit步长sort排序列orderdesc或者,以及所有列的键值对
var queryParams = getFormJson("searchForm"); var queryParams = getFormJson("searchForm");
queryParams.limit = params.limit; queryParams.limit = params.limit;
queryParams.offset = params.offset; queryParams.offset = params.offset;
return queryParams; return queryParams;
}, },
// //请求服务器数据时你可以通过重写参数的方式添加一些额外的参数例如 toolbar 中的参数 如果 // //请求服务器数据时你可以通过重写参数的方式添加一些额外的参数例如 toolbar 中的参数 如果
// queryParamsType = 'limit' ,返回参数必须包含 // queryParamsType = 'limit' ,返回参数必须包含
// limit, offset, search, sort, order 否则, 需要包含: // limit, offset, search, sort, order 否则, 需要包含:
// pageSize, pageNumber, searchText, sortName, // pageSize, pageNumber, searchText, sortName,
// sortOrder. // sortOrder.
// 返回false将会终止请求 // 返回false将会终止请求
responseHandler: function (rs) { responseHandler: function (rs) {
if (rs.code == 0) { if (rs.code == 0) {
return rs.data; return rs.data;
} else { } else {
parent.layer.alert(rs.msg) parent.layer.alert(rs.msg)
return {total: 0, rows: []}; return {total: 0, rows: []};
} }
}, },
columns : [ columns: [
{ {
checkbox : true checkbox: true
}, },
{ {
field : 'id', title: '序号',
title : '' formatter: function () {
}, return arguments[2] + 1;
{ }
field : 'bookId', },
title : '' {
}, field: 'bookName',
{ title: '书名'
field : 'indexNum', },
title : '' {
}, field: 'indexName',
{ title: '章节名'
field : 'indexName', },
title : '' {
}, title: '操作',
{ field: 'id',
title : '操作', align: 'center',
field : 'id', formatter: function (value, row, index) {
align : 'center', /*var d = '<a class="btn btn-primary btn-sm" href="#" mce_href="#" title="详情" onclick="detail(\''
formatter : function(value, row, index) { + row.id
var d = '<a class="btn btn-primary btn-sm" href="#" mce_href="#" title="详情" onclick="detail(\'' + '\')"><i class="fa fa-file"></i></a> ';
+ row.id var e = '<a class="btn btn-primary btn-sm" href="#" mce_href="#" title="编辑" onclick="edit(\''
+ '\')"><i class="fa fa-file"></i></a> '; + row.id
var e = '<a class="btn btn-primary btn-sm" href="#" mce_href="#" title="编辑" onclick="edit(\'' + '\')"><i class="fa fa-edit"></i></a> ';*/
+ row.id var r = '<a class="btn btn-warning btn-sm" href="#" title="删除" mce_href="#" onclick="remove(\''
+ '\')"><i class="fa fa-edit"></i></a> '; + row.id
var r = '<a class="btn btn-warning btn-sm" href="#" title="删除" mce_href="#" onclick="remove(\'' + '\')"><i class="fa fa-remove"></i></a> ';
+ row.id return r;
+ '\')"><i class="fa fa-remove"></i></a> '; }
return d + e + r ; }]
} });
} ]
});
} }
function reLoad() { function reLoad() {
$('#exampleTable').bootstrapTable('refresh'); $('#exampleTable').bootstrapTable('refresh');
} }
function add() { function add() {
layer.open({ layer.open({
type : 2, type: 2,
title : '增加', title: '增加',
maxmin : true, maxmin: true,
shadeClose : false, // 点击遮罩关闭层 shadeClose: false, // 点击遮罩关闭层
area : [ '800px', '520px' ], area: ['800px', '520px'],
content : prefix + '/add' // iframe的url content: prefix + '/add' // iframe的url
}); });
} }
function detail(id) { function detail(id) {
layer.open({ layer.open({
type : 2, type: 2,
title : '详情', title: '详情',
maxmin : true, maxmin: true,
shadeClose : false, // 点击遮罩关闭层 shadeClose: false, // 点击遮罩关闭层
area : [ '800px', '520px' ], area: ['800px', '520px'],
content : prefix + '/detail/' + id // iframe的url content: prefix + '/detail/' + id // iframe的url
}); });
} }
function edit(id) { function edit(id) {
layer.open({ layer.open({
type : 2, type: 2,
title : '编辑', title: '编辑',
maxmin : true, maxmin: true,
shadeClose : false, // 点击遮罩关闭层 shadeClose: false, // 点击遮罩关闭层
area : [ '800px', '520px' ], area: ['800px', '520px'],
content : prefix + '/edit/' + id // iframe的url content: prefix + '/edit/' + id // iframe的url
}); });
} }
function remove(id) { function remove(id) {
layer.confirm('确定要删除选中的记录', { layer.confirm('确定要删除选中的记录', {
btn : [ '确定', '取消' ] btn: ['确定', '取消']
}, function() { }, function () {
$.ajax({ $.ajax({
url : prefix+"/remove", url: prefix + "/remove",
type : "post", type: "post",
data : { data: {
'id' : id 'id': id
}, },
success : function(r) { success: function (r) {
if (r.code==0) { if (r.code == 0) {
layer.msg(r.msg); layer.msg(r.msg);
reLoad(); reLoad();
}else{ } else {
layer.msg(r.msg); layer.msg(r.msg);
} }
} }
}); });
}) })
} }
function resetPwd(id) { function resetPwd(id) {
} }
function batchRemove() {
var rows = $('#exampleTable').bootstrapTable('getSelections'); // 返回所有选择的行当没有选择的记录时返回一个空数组
if (rows.length == 0) {
layer.msg("请选择要删除的数据");
return;
}
layer.confirm("确认要删除选中的'" + rows.length + "'条数据吗?", {
btn : [ '确定', '取消' ]
// 按钮
}, function() {
var ids = new Array();
// 遍历所有选择的行数据取每条数据对应的ID
$.each(rows, function(i, row) {
ids[i] = row['id'];
});
$.ajax({
type : 'POST',
data : {
"ids" : ids
},
url : prefix + '/batchRemove',
success : function(r) {
if (r.code == 0) {
layer.msg(r.msg);
reLoad();
} else {
layer.msg(r.msg);
}
}
});
}, function() {
}); function batchRemove() {
var rows = $('#exampleTable').bootstrapTable('getSelections'); // 返回所有选择的行当没有选择的记录时返回一个空数组
if (rows.length == 0) {
layer.msg("请选择要删除的数据");
return;
}
layer.confirm("确认要删除选中的'" + rows.length + "'条数据吗?", {
btn: ['确定', '取消']
// 按钮
}, function () {
var ids = new Array();
// 遍历所有选择的行数据取每条数据对应的ID
$.each(rows, function (i, row) {
ids[i] = row['id'];
});
$.ajax({
type: 'POST',
data: {
"ids": ids
},
url: prefix + '/batchRemove',
success: function (r) {
if (r.code == 0) {
layer.msg(r.msg);
reLoad();
} else {
layer.msg(r.msg);
}
}
});
}, function () {
});
} }

View File

@ -0,0 +1,52 @@
var dictList = parent.dictList;
$(function () {
$(".chosen-select").each(function (index, domEle) {
var dictType = $(domEle).attr("dict-type");
var dictValue = $(domEle).attr("dict-value");
var changeFunc = $(domEle).attr("dict-change-func");
if (dictType) {
var html = "";
// 加载数据
for (var i = 0; i < dictList.length; i++) {
if (dictList[i].type == dictType) {
html += '<option value="' + dictList[i].value + '">' + dictList[i].name + '</option>'
}
}
$(domEle).append(html);
$(domEle).chosen({
maxHeight: 200
});
$(domEle).val(dictValue);
$(domEle).trigger("chosen:updated");
// 点击事件
$(domEle).on('change', function (e, params) {
eval(changeFunc+'()');
});
}
});
});
function formatDict(dictType, value) {
var name = "";
// 加载数据
for (var i = 0; i < dictList.length; i++) {
if (dictList[i].type == dictType && dictList[i].value == value) {
name = dictList[i].name;
}
}
return name;
}

View File

@ -0,0 +1,2 @@
src/js/util/ierange.js
src/js/util/poly-fill.js

View File

@ -0,0 +1,38 @@
{
"env": {
"browser": true,
"commonjs": true,
"es6": true
},
"globals": {
"ENV": true
},
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module"
},
"rules": {
"no-console":0,
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single",
{
"allowTemplateLiterals": true
}
],
"semi": [
"error",
"never"
],
"no-unused-vars": 0,
"no-debugger": 0
}
}

View File

@ -0,0 +1,22 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

View File

@ -0,0 +1,51 @@
#忽略
**/node_modules/*
node_modules/*
npm-debug.log
example/upload-files
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

View File

@ -0,0 +1,5 @@
node_modules/*
npm-debug.log
docs/*
src/*
example/*

View File

@ -0,0 +1,157 @@
# 问题记录
## 版本修复
### v3.0.1
- [done] 如何设置自动增加高度(补充文档)
- [done] src/js/editor/Bar 改为 Progress仅供上传图片使用
- [done] Panel 在右上角增加一个“关闭”按钮
- [done] 显示页面 table、quote、code 等样式,说明一下
- [done] 增加自定义上传回调函数,用以自定义返回图片的格式
- [done] 上传附带的参数,也加入到 form-data 中一份
- [done] 编辑器默认情况下菜单栏不能点击必须focus了编辑器求之后才能点击
- [done] 点击菜单弹出panel之后再点击编辑器区域其他地方panel不消失
- [done] 自定义filenamev2版本就有
- [done] ff 中的 bug
- [done] ff 中粘贴图片和文字出现问题 https://github.com/wangfupeng1988/wangEditor/issues/609
- [done] 火狐浏览器下,创建表格,编辑表格内容时,会出现两个控制点(有人提供了解决方案)
- [done] 配置最多上传的文件个数
- [done] 连续给两段内容 添加有/无序列表时,样式会出问题,且其他内容找不到了,并且编辑器不处于编辑状态。
- [done] onchange
- [done] IE11下面一直报错。并且表格无法正常使用
### v3.0.2
- [done] 用 onchange 完善 vue react 的 demo
- [done] 插入图片之后,光标移动到图片的前面,然后回车,图片消失,并且不能撤销
- [done] 修复上传图片 customInsert 无效的bug
- [done] 编辑区域 z-index 可配置
- [done] 上传图片不应该把状态码限制在 200而是 2xx
- [done] editor.txt.html() 之后,没有定位光标位置
### v3.0.3
- [done] 粘贴图片在低版本的谷歌浏览器中无法使用提示验证图片未通过undefined不是图片。
- [done] 动态赋值内容,会自动换行,因为给自动加了`<p><br></p>`
- [done] 不选中任何内容点击“加粗”报错Failed to execute 'setEnd' on 'Range'
- [done] toolbar 小图标的 z-index 可配置
### v3.0.4
- [done] 允许使用者通过`replace`实现多语言
- [done] `_alert()`,可自定义配置提示框
- [done] 支持用户自定义上传图片的事件,如用户要上传到七牛云、阿里云
### v3.0.5
- [done] 图片上传中insertLinkImg 方法中,去掉 img.onload 之后再插入的逻辑吧,这样会打乱多个图片的顺序
- [done] `<h>` 标签重叠问题,两行文字都是`h2`,然后将第一行选中设置为`h1`,结果是 `<h2><h1>测试1</h1>测试2</h2>`
- [done] 补充 ng 集成的示例 https://github.com/wangfupeng1988/wangEditor/issues/859
- [done] 菜单不能折叠的说明,加入到文档中
- [done] 上传图片 before 函数中,增加一个判断,可以让用户终止图片的上传
### v3.0.6
- [done] src/fonts 中的字体文件名改一下,用 icomoon 容易发生冲突
- [done] 将禁用编辑器的操作完善到文档中 https://www.kancloud.cn/wangfupeng/wangeditor3/368562
- [done] 开放表格中的粘贴功能(之前因不明问题而封闭)
- [done] 代码块中,光标定位到最后位置时,连续两次回车要跳出代码块
### v3.0.7
- [done] 紧急修复上一个版本导致的菜单图标不显示的 bug
### v3.0.8
- [done] 修复 backColor 和 foreColor 代码文件名混淆的问题
- [done] 修改 IE 中 “引用” 的功能
- [done] 增加粘贴过滤样式的可配置
- [done] 修复 IE 粘贴文字的问题
### v3.0.9
- [done] config 中,上传图片的 token 注视掉
- [done] 将一些常见 API 开放,写到文档中 https://www.kancloud.cn/wangfupeng/wangeditor3/404586
- [done] IE 火狐 插入多行代码有问题
- [done] 粘贴时,在`<p>`中,不能只粘贴纯文本,还得要图片
- [done] 粘贴内容中,过滤掉`<!--xxx-->`注释
- [done] **支持上传七牛云存储**
### v3.0.10
- [done] 支持插入网络图片的回调函数
- [done] 插入链接时候的格式校验
- [done] 支持拖拽上传
### v3.0.11
- [done] 如何用 textarea 创建编辑器,完善到文档中,许多人提问
- [done] 修复`editor.customConfig.customUploadImg`不触发的 bug
- [done] 修复有序列表和无序列表切换时 onchange 不触发的 bug
### v3.0.12
- [done] 增加 onfocus 和 onblur (感谢 [hold-baby](https://github.com/hold-baby) 提交的 [PR](https://github.com/wangfupeng1988/wangEditor/pull/1076)
- [done] 上传的自定义参数`editor.customConfig.uploadImgParams`是否拼接到 url 中,支持可配置
- [done] onchange 触发的延迟时间,支持可配置
### v3.0.13
- [done] 修复图片 选中/取消选中 时,触发 onchange 的问题
- [done] 修复只通过 length 判断 onchange 是否触发的问题
- [done] 增加插入网络图片的校验函数
- [done] 增加自定义处理粘贴文本的事件
- [done] 修复选中一个图片时点击删除键会误删除其他内容的 bug
- [done] 修复 window chrome 中“复制图片”然后粘贴图片,会粘贴为两张的 bug
- [done] 修复无法撤销“引用”的问题
### v3.0.14
- [done] 可以配置前景色、背景色
- [done] 回车时无法从`<p><code>....</code></p>`中跳出
- [done] 增加获取 JSON 格式内容的 API
### v3.0.15
- [done] 表情兼容图片和 emoji ,都可自定义配置
### v3.0.16
- [done] 修复粘贴图片的 bug
- [done] 修复`pasteTextHandle`执行两次的问题
- [done] 修复插入链接时,文字和链接为空时,`linkCheck`不执行的 bug
- [done] 粘贴 html 时,过滤掉其中的`data-xxx`属性
- [done] 修复中文输入法输入过程中出发 onchange 的问题,感谢 [github.com/CongAn](https://github.com/CongAn) PR
- [done] `editor.txt.html``editor.txt.text`中,替换`&#8203`字符为空字符串
- [done] 精确图片大小计算,将`maxSize / 1000 / 1000`改为`maxSize / 1024 / 1024`
- [done] 修复 droplist 类型菜单(颜色、背景色等)点击不准确的问题
### v3.0.17
- [done] 合并 pr [菜单和编辑区域分离 onfocus onblur 失效bug](https://github.com/wangfupeng1988/wangEditor/pull/1174) ,感谢 [hold-baby](https://github.com/hold-baby) 提供 pr
- [done] 使用`document.execCommand("styleWithCSS", null, true)`,这样设置字体颜色就会用 css 而不是用`<font color=xxx>`
### 近期计划解决
- 撤销的兼容性问题(会误伤其他编辑器或者 input textarea 等),考虑用 onchange 记录 undo 和 redo 的内容(但是得考虑直接修改 dom 的情况,如 quote code img list table 菜单)
- 列表撤销会删除一行https://github.com/wangfupeng1988/wangEditor/issues/1131
- 页面中有 input 等输入标签时undo redo 会误伤 https://github.com/wangfupeng1988/wangEditor/issues/1024
- 两个编辑器 undo 的问题 https://github.com/wangfupeng1988/wangEditor/issues/1010
- list undo redo 有问题。选中几行,先设置有序列表,再设置无序列表,然后撤销,就能复现问题
- 粘贴文字的样式问题(可暂时配置 `pasteTextHandle` 自行处理)
- 先输入文字,再粘贴 excel 表格,样式丢失 https://github.com/wangfupeng1988/wangEditor/issues/1000
- IE 11 直接输入文字会空一行在第二行出现内容 https://github.com/wangfupeng1988/wangEditor/issues/919
- windows 下 word excel 的粘贴,存在垃圾数据
## 待排期
- 调研 safari、IE 和ff中粘贴图片 https://github.com/wangfupeng1988/wangEditor/issues/831
- 图片调整大小,表格调整的方式,是否用 toolbar 的方式?
- 删除掉`./release`之后,执行`npm run release`会报错,原因是`fonts`文件没拷贝全,就要去替换`css`中的字体文件为`base64`格式,导致找不到文件。
- 先点击'B'再输入内容这种形式,前期先支持 webkit 和 IE火狐的支持后面再加上
- 图片压缩 canvas https://github.com/think2011/localResizeIMG
- github 徽章 https://github.com/EyreFree/GitHubBadgeIntroduction
- 将代码在进行拆分,做到“每个程序只做一件事”,不要出现过长的代码文件。例如 `src/js/command/index.js``src/js/selection/index.js`

View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2017 王福朋
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,70 @@
# wangEditor
## 介绍
**wangEditor** —— 轻量级 web 富文本编辑器,配置方便,使用简单。支持 IE10+ 浏览器。
- 官网:[www.wangEditor.com](http://www.wangeditor.com/)
- 文档:[www.kancloud.cn/wangfupeng/wangeditor3/332599](http://www.kancloud.cn/wangfupeng/wangeditor3/332599)
- 源码:[github.com/wangfupeng1988/wangEditor](https://github.com/wangfupeng1988/wangEditor) (欢迎 star
![图片](http://images2015.cnblogs.com/blog/138012/201705/138012-20170530202905633-1840158981.png)
*查看 v2 版本的代码和文档点击[这里](https://github.com/wangfupeng1988/wangEditor/tree/v2)*
## 下载
- 直接下载:[https://github.com/wangfupeng1988/wangEditor/releases](https://github.com/wangfupeng1988/wangEditor/releases)
- 使用`npm`下载:`npm install wangeditor` (注意 `wangeditor` 全部是**小写字母**
- 使用`bower`下载:`bower install wangEditor` (前提保证电脑已安装了`bower`
- 使用CDN[//unpkg.com/wangeditor/release/wangEditor.min.js](https://unpkg.com/wangeditor/release/wangEditor.min.js)
## 使用
```javascript
var E = window.wangEditor
var editor = new E('#div1')
editor.create()
```
## 运行 demo
- 下载源码 `git clone git@github.com:wangfupeng1988/wangEditor.git`
- 安装或者升级最新版本 node最低`v6.x.x`
- 进入目录,安装依赖包 `cd wangEditor && npm i`
- 安装包完成之后windows 用户运行`npm run win-example`Mac 用户运行`npm run example`
- 打开浏览器访问[localhost:3000/index.html](http://localhost:3000/index.html)
- 用于 React、vue 或者 angular 可查阅[文档](http://www.kancloud.cn/wangfupeng/wangeditor3/332599)中[其他](https://www.kancloud.cn/wangfupeng/wangeditor3/335783)章节中的相关介绍
## 交流
### QQ 群
以下 QQ 群欢迎加入交流问题(可能有些群已经满员)
- 164999061
- 281268320
### 提问
注意,作者只受理以下几种提问方式,其他方式直接忽略
- 直接在 [github issues](https://github.com/wangfupeng1988/wangEditor/issues) 提交问题
- 去[知乎](https://www.zhihu.com/)提问,并邀请[作者](https://www.zhihu.com/people/wang-fu-peng-54/activities)来回答
- 去[segmentfault](https://segmentfault.com)提问,并邀请[作者](https://segmentfault.com/u/wangfupeng1988)来回答
每次升级版本修复的问题记录在[这里](./ISSUE.md)
## 关于作者
- 关注作者的博客 - 《[深入理解javascript原型和闭包系列](http://www.cnblogs.com/wangfupeng1988/p/4001284.html)》《[深入理解javascript异步系列](https://github.com/wangfupeng1988/js-async-tutorial)》《[CSS知多少](http://www.cnblogs.com/wangfupeng1988/p/4325007.html)》
- 学习作者的教程 - 《[前端JS基础面试题](http://coding.imooc.com/class/115.html)》《[React.js模拟大众点评webapp](http://coding.imooc.com/class/99.html)》《[zepto设计与源码分析](http://www.imooc.com/learn/745)》《[用grunt搭建自动化的web前端开发环境](http://study.163.com/course/courseMain.htm?courseId=1103003)》《[json2.js源码解读](http://study.163.com/course/courseMain.htm?courseId=691008)》
如果你感觉有收获,欢迎给我打赏 ———— 以激励我更多输出优质开源内容
![图片](https://camo.githubusercontent.com/e1558b631931e0a1606c769a61f48770cc0ccb56/687474703a2f2f696d61676573323031352e636e626c6f67732e636f6d2f626c6f672f3133383031322f3230313730322f3133383031322d32303137303232383131323233373739382d313530373139363634332e706e67)

View File

@ -0,0 +1,20 @@
{
"name": "wangEditor",
"description": "wangEditor - 基于javascript和css开发的 web 富文本编辑器, 轻量、简洁、易用、开源免费",
"main": "release/wangEditor.js",
"authors": [
"wangfupeng <wangfupeng1988@163.com>"
],
"license": "MIT",
"keywords": [
"wangEditor",
"web 富文本编辑器"
],
"homepage": "https://github.com/wangfupeng1988/wangEditor",
"moduleType": [
"amd",
"cmd",
"node"
],
"private": true
}

View File

@ -0,0 +1,25 @@
面向开发者的文档
框架介绍
- 下载和运行
- 目录结构介绍
- `example`目录
- `src`目录(`js`目录,`less`目录)
- `package.json`
- `gulpfile.js`
如何提交 PR
上线
- 修改`package.json`版本
- 提交到github并创建tag
- 提交到 npm
- 更新 .md 文档
- 文档同步到 kancloud
- ……

View File

@ -0,0 +1,41 @@
# 简单的 demo
## 下载
- 点击 [https://github.com/wangfupeng1988/wangEditor/releases](https://github.com/wangfupeng1988/wangEditor/releases) 下载最新版。进入`release`文件夹下找到`wangEditor.js`或者`wangEditor.min.js`即可
- 使用CDN[//unpkg.com/wangeditor/release/wangEditor.min.js](https://unpkg.com/wangeditor/release/wangEditor.min.js)
- 使用`bower`下载:`bower install wangEditor` (前提保证电脑已安装了`bower`
*PS支持`npm`安装,请参见后面的章节*
## 制作 demo
编辑器效果如下。
![图片](https://camo.githubusercontent.com/f3d072718d8fcbbacf8cc80465a34cceffcf5b4a/687474703a2f2f696d61676573323031352e636e626c6f67732e636f6d2f626c6f672f3133383031322f3230313730352f3133383031322d32303137303533303230323930353633332d313834303135383938312e706e67)
代码示例如下。**注意,以下代码中无需引用任何 CSS 文件!!!**
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>wangEditor demo</title>
</head>
<body>
<div id="editor">
<p>欢迎使用 <b>wangEditor</b> 富文本编辑器</p>
</div>
<!-- 注意, 只需要引用 JS无需引用任何 CSS -->
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#editor')
// 或者 var editor = new E( document.getElementById('#editor') )
editor.create()
</script>
</body>
</html>
```

View File

@ -0,0 +1,49 @@
# 使用模块定义
wangEditor 除了直接使用`<script>`引用之外,还支持`AMD``CommonJS`的引用方式。
## AMD
`require.js`为例演示
先创建`main.js`,代码为
```javascript
require(['/wangEditor.min.js'], function (E) {
var editor = new E('#editor')
editor.create()
})
```
然后创建页面,代码为
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>wangEditor demo</title>
</head>
<body>
<div id="editor">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script data-main="./main.js" src="//cdn.bootcss.com/require.js/2.3.3/require.js"></script>
</body>
</html>
```
## CommonJS
可以使用`npm install wangeditor`安装(注意,这里`wangeditor`全是**小写字母**
```javascript
// 引用
var E = require('wangeditor') // 使用 npm 安装
var E = require('/wangEditor.min.js') // 使用下载的源码
// 创建编辑器
var editor = new E('#editor')
editor.create()
```

View File

@ -0,0 +1,48 @@
# 菜单和编辑区域分离
如果你想要像 知乎专栏、简书、石墨、网易云笔记 这些编辑页面一样,将编辑区域和菜单分离,也可以实现。
这样,菜单和编辑器区域就是使用者可自己控制的元素,可自定义样式。例如:将菜单`fixed`、编辑器区域高度自动增加等
## 代码示例
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>wangEditor 菜单和编辑器区域分离</title>
<style type="text/css">
.toolbar {
border: 1px solid #ccc;
}
.text {
border: 1px solid #ccc;
height: 400px;
}
</style>
</head>
<body>
<div id="div1" class="toolbar">
</div>
<div style="padding: 5px 0; color: #ccc">中间隔离带</div>
<div id="div2" class="text"> <!--可使用 min-height 实现编辑区域自动增加高度-->
<p>请输入内容</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor1 = new E('#div1', '#div2') // 两个参数也可以传入 elem 对象class 选择器
editor1.create()
</script>
</body>
</html>
```
## 显示效果
从上面代码可以看出,菜单和编辑区域其实就是两个单独的`<div>`,位置、尺寸都可以随便定义。
![](http://images2015.cnblogs.com/blog/138012/201705/138012-20170531224756289-7442240.png)

View File

@ -0,0 +1,50 @@
# 同一个页面创建多个编辑器
wangEditor 支持一个页面创建多个编辑器
## 代码示例
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>wangEditor 一个页面多个编辑器</title>
<style type="text/css">
.toolbar {
background-color: #f1f1f1;
border: 1px solid #ccc;
}
.text {
border: 1px solid #ccc;
height: 200px;
}
</style>
</head>
<body>
<div id="div1" class="toolbar">
</div>
<div style="padding: 5px 0; color: #ccc">中间隔离带</div>
<div id="div2" class="text">
<p>第一个 demo菜单和编辑器区域分开</p>
</div>
<div id="div3">
<p>第二个 demo常规</p>
</div>
<!-- 引用js -->
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor1 = new E('#div1', '#div2')
editor1.create()
var editor2 = new E('#div3')
editor2.create()
</script>
</body>
</html>
```

View File

@ -0,0 +1,46 @@
# 设置内容
以下方式中,如果条件允许,尽量使用第一种方式,效率最高。
## html 初始化内容
直接将内容写到要创建编辑器的`<div>`标签中
```html
<div id="div1">
<p>初始化的内容</p>
<p>初始化的内容</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
editor.create()
</script>
```
## js 设置内容
创建编辑器之后,使用`editor.txt.html(...)`设置编辑器内容
```html
<div id="div1">
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
editor.create()
editor.txt.html('<p>用 JS 设置的内容</p>')
</script>
```
## 追加内容
创建编辑器之后,可使用`editor.txt.append('<p>追加的内容</p>')`继续追加内容。
## 清空内容
可使用`editor.txt.clear()`清空编辑器内容

View File

@ -0,0 +1,80 @@
# 读取内容
可以`html``text`的方式读取编辑器的内容。
```html
<div id="div1">
<p>欢迎使用 wangEditor 编辑器</p>
</div>
<button id="btn1">获取html</button>
<button id="btn2">获取text</button>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
editor.create()
document.getElementById('btn1').addEventListener('click', function () {
// 读取 html
alert(editor.txt.html())
}, false)
document.getElementById('btn2').addEventListener('click', function () {
// 读取 text
alert(editor.txt.text())
}, false)
</script>
```
需要注意的是:**从编辑器中获取的 html 代码是不包含任何样式的纯 html**,如果显示的时候需要对其中的`<table>` `<code>` `<blockquote>`等标签进行自定义样式(这样既可实现多皮肤功能),下面提供了编辑器中使用的样式供参考
```css
/* table 样式 */
table {
border-top: 1px solid #ccc;
border-left: 1px solid #ccc;
}
table td,
table th {
border-bottom: 1px solid #ccc;
border-right: 1px solid #ccc;
padding: 3px 5px;
}
table th {
border-bottom: 2px solid #ccc;
text-align: center;
}
/* blockquote 样式 */
blockquote {
display: block;
border-left: 8px solid #d0e5f2;
padding: 5px 10px;
margin: 10px 0;
line-height: 1.4;
font-size: 100%;
background-color: #f1f1f1;
}
/* code 样式 */
code {
display: inline-block;
*display: inline;
*zoom: 1;
background-color: #f1f1f1;
border-radius: 3px;
padding: 3px 5px;
margin: 0 3px;
}
pre code {
display: block;
}
/* ul ol 样式 */
ul, ol {
margin: 10px 0 10px 20px;
}
```

View File

@ -0,0 +1,25 @@
# 使用 textarea
wangEditor 从`v3`版本开始不支持 textarea ,但是可以通过`onchange`来实现 textarea 中提交富文本内容。
```html
<div id="div1">
<p>欢迎使用 <b>wangEditor</b> 富文本编辑器</p>
</div>
<textarea id="text1" style="width:100%; height:200px;"></textarea>
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
<script type="text/javascript" src="../wangEditor.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
var $text1 = $('#text1')
editor.customConfig.onchange = function (html) {
// 监控变化,同步更新到 textarea
$text1.val(html)
}
editor.create()
// 初始化 textarea 的值
$text1.val(editor.txt.html())
</script>
```

View File

@ -0,0 +1,82 @@
# 获取 JSON 格式的内容
可以通过`editor.txt.getJSON`获取 JSON 格式的编辑器的内容,`v3.0.14`开始支持,示例如下
```html
<div id="div1">
<p>欢迎使用 <b>wangEditor</b> 富文本编辑器</p>
<img src="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo_top_ca79a146.png" style="max-width:100%;"/>
</div>
<button id="btn1">getJSON</button>
<script type="text/javascript" src="/wangEditor.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
editor.create()
document.getElementById('btn1').addEventListener('click', function () {
var json = editor.txt.getJSON() // 获取 JSON 格式的内容
var jsonStr = JSON.stringify(json)
console.log(json)
console.log(jsonStr)
})
</script>
```
-----
如果编辑器区域的 html 内容是如下:
```html
<p>欢迎使用 <b>wangEditor</b> 富文本编辑器</p>
<img src="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo_top_ca79a146.png" style="max-width:100%;"/>
```
那么获取的 JSON 格式就如下:
```json
[
{
"tag": "p",
"attrs": [],
"children": [
"欢迎使用 ",
{
"tag": "b",
"attrs": [],
"children": [
"wangEditor"
]
},
" 富文本编辑器"
]
},
{
"tag": "img",
"attrs": [
{
"name": "src",
"value": "https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo_top_ca79a146.png"
},
{
"name": "style",
"value": "max-width:100%;"
}
],
"children": []
},
{
"tag": "p",
"attrs": [],
"children": [
{
"tag": "br",
"attrs": [],
"children": []
}
]
}
]
```

View File

@ -0,0 +1,52 @@
# 自定义菜单
编辑器创建之前,可使用`editor.customConfig.menus`定义显示哪些菜单和菜单的顺序。**注意v3 版本的菜单不支持换行折叠了(因为换行之后菜单栏是在太难看),如果菜单栏宽度不够,建议精简菜单项。**
## 代码示例
```html
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
// 自定义菜单配置
editor.customConfig.menus = [
'head',
'bold',
'italic',
'underline'
]
editor.create()
</script>
```
## 默认菜单
编辑默认的菜单配置如下
```javascript
[
'head', // 标题
'bold', // 粗体
'italic', // 斜体
'underline', // 下划线
'strikeThrough', // 删除线
'foreColor', // 文字颜色
'backColor', // 背景颜色
'link', // 插入链接
'list', // 列表
'justify', // 对齐方式
'quote', // 引用
'emoticon', // 表情
'image', // 插入图片
'table', // 表格
'video', // 插入视频
'code', // 插入代码
'undo', // 撤销
'redo' // 重复
]
```

View File

@ -0,0 +1,21 @@
# 定义 debug 模式
可通过`editor.customConfig.debug = true`配置`debug`模式,`debug`模式下,有 JS 错误会以`throw Error`方式提示出来。默认值为`false`,即不会抛出异常。
但是,在实际开发中不建议直接定义为`true`或者`false`,可通过 url 参数进行干预,示例如下:
```html
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
// 通过 url 参数配置 debug 模式。url 中带有 wangeditor_debug_mode=1 才会开启 debug 模式
editor.customConfig.debug = location.href.indexOf('wangeditor_debug_mode=1') > 0
editor.create()
</script>
```

View File

@ -0,0 +1,40 @@
# 配置 onchange 函数
配置`onchange`函数之后,用户操作(鼠标点击、键盘打字等)导致的内容变化之后,会自动触发`onchange`函数执行。
但是,**用户自己使用 JS 修改了`div1``innerHTML`,不会自动触发`onchange`函数**,此时你可以通过执行`editor.change()`来手动触发`onchange`函数的执行。
```html
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<p>手动触发 onchange 函数执行</p>
<button id="btn1">change</button>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
editor.customConfig.onchange = function (html) {
// html 即变化之后的内容
console.log(html)
}
editor.create()
document.getElementById('btn1').addEventListener('click', function () {
// 如果未配置 editor.customConfig.onchange则 editor.change 为 undefined
editor.change && editor.change()
})
</script>
```
-----
另外,如果需要修改 onchange 触发的延迟时间onchange 会在用户无任何操作的 xxx 毫秒之后被触发),可通过如下配置
```js
// 自定义 onchange 触发的延迟时间,默认为 200 ms
editor.customConfig.onchangeTimeout = 1000 // 单位 ms
```

View File

@ -0,0 +1,19 @@
# 配置编辑区域的 z-index
编辑区域的`z-index`默认为`10000`,可自定义修改,代码配置如下。需改之后,编辑区域和菜单的`z-index`会同时生效。
```html
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
editor.customConfig.zIndex = 100
editor.create()
</script>
```

View File

@ -0,0 +1,30 @@
# 多语言
可以通过`lang`配置项配置多语言,其实就是通过该配置项中的配置,将编辑器显示的文字,替换成你需要的文字。
```html
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
editor.customConfig.lang = {
'设置标题': 'title',
'正文': 'p',
'链接文字': 'link text',
'链接': 'link',
'上传图片': 'upload image',
'上传': 'upload',
'创建': 'init'
// 还可自定添加更多
}
editor.create()
</script>
```
**注意,以上代码中的`链接文字`要写在`链接`前面,`上传图片`要写在`上传`前面,因为前者包含后者。如果不这样做,可能会出现替换不全的问题,切记切记!**

View File

@ -0,0 +1,33 @@
# 粘贴文本
**注意,以下配置暂时对 IE 无效。IE 暂时使用系统自带的粘贴功能,没有样式过滤!**
## 关闭粘贴样式的过滤
当从其他网页复制文本内容粘贴到编辑器中,编辑器会默认过滤掉复制文本中自带的样式,目的是让粘贴后的文本变得更加简洁和轻量。用户可通过`editor.customConfig.pasteFilterStyle = false`手动关闭掉粘贴样式的过滤。
## 自定义处理粘贴的文本内容
使用者可通过`editor.customConfig.pasteTextHandle`对粘贴的文本内容进行自定义的过滤、处理等操作,然后返回处理之后的文本内容。编辑器最终会粘贴用户处理之后并且返回的的内容。
## 示例代码
```html
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
// 关闭粘贴样式的过滤
editor.customConfig.pasteFilterStyle = false
// 自定义处理粘贴的文本内容
editor.customConfig.pasteTextHandle = function (content) {
// content 即粘贴过来的内容html 或 纯文本),可进行自定义处理然后返回
return content + '<p>在粘贴内容后面追加一行</p>'
}
editor.create()
</script>
```

View File

@ -0,0 +1,12 @@
# 插入网络图片的回调
插入网络图片时,可通过如下配置获取到图片的信息。`v3.0.10`开始支持。
```js
var E = window.wangEditor
var editor = new E('#div1')
editor.customConfig.linkImgCallback = function (url) {
console.log(url) // url 即插入图片的地址
}
editor.create()
```

View File

@ -0,0 +1,16 @@
# 插入链接的校验
插入链接时,可通过如下配置对文字和链接进行校验。`v3.0.10`开始支持。
```js
var E = window.wangEditor
var editor = new E('#div1')
editor.customConfig.linkCheck = function (text, link) {
console.log(text) // 插入的文字
console.log(link) // 插入的链接
return true // 返回 true 表示校验成功
// return '验证失败' // 返回字符串,即校验失败的提示信息
}
editor.create()
```

View File

@ -0,0 +1,19 @@
# 配置 onfocus 函数
配置`onfocus`函数之后,用户点击富文本区域会触发`onfocus`函数执行。
```html
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
editor.customConfig.onfocus = function () {
console.log("onfocus")
}
editor.create()
</script>
```

View File

@ -0,0 +1,20 @@
# 配置 onblur 函数
配置`onblur`函数之后,如果当前有手动获取焦点的富文本并且鼠标点击富文本以外的区域,则会触发`onblur`函数执行。
```html
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
editor.customConfig.onblur = function (html) {
// html 即编辑器中的内容
console.log('onblur', html)
}
editor.create()
</script>
```

View File

@ -0,0 +1,15 @@
# 插入网络图片的校验
插入网络图片时,可对图片地址做自定义校验。`v3.0.13`开始支持。
```js
var E = window.wangEditor
var editor = new E('#div1')
editor.customConfig.linkImgCheck = function (src) {
console.log(src) // 图片的链接
return true // 返回 true 表示校验成功
// return '验证失败' // 返回字符串,即校验失败的提示信息
}
editor.create()
```

View File

@ -0,0 +1,29 @@
# 配置字体颜色、背景色
编辑器的字体颜色和背景色,可以通过`editor.customConfig.colors`自定义配置
```html
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
// 自定义配置颜色(字体颜色、背景色)
editor.customConfig.colors = [
'#000000',
'#eeece0',
'#1c487f',
'#4d80bf',
'#c24f4a',
'#8baa4a',
'#7b5ba1',
'#46acc8',
'#f9963b',
'#ffffff'
]
editor.create()
</script>
```

View File

@ -0,0 +1,48 @@
# 配置表情
`v3.0.15`开始支持配置表情,支持图片格式和 emoji ,可通过`editor.customConfig.emotions`配置。**注意看代码示例中的注释:**
```html
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
// 表情面板可以有多个 tab ,因此要配置成一个数组。数组每个元素代表一个 tab 的配置
editor.customConfig.emotions = [
{
// tab 的标题
title: '默认',
// type -> 'emoji' / 'image'
type: 'image',
// content -> 数组
content: [
{
alt: '[坏笑]',
src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/50/pcmoren_huaixiao_org.png'
},
{
alt: '[舔屏]',
src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/40/pcmoren_tian_org.png'
}
]
},
{
// tab 的标题
title: 'emoji',
// type -> 'emoji' / 'image'
type: 'emoji',
// content -> 数组
content: ['😀', '😃', '😄', '😁', '😆']
}
]
editor.create()
</script>
```
温馨提示:需要表情图片可以去 https://api.weibo.com/2/emotions.json?source=1362404091 和 http://yuncode.net/code/c_524ba520e58ce30 逛一逛,或者自己搜索。

View File

@ -0,0 +1,52 @@
# 隐藏/显示 tab
## 显示“上传图片”tab
默认情况下编辑器不会显示“上传图片”的tab因为你还没有配置上传图片的信息。
![](http://images2015.cnblogs.com/blog/138012/201706/138012-20170601204308039-691571074.png)
参考一下示例显示“上传图片”tab
```html
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
// 下面两个配置使用其中一个即可显示“上传图片”的tab。但是两者不要同时使用
// editor.customConfig.uploadImgShowBase64 = true // 使用 base64 保存图片
// editor.customConfig.uploadImgServer = '/upload' // 上传图片到服务器
editor.create()
</script>
```
显示效果
![](http://images2015.cnblogs.com/blog/138012/201706/138012-20170601204504524-830243744.png)
## 隐藏“网络图片”tab
默认情况下“网络图片”tab是一直存在的。如果不需要可以参考一下示例来隐藏它。
```html
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
// 隐藏“网络图片”tab
editor.customConfig.showLinkImg = false
editor.create()
</script>
```

View File

@ -0,0 +1,23 @@
# 使用 base64 保存图片
如果需要使用 base64 编码直接将图片插入到内容中,可参考一下示例配置
```html
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
editor.customConfig.uploadImgShowBase64 = true // 使用 base64 保存图片
editor.create()
</script>
```
示例效果如下
![](http://images2015.cnblogs.com/blog/138012/201706/138012-20170601204759258-1412289899.png)

View File

@ -0,0 +1,188 @@
# 上传图片 & 配置
将图片上传到服务器上的配置方式
## 上传图片
参考如下代码
```html
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
// 配置服务器端地址
editor.customConfig.uploadImgServer = '/upload'
// 进行下文提到的其他配置
// ……
// ……
// ……
editor.create()
</script>
```
其中`/upload`是上传图片的服务器端接口,接口返回的**数据格式**如下(**实际返回数据时,不要加任何注释!!!**
```json
{
// errno 即错误代码0 表示没有错误。
// 如果有错误errno != 0可通过下文中的监听函数 fail 拿到该错误码进行自定义处理
"errno": 0,
// data 是一个数组,返回若干图片的线上地址
"data": [
"图片1地址",
"图片2地址",
"……"
]
}
```
## 限制图片大小
默认限制图片大小是 5M
```javascript
// 将图片大小限制为 3M
editor.customConfig.uploadImgMaxSize = 3 * 1024 * 1024
```
## 限制一次最多能传几张图片
默认为 10000 张(即不限制),需要限制可自己配置
```javascript
// 限制一次最多上传 5 张图片
editor.customConfig.uploadImgMaxLength = 5
```
## 自定义上传参数
上传图片时可自定义传递一些参数,例如传递验证的`token`等。参数会被添加到`formdata`中。
```javascript
editor.customConfig.uploadImgParams = {
token: 'abcdef12345' // 属性值会自动进行 encode ,此处无需 encode
}
```
如果**还需要**将参数拼接到 url 中,可再加上如下配置
```
editor.customConfig.uploadImgParamsWithUrl = true
```
## 自定义 fileName
上传图片时,可自定义`filename`,即在使用`formdata.append(name, file)`添加图片文件时,自定义第一个参数。
```javascript
editor.customConfig.uploadFileName = 'yourFileName'
```
## 自定义 header
上传图片时刻自定义设置 header
```javascript
editor.customConfig.uploadImgHeaders = {
'Accept': 'text/x-json'
}
```
## withCredentials跨域传递 cookie
跨域上传中如果需要传递 cookie 需设置 withCredentials
```javascript
editor.customConfig.withCredentials = true
```
## 自定义 timeout 时间
默认的 timeout 时间是 10 秒钟
```javascript
// 将 timeout 时间改为 3s
editor.customConfig.uploadImgTimeout = 3000
```
## 监听函数
可使用监听函数在上传图片的不同阶段做相应处理
```javascript
editor.customConfig.uploadImgHooks = {
before: function (xhr, editor, files) {
// 图片上传之前触发
// xhr 是 XMLHttpRequst 对象editor 是编辑器对象files 是选择的图片文件
// 如果返回的结果是 {prevent: true, msg: 'xxxx'} 则表示用户放弃上传
// return {
// prevent: true,
// msg: '放弃上传'
// }
},
success: function (xhr, editor, result) {
// 图片上传并返回结果,图片插入成功之后触发
// xhr 是 XMLHttpRequst 对象editor 是编辑器对象result 是服务器端返回的结果
},
fail: function (xhr, editor, result) {
// 图片上传并返回结果,但图片插入错误时触发
// xhr 是 XMLHttpRequst 对象editor 是编辑器对象result 是服务器端返回的结果
},
error: function (xhr, editor) {
// 图片上传出错时触发
// xhr 是 XMLHttpRequst 对象editor 是编辑器对象
},
timeout: function (xhr, editor) {
// 图片上传超时时触发
// xhr 是 XMLHttpRequst 对象editor 是编辑器对象
},
// 如果服务器端返回的不是 {errno:0, data: [...]} 这种格式,可使用该配置
// (但是,服务器端返回的必须是一个 JSON 格式字符串!!!否则会报错)
customInsert: function (insertImg, result, editor) {
// 图片上传并返回结果,自定义插入图片的事件(而不是编辑器自动插入图片!!!)
// insertImg 是插入图片的函数editor 是编辑器对象result 是服务器端返回的结果
// 举例:假如上传图片成功后,服务器端返回的是 {url:'....'} 这种格式,即可这样插入图片:
var url = result.url
insertImg(url)
// result 必须是一个 JSON 格式字符串!!!否则报错
}
}
```
## 自定义提示方法
上传图片的错误提示默认使用`alert`弹出,你也可以自定义用户体验更好的提示方式
```javascript
editor.customConfig.customAlert = function (info) {
// info 是需要提示的内容
alert('自定义提示:' + info)
}
```
## 自定义上传图片事件
如果想完全自己控制图片上传的过程,可以使用如下代码
```javascript
editor.customConfig.customUploadImg = function (files, insert) {
// files 是 input 中选中的文件列表
// insert 是获取图片 url 后,插入到编辑器的方法
// 上传代码返回结果之后,将图片插入到编辑器中
insert(imgUrl)
}
```

View File

@ -0,0 +1,115 @@
# 上传到七牛云存储
完整的 demo 请参见 https://github.com/wangfupeng1988/js-sdk ,可下载下来本地运行 demo
> 注意:配置了上传七牛云存储之后,**无法再使用插入网络图片**
核心代码如下:
```js
var E = window.wangEditor
var editor = new E('#div1')
// 允许上传到七牛云存储
editor.customConfig.qiniu = true
editor.create()
// 初始化七牛上传
uploadInit()
// 初始化七牛上传的方法
function uploadInit() {
// 获取相关 DOM 节点的 ID
var btnId = editor.imgMenuId;
var containerId = editor.toolbarElemId;
var textElemId = editor.textElemId;
// 创建上传对象
var uploader = Qiniu.uploader({
runtimes: 'html5,flash,html4', //上传模式,依次退化
browse_button: btnId, //上传选择的点选按钮,**必需**
uptoken_url: '/uptoken',
//Ajax请求upToken的Url**强烈建议设置**(服务端提供)
// uptoken : '<Your upload token>',
//若未指定uptoken_url,则必须指定 uptoken ,uptoken由其他程序生成
// unique_names: true,
// 默认 falsekey为文件名。若开启该选项SDK会为每个文件自动生成key文件名
// save_key: true,
// 默认 false。若在服务端生成uptoken的上传策略中指定了 `sava_key`则开启SDK在前端将不对key进行任何处理
domain: 'http://7xrjl5.com1.z0.glb.clouddn.com/',
//bucket 域名,下载资源时用到,**必需**
container: containerId, //上传区域DOM ID默认是browser_button的父元素
max_file_size: '100mb', //最大文件体积限制
flash_swf_url: '../js/plupload/Moxie.swf', //引入flash,相对路径
filters: {
mime_types: [
//只允许上传图片文件 注意extensions中逗号后面不要加空格
{ title: "图片文件", extensions: "jpg,gif,png,bmp" }
]
},
max_retries: 3, //上传失败最大重试次数
dragdrop: true, //开启可拖曳上传
drop_element: textElemId, //拖曳上传区域元素的ID拖曳文件或文件夹后可触发上传
chunk_size: '4mb', //分块上传时,每片的体积
auto_start: true, //选择文件后自动上传,若关闭需要自己绑定事件触发上传
init: {
'FilesAdded': function(up, files) {
plupload.each(files, function(file) {
// 文件添加进队列后,处理相关的事情
printLog('on FilesAdded');
});
},
'BeforeUpload': function(up, file) {
// 每个文件上传前,处理相关的事情
printLog('on BeforeUpload');
},
'UploadProgress': function(up, file) {
// 显示进度
printLog('进度 ' + file.percent)
},
'FileUploaded': function(up, file, info) {
// 每个文件上传成功后,处理相关的事情
// 其中 info 是文件上传成功后服务端返回的json形式如
// {
// "hash": "Fh8xVqod2MQ1mocfI4S4KpRL6D98",
// "key": "gogopher.jpg"
// }
printLog(info);
// 参考http://developer.qiniu.com/docs/v6/api/overview/up/response/simple-response.html
var domain = up.getOption('domain');
var res = $.parseJSON(info);
var sourceLink = domain + res.key; //获取上传成功后的文件的Url
printLog(sourceLink);
// 插入图片到editor
editor.cmd.do('insertHtml', '<img src="' + sourceLink + '" style="max-width:100%;"/>')
},
'Error': function(up, err, errTip) {
//上传出错时,处理相关的事情
printLog('on Error');
},
'UploadComplete': function() {
//队列文件处理完毕后,处理相关的事情
printLog('on UploadComplete');
}
// Key 函数如果有需要自行配置,无特殊需要请注释
//,
// 'Key': function(up, file) {
// // 若想在前端对每个文件的key进行个性化处理可以配置该函数
// // 该配置必须要在 unique_names: false , save_key: false 时才生效
// var key = "";
// // do something with key here
// return key
// }
}
// domain 为七牛空间bucket)对应的域名,选择某个空间后,可通过"空间设置->基本设置->域名设置"查看获取
// uploader 为一个plupload对象继承了所有plupload的方法参考http://plupload.com/docs
});
}
// 封装 console.log 函数
function printLog(title, info) {
window.console && console.log(title, info);
}
```

View File

@ -0,0 +1,10 @@
# 全屏 & 预览 & 查看源码
## 全屏
虽然 wangEditor 没有内置全屏功能但是你可以通过简单的代码来搞定作者已经做了一个demo来示范。通过运行 demo文档一开始就介绍了即可看到该示例页面直接查看页面源代码即可。
## 预览 & 查看源码
如果需要预览和查看源码的功能,也需要跟全屏功能一样,自己定义按钮。点击按钮时通过`editor.txt.html()`获取编辑器内容,然后自定义实现预览和查看源码功能。

View File

@ -0,0 +1,24 @@
# 关于上传附件
**有用户问到编辑器能否有上传附件的功能?我的建议是不要把附件做到内容中。**
原因很简单,如果将附件上传之后再插入到富文本内容中,其实就是一个链接的形式。如下图:
![](http://box.kancloud.cn/2016-02-19_56c718ec6f9bf.png)
而用户在用编辑器编辑文本时,操作是非常随意多样的,他把这个链接删了,你服务器要想实时删除上传的附件文件,是难监控到的。
还有,用户如果要上传很多个附件,也是很难管理的,还是因为富文本的内容变化多样,用户可以随便在什么地方插入附件,而且形式和链接一样。
-------
反过来,我们想一下平时用附件和编辑器最多的产品是什么——是邮箱。邮箱如何处理附件的,大家应该很清楚。它把文本内容和附件分开,这样附件就可以很轻松、明了的进行管理,绝对不会和编辑内容的链接产生混淆。
![](http://box.kancloud.cn/2016-02-19_56c718ec83f7e.png)
你能看到的所有的邮箱产品,几乎都是这样设计的。
-------
因此,在你提问编辑器能否上传附件这个问题的时候,可以想一下能否参照邮箱的实现来设计?

View File

@ -0,0 +1,12 @@
# 关于 markdown
**好多使用者问到wangEditor编辑器能否集成markdown——答案是富文本编辑器无法和markdown集成到一起。**
-----
你可以参考 [简书](http://www.jianshu.com/) 的实现方式,简书中编辑器也无法实现富文本和`markdown`的自由切换。要么使用富文本编写文章,要么使用`markdown`编写文章,不能公用。
本质上,富文本编辑器和`markdown`编辑器是两回事儿。

View File

@ -0,0 +1,23 @@
# 预防 XSS 攻击
> 术业有专攻
要想在前端预防 xss 攻击,还得依赖于其他工具,例如[xss.js](http://jsxss.com/zh/index.html)(如果打不开页面,就从百度搜一下)
代码示例如下
```html
<script src='/xss.js'></script>
<script src='/wangEditor.min.js'></script>
<script>
var E = window.wangEditor
var editor = new E('#div1')
document.getElementById('btn1').addEventListener('click', function () {
var html = editor.txt.html()
var filterHtml = filterXSS(html) // 此处进行 xss 攻击过滤
alert(filterHtml)
}, false)
</script>
```

View File

@ -0,0 +1,7 @@
# 用于 React
如果需要将 wangEditor 用于 React 中,可参见如下示例
- 下载源码 `git clone git@github.com:wangfupeng1988/wangEditor.git`
- 进入 React 示例目录 `cd wangEditor/example/demo/in-react/`,查看`src/App.js`即可
- 也可以运行`npm install && npm start`查看在 React 中的效果(`http://localhost:3000/`

View File

@ -0,0 +1,7 @@
# 用于 Vue
如果需要将 wangEditor 用于 Vue 中,可参见如下示例
- 下载源码 `git clone git@github.com:wangfupeng1988/wangEditor.git`
- 进入 vue 示例目录 `cd wangEditor/example/demo/in-vue/`,查看`src/components/Editor.vue`即可
- 也可以运行`npm install && npm run dev`查看在 vue 中的效果(`http://localhost:8080/`

View File

@ -0,0 +1,3 @@
# 用于 Angular
感谢 [@fengnovo](https://github.com/fengnovo) 提供了一个 angular2 的兼容示例,可供参考 https://github.com/fengnovo/wangEditor/tree/master/example/demo/in-ng2

View File

@ -0,0 +1,27 @@
# 常用 API
## 属性
- 获取编辑器的唯一标识 `editor.id`
- 获取编辑区域 DOM 节点 `editor.$textElem[0]`
- 获取菜单栏 DOM 节点 `editor.$toolbarElem[0]`
- 获取编辑器配置信息 `editor.config`
- 获取编辑区域 DOM 节点 ID `editor.textElemId`
- 获取菜单栏 DOM 节点 ID `editor.toolbarElemId`
- 获取菜单栏中“图片”菜单的 DOM 节点 ID `editor.imgMenuId`
## 方法
### 选取操作
- 获取选中的文字 `editor.selection.getSelectionText()`
- 获取选取所在的 DOM 节点 `editor.selection.getSelectionContainerElem()[0]`
- 开始节点 `editor.selection.getSelectionStartElem()[0]`
- 结束节点 `editor.selection.getSelectionEndElem()[0]`
- 折叠选取 `editor.selection.collapseRange()`
- 更多可参见[源码中](https://github.com/wangfupeng1988/wangEditor/blob/master/src/js/selection/index.js)定义的方法
### 编辑内容操作
- 插入 HTML `editor.cmd.do('insertHTML', '<p>...</p>')`
- 可通过`editor.cmd.do(name, value)`来执行`document.execCommand(name, false, value)`的操作

View File

@ -0,0 +1,3 @@
同步[../../README.md](../../README.md)的内容
将所有文档跟新到 www.kancloud.cn/wangfupeng/wangeditor3/332599 中

View File

@ -0,0 +1 @@
wangEditor demo

View File

@ -0,0 +1,19 @@
{
"name": "wangeditor-in-react",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^15.5.4",
"react-dom": "^15.5.4",
"wangeditor": ">=3.0.0"
},
"devDependencies": {
"react-scripts": "1.0.7"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,40 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "192x192",
"type": "image/png"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,24 @@
.App {
text-align: center;
}
.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 80px;
}
.App-header {
background-color: #222;
height: 150px;
padding: 20px;
color: white;
}
.App-intro {
font-size: large;
}
@keyframes App-logo-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

View File

@ -0,0 +1,48 @@
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import E from 'wangeditor'
class App extends Component {
constructor(props, context) {
super(props, context);
this.state = {
editorContent: ''
}
}
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to React</h2>
</div>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
{/* 将生成编辑器 */}
<div ref="editorElem" style={{textAlign: 'left'}}>
</div>
<button onClick={this.clickHandle.bind(this)}>获取内容</button>
</div>
);
}
componentDidMount() {
const elem = this.refs.editorElem
const editor = new E(elem)
// 使用 onchange 函数监听内容的变化并实时更新到 state
editor.customConfig.onchange = html => {
this.setState({
editorContent: html
})
}
editor.create()
}
clickHandle() {
alert(this.state.editorContent)
}
}
export default App;

View File

@ -0,0 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
});

View File

@ -0,0 +1,5 @@
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}

View File

@ -0,0 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import './index.css';
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,51 @@
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.
export default function register() {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
});
}
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}

View File

@ -0,0 +1,14 @@
{
"presets": [
["env", { "modules": false }],
"stage-2"
],
"plugins": ["transform-runtime"],
"comments": false,
"env": {
"test": {
"presets": ["env", "stage-2"],
"plugins": [ "istanbul" ]
}
}
}

View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

View File

@ -0,0 +1,8 @@
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
"plugins": {
// to edit target browsers: use "browserlist" field in package.json
"autoprefixer": {}
}
}

View File

@ -0,0 +1,35 @@
require('./check-versions')()
process.env.NODE_ENV = 'production'
var ora = require('ora')
var rm = require('rimraf')
var path = require('path')
var chalk = require('chalk')
var webpack = require('webpack')
var config = require('../config')
var webpackConfig = require('./webpack.prod.conf')
var spinner = ora('building for production...')
spinner.start()
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
webpack(webpackConfig, function (err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})
})

View File

@ -0,0 +1,48 @@
var chalk = require('chalk')
var semver = require('semver')
var packageConfig = require('../package.json')
var shell = require('shelljs')
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
var versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
},
]
if (shell.which('npm')) {
versionRequirements.push({
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function () {
var warnings = []
for (var i = 0; i < versionRequirements.length; i++) {
var mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (var i = 0; i < warnings.length; i++) {
var warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}

View File

@ -0,0 +1,9 @@
/* eslint-disable */
require('eventsource-polyfill')
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
hotClient.subscribe(function (event) {
if (event.action === 'reload') {
window.location.reload()
}
})

View File

@ -0,0 +1,89 @@
require('./check-versions')()
var config = require('../config')
if (!process.env.NODE_ENV) {
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
}
var opn = require('opn')
var path = require('path')
var express = require('express')
var webpack = require('webpack')
var proxyMiddleware = require('http-proxy-middleware')
var webpackConfig = require('./webpack.dev.conf')
// default port where dev server listens for incoming traffic
var port = process.env.PORT || config.dev.port
// automatically open browser, if not set will be false
var autoOpenBrowser = !!config.dev.autoOpenBrowser
// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
var proxyTable = config.dev.proxyTable
var app = express()
var compiler = webpack(webpackConfig)
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
quiet: true
})
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
log: () => {}
})
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
if (typeof options === 'string') {
options = { target: options }
}
app.use(proxyMiddleware(options.filter || context, options))
})
// handle fallback for HTML5 history API
app.use(require('connect-history-api-fallback')())
// serve webpack bundle output
app.use(devMiddleware)
// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware)
// serve pure static assets
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))
var uri = 'http://localhost:' + port
var _resolve
var readyPromise = new Promise(resolve => {
_resolve = resolve
})
console.log('> Starting dev server...')
devMiddleware.waitUntilValid(() => {
console.log('> Listening at ' + uri + '\n')
// when env is testing, don't need open it
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
opn(uri)
}
_resolve()
})
var server = app.listen(port)
module.exports = {
ready: readyPromise,
close: () => {
server.close()
}
}

View File

@ -0,0 +1,71 @@
var path = require('path')
var config = require('../config')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
exports.assetsPath = function (_path) {
var assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
options = options || {}
var cssLoader = {
loader: 'css-loader',
options: {
minimize: process.env.NODE_ENV === 'production',
sourceMap: options.sourceMap
}
}
// generate loader string to be used with extract text plugin
function generateLoaders (loader, loaderOptions) {
var loaders = [cssLoader]
if (loader) {
loaders.push({
loader: loader + '-loader',
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader'
})
} else {
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
var output = []
var loaders = exports.cssLoaders(options)
for (var extension in loaders) {
var loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}

View File

@ -0,0 +1,12 @@
var utils = require('./utils')
var config = require('../config')
var isProduction = process.env.NODE_ENV === 'production'
module.exports = {
loaders: utils.cssLoaders({
sourceMap: isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap,
extract: isProduction
})
}

View File

@ -0,0 +1,58 @@
var path = require('path')
var utils = require('./utils')
var config = require('../config')
var vueLoaderConfig = require('./vue-loader.conf')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src')
}
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
}
}

View File

@ -0,0 +1,35 @@
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})
module.exports = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
},
// cheap-module-eval-source-map is faster for development
devtool: '#cheap-module-eval-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': config.dev.env
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
new FriendlyErrorsPlugin()
]
})

View File

@ -0,0 +1,120 @@
var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var CopyWebpackPlugin = require('copy-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var env = config.build.env
var webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
if (config.build.productionGzip) {
var CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig

View File

@ -0,0 +1,6 @@
var merge = require('webpack-merge')
var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})

View File

@ -0,0 +1,38 @@
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')
module.exports = {
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css'],
// Run the build command with an extra argument to
// View the bundle analyzer report after build finishes:
// `npm run build --report`
// Set to `true` or `false` to always turn it on or off
bundleAnalyzerReport: process.env.npm_config_report
},
dev: {
env: require('./dev.env'),
port: 8080,
autoOpenBrowser: true,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: false
}
}

View File

@ -0,0 +1,3 @@
module.exports = {
NODE_ENV: '"production"'
}

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>wangeditor-in-vue</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@ -0,0 +1,60 @@
{
"name": "wangeditor-in-vue",
"version": "1.0.0",
"description": "A Vue.js project",
"author": "git <git@git.com>",
"private": true,
"scripts": {
"dev": "node build/dev-server.js",
"start": "node build/dev-server.js",
"build": "node build/build.js"
},
"dependencies": {
"vue": "^2.3.3",
"wangeditor": ">=3.0.0"
},
"devDependencies": {
"autoprefixer": "^6.7.2",
"babel-core": "^6.22.1",
"babel-loader": "^6.2.10",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-preset-env": "^1.3.2",
"babel-preset-stage-2": "^6.22.0",
"babel-register": "^6.22.0",
"chalk": "^1.1.3",
"connect-history-api-fallback": "^1.3.0",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.28.0",
"eventsource-polyfill": "^0.9.6",
"express": "^4.14.1",
"extract-text-webpack-plugin": "^2.0.0",
"file-loader": "^0.11.1",
"friendly-errors-webpack-plugin": "^1.1.3",
"html-webpack-plugin": "^2.28.0",
"http-proxy-middleware": "^0.17.3",
"webpack-bundle-analyzer": "^2.2.1",
"semver": "^5.3.0",
"shelljs": "^0.7.6",
"opn": "^4.0.2",
"optimize-css-assets-webpack-plugin": "^1.3.0",
"ora": "^1.2.0",
"rimraf": "^2.6.0",
"url-loader": "^0.5.8",
"vue-loader": "^12.1.0",
"vue-style-loader": "^3.0.1",
"vue-template-compiler": "^2.3.3",
"webpack": "^2.6.1",
"webpack-dev-middleware": "^1.10.0",
"webpack-hot-middleware": "^2.18.0",
"webpack-merge": "^4.1.0"
},
"engines": {
"node": ">= 4.0.0",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}

View File

@ -0,0 +1,31 @@
<template>
<div id="app">
<img src="./assets/logo.png">
<hello></hello>
<editor></editor>
</div>
</template>
<script>
import Hello from './components/Hello'
import Editor from './components/Editor'
export default {
name: 'app',
components: {
Hello,
Editor
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -0,0 +1,34 @@
<template>
<div>
<div ref="editor" style="text-align:left"></div>
<button v-on:click="getContent">查看内容</button>
</div>
</template>
<script>
import E from 'wangeditor'
export default {
name: 'editor',
data () {
return {
editorContent: ''
}
},
methods: {
getContent: function () {
alert(this.editorContent)
}
},
mounted() {
var editor = new E(this.$refs.editor)
editor.customConfig.onchange = (html) => {
this.editorContent = html
}
editor.create()
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,53 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<h2>Essential Links</h2>
<ul>
<li><a href="https://vuejs.org" target="_blank">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank">Forum</a></li>
<li><a href="https://gitter.im/vuejs/vue" target="_blank">Gitter Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank">Twitter</a></li>
<br>
<li><a href="http://vuejs-templates.github.io/webpack/" target="_blank">Docs for This Template</a></li>
</ul>
<h2>Ecosystem</h2>
<ul>
<li><a href="http://router.vuejs.org/" target="_blank">vue-router</a></li>
<li><a href="http://vuex.vuejs.org/" target="_blank">vuex</a></li>
<li><a href="http://vue-loader.vuejs.org/" target="_blank">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'hello',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@ -0,0 +1,13 @@
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
template: '<App/>',
components: { App }
})

View File

@ -0,0 +1,4 @@
require(['/wangEditor.min.js'], function (E) {
var editor2 = new E('#div3')
editor2.create()
})

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>wangEditor 使用 AMD 加载</title>
</head>
<body>
<p>wangEditor 使用 AMD 加载</p>
<div id="div3">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script data-main="./test-amd-main.js" src="//cdn.bootcss.com/require.js/2.3.3/require.js"></script>
</body>
</html>

View File

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>wangEditor css reset</title>
<style type="text/css">
/* reset 代码参考 https://meyerweb.com/eric/tools/css/reset/ */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
</style>
</head>
<body>
<p>wangEditor css reset</p>
<div id="div1">
<p>欢迎使用 <b>wangEditor</b> 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
editor.create()
</script>
</body>
</html>

View File

@ -0,0 +1,84 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>wangEditor 配置表情</title>
</head>
<body>
<p>wangEditor 配置表情</p>
<div id="div1">
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor = new E('#div1')
editor.customConfig.emotions = [
{
// tab 的标题
title: '默认',
// type -> 'emoji' / 'image'
type: 'image',
// content -> 数组
content: [
{
alt: '[坏笑]',
src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/50/pcmoren_huaixiao_org.png'
},
{
alt: '[舔屏]',
src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/40/pcmoren_tian_org.png'
},
{
alt: '[]',
src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/3c/pcmoren_wu_org.png'
},
{
alt: '[允悲]',
src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/2c/moren_yunbei_org.png'
}
]
},
{
// tab 的标题
title: '新浪',
// type -> 'emoji' / 'image'
type: 'image',
// content -> 数组
content: [
{
src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/7a/shenshou_thumb.gif',
alt: '[草泥马]'
},
{
src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/60/horse2_thumb.gif',
alt: '[神马]'
},
{
src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/bc/fuyun_thumb.gif',
alt: '[浮云]'
}
]
},
{
// tab 的标题
title: 'emoji',
// type -> 'emoji' / 'image'
type: 'emoji',
// content -> 数组
content: '😀 😃 😄 😁 😆 😅 😂 😊 😇 🙂 🙃 😉 😌 😍 😘 😗 😙 😚 😋 😜 😝 😛 🤑 🤗 🤓 😎 😏 😒 😞 😔 😟 😕 🙁 😣 😖 😫 😩 😤 😠 😡 😶 😐 😑 😯 😦 😧 😮 😲 😵 😳 😱 😨 😰 😢 😥 😭 😓 😪 😴 🙄 🤔 😬 🤐'.split(/\s/)
},
{
// tab 的标题
title: 'emoji手势',
// type -> 'emoji' / 'image'
type: 'emoji',
// content -> 数组
content: ['🙌', '👏', '👋', '👍', '👎', '👊', '✊', '👌', '✋', '👐', '💪', '🙏', '👆', '👇', '👈', '👉', '🖕', '🖐', '🤘']
}
]
editor.create()
</script>
</body>
</html>

View File

@ -0,0 +1,114 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>wangEditor 全屏</title>
<style type="text/css">
#container {
width: 800px;
margin: 0 auto;
}
#toolbar-container {
border: 1px solid #ccc;
background-color: #fff;
}
#toolbar-container:after {
display: table;
content: '';
clear: both;
}
#editor-toolbar {
float: left;
}
#btn-container {
float: right;
text-align: right;
}
#editor-text {
border: 1px solid #ccc;
border-top: 0;
height: 300px;
background-color: #fff;
}
#cover {
display: none;
position: fixed;
z-index: 100;
top: 50px;
left: 50px;
right: 50px;
height: 500px;
padding: 20px;
background-color: #f1f1f1;
}
</style>
</head>
<body>
<p>wangEditor 全屏</p>
<!--非全屏模式-->
<div id="container">
<!--菜单栏-->
<div id="toolbar-container">
<div id="editor-toolbar"></div>
<div id="btn-container">
<button id="btn1">全屏</button>
</div>
</div>
<!--编辑区域-->
<div id="editor-text">
<p>wangEditor 本身不包含全屏功能不过可以很简单的开发出来</p>
<p>注意全屏模式与<code>max-height</code>有冲突尽量避免一起使用</p>
</div>
</div>
<!--全屏模式-->
<div id="cover"></div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
// 创建编辑器
var E = window.wangEditor
var editor2 = new E('#editor-toolbar', '#editor-text')
editor2.create()
// 获取使用到的元素
var toolbarContaner = document.getElementById('toolbar-container')
var editorText = document.getElementById('editor-text')
var cover = document.getElementById('cover')
var container = document.getElementById('container')
// 全屏事件
function doFullScreen() {
cover.style.display = 'block'
editorText.style.height = '460px';
cover.appendChild(toolbarContaner)
cover.appendChild(editorText)
}
// 退出全屏事件
function unDoFullScreen() {
container.appendChild(toolbarContaner)
container.appendChild(editorText)
editorText.style.height = '300px';
cover.style.display = 'none'
}
// 是否是全屏的标志
var isFullScreen = false
// 点击事件
var btn1 = document.getElementById('btn1')
btn1.addEventListener('click', function () {
if (isFullScreen) {
isFullScreen = false
unDoFullScreen()
} else {
isFullScreen = true
doFullScreen()
}
}, false)
</script>
</body>
</html>

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>wangEditor 获取内容</title>
</head>
<body>
<p>wangEditor 获取内容</p>
<div id="div3">
<p>欢迎使用 wangEditor 富文本编辑器</p>
<p>欢迎使用 wangEditor 富文本编辑器</p>
</div>
<div>
<button id="btn1">获取html</button>
<button id="btn2">获取text</button>
</div>
<script type="text/javascript" src="/wangEditor.min.js"></script>
<script type="text/javascript">
var E = window.wangEditor
var editor2 = new E('#div3')
editor2.create()
document.getElementById('btn1').addEventListener('click', function () {
console.log(editor2.txt.html())
alert(editor2.txt.html())
}, false)
document.getElementById('btn2').addEventListener('click', function () {
alert(editor2.txt.text())
}, false)
</script>
</body>
</html>

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