From b9d82ae29c044394c7c29d81c7546359883207ea Mon Sep 17 00:00:00 2001 From: huangzj Date: Tue, 30 Jun 2020 16:16:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(Java=5FSpringExample):=20=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=B1=BB=E5=90=8D=E3=80=81@auther=E3=80=81=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=8E=A7=E5=88=B6=E5=B1=82=E6=B5=8B=E8=AF=95=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20CacheController=20=E7=BC=93=E5=AD=98=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20EncodedController=20=E7=BC=96?= =?UTF-8?q?=E7=A0=81=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20FreemarkerController?= =?UTF-8?q?=20=E6=A8=A1=E6=9D=BF=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20FullFillT?= =?UTF-8?q?imeValueController=20=E8=A1=A5=E6=97=B6=E9=97=B4=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20ITextController=20pdf=E7=94=9F=E6=88=90=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20LoginController=20=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20RedisControl?= =?UTF-8?q?ler=20redis=E6=93=8D=E4=BD=9C=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?RequestLimitController=20=E6=8E=A5=E5=8F=A3=E8=AE=BF=E9=97=AE?= =?UTF-8?q?=E9=A2=91=E7=8E=87=E6=8E=A7=E5=88=B6=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20?= =?UTF-8?q?=20=20WangEditorController=20WangEditor=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E5=99=A8=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Readme.md | 15 +- src/main/java/Aspect/AopTestClass.java | 13 +- src/main/java/Aspect/SensitiveAop.java | 5 +- .../FreeMarkerConfigurationAdapter.java | 52 +- src/main/java/annotation/InTest.java | 12 - .../java/config/ContextComponentLoader.java | 5 - src/main/java/config/MvcComponentLoader.java | 11 +- src/main/java/config/WebInitializer.java | 9 +- .../AnnoExceptionHandler.java | 15 +- .../java/config/response/ResponseObject.java | 2 +- ...MainController.java => AopController.java} | 10 +- src/main/java/controller/CacheController.java | 29 + .../java/controller/EncodedController.java | 49 + .../java/controller/FreemarkerController.java | 72 + .../FullFillTimeValueController.java | 73 + src/main/java/controller/ITextController.java | 104 + src/main/java/controller/LoginController.java | 66 + .../controller/PropertiesGetController.java | 4 +- src/main/java/controller/RedisController.java | 161 + .../controller/RequestLimitController.java | 37 + .../java/controller/WangEditorController.java | 112 + .../java/extention/AlarmTplConfiguration.java | 15 - src/main/java/filter/FilterDelegator.java | 1 + src/main/java/filter/LoginFilter.java | 99 + .../java/filter/WelcomeHandlingFilter.java | 2 - .../requestLimit/RequestLimitAspect.java | 2 - .../model/fullFillTime/OriginTimeValue.java | 41 + .../model/fullFillTime/TargetTimeValue.java | 30 + src/main/java/model/redis/RedisUser.java | 67 + src/main/java/model/tpl/TplMessage.java | 56 + .../{MainService.java => CommonService.java} | 6 +- ...erviceImpl.java => CommonServiceImpl.java} | 12 +- src/main/java/service/TplService.java | 24 + src/main/java/tool/WangEditorTool.java | 267 + src/main/java/util/Base64Util.java | 293 ++ .../java/util/itext/FillTemplateHelper.java | 196 + src/main/java/util/itext/PDFUtils.java | 175 + .../java/util/time/FillFullTimeValueUtil.java | 119 + src/main/resources/tpl/alarm.tpl | 3 + src/main/webapp/html/wangEditor.html | 83 + .../webapp/js/wangEdit/fonts/w-e-icon.woff | Bin 0 -> 5568 bytes src/main/webapp/js/wangEdit/wangEditor.css | 404 ++ src/main/webapp/js/wangEdit/wangEditor.js | 4340 +++++++++++++++++ .../webapp/js/wangEdit/wangEditor.min.css | 1 + src/main/webapp/js/wangEdit/wangEditor.min.js | 3 + .../webapp/js/wangEdit/wangEditor.min.js.map | 1 + 46 files changed, 6988 insertions(+), 108 deletions(-) delete mode 100644 src/main/java/annotation/InTest.java rename src/main/java/controller/{MainController.java => AopController.java} (80%) create mode 100644 src/main/java/controller/CacheController.java create mode 100644 src/main/java/controller/EncodedController.java create mode 100644 src/main/java/controller/FreemarkerController.java create mode 100644 src/main/java/controller/FullFillTimeValueController.java create mode 100644 src/main/java/controller/ITextController.java create mode 100644 src/main/java/controller/LoginController.java create mode 100644 src/main/java/controller/RedisController.java create mode 100644 src/main/java/controller/RequestLimitController.java create mode 100644 src/main/java/controller/WangEditorController.java delete mode 100644 src/main/java/extention/AlarmTplConfiguration.java create mode 100644 src/main/java/filter/LoginFilter.java create mode 100644 src/main/java/model/fullFillTime/OriginTimeValue.java create mode 100644 src/main/java/model/fullFillTime/TargetTimeValue.java create mode 100644 src/main/java/model/redis/RedisUser.java create mode 100644 src/main/java/model/tpl/TplMessage.java rename src/main/java/service/{MainService.java => CommonService.java} (70%) rename src/main/java/service/{MainServiceImpl.java => CommonServiceImpl.java} (54%) create mode 100644 src/main/java/service/TplService.java create mode 100644 src/main/java/tool/WangEditorTool.java create mode 100644 src/main/java/util/Base64Util.java create mode 100644 src/main/java/util/itext/FillTemplateHelper.java create mode 100644 src/main/java/util/itext/PDFUtils.java create mode 100644 src/main/java/util/time/FillFullTimeValueUtil.java create mode 100644 src/main/resources/tpl/alarm.tpl create mode 100644 src/main/webapp/html/wangEditor.html create mode 100644 src/main/webapp/js/wangEdit/fonts/w-e-icon.woff create mode 100644 src/main/webapp/js/wangEdit/wangEditor.css create mode 100644 src/main/webapp/js/wangEdit/wangEditor.js create mode 100644 src/main/webapp/js/wangEdit/wangEditor.min.css create mode 100644 src/main/webapp/js/wangEdit/wangEditor.min.js create mode 100644 src/main/webapp/js/wangEdit/wangEditor.min.js.map diff --git a/Readme.md b/Readme.md index 75c0974..29a06c8 100644 --- a/Readme.md +++ b/Readme.md @@ -8,4 +8,17 @@ 2020/5/29 : 增加Properties文件字段读取两种方式(propertiesScan),PostConstruct的使用、BeanPostProcessor在Bean初始化前后进行处理(init)、统一异常处理的两种方式(exceptionHandler)、统一出参报文处理(response) 新增上下文刷新判断处理类(refreshEvent)、添加HttpSession监听器(listener) -2020/6/3 :增加Servlet监听器启动Schedule定时任务(SchemeTaskListener) \ No newline at end of file +2020/6/3 :增加Servlet监听器启动Schedule定时任务(SchemeTaskListener) + +2020/6/30 : 新增WangEditor使用示例 + +2020/6/30 :优化类名、@auther、添加控制层测试 + CacheController 缓存 + EncodedController 编码 + FreemarkerController 模板 + FullFillTimeValueController 补时间 + ITextController pdf生成 + LoginController 登录控制 + RedisController redis操作 + RequestLimitController 接口访问频率控制 + WangEditorController WangEditor编辑器使用 \ No newline at end of file diff --git a/src/main/java/Aspect/AopTestClass.java b/src/main/java/Aspect/AopTestClass.java index 3a441ea..aa65c94 100644 --- a/src/main/java/Aspect/AopTestClass.java +++ b/src/main/java/Aspect/AopTestClass.java @@ -10,11 +10,11 @@ import org.aspectj.lang.annotation.*; */ @Aspect public class AopTestClass { - @Before("execution(* service.MainServiceImpl.*(..))") + @Before("execution(* service.CommonServiceImpl.*(..))") public void begin(){ System.out.println("表演开始"); } - @After("execution(* service.MainServiceImpl.*(..))") + @After("execution(* service.CommonServiceImpl.*(..))") public void end(){ System.out.println("表演结束"); } @@ -23,11 +23,11 @@ public class AopTestClass { * 通过这个声明来实现环绕通知,所谓的环绕通知就是说在在一个方法中规定了类运行几个状态直接的通知事件 * 可以这样声明来实现不用写一大串的代码 */ - @Pointcut("execution(* service.MainServiceImpl.*(..))") + @Pointcut("execution(* service.CommonServiceImpl.*(..))") public void perform(){} @Around("perform()") - public void AroundAop(ProceedingJoinPoint proceedingJoinPoint){ + public Object aroundAop(ProceedingJoinPoint proceedingJoinPoint){ try{ System.out.println("我在程序运行之前进行了通知"); //这个方法代表被切接口进行执行,上面的方法表示之前执行,下面的方法表示之后执行 @@ -37,17 +37,18 @@ public class AopTestClass { throwable.printStackTrace(); System.out.println("运行错误产生的aop通知"); } + return null; } /** * 这个是测试有传参的Aop接口环绕 * @param testNum 入参,根据方法决定入参的数量与类型 */ - @Pointcut("execution(* service.MainServiceImpl.testAopParam(int)) && args(testNum)") + @Pointcut("execution(* service.CommonServiceImpl.testAopParam(int)) && args(testNum)") public void testAopParams(int testNum){ } - @After("testAopParams(testNum)") + @After(value = "testAopParams(testNum)", argNames = "testNum") public void testHasParam(int testNum){ System.out.println(testNum); } diff --git a/src/main/java/Aspect/SensitiveAop.java b/src/main/java/Aspect/SensitiveAop.java index c7f2de4..4215ea4 100644 --- a/src/main/java/Aspect/SensitiveAop.java +++ b/src/main/java/Aspect/SensitiveAop.java @@ -29,6 +29,7 @@ public class SensitiveAop { */ @Around("execution(* controller.*.*(..)) && @annotation(sensitive)") public Object getClassMessage(ProceedingJoinPoint pjp, Sensitive sensitive) { - - } + //todo 这边需要怎么去处理,我还没想清楚 + return null; + } } diff --git a/src/main/java/adapter/FreeMarkerConfigurationAdapter.java b/src/main/java/adapter/FreeMarkerConfigurationAdapter.java index c1a276f..10dc5b4 100644 --- a/src/main/java/adapter/FreeMarkerConfigurationAdapter.java +++ b/src/main/java/adapter/FreeMarkerConfigurationAdapter.java @@ -36,19 +36,23 @@ import java.lang.reflect.InvocationTargetException; */ public class FreeMarkerConfigurationAdapter { - //配置器的类路径 + /** + * 配置器的类路径 + */ private String filePath; - //默认路径配置 - private final String defaultPath = "tpl"; - //编码配置 + /** + * 编码配置 + */ private String encoding; - //默认编码配置 - private final String defaultEncoding = "UTF-8"; - //错误解决类配置 + /** + * 错误解决类配置 + */ private TemplateExceptionHandler exceptionHandler; - //默认错误解决类配置 + /** + * 默认错误解决类配置 + */ private final TemplateExceptionHandler defaultExceptionHandler = TemplateExceptionHandler.RETHROW_HANDLER; public FreeMarkerConfigurationAdapter() { @@ -64,25 +68,21 @@ public class FreeMarkerConfigurationAdapter { * * 我们使用反射的机制,构造有参数的构造器,作为一个适配器,接受生成继承Configuration的子类对象

* 这里使用了反射的机制,可以了解一下 - * @param clazz - * @param - * @return - * @throws IOException - * @throws IllegalAccessException - * @throws InstantiationException - * @throws NoSuchMethodException - * @throws InvocationTargetException */ + @SuppressWarnings("unchecked") public T createConfiguration(Class clazz) throws IOException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { - Constructor constructor = clazz.getDeclaredConstructor(new Class[]{Version.class}); - constructor.setAccessible(true); //设置语法检查? + Constructor constructor = clazz.getDeclaredConstructor(Version.class); + //设置语法检查? + constructor.setAccessible(true); T t = (T) constructor.newInstance(Configuration.VERSION_2_3_22); - - // Configuration configuration = new Configuration(Configuration.VERSION_2_3_22); + //默认路径配置 + String defaultPath = "tpl"; Resource resource1 = new ClassPathResource(defaultPath); t.setDirectoryForTemplateLoading(resource1.getFile()); + //默认编码配置 + String defaultEncoding = "UTF-8"; t.setDefaultEncoding(defaultEncoding); t.setTemplateExceptionHandler(defaultExceptionHandler); if (StringUtils.isNotEmpty(filePath)) { @@ -98,16 +98,4 @@ public class FreeMarkerConfigurationAdapter { return t; } - - public void setFilePath(String filePath) { - this.filePath = filePath; - } - - public void setEncoding(String encoding) { - this.encoding = encoding; - } - - public void setExceptionHandler(TemplateExceptionHandler exceptionHandler) { - this.exceptionHandler = exceptionHandler; - } } diff --git a/src/main/java/annotation/InTest.java b/src/main/java/annotation/InTest.java deleted file mode 100644 index 42f759d..0000000 --- a/src/main/java/annotation/InTest.java +++ /dev/null @@ -1,12 +0,0 @@ - - -package annotation; - -import java.lang.annotation.*; - -@Retention(RetentionPolicy.RUNTIME) -@Target(value = {ElementType.METHOD,ElementType.FIELD}) -@Documented -public @interface InTest { - -} \ No newline at end of file diff --git a/src/main/java/config/ContextComponentLoader.java b/src/main/java/config/ContextComponentLoader.java index 568af76..be5be14 100644 --- a/src/main/java/config/ContextComponentLoader.java +++ b/src/main/java/config/ContextComponentLoader.java @@ -1,10 +1,8 @@ package config; -import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.mapper.MapperScannerConfigurer; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; @@ -47,9 +45,6 @@ import java.util.Properties; @ComponentScan public class ContextComponentLoader { - @Autowired - private SqlSessionFactory sqlSessionFactory; - /** * 配置数据源 */ diff --git a/src/main/java/config/MvcComponentLoader.java b/src/main/java/config/MvcComponentLoader.java index b2e6535..2330a5b 100644 --- a/src/main/java/config/MvcComponentLoader.java +++ b/src/main/java/config/MvcComponentLoader.java @@ -1,7 +1,6 @@ package config; import adapter.FreeMarkerConfigurationAdapter; -import extention.AlarmTplConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -18,6 +17,7 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver; import scheduler.JobScheduler; import scheduler.adapter.BaseJob; import scheduler.adapter.JobAdapter; +import service.TplService; import java.io.IOException; import java.lang.reflect.InvocationTargetException; @@ -27,6 +27,7 @@ import java.util.Properties; /** * Created by com on 2017/7/25. + * @author 志军 * 配置自动加载注解类 * * 没有代码配置出现的情况 @@ -168,19 +169,17 @@ public class MvcComponentLoader extends WebMvcConfigurerAdapter { * 1.为什么不使用配置的引用,而是新建一份配置,增加内存风险?答:配置信息如果被修改等,可能造成多线程并发问题。新建一份配置信息进行 * temple的生成可能造成内存风险,但是这里运用了cache的方式,减轻了这个风险 * Date: 2018/5/21/021 9:52 - * @return 返回类型描述 - * @throws Exception 异常说明 */ @Bean - public AlarmTplConfiguration AlarmTplConfiguration() throws IOException, InvocationTargetException, + public TplService AlarmTplConfiguration() throws IOException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { FreeMarkerConfigurationAdapter freeMarkerConfigurationAdapter = new FreeMarkerConfigurationAdapter(); - /** + /* * 这里的思路是:
* 1.类的有一些参数是可以使用默认设置的,我们这边给他一个使用默认设置的机会 * 2.类的有一些参数也可以是用户来配置的,所以说这里我们提供了一个设置参数的机会 */ - return freeMarkerConfigurationAdapter.createConfiguration(AlarmTplConfiguration.class); + return freeMarkerConfigurationAdapter.createConfiguration(TplService.class); } diff --git a/src/main/java/config/WebInitializer.java b/src/main/java/config/WebInitializer.java index 9a6480e..c6bf8fb 100644 --- a/src/main/java/config/WebInitializer.java +++ b/src/main/java/config/WebInitializer.java @@ -19,18 +19,17 @@ import filter.FilterDelegator; import filter.WelcomeHandlingFilter; /** + * * Created by com on 2017/7/25. *

- * 相当于web.xml的加载配置 - */ - -/*** + * 相当于web.xml的加载配置 + *

* FilterRegistration.Dynamic welcomePagFilter = container.addFilter("WelcomePagFilter", new WelcomePagFilter()); * welcomePagFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); * 上述是配置过滤器作用的请求. * 这里已经包装起来了 + * @author 志军 */ - public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { diff --git a/src/main/java/config/exceptionHandler/AnnoExceptionHandler.java b/src/main/java/config/exceptionHandler/AnnoExceptionHandler.java index 4b2bdea..4b29f73 100644 --- a/src/main/java/config/exceptionHandler/AnnoExceptionHandler.java +++ b/src/main/java/config/exceptionHandler/AnnoExceptionHandler.java @@ -8,19 +8,18 @@ import org.springframework.web.servlet.ModelAndView; public class AnnoExceptionHandler { /** - * @a - * @param exception - * @return + * 可以直接写@ExceptionHandler,不指明异常类,会自动映射 */ - @ExceptionHandler(CustomGenericException.class)//可以直接写@ExceptionHandler,不指明异常类,会自动映射 - public ModelAndView customGenericExceptionHnadler(CustomGenericException exception){ //还可以声明接收其他任意参数 + @ExceptionHandler(CustomGenericException.class) + public ModelAndView customGenericExceptionHnadler(CustomGenericException exception){ + //还可以声明接收其他任意参数 ModelAndView modelAndView = new ModelAndView("generic_error"); modelAndView.addObject("errCode",exception.getErrCode()); modelAndView.addObject("errMsg",exception.getErrMsg()); return modelAndView; } - @ExceptionHandler(Exception.class)//可以直接写@EceptionHandler,IOExeption继承于Exception + @ExceptionHandler(Exception.class) public ModelAndView allExceptionHandler(Exception exception){ ModelAndView modelAndView = new ModelAndView("generic_error"); modelAndView.addObject("errMsg", "this is Exception.class"); @@ -34,12 +33,12 @@ public class AnnoExceptionHandler { private String errCode; private String errMsg; - public String getErrCode() { + String getErrCode() { return errCode; } - public String getErrMsg() { + String getErrMsg() { return errMsg; } diff --git a/src/main/java/config/response/ResponseObject.java b/src/main/java/config/response/ResponseObject.java index 6fb780d..6462df1 100644 --- a/src/main/java/config/response/ResponseObject.java +++ b/src/main/java/config/response/ResponseObject.java @@ -41,7 +41,7 @@ public class ResponseObject { */ private T dataView; - public ResponseObject(String code, String message, T value) { + ResponseObject(String code, String message, T value) { this.status = code; this.message = message; this.dataView = value; diff --git a/src/main/java/controller/MainController.java b/src/main/java/controller/AopController.java similarity index 80% rename from src/main/java/controller/MainController.java rename to src/main/java/controller/AopController.java index da46938..f60b58f 100644 --- a/src/main/java/controller/MainController.java +++ b/src/main/java/controller/AopController.java @@ -4,22 +4,22 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; -import service.MainService; +import service.CommonService; @Controller -public class MainController { +public class AopController { @Autowired - private MainService mainService; + private CommonService commonService; @RequestMapping(value = "/test/aop/param", method = RequestMethod.GET) public void testAopParam(){ - mainService.testAopParam(1); + commonService.testAopParam(1); } @RequestMapping(value = "/test/aop", method = RequestMethod.GET) public void testAop(){ - mainService.testAop(); + commonService.testAop(); } @RequestMapping(value = "/test/aop/getClassMessage", method = RequestMethod.GET) diff --git a/src/main/java/controller/CacheController.java b/src/main/java/controller/CacheController.java new file mode 100644 index 0000000..be0a7e8 --- /dev/null +++ b/src/main/java/controller/CacheController.java @@ -0,0 +1,29 @@ +package controller; + + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import service.CommonService; + +/** + * @author selfimpr + * @description 测试cache缓存是否设置成功,可以在配置文件中指定位置看到cache,这边的配置是放在 + * @see config.CacheSetFromProperty + * @date : 2017/11/11 21:46 + */ +@Controller +public class CacheController { + + @Autowired + private CommonService commonService; + + @RequestMapping(value = "/cache/cache_test", method = RequestMethod.GET) + public String cacheTest() { + String cache = commonService.cacheTest(); + System.out.println(cache); + return "success"; + } + +} diff --git a/src/main/java/controller/EncodedController.java b/src/main/java/controller/EncodedController.java new file mode 100644 index 0000000..c6acb9e --- /dev/null +++ b/src/main/java/controller/EncodedController.java @@ -0,0 +1,49 @@ +/* + * @(#) EncodedTestController + * 版权声明 网宿科技, 版权所有 违者必究 + * + *
Copyright: Copyright (c) 2018 + *
Company:网宿科技 + *
@author Administrator + *
@description 功能描述 + *
2018-12-26 22:11:24 + */ + +package controller; + + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.servlet.http.HttpServletRequest; + +@Controller +@RequestMapping("/encoded/test/") +public class EncodedController { + + /** + * POST的方式,如果后端不设置过滤器去拦截请求并设置请求编码的话 + * 会根据Content-type的character来标识 + */ + + @RequestMapping(value = "/post-default") + public void postDefault(HttpServletRequest request){ + System.out.println(request.getCharacterEncoding()); + } + + @RequestMapping(value = "/post-utf8") + public void postUtf8(HttpServletRequest request){ + System.out.println(request.getCharacterEncoding()); + } + + + @RequestMapping(value = "/post-gbk") + public void postGbk(HttpServletRequest request){ + System.out.println(request.getCharacterEncoding()); + } + + @RequestMapping(value = "/get") + public void getGbk(HttpServletRequest request){ + System.out.println(request.getParameter("testName")); + } +} diff --git a/src/main/java/controller/FreemarkerController.java b/src/main/java/controller/FreemarkerController.java new file mode 100644 index 0000000..145253a --- /dev/null +++ b/src/main/java/controller/FreemarkerController.java @@ -0,0 +1,72 @@ +/* + * @(#) freemarkerController + * 版权声明 黄志军, 版权所有 违者必究 + * + *
Copyright: Copyright (c) 2018 + *
Company:黄志军 + *
@author selfImpr + *
2018-05-21 10:04:51 + *
@description + * + * + */ + +package controller; + +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import model.tpl.TplMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.freemarker.FreeMarkerConfigurationFactory; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; +import service.TplService; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + + + +@Controller +public class FreemarkerController { + + @Autowired + private TplService tplService; + + @RequestMapping("/freemaker") + private String freeMaker() throws IOException, TemplateException { + TplMessage tplMessage = new TplMessage(); + tplMessage.setIdentification("[mx1234567890]"); + DateFormat dateFormat = new SimpleDateFormat("yyyy年mm月dd HH:MM:ss"); + tplMessage.setTime(dateFormat.format(new Date())); + tplMessage.setPlatform("XXXX平台"); + tplMessage.setStatistic("123333"); + + Template temp = tplService.getTemplate("alarm.tpl"); + Writer out = new StringWriter(); + temp.process(tplMessage, out); + System.out.print(temp.toString()); + return ""; + } + + + @RequestMapping("springMaker") + public void springMarker() throws IOException, TemplateException { + FreeMarkerConfigurationFactory freeMarkerConfigurationFactory = new FreeMarkerConfigurationFactory(); + freeMarkerConfigurationFactory.setDefaultEncoding("UTF-8"); + Configuration configuration = freeMarkerConfigurationFactory.createConfiguration(); + + FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer(); + freeMarkerConfigurer.setConfiguration(configuration); + + } + +} + + diff --git a/src/main/java/controller/FullFillTimeValueController.java b/src/main/java/controller/FullFillTimeValueController.java new file mode 100644 index 0000000..2b29133 --- /dev/null +++ b/src/main/java/controller/FullFillTimeValueController.java @@ -0,0 +1,73 @@ +/* + * @(#) FullFillTimeValueController + * 版权声明 网宿科技, 版权所有 违者必究 + * + *
Copyright: Copyright (c) 2018 + *
Company:网宿科技 + *
@author Administrator + *
@description 功能描述 + *
2018-11-24 01:14:40 + */ + +package controller; + +import com.google.common.collect.Lists; +import model.fullFillTime.OriginTimeValue; +import model.fullFillTime.TargetTimeValue; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import util.time.FillFullTimeValueUtil; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.TimeZone; + +/** + * @author 志军 + * 测试补时间点 + */ +@Controller +public class FullFillTimeValueController { + + + @RequestMapping(value = "/time-test") + public void fullFillTimeValue() { + List originTimeValueList = Lists.newArrayList(); + originTimeValueList.add(new OriginTimeValue(1542995383, 109991)); + + List targetTimeValues1 = FillFullTimeValueUtil.MINUTE.fullFillTimeValue(LocalDateTime.now(), LocalDateTime.now() + .plusDays(1), 1, originTimeValueList, + TimeZone.getDefault()); + + + List targetTimeValues2 = FillFullTimeValueUtil.HOUR.fullFillTimeValue(LocalDateTime.now(), LocalDateTime.now() + .plusDays(1), 2, originTimeValueList, + TimeZone.getDefault()); + + List targetTimeValues3 = FillFullTimeValueUtil.DAY.fullFillTimeValue(LocalDateTime.now(), LocalDateTime.now() + .plusDays(100), 10, originTimeValueList, + TimeZone.getDefault()); + + List targetTimeValues4 = FillFullTimeValueUtil.MINUTE.fullFillTimeValue(LocalDateTime.now(), LocalDateTime.now() + .plusDays(1), 1, null, + TimeZone.getDefault()); + + + targetTimeValues1.forEach(targetTimeValue -> { + System.out.println(targetTimeValue.getTime() + " , " + targetTimeValue.getValue()); + }); + + System.out.println("---------------------------------------------------------------------"); + + targetTimeValues2.forEach(targetTimeValue -> { + System.out.println(targetTimeValue.getTime() + " , " + targetTimeValue.getValue()); + }); + + System.out.println("---------------------------------------------------------------------"); + + targetTimeValues3.forEach(targetTimeValue -> { + System.out.println(targetTimeValue.getTime() + " , " + targetTimeValue.getValue()); + }); + + } +} diff --git a/src/main/java/controller/ITextController.java b/src/main/java/controller/ITextController.java new file mode 100644 index 0000000..edf081b --- /dev/null +++ b/src/main/java/controller/ITextController.java @@ -0,0 +1,104 @@ +package controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import util.itext.PDFUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; + +@Controller +public class ITextController { + + //itext 生成pdf的 工具类 + private PDFUtils pdfUtils = new PDFUtils(); + + /** 将一个html转移成String + String htmlString = StringEscapeUtils.unescapeHtml(html); + * 参数介绍: + * html:从页面获取的html信息(获取到的html是没有标签的,需要后期加上) + * cssName:从页面获取的css的名字 + * cssPath:css对应的绝对地址 + * htmlReadyToPdf:将html的标签加上,补充完整的html文件 + * waterMarkImage:水印的图片的名字 + * waterMarkImagePath:水印图片地址 + * templePdfname:pdf模板的名字 + * templePdfPath:pdf模板的地址 + * + * pdfFileDirs:存放pdf文件的路径 + * pdfFileSavePath:生成pdf文件存储路径的文件 + * + * pdfFileName:没有水印之前的pdf名字 + * pdfFilePath:没有水印的pdf的路径 + * pdfFile:没有水印的pdf文件 + * pdfFileWithWaterMarkName:加了水印的pdf文件名 + * pdfFileWithWaterMarkPath:加了水印的pdf的地址 + * pdfFileWithWaterMark:加了水印的pdf的文件 + * + * programPath:工程地址 + * + * responseUrl:返回到页面的工程地址 + **/ + @RequestMapping(value = "/ITextTest", method = RequestMethod.POST) + public void itextTest(HttpServletResponse response, HttpServletRequest request, @RequestBody String body) throws Exception { + + String html = request.getParameter("html"); + String cssName = request.getParameter("cssName"); + String htmlReadyToPdf = pdfUtils.transToHtml(html); + String waterMarkImage = "joint_theme_pdf_stamp.png"; + String templePdfname = "joint_theme_pdf_template.pdf"; + String pdfFileName = new SimpleDateFormat("YYYYMMddHHmmss").format(new Date()) + "temporary"; + String pdfFileWithWaterMarkName = new SimpleDateFormat("YYYYMMddHHmmss").format(new Date()); + + //工程地址 + String programPath = request.getSession().getServletContext().getRealPath(""); + + //生成css的绝对地址 + String cssPath = (programPath + "css\\" + cssName).replace("\\", System.getProperty("file.separator")); + + //生成水印的地址 + String waterMarkImagePath = (programPath + "WEB-INF\\pdf\\" + waterMarkImage).replace("\\", System.getProperty("file.separator")); + + //生成模板地址 + String templePdfPath = (programPath + "WEB-INF\\pdf\\" + templePdfname).replace("\\", System.getProperty("file.separator")); + + //生成存放PDF的地址 + String pdfFileDirs = programPath + "pdf" + System.getProperty("file.separator") + new SimpleDateFormat("YYYYMMdd").format(new Date()); + File pdfFileSavePath = new File(pdfFileDirs); + if (!pdfFileSavePath.exists()) { + pdfFileSavePath.mkdirs(); + } + + //生成临时pdf(没有水印)和有水印的pdf的路径 + String pdfFilePath = pdfFileDirs + System.getProperty("file.separator") + pdfFileName + ".pdf"; + String pdfFileWithWaterMarkPath = pdfFileDirs + System.getProperty("file.separator") + pdfFileWithWaterMarkName + ".pdf"; + + //获取在js中显示的pdf地址 + String responseUrl = ("pdf" + "\\" + new SimpleDateFormat("YYYYMMdd").format(new Date()) + "\\" + pdfFileWithWaterMarkName + ".pdf").replace("\\", "/"); + + //生成临时pdf和最终pdf的文件 + File pdfFile = new File(pdfFilePath); + File pdfFileWithWaterMark = new File(pdfFileWithWaterMarkPath); + + //调用PDFUtils生成pdf文件 + PDFUtils.generalPDF(templePdfPath, pdfFilePath, cssPath, htmlReadyToPdf); + + + //进行水印操作 + PDFUtils.sealDocument(pdfFilePath, pdfFileWithWaterMarkPath, waterMarkImagePath, "小胖子是谁:陈毅聪啊", "出具时间", ""); + //把临时pdf删掉 + pdfFile.delete(); + + PrintWriter writer = response.getWriter(); + //输出产生的PDF地址 + writer.write(responseUrl); + writer.flush(); + writer.close(); + } +} diff --git a/src/main/java/controller/LoginController.java b/src/main/java/controller/LoginController.java new file mode 100644 index 0000000..55a2b76 --- /dev/null +++ b/src/main/java/controller/LoginController.java @@ -0,0 +1,66 @@ +package controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +@Controller +public class LoginController { + @RequestMapping("/login/index") + public String loginIndex() { + return "login"; + } + + @RequestMapping("login/loginValidate") + public String login(HttpServletResponse response, HttpServletRequest request) { + /** + * 这里有一个更佳的设置用户未操作的过期时间 + * 1.即在session创建的时候把session的创建时间放在全局里面 + * 2.过滤器过滤浏览器请求时,判断session的创建时间是不是在指定的范围内 + * 如果是的话,就更新这个时间 + * 如果不是的话就清空跳转到登陆页面 + */ + request.getSession().setAttribute("sessionTime", request.getSession().getCreationTime()); + HttpSession session = request.getSession(); + ServletContext servletContext = session.getServletContext(); + String name = request.getParameter("name"); + String password = request.getParameter("password"); + /** + * 如何实现一个用户登陆,然后把上一个用户踢掉 + * 1.java后端可以通过servletContext来保存用户的登陆的会话信息 + * 2.当第二个用户进行登陆的时候把第一个用户的session清空 + * 3.在前台设置一个轮询的操作,判断session是否为空。 + * + * 这个测试要联合login.html,loginTest.html来调试,还有登陆过滤器。 + * 还是不知道怎么把已经登陆的用户挤掉???不可能在每一个页面都设置一个函数来轮询监听后台的事件 + * 除非用户刷新页面,或者发送请求。 + */ + String newSessionId = session.getId(); + //不管用户账号密码对不对都让他登陆 + session.setAttribute("name", name); + if (servletContext.getAttribute(name) != null) { + String oldSessionId = String.valueOf(servletContext.getAttribute(name)); + HttpSession oldSession = (HttpSession) servletContext.getAttribute(oldSessionId); + if (!oldSessionId.equals(newSessionId)) { + oldSession.invalidate();//清空之前的session信息 + servletContext.setAttribute(name, newSessionId); + servletContext.setAttribute(newSessionId, session); + } + } else { + servletContext.setAttribute(name, newSessionId); + servletContext.setAttribute(newSessionId, session); + } + + return "loginTest"; + + } + + @RequestMapping("/login/test.do") + public String loginTest() { + return "loginTest"; + } +} diff --git a/src/main/java/controller/PropertiesGetController.java b/src/main/java/controller/PropertiesGetController.java index ab3f1d2..aaee24a 100644 --- a/src/main/java/controller/PropertiesGetController.java +++ b/src/main/java/controller/PropertiesGetController.java @@ -6,8 +6,8 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** - * @author selfimpr - * @description 测试SpringConfig利用注解来实现properties配置文件的读取 + * @author 志军 + * @description 测试SpringCofig利用注解来实现properties配置文件的读取 * @date 2017/11/11 21:26 */ diff --git a/src/main/java/controller/RedisController.java b/src/main/java/controller/RedisController.java new file mode 100644 index 0000000..42d1014 --- /dev/null +++ b/src/main/java/controller/RedisController.java @@ -0,0 +1,161 @@ +/* + * @(#) RedisController + * 版权声明 黄志军, 版权所有 违者必究 + * + *
Copyright: Copyright (c) 2018 + *
Company:黄志军 + *
@author selfImpr + *
2018-06-22 09:16:02 + *
@description + * + * + */ + +package controller; + +import model.redis.RedisUser; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * 为了方便使用,直接把到层的操作放在这里面 + */ + @Controller +public class RedisController { + + @Resource(name = "redisTemplate") + private RedisTemplate redisTemplate; + + + /** + * 非复杂对象的操作 + * 参考地址: http://www.cnblogs.com/qlqwjy/p/8562703.html + */ + + @RequestMapping(value = "/redis/simple", method = RequestMethod.GET) + public String simpleOperation() { + //String操作 + redisTemplate.opsForValue().set("myStr", "skyLine"); + redisTemplate.delete("myStr"); + redisTemplate.opsForValue().set("myStr", "skyLine"); + System.out.println(redisTemplate.opsForValue().get("myStr")); + System.out.println("---------------"); + // List读写 + redisTemplate.delete("myList"); + redisTemplate.opsForList().rightPush("myList", "T"); + redisTemplate.opsForList().rightPush("myList", "L"); + redisTemplate.opsForList().leftPush("myList", "A"); + List listCache = redisTemplate.opsForList().range("myList", 0, -1); + for (String s : listCache) { + System.out.println(s); + } + System.out.println("---------------"); + // Set读写 + redisTemplate.delete("mySet"); + redisTemplate.opsForSet().add("mySet", "A"); + redisTemplate.opsForSet().add("mySet", "B"); + redisTemplate.opsForSet().add("mySet", "C"); + Set setCache = redisTemplate.opsForSet().members("mySet"); + for (String s : setCache) { + System.out.println(s); + } + System.out.println("---------------"); + + // Hash读写 + redisTemplate.delete("myHash"); + redisTemplate.opsForHash().put("myHash", "BJ", "北京"); + redisTemplate.opsForHash().put("myHash", "SH", "上海"); + redisTemplate.opsForHash().put("myHash", "HN", "河南"); + Map hashCache = redisTemplate.opsForHash().entries("myHash"); + for (Map.Entry entry : hashCache.entrySet()) { + System.out.println(entry.getKey() + " - " + entry.getValue()); + } + System.out.println("---------------"); + return ""; + } + + //操作复杂的数据类型:自定义的数据类型 + @RequestMapping(value = "/redis/complex", method = RequestMethod.GET) + public void conplexOperation() { + //新增两个用户 + RedisUser user = new RedisUser(); + user.setId("aaaaaa"); + user.setSex("男"); + user.setUserName("小胖子"); + user.setAge(25); + RedisUser user1 = new RedisUser(); + user1.setId("bbbbbb"); + user1.setSex("男"); + user1.setUserName("大胖子"); + user1.setAge(50); + + add(user); + add(user1); + delete(user.getId()); + user1.setAge(100); + update(user1); + + RedisUser user2 = get(user1.getId()); + System.out.print("获取到的对象信息" + user2.toString()); + } + + + public boolean add(final RedisUser user) { + return (boolean) redisTemplate.execute((RedisCallback) connection -> { + ValueOperations valueOper = redisTemplate.opsForValue(); + valueOper.set(user.getId(), user); + return true; + }, false, true); + } + + public boolean batchAdd(final List users) { + return (boolean) redisTemplate.execute((RedisCallback) connection -> { + ValueOperations valueOper = redisTemplate.opsForValue(); + for (RedisUser user : users) { + valueOper.set(user.getId(), user); + } + return true; + }, false, true); + } + + public void delete(String key) { + List list = new ArrayList<>(); + list.add(key); + delete(list); + } + + public void delete(List keys) { + redisTemplate.delete(keys); + } + + public boolean update(final RedisUser user) { + String id = user.getId(); + if (get(id) == null) { + throw new NullPointerException("数据行不存在, key = " + id); + } + return (boolean) redisTemplate.execute((RedisCallback) connection -> { + ValueOperations valueOper = redisTemplate.opsForValue(); + valueOper.set(user.getId(), user); + return true; + }); + } + + public RedisUser get(final String keyId) { + return (RedisUser) redisTemplate.execute((RedisCallback) connection -> { + ValueOperations operations = redisTemplate.opsForValue(); + RedisUser user = (RedisUser) operations.get(keyId); + return user; + }); + } +} diff --git a/src/main/java/controller/RequestLimitController.java b/src/main/java/controller/RequestLimitController.java new file mode 100644 index 0000000..58c0c7e --- /dev/null +++ b/src/main/java/controller/RequestLimitController.java @@ -0,0 +1,37 @@ +/* + * @(#) RequestLimitController + * 版权声明 网宿科技, 版权所有 违者必究 + * + *
Copyright: Copyright (c) 2018 + *
Company:网宿科技 + *
@author huangzj1 + *
@description 功能描述 + *
2018-12-16 17:27:24 + */ + +package controller; + +import logic.requestLimit.RequestLimit; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletRequest; + +/** + * ${DESCRIPTION} + * + * @author huangzj1 + * @date 2018-12-16 17:27 + */ +@Controller +public class RequestLimitController { + + @RequestMapping(value = "/request/limit") + @RequestLimit(time = 55, count = 2, waits = 100) + @ResponseBody + public String requestLimit(HttpServletRequest request) { + System.out.println("我被打扰了"); + return "我被打扰了"; + } +} diff --git a/src/main/java/controller/WangEditorController.java b/src/main/java/controller/WangEditorController.java new file mode 100644 index 0000000..17caa86 --- /dev/null +++ b/src/main/java/controller/WangEditorController.java @@ -0,0 +1,112 @@ +package controller; + + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.apache.commons.fileupload.FileUploadException; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import tool.WangEditorTool; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; + + +/** + * @author 志军 + * 用来测试WangEditor插件 + */ + +@Controller +@RequestMapping("") +public class WangEditorController { + + @RequestMapping(value = "/Editor/index",method = RequestMethod.GET) + public String index(){ + return "wangEditor"; + } + + + @RequestMapping("/Editor/updateImage") + public void updateImage(HttpServletRequest request, HttpServletResponse response) throws FileUploadException, InterruptedException, IOException { + WangEditorTool tool = new WangEditorTool(request); + tool.execRequest(); + String fileName = "http://localhost:8033/Spring/" + tool.getMap().get("FilePath").toString().substring(78); + + String href = "


"; + System.out.print(href); + + //直接返回会报错,因为富文本不接受这样格式的报文返回 + // sendResponse(response,href); + sendResponseJson(response,href); + System.out.println("富文本图片进来了"); + } + + @RequestMapping("/Editor/sendText") + public void sendText(HttpServletRequest request, HttpServletResponse response, @RequestBody String body) throws UnsupportedEncodingException { + + //通过过在MvcComponentLoder配置了一个编码过滤器,所以这里解析是正常的。我们可以把这个东西保存到数据库 + System.out.println(request.getParameter("data")); + + System.out.println("富文本上传数据进来了"); + } + + @RequestMapping("/Editor/getDataAndShow") + public void getDataAndShow(HttpServletResponse response,HttpServletRequest request){ + String data = "

欢迎使用 wangEditor 富文本编辑器


"; + + sendResponse(response,data); + + } + + /** + * 发送数据到前端 -- 通过设置response的编码保证正确的返回 + */ + public void sendResponse(HttpServletResponse response,String data) { + response.setCharacterEncoding("UTF-8"); + PrintWriter writer = null; + try { + writer = response.getWriter(); + writer.print(data); + }catch(Exception ex){ + //logger输出堆栈信息 + }finally{ + assert writer != null; + writer.flush(); + writer.close(); + } + + } + + /** + * 发送数据到前端 -- 通过设置response的编码保证正确的返回 + */ + public void sendResponseJson(HttpServletResponse response,String data) { + response.setCharacterEncoding("UTF-8"); + response.setContentType("application/json"); + + JSONObject jsonObject = new JSONObject(); + jsonObject.put("errno","0"); + JSONArray jsonArray = new JSONArray(); + jsonArray.add(data); + jsonObject.put("data",jsonArray); + PrintWriter writer = null; + try { + writer = response.getWriter(); + + writer.print(jsonObject); + } catch(Exception ex){ + ex.printStackTrace(); + }finally{ + assert writer != null; + writer.flush(); + writer.close(); + } + + } +} diff --git a/src/main/java/extention/AlarmTplConfiguration.java b/src/main/java/extention/AlarmTplConfiguration.java deleted file mode 100644 index b465ffb..0000000 --- a/src/main/java/extention/AlarmTplConfiguration.java +++ /dev/null @@ -1,15 +0,0 @@ - - -package extention; - -import freemarker.template.Configuration; -import freemarker.template.Version; - -public class AlarmTplConfiguration extends Configuration { - public AlarmTplConfiguration() { - } - - public AlarmTplConfiguration(Version incompatibleImprovements) { - super(incompatibleImprovements); - } -} diff --git a/src/main/java/filter/FilterDelegator.java b/src/main/java/filter/FilterDelegator.java index 8c96c2f..e5acdf9 100644 --- a/src/main/java/filter/FilterDelegator.java +++ b/src/main/java/filter/FilterDelegator.java @@ -4,6 +4,7 @@ import javax.servlet.Filter; /** * 过滤器委托.(过滤器类) + * @author 志军 * Created by com on 2017/8/21. */ public class FilterDelegator { diff --git a/src/main/java/filter/LoginFilter.java b/src/main/java/filter/LoginFilter.java new file mode 100644 index 0000000..6e71f93 --- /dev/null +++ b/src/main/java/filter/LoginFilter.java @@ -0,0 +1,99 @@ +package filter; + + +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; +import util.MessageUtils; + +import javax.servlet.*; +import javax.servlet.annotation.WebFilter; +import javax.servlet.annotation.WebInitParam; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; + +/** + * 这里有一个更佳的设置用户未操作的过期时间 + * 1.即在session创建的时候把session的创建时间放在全局里面 (应该是登陆时间) + * 2.过滤器过滤浏览器请求时,判断session的创建时间是不是在指定的范围内 + * 如果是的话,就更新这个时间 + * 如果不是的话就清空跳转到登陆页面 + * + * 当这个未过期的登陆时间判断通过以后,去过滤器链发送请求会再经过这个过滤器,更新一次时间,不过这是没关系的啊。 + * 只做do请求的拦截 + * + * 所有的测试请求都在 LoginController 中,与本工程其他无关 + */ +@WebFilter(urlPatterns = "*.do", initParams = {@WebInitParam(name = "EXCLUDED_PAGES", value = "/login;/login/index")}) +public class LoginFilter implements Filter { + + private Logger logger = Logger.getLogger(LoginFilter.class); + + @Override + public void init(FilterConfig filterConfig) { + System.out.println("登陆验证过滤器"); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + HttpServletRequest request1 = (HttpServletRequest) request; + HttpServletResponse response1 = (HttpServletResponse) response; + HttpSession session = request1.getSession(); + String url = request1.getRequestURI(); + String name = (String) session.getAttribute("name"); + String password = request1.getParameter("password"); + String userCode = request1.getRemoteUser(); + String userRole = (String) session.getAttribute("role"); + System.out.println(password + " " + userCode + " " + userRole ); + //登录界面展示不拦截,直接放过(其他界面需要验证登录者的信息) + if (url.contains("/login/index") || url.contains("/login/loginValidate")) { + chain.doFilter(request, response); + logger.debug("登陆请求不验证拦劫 "); + return; + } + + String processUrl = buildWelcomeRequestUrl(request1); + + //如果用户名是空,说明没有登录信息,需要重定向到登录界面重新登录 + if (StringUtils.isBlank(name)) { + response1.sendRedirect(processUrl + "/login/index"); + return; + } + long createTime = Long.parseLong(String.valueOf(session.getAttribute("sessionTime"))); + long nowTime = Long.parseLong(String.valueOf(System.currentTimeMillis())); + + /* + 判断session是否过期,这边是需要把过期的用户强制退出 + 设置不操作的超时时间 --比如10秒 -- 这里设置时间久一点,是为了测试后一个用户登陆把上一个用户挤掉的例子 + */ + long overTime = 5000000; + if (nowTime - createTime > overTime) { + logger.debug("session过期" + (nowTime - createTime)); + session.invalidate();//清除session信息? + response1.sendRedirect(processUrl + "/login/index"); + } else { + session.setAttribute("sessionTime", nowTime); + logger.debug("session没有过期"); + chain.doFilter(request, response); + } + } + + + private String buildWelcomeRequestUrl(HttpServletRequest request) { + String pattern = "{0}://{1}:{2}{3}"; + String scheme = request.getScheme(); + String serverName = request.getServerName(); + String serverPort = String.valueOf(request.getServerPort()); + String contextPath = org.springframework.util.StringUtils.isEmpty(request.getContextPath()) ? "" : request.getContextPath(); + return MessageUtils.format(pattern, scheme, serverName, serverPort, contextPath); + } + + @Override + public void destroy() { + + } + +} + + diff --git a/src/main/java/filter/WelcomeHandlingFilter.java b/src/main/java/filter/WelcomeHandlingFilter.java index 3ad39d6..8aaeea4 100644 --- a/src/main/java/filter/WelcomeHandlingFilter.java +++ b/src/main/java/filter/WelcomeHandlingFilter.java @@ -14,8 +14,6 @@ import java.io.IOException; /** * 处理Welcome请求路径的过滤器:在Welcome请求路径的末尾添加/,解决Servlet 3.0+不能以编程方式来配置Welcome页面的问题。 - * - * @author Kison 2017年7月6日 */ public class WelcomeHandlingFilter implements Filter { diff --git a/src/main/java/logic/requestLimit/RequestLimitAspect.java b/src/main/java/logic/requestLimit/RequestLimitAspect.java index aecd485..b0d6990 100644 --- a/src/main/java/logic/requestLimit/RequestLimitAspect.java +++ b/src/main/java/logic/requestLimit/RequestLimitAspect.java @@ -52,8 +52,6 @@ public class RequestLimitAspect { if (body.getDelayClickUtilTIme() != null && DateTimeUtil.getNowSeconds() - body.getDelayClickUtilTIme() <= limit.waits()) { System.out.println("超过点击次数,还需要等待" + (limit.waits() - (DateTimeUtil.getNowSeconds() - body.getDelayClickUtilTIme())) + "秒"); throw new RequestLimitException("超过点击次数,还需要等待" + (limit.waits() - (DateTimeUtil.getNowSeconds() - body.getDelayClickUtilTIme())) + "秒"); - }else{ - } //如果最后一次点击在规定时间之内并且点击次数超过规定点击次数.进入延迟点击 diff --git a/src/main/java/model/fullFillTime/OriginTimeValue.java b/src/main/java/model/fullFillTime/OriginTimeValue.java new file mode 100644 index 0000000..9a7d7d6 --- /dev/null +++ b/src/main/java/model/fullFillTime/OriginTimeValue.java @@ -0,0 +1,41 @@ + + +package model.fullFillTime; + +/** + * 原始的时间点 + * @author huangzj + */ +public class OriginTimeValue { + + /** + * 时间 + */ + private long time; + + /** + * 数值 + */ + private long value; + + public OriginTimeValue(long time, long value) { + this.time = time; + this.value = value; + } + + public long getTime() { + return time; + } + + public void setTime(long time) { + this.time = time; + } + + public long getValue() { + return value; + } + + public void setValue(long value) { + this.value = value; + } +} diff --git a/src/main/java/model/fullFillTime/TargetTimeValue.java b/src/main/java/model/fullFillTime/TargetTimeValue.java new file mode 100644 index 0000000..3b39941 --- /dev/null +++ b/src/main/java/model/fullFillTime/TargetTimeValue.java @@ -0,0 +1,30 @@ + + +package model.fullFillTime; + +/** + * 补时间点后的对象 + */ +public class TargetTimeValue { + private long time; + /** + * 统计值,其中null值表示没有数据 + */ + private Long value; + + public void setTime(long time) { + this.time = time; + } + + public void setValue(Long value) { + this.value = value; + } + + public long getTime() { + return time; + } + + public Long getValue() { + return value; + } +} diff --git a/src/main/java/model/redis/RedisUser.java b/src/main/java/model/redis/RedisUser.java new file mode 100644 index 0000000..226a428 --- /dev/null +++ b/src/main/java/model/redis/RedisUser.java @@ -0,0 +1,67 @@ +/* + * @(#) RedisUser + * 版权声明 网宿科技, 版权所有 违者必究 + * + *
Copyright: Copyright (c) 2018 + *
Company:网宿科技 + *
@author Administrator + *
@description 功能描述 + *
2018-11-13 20:42:05 + */ + +package model.redis; + +import java.io.Serializable; + +public class RedisUser implements Serializable { + + /** + * 这个是用来测试redis存储复杂对象方法的对象 + */ + private static final long serialVersionUID = -5244288298702801619L; + + private String id; + + private String userName; + + private String sex; + + private int age; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getSex() { + return sex; + } + + public void setSex(String sex) { + this.sex = sex; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + @Override + public String toString() { + return "User [id=" + id + ", userName=" + userName + ", sex=" + sex + ", age=" + age + "]"; + } +} diff --git a/src/main/java/model/tpl/TplMessage.java b/src/main/java/model/tpl/TplMessage.java new file mode 100644 index 0000000..8f7b386 --- /dev/null +++ b/src/main/java/model/tpl/TplMessage.java @@ -0,0 +1,56 @@ +/* + * @(#) TplMessage + * 版权声明 网宿科技, 版权所有 违者必究 + * + *
Copyright: Copyright (c) 2018 + *
Company:网宿科技 + *
@author Administrator + *
@description 功能描述 + *
2018-11-13 20:42:05 + */ + +package model.tpl; + +public class TplMessage { + + + private String identification; + + private String time; + + private String platform; + + private String statistic; + + public String getIdentification() { + return identification; + } + + public void setIdentification(String identification) { + this.identification = identification; + } + + public String getTime() { + return time; + } + + public void setTime(String time) { + this.time = time; + } + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + public String getStatistic() { + return statistic; + } + + public void setStatistic(String statistic) { + this.statistic = statistic; + } +} diff --git a/src/main/java/service/MainService.java b/src/main/java/service/CommonService.java similarity index 70% rename from src/main/java/service/MainService.java rename to src/main/java/service/CommonService.java index 59e63f2..b2ffe2d 100644 --- a/src/main/java/service/MainService.java +++ b/src/main/java/service/CommonService.java @@ -4,10 +4,14 @@ package service; * Created by Administrator on 2017/7/18. * @author 志军 */ - public interface MainService { + public interface CommonService { String testAopParam(int a); void testAop(); + + + String cacheTest(); + } diff --git a/src/main/java/service/MainServiceImpl.java b/src/main/java/service/CommonServiceImpl.java similarity index 54% rename from src/main/java/service/MainServiceImpl.java rename to src/main/java/service/CommonServiceImpl.java index 21e1f0f..720eee5 100644 --- a/src/main/java/service/MainServiceImpl.java +++ b/src/main/java/service/CommonServiceImpl.java @@ -1,7 +1,7 @@ package service; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; /** @@ -9,8 +9,7 @@ import org.springframework.transaction.annotation.Transactional; * @author 志军 */ @Service -@Transactional -public class MainServiceImpl implements MainService { +public class CommonServiceImpl implements CommonService { @Override @@ -23,4 +22,11 @@ public class MainServiceImpl implements MainService { System.out.println("测试Aop"); } + + @Override + @Cacheable(cacheNames = "bm") + public String cacheTest() { + System.out.println("我是cache,我执行了"); + return "123444"; + } } \ No newline at end of file diff --git a/src/main/java/service/TplService.java b/src/main/java/service/TplService.java new file mode 100644 index 0000000..b6081d0 --- /dev/null +++ b/src/main/java/service/TplService.java @@ -0,0 +1,24 @@ +/* + * @(#) AlarmTplConfiguration + * 版权声明 网宿科技, 版权所有 违者必究 + * + *
Copyright: Copyright (c) 2018 + *
Company:网宿科技 + *
@author Administrator + *
@description 功能描述 + *
2018-11-13 20:34:52 + */ + +package service; + +import freemarker.template.Configuration; +import freemarker.template.Version; + +public class TplService extends Configuration { + public TplService() { + } + + public TplService(Version incompatibleImprovements) { + super(incompatibleImprovements); + } +} diff --git a/src/main/java/tool/WangEditorTool.java b/src/main/java/tool/WangEditorTool.java new file mode 100644 index 0000000..e9b8a35 --- /dev/null +++ b/src/main/java/tool/WangEditorTool.java @@ -0,0 +1,267 @@ +package tool; + +import org.apache.commons.fileupload.FileItemIterator; +import org.apache.commons.fileupload.FileItemStream; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.fileupload.util.Streams; +import util.Base64Util; + +import javax.servlet.http.HttpServletRequest; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * @author 志军 + * 配合Wangeditor一起处理的工具类 + */ +public class WangEditorTool { + //对request请求进行保存 + private HttpServletRequest request; + //保存文件名称 + private String fileName; + //保存文件的后缀 + private String suffix; + //保存完整的文件名 + private String completeFileName; + //用来存储FileItemStream的信息,看最后这些东西是什么 + private ArrayList fileItemStreamNames = new ArrayList(); + //用来保存传过来的参数? + private HashMap map = new HashMap(); + //用来缓存文件流的 --发现说流是缓存不下来的,因为流一旦读取完毕就是close状态 + private InputStream inputStream; + //通过list来缓存byte,因为在文件读取的时候这个byte大小可能不一定全是那么大就会大致,写入失败 + private List byteList = new ArrayList(); + + + public WangEditorTool() { + } + + public WangEditorTool(boolean b) { + if (!b) { + this.completeFileName = "我怀疑你是一个小胖子"; + } + } + + public WangEditorTool(HttpServletRequest request) { + System.out.println(request.getParameter("name")); + System.out.println(request.getRequestURI()); + this.request = request; + + } + + //参考地址:https://commons.apache.org/proper/commons-fileupload/streaming.html + /** + * 通过spring提供的类来实现request的解析,读取他的流和流中对应的数据 + * 请注意field和非field的区别 + */ + public WangEditorTool execRequest() throws FileUploadException, IOException, InterruptedException { + + //判断是不是ajax请求? + boolean isAjaxUpload = request.getHeader("X_Requested_With") != null; + //判断是不是文件请求 + if (!ServletFileUpload.isMultipartContent(request)) { + return new WangEditorTool(false); + } + //spring提供的对于request请求携带文件信息的处理工具类 + ServletFileUpload servletFileUpload = new ServletFileUpload(new DiskFileItemFactory()); + //设置头部编码? + if (isAjaxUpload) { + servletFileUpload.setHeaderEncoding("UTF-8"); + } + //解析获取request中的流 + FileItemIterator iterator = servletFileUpload.getItemIterator(request); + //用来判断是否没有数据和接收数据 + FileItemStream fileItemStream = null; + + while (iterator.hasNext()) { + //对迭代器进行读取,这里的读取是跟浏览器进行信息交互,所以流打开和读取是一次性的,为了多次读取,试试能不能把流缓存在本地 + fileItemStream = iterator.next(); + fileItemStreamNames.add("name = " + fileItemStream.getName()); + fileItemStreamNames.add("fieldNames = " + fileItemStream.getFieldName()); + //判断是不是表单数据 + if (fileItemStream.isFormField()) { + //打开流 + InputStream inputStream = fileItemStream.openStream(); + String id = fileItemStream.getFieldName(); + String value = Streams.asString(inputStream, "UTF-8"); + //进行表单数据的缓存 -- 该流已经结束读取 和浏览器之间的读取 + map.put(id, value); + //关闭流 + inputStream.close(); + } + //不是表单数据,是文件?进行读取? --文件不是表单数据 + if (!fileItemStream.isFormField()) { + //文件名 + String fileName = fileItemStream.getName(); + //文件类型 + String ContentType = fileItemStream.getContentType(); + map.put("fileName", fileName); + map.put("ContentType", ContentType); + + InputStream inputStream = fileItemStream.openStream(); + //文件大小? + int size = inputStream.available(); + map.put("size", size); + map.put("inputStream", inputStream); + this.inputStream = inputStream; + //读取文件的字节 + byte[] bytes = new byte[size]; + //用来读取的字节大小 + byte[] bytes1 = new byte[1024]; + + int read; + String path = getRandomFilePath(request, bytes, fileName); + map.put("FilePath",path); + BufferedOutputStream bof = new BufferedOutputStream(new FileOutputStream(new File(path))); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + StringBuilder bytesS = new StringBuilder(); + //放在外面读而不是把字节存下来读取,可以保证顺序存储不出错 + while ((read = inputStream.read(bytes1)) != -1) { + bof.write(bytes1,0,read); + byteList.add(bytes1); + bytesS.append(Arrays.toString(bytes1)); + byteArrayOutputStream.write(bytes1, 0, read); + } + map.put("byteList",byteList); + map.put("bytes", bytesS.toString()); + byte[] fileByteArray = byteArrayOutputStream.toByteArray(); + byteArrayOutputStream.close(); + + //也就是说我不可能缓存流,但是可以把流中的字节缓存下来。 + map.put("fileByteArray",fileByteArray); + fileByteArray = (byte[]) map.get("fileByteArray"); + + //将字节转为String?类似在数据库中的存储 + String fileBase64 = Base64Util.encode(fileByteArray); + inputStream.close(); + System.out.println(fileBase64); + + Thread.sleep(1000); + //对流进行转换并读写 + writeStringToByte(fileBase64,fileName); + + Thread.sleep(1000); + //对流进行转换并读写 ---不进行转码 + writeStringToByte(fileName,fileByteArray); + } + } + + //如果遍历没给值,说明为空 + if (fileItemStream == null) { + return new WangEditorTool(false); + } + + return new WangEditorTool(true); + } + + /** + * 通过转成base64的String 模拟进数据库,在回头去把数据decode回来成byte[],然后写成文件 + */ + public void writeStringToByte(String fileBase64,String fileName) throws IOException { + String path = getRandomFilePath(request, new byte[0], fileName); + BufferedOutputStream bof = new BufferedOutputStream(new FileOutputStream(new File(path))); + byte[] bytes = Base64Util.decodeByte(fileBase64); + + bof.write(bytes); + bof.flush(); + bof.close(); + } + + /** + * 通过byteArrayOutputStream来获取这个byte流的信息,然后直接传进来读取 + * 实验一下如果缓存byte在本地可以读取 的操作 + */ + public void writeStringToByte(String fileName,byte[] unicodeByte) throws IOException { + String path = getRandomFilePath(request, new byte[0], fileName); + + BufferedOutputStream bof = new BufferedOutputStream(new FileOutputStream(new File(path))); + InputStream fis = new ByteArrayInputStream(unicodeByte); + byte[] bytes = new byte[1024]; + + int read; + while((read = fis.read(bytes) )!=-1){ + bof.write(bytes,0,read); + } + + bof.flush(); + fis.close(); + bof.close(); + } + + /** + * 流存不下来,他会自动关闭掉,那我应该怎么办??? -- 我尝试用缓存的byte来做??? + */ + public void getStreamAndRead(String fileName) throws IOException { + //用来读取的字节大小 + byte[] bytes1 = new byte[1024]; + + String bytesS = (String) map.get("bytes"); + + String path = getRandomFilePath(request, bytes1, fileName); + BufferedOutputStream bof = new BufferedOutputStream(new FileOutputStream(new File(path))); + + byte[] bytes2 = bytesS.getBytes(StandardCharsets.UTF_8); + System.out.println(bytesS); + + bof.write(bytes2); + } + + /** + * 进行缓存参数的输出 + */ + public void getThingsInRequest() { + System.out.println("list数据"); + for (int i = 0; i < fileItemStreamNames.size(); i++) { + System.out.println((i + 1) + ":" + fileItemStreamNames.get(i) + ";"); + } + System.out.println("map的数据"); + for (Map.Entry esi : map.entrySet()) { + System.out.println(esi.getKey() + ":" + esi.getValue()); + } + } + + /** + * 获取文件名和文件地址 + */ + public String getRandomFilePath(HttpServletRequest request, byte[] bytes, String fileName) throws IOException { + String springPath; + springPath = request.getServletContext().getRealPath("/"); + SimpleDateFormat.getInstance().format(new Date()); + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); + String dir = simpleDateFormat.format(new Date()); + System.out.println(dir); + //通过正确的间隔符来标识文件路径 + File.separator + springPath = springPath + dir + File.separator; + System.out.println("文件路径" + springPath); + File file1 = new File(springPath); + if (file1.exists() && file1.isDirectory()) { + } else { + file1.mkdirs(); + } + + springPath = springPath + File.separator + fileName; + File file = new File(springPath); + if (!file.exists()) { + file.createNewFile(); + } + + return springPath; + } + + public HttpServletRequest getRequest() { + return request; + } + + public void setRequest(HttpServletRequest request) { + this.request = request; + } + + public HashMap getMap(){ + return this.map; + } +} diff --git a/src/main/java/util/Base64Util.java b/src/main/java/util/Base64Util.java new file mode 100644 index 0000000..5644daa --- /dev/null +++ b/src/main/java/util/Base64Util.java @@ -0,0 +1,293 @@ +package util; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; + +/** + * 该工具是很久之前在网上找的,现在已经忘记了对应的代码地址 + */ +public class Base64Util { + private static final char[] S_BASE64CHAR = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; + private static final char S_BASE64PAD = '='; + private static final byte[] S_DECODETABLE = new byte[128]; + private static final char EQUAL_SIGN = '='; + + public Base64Util() { + } + + private static int decode0(char[] ibuf, byte[] obuf, int wp) { + int outlen = 3; + if (ibuf[3] == EQUAL_SIGN) { + outlen = 2; + } + + if (ibuf[2] ==EQUAL_SIGN ) { + outlen = 1; + } + + int b0 = S_DECODETABLE[ibuf[0]]; + int b1 = S_DECODETABLE[ibuf[1]]; + int b2 = S_DECODETABLE[ibuf[2]]; + int b3 = S_DECODETABLE[ibuf[3]]; + switch (outlen) { + case 1: + obuf[wp] = (byte) (b0 << 2 & 252 | b1 >> 4 & 3); + return 1; + case 2: + obuf[wp++] = (byte) (b0 << 2 & 252 | b1 >> 4 & 3); + obuf[wp] = (byte) (b1 << 4 & 240 | b2 >> 2 & 15); + return 2; + case 3: + obuf[wp++] = (byte) (b0 << 2 & 252 | b1 >> 4 & 3); + obuf[wp++] = (byte) (b1 << 4 & 240 | b2 >> 2 & 15); + obuf[wp] = (byte) (b2 << 6 & 192 | b3 & 63); + return 3; + default: + throw new RuntimeException("Internal Errror"); + } + } + + public static byte[] decode(char[] data, int off, int len) { + char[] ibuf = new char[4]; + int ibufcount = 0; + byte[] obuf = new byte[len / 4 * 3 + 3]; + int obufcount = 0; + + for (int i = off; i < off + len; ++i) { + char ch = data[i]; + if (ch == '=' || ch < S_DECODETABLE.length && S_DECODETABLE[ch] != 127) { + ibuf[ibufcount++] = ch; + if (ibufcount == ibuf.length) { + ibufcount = 0; + obufcount += decode0(ibuf, obuf, obufcount); + } + } + } + return decodeResult(obufcount,obuf); + } + + private static byte[] decodeResult(int obufcount, byte[] obuf) { + if (obufcount == obuf.length) { + return obuf; + } else { + byte[] ret = new byte[obufcount]; + System.arraycopy(obuf, 0, ret, 0, obufcount); + return ret; + } + } + + public static byte[] decode(String data) { + char[] ibuf = new char[4]; + int ibufcount = 0; + byte[] obuf = new byte[data.length() / 4 * 3 + 3]; + int obufcount = 0; + + for (int i = 0; i < data.length(); ++i) { + char ch = data.charAt(i); + if (ch == '=' || ch < S_DECODETABLE.length && S_DECODETABLE[ch] != 127) { + ibuf[ibufcount++] = ch; + if (ibufcount == ibuf.length) { + ibufcount = 0; + obufcount += decode0(ibuf, obuf, obufcount); + } + } + } + + return decodeResult(obufcount,obuf); + } + + public static void decode(char[] data, int off, int len, OutputStream ostream) throws IOException { + char[] ibuf = new char[4]; + int ibufcount = 0; + byte[] obuf = new byte[3]; + + for (int i = off; i < off + len; ++i) { + char ch = data[i]; + decodeProcess(ch,ibuf,ibufcount,ostream,obuf); + } + + } + + private static void decodeProcess(char ch, char[] ibuf, int ibufcount, OutputStream ostream, byte[] obuf) throws IOException { + if (ch == '=' || ch < S_DECODETABLE.length && S_DECODETABLE[ch] != 127) { + ibuf[ibufcount++] = ch; + if (ibufcount == ibuf.length) { + ibufcount = 0; + int obufcount = decode0(ibuf, obuf, 0); + ostream.write(obuf, 0, obufcount); + } + } + } + + public static void decode(String data, OutputStream ostream) throws IOException { + char[] ibuf = new char[4]; + int ibufcount = 0; + byte[] obuf = new byte[3]; + + for (int i = 0; i < data.length(); ++i) { + char ch = data.charAt(i); + decodeProcess(ch,ibuf,ibufcount,ostream,obuf); + } + + } + + public static String encode(byte[] data) { + return encode(data, 0, data.length); + } + + public static String encode(byte[] data, int off, int len) { + if (len <= 0) { + return ""; + } else { + char[] out = new char[len / 3 * 4 + 4]; + int rindex = off; + int windex = 0; + + int rest; + int i; + for (rest = len; rest >= 3; rest -= 3) { + i = ((data[rindex] & 255) << 16) + ((data[rindex + 1] & 255) << 8) + (data[rindex + 2] & 255); + out[windex++] = S_BASE64CHAR[i >> 18]; + out[windex++] = S_BASE64CHAR[i >> 12 & 63]; + out[windex++] = S_BASE64CHAR[i >> 6 & 63]; + out[windex++] = S_BASE64CHAR[i & 63]; + rindex += 3; + } + + if (rest == 1) { + i = data[rindex] & 255; + out[windex++] = S_BASE64CHAR[i >> 2]; + out[windex++] = S_BASE64CHAR[i << 4 & 63]; + out[windex++] = '='; + out[windex++] = '='; + } else if (rest == 2) { + i = ((data[rindex] & 255) << 8) + (data[rindex + 1] & 255); + out[windex++] = S_BASE64CHAR[i >> 10]; + out[windex++] = S_BASE64CHAR[i >> 4 & 63]; + out[windex++] = S_BASE64CHAR[i << 2 & 63]; + out[windex++] = '='; + } + + return new String(out, 0, windex); + } + } + + public static void encode(byte[] data, int off, int len, OutputStream ostream) throws IOException { + if (len > 0) { + byte[] out = new byte[4]; + int rindex = off; + + int rest; + int i; + for (rest = len; rest >= 3; rest -= 3) { + i = ((data[rindex] & 255) << 16) + ((data[rindex + 1] & 255) << 8) + (data[rindex + 2] & 255); + out[0] = (byte) S_BASE64CHAR[i >> 18]; + out[1] = (byte) S_BASE64CHAR[i >> 12 & 63]; + out[2] = (byte) S_BASE64CHAR[i >> 6 & 63]; + out[3] = (byte) S_BASE64CHAR[i & 63]; + ostream.write(out, 0, 4); + rindex += 3; + } + + if (rest == 1) { + i = data[rindex] & 255; + out[0] = (byte) S_BASE64CHAR[i >> 2]; + out[1] = (byte) S_BASE64CHAR[i << 4 & 63]; + out[2] = 61; + out[3] = 61; + ostream.write(out, 0, 4); + } else if (rest == 2) { + i = ((data[rindex] & 255) << 8) + (data[rindex + 1] & 255); + out[0] = (byte) S_BASE64CHAR[i >> 10]; + out[1] = (byte) S_BASE64CHAR[i >> 4 & 63]; + out[2] = (byte) S_BASE64CHAR[i << 2 & 63]; + out[3] = 61; + ostream.write(out, 0, 4); + } + + } + } + + public static void encode(byte[] data, int off, int len, Writer writer) throws IOException { + if (len > 0) { + char[] out = new char[4]; + int rindex = off; + int rest = len; + int output = 0; + + int i; + while (rest >= 3) { + i = ((data[rindex] & 255) << 16) + ((data[rindex + 1] & 255) << 8) + (data[rindex + 2] & 255); + out[0] = S_BASE64CHAR[i >> 18]; + out[1] = S_BASE64CHAR[i >> 12 & 63]; + out[2] = S_BASE64CHAR[i >> 6 & 63]; + out[3] = S_BASE64CHAR[i & 63]; + writer.write(out, 0, 4); + rindex += 3; + rest -= 3; + output += 4; + if (output % 76 == 0) { + writer.write("\n"); + } + } + + if (rest == 1) { + i = data[rindex] & 255; + out[0] = S_BASE64CHAR[i >> 2]; + out[1] = S_BASE64CHAR[i << 4 & 63]; + out[2] = '='; + out[3] = '='; + writer.write(out, 0, 4); + } else if (rest == 2) { + i = ((data[rindex] & 255) << 8) + (data[rindex + 1] & 255); + out[0] = S_BASE64CHAR[i >> 10]; + out[1] = S_BASE64CHAR[i >> 4 & 63]; + out[2] = S_BASE64CHAR[i << 2 & 63]; + out[3] = '='; + writer.write(out, 0, 4); + } + + } + } + + static { + int i; + for (i = 0; i < S_DECODETABLE.length; ++i) { + S_DECODETABLE[i] = 127; + } + + for (i = 0; i < S_BASE64CHAR.length; ++i) { + S_DECODETABLE[S_BASE64CHAR[i]] = (byte) i; + } + + } + + public static byte[] decodeByte(String data) { + + char[] ibuf = new char[4]; + int ibufcount = 0; + byte[] obuf = new byte[data.length() / 4 * 3 + 3]; + int obufcount = 0; + for (int i = 0; i < data.length(); i++) { + char ch = data.charAt(i); + if (ch == S_BASE64PAD || ch < S_DECODETABLE.length && S_DECODETABLE[ch] != Byte.MAX_VALUE) { + ibuf[ibufcount++] = ch; + if (ibufcount == ibuf.length) { + ibufcount = 0; + obufcount += decode0(ibuf, obuf, obufcount); + } + } + } + if (obufcount == obuf.length) { + return obuf; + } + byte[] ret = new byte[obufcount]; + System.arraycopy(obuf, 0, ret, 0, obufcount); + return ret; + } + + +} diff --git a/src/main/java/util/itext/FillTemplateHelper.java b/src/main/java/util/itext/FillTemplateHelper.java new file mode 100644 index 0000000..aa2e29f --- /dev/null +++ b/src/main/java/util/itext/FillTemplateHelper.java @@ -0,0 +1,196 @@ +/* + * @(#) FillTemplateHelper + * 版权声明 网宿科技, 版权所有 违者必究 + * + *
Copyright: Copyright (c) 2018 + *
Company:网宿科技 + *
@author Administrator + *
@description 功能描述 + *
2018-11-13 20:19:49 + */ + +package util.itext; + +import com.itextpdf.text.*; +import com.itextpdf.text.pdf.*; +import com.itextpdf.tool.xml.ElementList; +import com.itextpdf.tool.xml.XMLWorker; +import com.itextpdf.tool.xml.XMLWorkerHelper; +import com.itextpdf.tool.xml.css.CssFile; +import com.itextpdf.tool.xml.css.StyleAttrCSSResolver; +import com.itextpdf.tool.xml.html.TagProcessorFactory; +import com.itextpdf.tool.xml.parser.XMLParser; +import com.itextpdf.tool.xml.pipeline.css.CSSResolver; +import com.itextpdf.tool.xml.pipeline.css.CssResolverPipeline; +import com.itextpdf.tool.xml.pipeline.end.ElementHandlerPipeline; +import com.itextpdf.tool.xml.pipeline.html.HtmlPipeline; +import com.itextpdf.tool.xml.pipeline.html.HtmlPipelineContext; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +/** + * @author iText + */ +public class FillTemplateHelper extends PdfPageEventHelper { + + // initialized in constructor + protected PdfReader reader; + protected Rectangle pageSize; + protected Rectangle body; + protected float mLeft, mRight, mTop, mBottom; + protected Rectangle to; + protected Rectangle from; + protected Rectangle date; + protected Rectangle footer; + protected BaseFont basefont; + protected Font font; + // initialized with setter + protected String sender = ""; + protected String receiver = ""; + // initialized upon opening the document + protected PdfTemplate background; + protected PdfTemplate total; + + public FillTemplateHelper(String stationery) throws IOException, DocumentException { + reader = new PdfReader(stationery); + AcroFields fields = reader.getAcroFields(); + pageSize = reader.getPageSize(1); + body = fields.getFieldPositions("body").get(0).position; + mLeft = body.getLeft() - pageSize.getLeft(); + mRight = pageSize.getRight() - body.getRight(); + mTop = pageSize.getTop() - body.getTop(); + mBottom = body.getBottom() - pageSize.getBottom(); + footer = fields.getFieldPositions("footer").get(0).position; + basefont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); + font = new Font(basefont, 12); + + } + + public static ElementList parseHtml(String content, String style, TagProcessorFactory tagProcessors) throws IOException { + // CSS + CSSResolver cssResolver = new StyleAttrCSSResolver(); + CssFile cssFile = XMLWorkerHelper.getCSS(new FileInputStream(style)); + cssResolver.addCss(cssFile); + // HTML + HtmlPipelineContext htmlContext = new HtmlPipelineContext(null); + htmlContext.setTagFactory(tagProcessors); + htmlContext.autoBookmark(false); + // Pipelines + ElementList elements = new ElementList(); + ElementHandlerPipeline end = new ElementHandlerPipeline(elements, null); + HtmlPipeline html = new HtmlPipeline(htmlContext, end); + CssResolverPipeline css = new CssResolverPipeline(cssResolver, html); + // XML Worker + XMLWorker worker = new XMLWorker(css, true); + XMLParser p = new XMLParser(worker); + p.parse(new FileInputStream(content), Charset.forName("utf8")); + return elements; + } + + public static ElementList parseHtmlContent(String content, String style, TagProcessorFactory tagProcessors) throws IOException { + // CSS + CSSResolver cssResolver = new StyleAttrCSSResolver(); + CssFile cssFile = XMLWorkerHelper.getCSS(new FileInputStream(style)); + cssResolver.addCss(cssFile); + // HTML + HtmlPipelineContext htmlContext = new HtmlPipelineContext(null); + htmlContext.setTagFactory(tagProcessors); + htmlContext.autoBookmark(false); + // Pipelines + ElementList elements = new ElementList(); + ElementHandlerPipeline end = new ElementHandlerPipeline(elements, null); + HtmlPipeline html = new HtmlPipeline(htmlContext, end); + CssResolverPipeline css = new CssResolverPipeline(cssResolver, html); + // XML Worker + XMLWorker worker = new XMLWorker(css, true); + XMLParser p = new XMLParser(worker); + try { + p.parse(getStringStream(content), Charset.forName("utf8")); + } catch (Exception e) { + e.printStackTrace(); + } + return elements; + } + + public static InputStream getStringStream(String sInputString) { + if (sInputString != null && !sInputString.trim().equals("")) { + try { + ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(sInputString.getBytes("UTF-8")); + return tInputStringStream; + } catch (Exception ex) { + ex.printStackTrace(); + } + } + return null; + } + + public void setSender(String sender) { + this.sender = sender; + } + + public void setReceiver(String receiver) { + this.receiver = receiver; + } + + public Rectangle getPageSize() { + return pageSize; + } + + public float getmLeft() { + return mLeft; + } + + public float getmRight() { + return mRight; + } + + public float getmTop() { + return mTop; + } + + public float getmBottom() { + return mBottom; + } + + public Rectangle getBody() { + return body; + } + + @Override + public void onOpenDocument(PdfWriter writer, Document document) { + background = writer.getImportedPage(reader, 1); + total = writer.getDirectContent().createTemplate(30, 15); + } + + @Override + public void onEndPage(PdfWriter writer, Document document) { + PdfContentByte canvas = writer.getDirectContentUnder(); + // background + canvas.addTemplate(background, 0, 0); + try { + ColumnText ct = new ColumnText(canvas); + ct.setSimpleColumn(footer); + ct.addText(new Chunk(" " + writer.getPageNumber(), font)); + ct.addText(new Chunk(Image.getInstance(total), 0, 0)); + ct.go(); + } catch (DocumentException e) { + // can never happen, but if it does, we want to know! + throw new ExceptionConverter(e); + + } + } + + @Override + public void onCloseDocument(PdfWriter writer, Document document) { + // 关闭时可以知道总页数,写入页码 + String s = "/ " + (writer.getPageNumber()); + Phrase p = new Phrase(12, s, font); + ColumnText.showTextAligned(total, Element.ALIGN_LEFT, p, 0.5f, 0, 0); + } + + +} \ No newline at end of file diff --git a/src/main/java/util/itext/PDFUtils.java b/src/main/java/util/itext/PDFUtils.java new file mode 100644 index 0000000..b5be289 --- /dev/null +++ b/src/main/java/util/itext/PDFUtils.java @@ -0,0 +1,175 @@ + + +package util.itext; + +import com.itextpdf.text.*; +import com.itextpdf.text.pdf.*; +import com.itextpdf.tool.xml.ElementList; +import com.itextpdf.tool.xml.html.Tags; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Created by huangzhijun on 2017/8/22. + * itext 用于生成pdf的工具类 + */ +public class PDFUtils { + /** + * 通过模板生成pdf文件 + * + * @param templatePath 模板路径 + * @param resultPath 生成路径 + * @param cssPath css路径 + * @param content 生成pdf的内容 + * @throws Exception + */ + public static void generalPDF(String templatePath, String resultPath, String cssPath, String content) throws Exception { + // 读取模板 + FillTemplateHelper template = new FillTemplateHelper(templatePath); + Document document = new Document(template.getPageSize(), + template.getmLeft(), template.getmRight(), template.getmTop(), template.getmBottom()); + // 创建pdf写对象 + PdfWriter writer = null; + FileOutputStream outputStream = new FileOutputStream(resultPath); + try { + writer = PdfWriter.getInstance(document, outputStream); + } catch (Exception e) { + e.printStackTrace(); + } + writer.setPageEvent(template); + //打开文件 + document.open(); + // 填充数据到文件中 + ElementList elements = FillTemplateHelper.parseHtmlContent(content, cssPath, Tags.getHtmlTagProcessorFactory()); + for (Element e : elements) { + document.add(e); + } + writer.setPageEmpty(false); + //关闭流操作 + document.close(); + writer.close(); + outputStream.close(); + } + + /** + * PDF盖章操作 + * 在PDF文档的最后一页盖章 + * @param RES 需要盖章的pdf文件地址 + * @param DEST 生成的新pdf文件地址 + * @param SEAL 印章图片的地址 + * @param company 落款名称及部门 + * @param dateName 落款时间名称 + * @throws DocumentException + * @throws IOException + */ + public static void sealDocument(String RES, String DEST, String SEAL, String company, String dateName, String time) throws DocumentException, IOException { + + PdfReader reader = new PdfReader(RES); + int numberOfPages = reader.getNumberOfPages(); + Rectangle pageSize = reader.getPageSize(1); + Document doc = new Document(pageSize); + PdfWriter writer_ = PdfWriter.getInstance(doc, new FileOutputStream(DEST)); + // 打开文档 + doc.open(); + PdfContentByte cb = writer_.getDirectContent(); + for (int i = 0; i < numberOfPages; ) { + { + i++; + //设置指定页的PagSize 包含Rotation(页面旋转度) +// doc.setPageSize(reader.getPageSizeWithRotation(i)); + //创建一个新的页面,需要注意的调用NewPage() ,PdfContentByte cb 对象会默认清空 + doc.newPage(); +// //获取指定页面的旋转度 +// int rotation = reader.getPageRotation(i); + //获取加载PDF的指定页内容 + PdfImportedPage page1 = writer_.getImportedPage(reader, i); + + //添加内容页到新的页面,并更加旋转度设置对应的旋转 + /* switch (rotation) { + case 90: + cb.addTemplate(page1, 0, -1, 1, 0, 0, reader.getPageSizeWithRotation(i).getHeight()); + break; + case 180: + cb.addTemplate(page1, -1, 0, 0, -1, reader.getPageSizeWithRotation(i).getWidth(), reader.getPageSizeWithRotation(i).getHeight()); + break; + case 270: + cb.addTemplate(page1, 0, 1, -1, 0, reader.getPageSizeWithRotation(i).getWidth(), 0); + break; + default: + cb.addTemplate(page1, 1, 0, 0, 1, 0, 0);//等同于 cb.AddTemplate(page1, 0,0) + break; + }*/ + cb.addTemplate(page1, 0, 0); + + if (i == numberOfPages)//如果是最后一页加入指定的图片 + { + //创建一个图片对象 + Image img = Image.getInstance(SEAL); + //设置图片的指定大小 + //img.ScaleToFit(140F, 320F); + //按比例缩放 + img.scalePercent(100); + BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); + cb.beginText(); + //设置字体 大小 + cb.setFontAndSize(bf, 13); + //指定添加文字的绝对位置 + cb.setTextMatrix(350, 75); + //增加文本 + cb.showText(company); + //结束 + cb.endText(); + cb.beginText(); + //设置字体 大小 + cb.setFontAndSize(bf, 13); + //指定添加文字的绝对位置 + cb.setTextMatrix(350, 55); + if (org.apache.commons.lang3.StringUtils.isBlank(time)) { + Date date = new Date(); + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + time = format.format(date); + } + //增加文本 + cb.showText(dateName + ":" + time); + //结束 + cb.endText(); + //把图片增加到内容页的指定位子 b width c height e bottom f left +// use addImage(image, image_width, 0, 0, image_height, x, y) + cb.addImage(img, 130, 0, 0, 130, 400, 55); + } + } + } + System.out.println("盖章成功"); + doc.close(); + writer_.close(); + reader.close(); + + } + + /** + * 将页面传过来的内容重新组装为html + * @param content + * @return + */ + public String transToHtml(String content) { + StringBuffer sb = new StringBuffer(); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append("
"); + sb.append("
"); + sb.append(content); + sb.append("
"); + sb.append("
"); + sb.append(""); + sb.append(""); + return sb.toString(); + } + + +} diff --git a/src/main/java/util/time/FillFullTimeValueUtil.java b/src/main/java/util/time/FillFullTimeValueUtil.java new file mode 100644 index 0000000..1860e6c --- /dev/null +++ b/src/main/java/util/time/FillFullTimeValueUtil.java @@ -0,0 +1,119 @@ + + +package util.time; + +import com.google.common.collect.Lists; +import model.fullFillTime.OriginTimeValue; +import model.fullFillTime.TargetTimeValue; +import org.springframework.util.CollectionUtils; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 补时间点工具类 + * 之前在ws上班的时候,处理图标数据会遇到某些点没有数据的情况,就需要对这些时间点进行补点处理 + * + * @author huangzj + * @date 2018-11-23 22:43:36 + */ +public enum FillFullTimeValueUtil { + /** + * 分钟粒度 + */ + MINUTE { + @Override + public List fullFillTimeValue(LocalDateTime startDate, LocalDateTime endDate, int timeInterval, + List originTimeValues, TimeZone timeZone) { + //Myview 向前聚合,返回前XX秒为最开始计数时间 + Instant firstInstant = toInstant(startDate, timeZone); + Instant finalInstant = toInstant(endDate, timeZone); + //补时间点--规则一样,间隔转换成秒 + return fullFillRule(originTimeValues, firstInstant, finalInstant, timeInterval * 60); + } + }, + /** + * 小时粒度 + */ + HOUR { + @Override + public List fullFillTimeValue(LocalDateTime startDate, LocalDateTime endDate, int timeInterval, + List originTimeValues, TimeZone timeZone) { + Instant firstInstant = toInstant(startDate, timeZone); + Instant finalInstant = toInstant(endDate, timeZone); + //补时间点--规则一样 + return fullFillRule(originTimeValues, firstInstant, finalInstant, timeInterval * 60 * 60); + } + }, + /** + * 天粒度 + */ + DAY { + @Override + public List fullFillTimeValue(LocalDateTime startDate, LocalDateTime endDate, int timeInterval, + List originTimeValues, TimeZone timeZone) { + //区别,把时间转成天 + Instant firstInstant = toInstant(LocalDateTime.of(startDate.toLocalDate(), LocalTime.of(0, 0, 0)), timeZone); + Instant finalInstant = toInstant(LocalDateTime.of(endDate.toLocalDate(), LocalTime.of(0, 0, 0)), timeZone); + //补时间点--规则一样 + return fullFillRule(originTimeValues, firstInstant, finalInstant, timeInterval * 60 * 60 * 24); + } + }; + + + /** + * 抽象方法,实现补充时间点 + * @param startDate 开始时间 格式如:yyyy-MM-dd 00:00:00 + * @param endDate 结束时间:格式如:yyyy-MM-dd 23:59:59 + * @param timeInterval 数据采集粒度,单位:分钟、小时、天 + * @param originTimeValues 缺少时间点对象(统一处理成long、long) + * @param timeZone 时区 + * @return 补时间点后对象,没有补null + */ + public abstract List fullFillTimeValue(LocalDateTime startDate, LocalDateTime endDate, + int timeInterval, List originTimeValues, TimeZone timeZone); + + + /** + * + * @param dateTime 时间 + * @param timeZone 时区 + * @return Instant,和TimeStamp类似 + */ + private static Instant toInstant(LocalDateTime dateTime, TimeZone timeZone) { + return dateTime.atZone(timeZone.toZoneId()) + .toInstant(); + } + + + private static List fullFillRule(List originTimeValues, Instant firstInstant, Instant finalInstant, + int seconds) { + //转换为Time-value形式的map + if (CollectionUtils.isEmpty(originTimeValues)) { + originTimeValues = Lists.newArrayList(); + } + Map timeValueMap = originTimeValues.stream().collect(Collectors.toMap(OriginTimeValue::getTime, + OriginTimeValue::getValue)); + final List resultList = new ArrayList<>(); + + Instant cursorInstant = firstInstant; + //循环按照时间粒度补充时间点。 + while (cursorInstant.isBefore(finalInstant) || cursorInstant.equals(finalInstant)) { + long epochSecond = cursorInstant.getEpochSecond(); + TargetTimeValue timeValueInRule = new TargetTimeValue(); + timeValueInRule.setValue(timeValueMap.getOrDefault(epochSecond, null)); + timeValueInRule.setTime(epochSecond); + resultList.add(timeValueInRule); + + //移动到下一个数据点 + cursorInstant = cursorInstant.plusSeconds(seconds); + } + //按时间排序,最新的排在最后面 + return resultList.parallelStream() + .sorted(Comparator.comparing(TargetTimeValue::getTime)) + .collect(Collectors.toList()); + } +} diff --git a/src/main/resources/tpl/alarm.tpl b/src/main/resources/tpl/alarm.tpl new file mode 100644 index 0000000..b6c6629 --- /dev/null +++ b/src/main/resources/tpl/alarm.tpl @@ -0,0 +1,3 @@ +[${identification}!] +${time}!:${platform}!: +${statistic}!详细信息请查看审计子系统,并及时处理 diff --git a/src/main/webapp/html/wangEditor.html b/src/main/webapp/html/wangEditor.html new file mode 100644 index 0000000..bd0b115 --- /dev/null +++ b/src/main/webapp/html/wangEditor.html @@ -0,0 +1,83 @@ + + + + + 富文本编辑器测试 + + + + + +

卧槽。配置这么简单???

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ +


+ + + + + + + + + diff --git a/src/main/webapp/js/wangEdit/fonts/w-e-icon.woff b/src/main/webapp/js/wangEdit/fonts/w-e-icon.woff new file mode 100644 index 0000000000000000000000000000000000000000..fa64c4d86cef0bd3aadc1906f25b2c0ce6bf96da GIT binary patch literal 5568 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 QY+$Fm0gCU~= 0 && matches.item(i) !== this) {} + return i > -1; + }; + } +}; + +/* + DOM 操作 API +*/ + +// 根据 html 代码片段创建 dom 对象 +function createElemByHTML(html) { + var div = void 0; + div = document.createElement('div'); + div.innerHTML = html; + return div.children; +} + +// 是否是 DOM List +function isDOMList(selector) { + if (!selector) { + return false; + } + if (selector instanceof HTMLCollection || selector instanceof NodeList) { + return true; + } + return false; +} + +// 封装 document.querySelectorAll +function querySelectorAll(selector) { + var result = document.querySelectorAll(selector); + if (isDOMList(result)) { + return result; + } else { + return [result]; + } +} + +// 创建构造函数 +function DomElement(selector) { + if (!selector) { + return; + } + + // selector 本来就是 DomElement 对象,直接返回 + if (selector instanceof DomElement) { + return selector; + } + + this.selector = selector; + var nodeType = selector.nodeType; + + // 根据 selector 得出的结果(如 DOM,DOM List) + var selectorResult = []; + if (nodeType === 9) { + // document 节点 + selectorResult = [selector]; + } else if (nodeType === 1) { + // 单个 DOM 节点 + selectorResult = [selector]; + } else if (isDOMList(selector)) { + // DOM List + selectorResult = selector; + } else if (typeof selector === 'string') { + // 字符串 + selector = selector.replace('/\n/mg', '').trim(); + if (selector.indexOf('<') === 0) { + // 如
+ selectorResult = createElemByHTML(selector); + } else { + // 如 #id .class + selectorResult = querySelectorAll(selector); + } + } + + var length = selectorResult.length; + if (!length) { + // 空数组 + return this; + } + + // 加入 DOM 节点 + var i = void 0; + for (i = 0; i < length; i++) { + this[i] = selectorResult[i]; + } + this.length = length; +} + +// 修改原型 +DomElement.prototype = { + constructor: DomElement, + + // 类数组,forEach + forEach: function forEach(fn) { + var i = void 0; + for (i = 0; i < this.length; i++) { + var elem = this[i]; + var result = fn.call(elem, elem, i); + if (result === false) { + break; + } + } + return this; + }, + + // 获取第几个元素 + get: function get(index) { + var length = this.length; + if (index >= length) { + index = index % length; + } + return $(this[index]); + }, + + // 第一个 + first: function first() { + return this.get(0); + }, + + // 最后一个 + last: function last() { + var length = this.length; + return this.get(length - 1); + }, + + // 绑定事件 + on: function on(type, selector, fn) { + // selector 不为空,证明绑定事件要加代理 + if (!fn) { + fn = selector; + selector = null; + } + + // type 是否有多个 + var types = []; + types = type.split(/\s+/); + + return this.forEach(function (elem) { + types.forEach(function (type) { + if (!type) { + return; + } + + if (!selector) { + // 无代理 + elem.addEventListener(type, fn, false); + return; + } + + // 有代理 + elem.addEventListener(type, function (e) { + var target = e.target; + if (target.matches(selector)) { + fn.call(target, e); + } + }, false); + }); + }); + }, + + // 取消事件绑定 + off: function off(type, fn) { + return this.forEach(function (elem) { + elem.removeEventListener(type, fn, false); + }); + }, + + // 获取/设置 属性 + attr: function attr(key, val) { + if (val == null) { + // 获取值 + return this[0].getAttribute(key); + } else { + // 设置值 + return this.forEach(function (elem) { + elem.setAttribute(key, val); + }); + } + }, + + // 添加 class + addClass: function addClass(className) { + if (!className) { + return this; + } + return this.forEach(function (elem) { + var arr = void 0; + if (elem.className) { + // 解析当前 className 转换为数组 + arr = elem.className.split(/\s/); + arr = arr.filter(function (item) { + return !!item.trim(); + }); + // 添加 class + if (arr.indexOf(className) < 0) { + arr.push(className); + } + // 修改 elem.class + elem.className = arr.join(' '); + } else { + elem.className = className; + } + }); + }, + + // 删除 class + removeClass: function removeClass(className) { + if (!className) { + return this; + } + return this.forEach(function (elem) { + var arr = void 0; + if (elem.className) { + // 解析当前 className 转换为数组 + arr = elem.className.split(/\s/); + arr = arr.filter(function (item) { + item = item.trim(); + // 删除 class + if (!item || item === className) { + return false; + } + return true; + }); + // 修改 elem.class + elem.className = arr.join(' '); + } + }); + }, + + // 修改 css + css: function css(key, val) { + var currentStyle = key + ':' + val + ';'; + return this.forEach(function (elem) { + var style = (elem.getAttribute('style') || '').trim(); + var styleArr = void 0, + resultArr = []; + if (style) { + // 将 style 按照 ; 拆分为数组 + styleArr = style.split(';'); + styleArr.forEach(function (item) { + // 对每项样式,按照 : 拆分为 key 和 value + var arr = item.split(':').map(function (i) { + return i.trim(); + }); + if (arr.length === 2) { + resultArr.push(arr[0] + ':' + arr[1]); + } + }); + // 替换或者新增 + resultArr = resultArr.map(function (item) { + if (item.indexOf(key) === 0) { + return currentStyle; + } else { + return item; + } + }); + if (resultArr.indexOf(currentStyle) < 0) { + resultArr.push(currentStyle); + } + // 结果 + elem.setAttribute('style', resultArr.join('; ')); + } else { + // style 无值 + elem.setAttribute('style', currentStyle); + } + }); + }, + + // 显示 + show: function show() { + return this.css('display', 'block'); + }, + + // 隐藏 + hide: function hide() { + return this.css('display', 'none'); + }, + + // 获取子节点 + children: function children() { + var elem = this[0]; + if (!elem) { + return null; + } + + return $(elem.children); + }, + + // 增加子节点 + append: function append($children) { + return this.forEach(function (elem) { + $children.forEach(function (child) { + elem.appendChild(child); + }); + }); + }, + + // 移除当前节点 + remove: function remove() { + return this.forEach(function (elem) { + if (elem.remove) { + elem.remove(); + } else { + var parent = elem.parentElement; + parent && parent.removeChild(elem); + } + }); + }, + + // 是否包含某个子节点 + isContain: function isContain($child) { + var elem = this[0]; + var child = $child[0]; + return elem.contains(child); + }, + + // 尺寸数据 + getSizeData: function getSizeData() { + var elem = this[0]; + return elem.getBoundingClientRect(); // 可得到 bottom height left right top width 的数据 + }, + + // 封装 nodeName + getNodeName: function getNodeName() { + var elem = this[0]; + return elem.nodeName; + }, + + // 从当前元素查找 + find: function find(selector) { + var elem = this[0]; + return $(elem.querySelectorAll(selector)); + }, + + // 获取当前元素的 text + text: function text(val) { + if (!val) { + // 获取 text + var elem = this[0]; + return elem.innerHTML.replace(/<.*?>/g, function () { + return ''; + }); + } else { + // 设置 text + return this.forEach(function (elem) { + elem.innerHTML = val; + }); + } + }, + + // 获取 html + html: function html(value) { + var elem = this[0]; + if (value == null) { + return elem.innerHTML; + } else { + elem.innerHTML = value; + return this; + } + }, + + // 获取 value + val: function val() { + var elem = this[0]; + return elem.value.trim(); + }, + + // focus + focus: function focus() { + return this.forEach(function (elem) { + elem.focus(); + }); + }, + + // parent + parent: function parent() { + var elem = this[0]; + return $(elem.parentElement); + }, + + // parentUntil 找到符合 selector 的父节点 + parentUntil: function parentUntil(selector, _currentElem) { + var results = document.querySelectorAll(selector); + var length = results.length; + if (!length) { + // 传入的 selector 无效 + return null; + } + + var elem = _currentElem || this[0]; + if (elem.nodeName === 'BODY') { + return null; + } + + var parent = elem.parentElement; + var i = void 0; + for (i = 0; i < length; i++) { + if (parent === results[i]) { + // 找到,并返回 + return $(parent); + } + } + + // 继续查找 + return this.parentUntil(selector, parent); + }, + + // 判断两个 elem 是否相等 + equal: function equal($elem) { + if ($elem.nodeType === 1) { + return this[0] === $elem; + } else { + return this[0] === $elem[0]; + } + }, + + // 将该元素插入到某个元素前面 + insertBefore: function insertBefore(selector) { + var $referenceNode = $(selector); + var referenceNode = $referenceNode[0]; + if (!referenceNode) { + return this; + } + return this.forEach(function (elem) { + var parent = referenceNode.parentNode; + parent.insertBefore(elem, referenceNode); + }); + }, + + // 将该元素插入到某个元素后面 + insertAfter: function insertAfter(selector) { + var $referenceNode = $(selector); + var referenceNode = $referenceNode[0]; + if (!referenceNode) { + return this; + } + return this.forEach(function (elem) { + var parent = referenceNode.parentNode; + if (parent.lastChild === referenceNode) { + // 最后一个元素 + parent.appendChild(elem); + } else { + // 不是最后一个元素 + parent.insertBefore(elem, referenceNode.nextSibling); + } + }); + } +}; + +// new 一个对象 +function $(selector) { + return new DomElement(selector); +} + +/* + 配置信息 +*/ + +var config = { + + // 默认菜单配置 + menus: ['head', 'bold', 'italic', 'underline', 'strikeThrough', 'foreColor', 'backColor', 'link', 'list', 'justify', 'quote', 'emoticon', 'image', 'table', 'video', 'code', 'undo', 'redo'], + + // // 语言配置 + // lang: { + // '设置标题': 'title', + // '正文': 'p', + // '链接文字': 'link text', + // '链接': 'link', + // '插入': 'insert', + // '创建': 'init' + // }, + + // 编辑区域的 z-index + zIndex: 10000, + + // 是否开启 debug 模式(debug 模式下错误会 throw error 形式抛出) + debug: false, + + // 插入链接时候的格式校验 + linkCheck: function linkCheck(text, link) { + // text 是插入的文字 + // link 是插入的链接 + return true; // 返回 true 即表示成功 + // return '校验失败' // 返回字符串即表示失败的提示信息 + }, + + // 插入网络图片的校验 + linkImgCheck: function linkImgCheck(src) { + // src 即图片的地址 + return true; // 返回 true 即表示成功 + // return '校验失败' // 返回字符串即表示失败的提示信息 + }, + + // 粘贴过滤样式,默认开启 + pasteFilterStyle: true, + + // 对粘贴的文字进行自定义处理,返回处理后的结果。编辑器会将处理后的结果粘贴到编辑区域中。 + // IE 暂时不支持 + pasteTextHandle: function pasteTextHandle(content) { + // content 即粘贴过来的内容(html 或 纯文本),可进行自定义处理然后返回 + return content; + }, + + // onchange 事件 + // onchange: function (html) { + // // html 即变化之后的内容 + // console.log(html) + // }, + + // 是否显示添加网络图片的 tab + showLinkImg: true, + + // 插入网络图片的回调 + linkImgCallback: function linkImgCallback(url) { + // console.log(url) // url 即插入图片的地址 + }, + + // 默认上传图片 max size: 5M + uploadImgMaxSize: 5 * 1024 * 1024, + + // 配置一次最多上传几个图片 + // uploadImgMaxLength: 5, + + // 上传图片,是否显示 base64 格式 + uploadImgShowBase64: false, + + // 上传图片,server 地址(如果有值,则 base64 格式的配置则失效) + // uploadImgServer: '/upload', + + // 自定义配置 filename + uploadFileName: '', + + // 上传图片的自定义参数 + uploadImgParams: { + // token: 'abcdef12345' + }, + + // 上传图片的自定义header + uploadImgHeaders: { + // 'Accept': 'text/x-json' + }, + + // 配置 XHR withCredentials + withCredentials: false, + + // 自定义上传图片超时时间 ms + uploadImgTimeout: 10000, + + // 上传图片 hook + uploadImgHooks: { + // customInsert: function (insertLinkImg, result, editor) { + // console.log('customInsert') + // // 图片上传并返回结果,自定义插入图片的事件,而不是编辑器自动插入图片 + // const data = result.data1 || [] + // data.forEach(link => { + // insertLinkImg(link) + // }) + // }, + before: function before(xhr, editor, files) { + // 图片上传之前触发 + + // 如果返回的结果是 {prevent: true, msg: 'xxxx'} 则表示用户放弃上传 + // return { + // prevent: true, + // msg: '放弃上传' + // } + }, + success: function success(xhr, editor, result) { + // 图片上传并返回结果,图片插入成功之后触发 + }, + fail: function fail(xhr, editor, result) { + // 图片上传并返回结果,但图片插入错误时触发 + }, + error: function error(xhr, editor) { + // 图片上传出错时触发 + }, + timeout: function timeout(xhr, editor) { + // 图片上传超时时触发 + } + }, + + // 是否上传七牛云,默认为 false + qiniu: false + +}; + +/* + 工具 +*/ + +// 和 UA 相关的属性 +var UA = { + _ua: navigator.userAgent, + + // 是否 webkit + isWebkit: function isWebkit() { + var reg = /webkit/i; + return reg.test(this._ua); + }, + + // 是否 IE + isIE: function isIE() { + return 'ActiveXObject' in window; + } +}; + +// 遍历对象 +function objForEach(obj, fn) { + var key = void 0, + result = void 0; + for (key in obj) { + if (obj.hasOwnProperty(key)) { + result = fn.call(obj, key, obj[key]); + if (result === false) { + break; + } + } + } +} + +// 遍历类数组 +function arrForEach(fakeArr, fn) { + var i = void 0, + item = void 0, + result = void 0; + var length = fakeArr.length || 0; + for (i = 0; i < length; i++) { + item = fakeArr[i]; + result = fn.call(fakeArr, item, i); + if (result === false) { + break; + } + } +} + +// 获取随机数 +function getRandom(prefix) { + return prefix + Math.random().toString().slice(2); +} + +// 替换 html 特殊字符 +function replaceHtmlSymbol(html) { + if (html == null) { + return ''; + } + return html.replace(//gm, '>').replace(/"/gm, '"'); +} + +// 返回百分比的格式 + +/* + bold-menu +*/ +// 构造函数 +function Bold(editor) { + this.editor = editor; + this.$elem = $('
\n \n
'); + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Bold.prototype = { + constructor: Bold, + + // 点击事件 + onClick: function onClick(e) { + // 点击菜单将触发这里 + + var editor = this.editor; + var isSeleEmpty = editor.selection.isSelectionEmpty(); + + if (isSeleEmpty) { + // 选区是空的,插入并选中一个“空白” + editor.selection.createEmptyRange(); + } + + // 执行 bold 命令 + editor.cmd.do('bold'); + + if (isSeleEmpty) { + // 需要将选取折叠起来 + editor.selection.collapseRange(); + editor.selection.restoreSelection(); + } + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + if (editor.cmd.queryCommandState('bold')) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + 替换多语言 + */ + +var replaceLang = function (editor, str) { + var langArgs = editor.config.langArgs || []; + var result = str; + + langArgs.forEach(function (item) { + var reg = item.reg; + var val = item.val; + + if (reg.test(result)) { + result = result.replace(reg, function () { + return val; + }); + } + }); + + return result; +}; + +/* + droplist +*/ +var _emptyFn = function _emptyFn() {}; + +// 构造函数 +function DropList(menu, opt) { + var _this = this; + + // droplist 所依附的菜单 + var editor = menu.editor; + this.menu = menu; + this.opt = opt; + // 容器 + var $container = $('
'); + + // 标题 + var $title = opt.$title; + var titleHtml = void 0; + if ($title) { + // 替换多语言 + titleHtml = $title.html(); + titleHtml = replaceLang(editor, titleHtml); + $title.html(titleHtml); + + $title.addClass('w-e-dp-title'); + $container.append($title); + } + + var list = opt.list || []; + var type = opt.type || 'list'; // 'list' 列表形式(如“标题”菜单) / 'inline-block' 块状形式(如“颜色”菜单) + var onClick = opt.onClick || _emptyFn; + + // 加入 DOM 并绑定事件 + var $list = $('
    '); + $container.append($list); + list.forEach(function (item) { + var $elem = item.$elem; + + // 替换多语言 + var elemHtml = $elem.html(); + elemHtml = replaceLang(editor, elemHtml); + $elem.html(elemHtml); + + var value = item.value; + var $li = $('
  • '); + if ($elem) { + $li.append($elem); + $list.append($li); + $elem.on('click', function (e) { + onClick(value); + + // 隐藏 + _this.hideTimeoutId = setTimeout(function () { + _this.hide(); + }, 0); + }); + } + }); + + // 绑定隐藏事件 + $container.on('mouseleave', function (e) { + _this.hideTimeoutId = setTimeout(function () { + _this.hide(); + }, 0); + }); + + // 记录属性 + this.$container = $container; + + // 基本属性 + this._rendered = false; + this._show = false; +} + +// 原型 +DropList.prototype = { + constructor: DropList, + + // 显示(插入DOM) + show: function show() { + if (this.hideTimeoutId) { + // 清除之前的定时隐藏 + clearTimeout(this.hideTimeoutId); + } + + var menu = this.menu; + var $menuELem = menu.$elem; + var $container = this.$container; + if (this._show) { + return; + } + if (this._rendered) { + // 显示 + $container.show(); + } else { + // 加入 DOM 之前先定位位置 + var menuHeight = $menuELem.getSizeData().height || 0; + var width = this.opt.width || 100; // 默认为 100 + $container.css('margin-top', menuHeight + 'px').css('width', width + 'px'); + + // 加入到 DOM + $menuELem.append($container); + this._rendered = true; + } + + // 修改属性 + this._show = true; + }, + + // 隐藏(移除DOM) + hide: function hide() { + if (this.showTimeoutId) { + // 清除之前的定时显示 + clearTimeout(this.showTimeoutId); + } + + var $container = this.$container; + if (!this._show) { + return; + } + // 隐藏并需改属性 + $container.hide(); + this._show = false; + } +}; + +/* + menu - header +*/ +// 构造函数 +function Head(editor) { + var _this = this; + + this.editor = editor; + this.$elem = $('
    '); + this.type = 'droplist'; + + // 当前是否 active 状态 + this._active = false; + + // 初始化 droplist + this.droplist = new DropList(this, { + width: 100, + $title: $('

    设置标题

    '), + type: 'list', // droplist 以列表形式展示 + list: [{ $elem: $('

    H1

    '), value: '

    ' }, { $elem: $('

    H2

    '), value: '

    ' }, { $elem: $('

    H3

    '), value: '

    ' }, { $elem: $('

    H4

    '), value: '

    ' }, { $elem: $('

    H5
    '), value: '
    ' }, { $elem: $('

    正文

    '), value: '

    ' }], + onClick: function onClick(value) { + // 注意 this 是指向当前的 Head 对象 + _this._command(value); + } + }); +} + +// 原型 +Head.prototype = { + constructor: Head, + + // 执行命令 + _command: function _command(value) { + var editor = this.editor; + + var $selectionElem = editor.selection.getSelectionContainerElem(); + if (editor.$textElem.equal($selectionElem)) { + // 不能选中多行来设置标题,否则会出现问题 + // 例如选中的是

    xxx

    yyy

    来设置标题,设置之后会成为

    xxx
    yyy

    不符合预期 + return; + } + + editor.cmd.do('formatBlock', value); + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + var reg = /^h/i; + var cmdValue = editor.cmd.queryCommandValue('formatBlock'); + if (reg.test(cmdValue)) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + panel +*/ + +var emptyFn = function emptyFn() {}; + +// 记录已经显示 panel 的菜单 +var _isCreatedPanelMenus = []; + +// 构造函数 +function Panel(menu, opt) { + this.menu = menu; + this.opt = opt; +} + +// 原型 +Panel.prototype = { + constructor: Panel, + + // 显示(插入DOM) + show: function show() { + var _this = this; + + var menu = this.menu; + if (_isCreatedPanelMenus.indexOf(menu) >= 0) { + // 该菜单已经创建了 panel 不能再创建 + return; + } + + var editor = menu.editor; + var $body = $('body'); + var $textContainerElem = editor.$textContainerElem; + var opt = this.opt; + + // panel 的容器 + var $container = $('
    '); + var width = opt.width || 300; // 默认 300px + $container.css('width', width + 'px').css('margin-left', (0 - width) / 2 + 'px'); + + // 添加关闭按钮 + var $closeBtn = $(''); + $container.append($closeBtn); + $closeBtn.on('click', function () { + _this.hide(); + }); + + // 准备 tabs 容器 + var $tabTitleContainer = $('
      '); + var $tabContentContainer = $('
      '); + $container.append($tabTitleContainer).append($tabContentContainer); + + // 设置高度 + var height = opt.height; + if (height) { + $tabContentContainer.css('height', height + 'px').css('overflow-y', 'auto'); + } + + // tabs + var tabs = opt.tabs || []; + var tabTitleArr = []; + var tabContentArr = []; + tabs.forEach(function (tab, tabIndex) { + if (!tab) { + return; + } + var title = tab.title || ''; + var tpl = tab.tpl || ''; + + // 替换多语言 + title = replaceLang(editor, title); + tpl = replaceLang(editor, tpl); + + // 添加到 DOM + var $title = $('
    • ' + title + '
    • '); + $tabTitleContainer.append($title); + var $content = $(tpl); + $tabContentContainer.append($content); + + // 记录到内存 + $title._index = tabIndex; + tabTitleArr.push($title); + tabContentArr.push($content); + + // 设置 active 项 + if (tabIndex === 0) { + $title._active = true; + $title.addClass('w-e-active'); + } else { + $content.hide(); + } + + // 绑定 tab 的事件 + $title.on('click', function (e) { + if ($title._active) { + return; + } + // 隐藏所有的 tab + tabTitleArr.forEach(function ($title) { + $title._active = false; + $title.removeClass('w-e-active'); + }); + tabContentArr.forEach(function ($content) { + $content.hide(); + }); + + // 显示当前的 tab + $title._active = true; + $title.addClass('w-e-active'); + $content.show(); + }); + }); + + // 绑定关闭事件 + $container.on('click', function (e) { + // 点击时阻止冒泡 + e.stopPropagation(); + }); + $body.on('click', function (e) { + _this.hide(); + }); + + // 添加到 DOM + $textContainerElem.append($container); + + // 绑定 opt 的事件,只有添加到 DOM 之后才能绑定成功 + tabs.forEach(function (tab, index) { + if (!tab) { + return; + } + var events = tab.events || []; + events.forEach(function (event) { + var selector = event.selector; + var type = event.type; + var fn = event.fn || emptyFn; + var $content = tabContentArr[index]; + $content.find(selector).on(type, function (e) { + e.stopPropagation(); + var needToHide = fn(e); + // 执行完事件之后,是否要关闭 panel + if (needToHide) { + _this.hide(); + } + }); + }); + }); + + // focus 第一个 elem + var $inputs = $container.find('input[type=text],textarea'); + if ($inputs.length) { + $inputs.get(0).focus(); + } + + // 添加到属性 + this.$container = $container; + + // 隐藏其他 panel + this._hideOtherPanels(); + // 记录该 menu 已经创建了 panel + _isCreatedPanelMenus.push(menu); + }, + + // 隐藏(移除DOM) + hide: function hide() { + var menu = this.menu; + var $container = this.$container; + if ($container) { + $container.remove(); + } + + // 将该 menu 记录中移除 + _isCreatedPanelMenus = _isCreatedPanelMenus.filter(function (item) { + if (item === menu) { + return false; + } else { + return true; + } + }); + }, + + // 一个 panel 展示时,隐藏其他 panel + _hideOtherPanels: function _hideOtherPanels() { + if (!_isCreatedPanelMenus.length) { + return; + } + _isCreatedPanelMenus.forEach(function (menu) { + var panel = menu.panel || {}; + if (panel.hide) { + panel.hide(); + } + }); + } +}; + +/* + menu - link +*/ +// 构造函数 +function Link(editor) { + this.editor = editor; + this.$elem = $('
      '); + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Link.prototype = { + constructor: Link, + + // 点击事件 + onClick: function onClick(e) { + var editor = this.editor; + var $linkelem = void 0; + + if (this._active) { + // 当前选区在链接里面 + $linkelem = editor.selection.getSelectionContainerElem(); + if (!$linkelem) { + return; + } + // 将该元素都包含在选取之内,以便后面整体替换 + editor.selection.createRangeByElem($linkelem); + editor.selection.restoreSelection(); + // 显示 panel + this._createPanel($linkelem.text(), $linkelem.attr('href')); + } else { + // 当前选区不在链接里面 + if (editor.selection.isSelectionEmpty()) { + // 选区是空的,未选中内容 + this._createPanel('', ''); + } else { + // 选中内容了 + this._createPanel(editor.selection.getSelectionText(), ''); + } + } + }, + + // 创建 panel + _createPanel: function _createPanel(text, link) { + var _this = this; + + // panel 中需要用到的id + var inputLinkId = getRandom('input-link'); + var inputTextId = getRandom('input-text'); + var btnOkId = getRandom('btn-ok'); + var btnDelId = getRandom('btn-del'); + + // 是否显示“删除链接” + var delBtnDisplay = this._active ? 'inline-block' : 'none'; + + // 初始化并显示 panel + var panel = new Panel(this, { + width: 300, + // panel 中可包含多个 tab + tabs: [{ + // tab 的标题 + title: '链接', + // 模板 + tpl: '
      \n \n \n
      \n \n \n
      \n
      ', + // 事件绑定 + events: [ + // 插入链接 + { + selector: '#' + btnOkId, + type: 'click', + fn: function fn() { + // 执行插入链接 + var $link = $('#' + inputLinkId); + var $text = $('#' + inputTextId); + var link = $link.val(); + var text = $text.val(); + _this._insertLink(text, link); + + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, + // 删除链接 + { + selector: '#' + btnDelId, + type: 'click', + fn: function fn() { + // 执行删除链接 + _this._delLink(); + + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + } // tab end + ] // tabs end + }); + + // 显示 panel + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 删除当前链接 + _delLink: function _delLink() { + if (!this._active) { + return; + } + var editor = this.editor; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + var selectionText = editor.selection.getSelectionText(); + editor.cmd.do('insertHTML', '' + selectionText + ''); + }, + + // 插入链接 + _insertLink: function _insertLink(text, link) { + if (!text || !link) { + return; + } + var editor = this.editor; + var config = editor.config; + var linkCheck = config.linkCheck; + var checkResult = true; // 默认为 true + if (linkCheck && typeof linkCheck === 'function') { + checkResult = linkCheck(text, link); + } + if (checkResult === true) { + editor.cmd.do('insertHTML', '' + text + ''); + } else { + alert(checkResult); + } + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + if ($selectionELem.getNodeName() === 'A') { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + italic-menu +*/ +// 构造函数 +function Italic(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Italic.prototype = { + constructor: Italic, + + // 点击事件 + onClick: function onClick(e) { + // 点击菜单将触发这里 + + var editor = this.editor; + var isSeleEmpty = editor.selection.isSelectionEmpty(); + + if (isSeleEmpty) { + // 选区是空的,插入并选中一个“空白” + editor.selection.createEmptyRange(); + } + + // 执行 italic 命令 + editor.cmd.do('italic'); + + if (isSeleEmpty) { + // 需要将选取折叠起来 + editor.selection.collapseRange(); + editor.selection.restoreSelection(); + } + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + if (editor.cmd.queryCommandState('italic')) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + redo-menu +*/ +// 构造函数 +function Redo(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Redo.prototype = { + constructor: Redo, + + // 点击事件 + onClick: function onClick(e) { + // 点击菜单将触发这里 + + var editor = this.editor; + + // 执行 redo 命令 + editor.cmd.do('redo'); + } +}; + +/* + strikeThrough-menu +*/ +// 构造函数 +function StrikeThrough(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +StrikeThrough.prototype = { + constructor: StrikeThrough, + + // 点击事件 + onClick: function onClick(e) { + // 点击菜单将触发这里 + + var editor = this.editor; + var isSeleEmpty = editor.selection.isSelectionEmpty(); + + if (isSeleEmpty) { + // 选区是空的,插入并选中一个“空白” + editor.selection.createEmptyRange(); + } + + // 执行 strikeThrough 命令 + editor.cmd.do('strikeThrough'); + + if (isSeleEmpty) { + // 需要将选取折叠起来 + editor.selection.collapseRange(); + editor.selection.restoreSelection(); + } + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + if (editor.cmd.queryCommandState('strikeThrough')) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + underline-menu +*/ +// 构造函数 +function Underline(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Underline.prototype = { + constructor: Underline, + + // 点击事件 + onClick: function onClick(e) { + // 点击菜单将触发这里 + + var editor = this.editor; + var isSeleEmpty = editor.selection.isSelectionEmpty(); + + if (isSeleEmpty) { + // 选区是空的,插入并选中一个“空白” + editor.selection.createEmptyRange(); + } + + // 执行 underline 命令 + editor.cmd.do('underline'); + + if (isSeleEmpty) { + // 需要将选取折叠起来 + editor.selection.collapseRange(); + editor.selection.restoreSelection(); + } + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + if (editor.cmd.queryCommandState('underline')) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + undo-menu +*/ +// 构造函数 +function Undo(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Undo.prototype = { + constructor: Undo, + + // 点击事件 + onClick: function onClick(e) { + // 点击菜单将触发这里 + + var editor = this.editor; + + // 执行 undo 命令 + editor.cmd.do('undo'); + } +}; + +/* + menu - list +*/ +// 构造函数 +function List(editor) { + var _this = this; + + this.editor = editor; + this.$elem = $('
      '); + this.type = 'droplist'; + + // 当前是否 active 状态 + this._active = false; + + // 初始化 droplist + this.droplist = new DropList(this, { + width: 120, + $title: $('

      设置列表

      '), + type: 'list', // droplist 以列表形式展示 + list: [{ $elem: $(' 有序列表'), value: 'insertOrderedList' }, { $elem: $(' 无序列表'), value: 'insertUnorderedList' }], + onClick: function onClick(value) { + // 注意 this 是指向当前的 List 对象 + _this._command(value); + } + }); +} + +// 原型 +List.prototype = { + constructor: List, + + // 执行命令 + _command: function _command(value) { + var editor = this.editor; + var $textElem = editor.$textElem; + editor.selection.restoreSelection(); + if (editor.cmd.queryCommandState(value)) { + return; + } + editor.cmd.do(value); + + // 验证列表是否被包裹在

      之内 + var $selectionElem = editor.selection.getSelectionContainerElem(); + if ($selectionElem.getNodeName() === 'LI') { + $selectionElem = $selectionElem.parent(); + } + if (/^ol|ul$/i.test($selectionElem.getNodeName()) === false) { + return; + } + if ($selectionElem.equal($textElem)) { + // 证明是顶级标签,没有被

      包裹 + return; + } + var $parent = $selectionElem.parent(); + if ($parent.equal($textElem)) { + // $parent 是顶级标签,不能删除 + return; + } + + $selectionElem.insertAfter($parent); + $parent.remove(); + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + if (editor.cmd.queryCommandState('insertUnOrderedList') || editor.cmd.queryCommandState('insertOrderedList')) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + menu - justify +*/ +// 构造函数 +function Justify(editor) { + var _this = this; + + this.editor = editor; + this.$elem = $('

      '); + this.type = 'droplist'; + + // 当前是否 active 状态 + this._active = false; + + // 初始化 droplist + this.droplist = new DropList(this, { + width: 100, + $title: $('

      对齐方式

      '), + type: 'list', // droplist 以列表形式展示 + list: [{ $elem: $(' 靠左'), value: 'justifyLeft' }, { $elem: $(' 居中'), value: 'justifyCenter' }, { $elem: $(' 靠右'), value: 'justifyRight' }], + onClick: function onClick(value) { + // 注意 this 是指向当前的 List 对象 + _this._command(value); + } + }); +} + +// 原型 +Justify.prototype = { + constructor: Justify, + + // 执行命令 + _command: function _command(value) { + var editor = this.editor; + editor.cmd.do(value); + } +}; + +/* + menu - Forecolor +*/ +// 构造函数 +function ForeColor(editor) { + var _this = this; + + this.editor = editor; + this.$elem = $('
      '); + this.type = 'droplist'; + + // 当前是否 active 状态 + this._active = false; + + // 初始化 droplist + this.droplist = new DropList(this, { + width: 120, + $title: $('

      文字颜色

      '), + type: 'inline-block', // droplist 内容以 block 形式展示 + list: [{ $elem: $(''), value: '#000000' }, { $elem: $(''), value: '#eeece0' }, { $elem: $(''), value: '#1c487f' }, { $elem: $(''), value: '#4d80bf' }, { $elem: $(''), value: '#c24f4a' }, { $elem: $(''), value: '#8baa4a' }, { $elem: $(''), value: '#7b5ba1' }, { $elem: $(''), value: '#46acc8' }, { $elem: $(''), value: '#f9963b' }, { $elem: $(''), value: '#ffffff' }], + onClick: function onClick(value) { + // 注意 this 是指向当前的 ForeColor 对象 + _this._command(value); + } + }); +} + +// 原型 +ForeColor.prototype = { + constructor: ForeColor, + + // 执行命令 + _command: function _command(value) { + var editor = this.editor; + editor.cmd.do('foreColor', value); + } +}; + +/* + menu - BackColor +*/ +// 构造函数 +function BackColor(editor) { + var _this = this; + + this.editor = editor; + this.$elem = $('
      '); + this.type = 'droplist'; + + // 当前是否 active 状态 + this._active = false; + + // 初始化 droplist + this.droplist = new DropList(this, { + width: 120, + $title: $('

      背景色

      '), + type: 'inline-block', // droplist 内容以 block 形式展示 + list: [{ $elem: $(''), value: '#000000' }, { $elem: $(''), value: '#eeece0' }, { $elem: $(''), value: '#1c487f' }, { $elem: $(''), value: '#4d80bf' }, { $elem: $(''), value: '#c24f4a' }, { $elem: $(''), value: '#8baa4a' }, { $elem: $(''), value: '#7b5ba1' }, { $elem: $(''), value: '#46acc8' }, { $elem: $(''), value: '#f9963b' }, { $elem: $(''), value: '#ffffff' }], + onClick: function onClick(value) { + // 注意 this 是指向当前的 BackColor 对象 + _this._command(value); + } + }); +} + +// 原型 +BackColor.prototype = { + constructor: BackColor, + + // 执行命令 + _command: function _command(value) { + var editor = this.editor; + editor.cmd.do('backColor', value); + } +}; + +/* + menu - quote +*/ +// 构造函数 +function Quote(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Quote.prototype = { + constructor: Quote, + + onClick: function onClick(e) { + var editor = this.editor; + var $selectionElem = editor.selection.getSelectionContainerElem(); + var nodeName = $selectionElem.getNodeName(); + + if (!UA.isIE()) { + if (nodeName === 'BLOCKQUOTE') { + // 撤销 quote + editor.cmd.do('formatBlock', '

      '); + } else { + // 转换为 quote + editor.cmd.do('formatBlock', '

      '); + } + return; + } + + // IE 中不支持 formatBlock
      ,要用其他方式兼容 + var content = void 0, + $targetELem = void 0; + if (nodeName === 'P') { + // 将 P 转换为 quote + content = $selectionElem.text(); + $targetELem = $('
      ' + content + '
      '); + $targetELem.insertAfter($selectionElem); + $selectionElem.remove(); + return; + } + if (nodeName === 'BLOCKQUOTE') { + // 撤销 quote + content = $selectionElem.text(); + $targetELem = $('

      ' + content + '

      '); + $targetELem.insertAfter($selectionElem); + $selectionElem.remove(); + } + }, + + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + var reg = /^BLOCKQUOTE$/i; + var cmdValue = editor.cmd.queryCommandValue('formatBlock'); + if (reg.test(cmdValue)) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + menu - code +*/ +// 构造函数 +function Code(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Code.prototype = { + constructor: Code, + + onClick: function onClick(e) { + var editor = this.editor; + var $startElem = editor.selection.getSelectionStartElem(); + var $endElem = editor.selection.getSelectionEndElem(); + var isSeleEmpty = editor.selection.isSelectionEmpty(); + var selectionText = editor.selection.getSelectionText(); + var $code = void 0; + + if (!$startElem.equal($endElem)) { + // 跨元素选择,不做处理 + editor.selection.restoreSelection(); + return; + } + if (!isSeleEmpty) { + // 选取不是空,用 包裹即可 + $code = $('' + selectionText + ''); + editor.cmd.do('insertElem', $code); + editor.selection.createRangeByElem($code, false); + editor.selection.restoreSelection(); + return; + } + + // 选取是空,且没有夸元素选择,则插入
      
      +        if (this._active) {
      +            // 选中状态,将编辑内容
      +            this._createPanel($startElem.html());
      +        } else {
      +            // 未选中状态,将创建内容
      +            this._createPanel();
      +        }
      +    },
      +
      +    _createPanel: function _createPanel(value) {
      +        var _this = this;
      +
      +        // value - 要编辑的内容
      +        value = value || '';
      +        var type = !value ? 'new' : 'edit';
      +        var textId = getRandom('texxt');
      +        var btnId = getRandom('btn');
      +
      +        var panel = new Panel(this, {
      +            width: 500,
      +            // 一个 Panel 包含多个 tab
      +            tabs: [{
      +                // 标题
      +                title: '插入代码',
      +                // 模板
      +                tpl: '
      \n \n
      \n \n
      \n
      ', + // 事件绑定 + events: [ + // 插入代码 + { + selector: '#' + btnId, + type: 'click', + fn: function fn() { + var $text = $('#' + textId); + var text = $text.val() || $text.html(); + text = replaceHtmlSymbol(text); + if (type === 'new') { + // 新插入 + _this._insertCode(text); + } else { + // 编辑更新 + _this._updateCode(text); + } + + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + } // first tab end + ] // tabs end + }); // new Panel end + + // 显示 panel + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 插入代码 + _insertCode: function _insertCode(value) { + var editor = this.editor; + editor.cmd.do('insertHTML', '
      ' + value + '


      '); + }, + + // 更新代码 + _updateCode: function _updateCode(value) { + var editor = this.editor; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + $selectionELem.html(value); + editor.selection.restoreSelection(); + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + var $parentElem = $selectionELem.parent(); + if ($selectionELem.getNodeName() === 'CODE' && $parentElem.getNodeName() === 'PRE') { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + menu - emoticon +*/ +// 构造函数 +function Emoticon(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Emoticon.prototype = { + constructor: Emoticon, + + onClick: function onClick() { + this._createPanel(); + }, + + _createPanel: function _createPanel() { + var _this = this; + + // 拼接表情字符串 + var faceHtml = ''; + var faceStr = '😀 😃 😄 😁 😆 😅 😂 😊 😇 🙂 🙃 😉 😌 😍 😘 😗 😙 😚 😋 😜 😝 😛 🤑 🤗 🤓 😎 😏 😒 😞 😔 😟 😕 🙁 😣 😖 😫 😩 😤 😠 😡 😶 😐 😑 😯 😦 😧 😮 😲 😵 😳 😱 😨 😰 😢 😥 😭 😓 😪 😴 🙄 🤔 😬 🤐'; + faceStr.split(/\s/).forEach(function (item) { + if (item) { + faceHtml += '' + item + ''; + } + }); + + var handHtml = ''; + var handStr = '🙌 👏 👋 👍 👎 👊 ✊ ️👌 ✋ 👐 💪 🙏 ️👆 👇 👈 👉 🖕 🖐 🤘 🖖'; + handStr.split(/\s/).forEach(function (item) { + if (item) { + handHtml += '' + item + ''; + } + }); + + var panel = new Panel(this, { + width: 300, + height: 200, + // 一个 Panel 包含多个 tab + tabs: [{ + // 标题 + title: '表情', + // 模板 + tpl: '
      ' + faceHtml + '
      ', + // 事件绑定 + events: [{ + selector: 'span.w-e-item', + type: 'click', + fn: function fn(e) { + var target = e.target; + _this._insert(target.innerHTML); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + }, // first tab end + { + // 标题 + title: '手势', + // 模板 + tpl: '
      ' + handHtml + '
      ', + // 事件绑定 + events: [{ + selector: 'span.w-e-item', + type: 'click', + fn: function fn(e) { + var target = e.target; + _this._insert(target.innerHTML); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + } // second tab end + ] // tabs end + }); + + // 显示 panel + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 插入表情 + _insert: function _insert(emoji) { + var editor = this.editor; + editor.cmd.do('insertHTML', '' + emoji + ''); + } +}; + +/* + menu - table +*/ +// 构造函数 +function Table(editor) { + this.editor = editor; + this.$elem = $('
      '); + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Table.prototype = { + constructor: Table, + + onClick: function onClick() { + if (this._active) { + // 编辑现有表格 + this._createEditPanel(); + } else { + // 插入新表格 + this._createInsertPanel(); + } + }, + + // 创建插入新表格的 panel + _createInsertPanel: function _createInsertPanel() { + var _this = this; + + // 用到的 id + var btnInsertId = getRandom('btn'); + var textRowNum = getRandom('row'); + var textColNum = getRandom('col'); + + var panel = new Panel(this, { + width: 250, + // panel 包含多个 tab + tabs: [{ + // 标题 + title: '插入表格', + // 模板 + tpl: '
      \n

      \n \u521B\u5EFA\n \n \u884C\n \n \u5217\u7684\u8868\u683C\n

      \n
      \n \n
      \n
      ', + // 事件绑定 + events: [{ + // 点击按钮,插入表格 + selector: '#' + btnInsertId, + type: 'click', + fn: function fn() { + var rowNum = parseInt($('#' + textRowNum).val()); + var colNum = parseInt($('#' + textColNum).val()); + + if (rowNum && colNum && rowNum > 0 && colNum > 0) { + // form 数据有效 + _this._insert(rowNum, colNum); + } + + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + } // first tab end + ] // tabs end + }); // panel end + + // 展示 panel + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 插入表格 + _insert: function _insert(rowNum, colNum) { + // 拼接 table 模板 + var r = void 0, + c = void 0; + var html = ''; + for (r = 0; r < rowNum; r++) { + html += ''; + if (r === 0) { + for (c = 0; c < colNum; c++) { + html += ''; + } + } else { + for (c = 0; c < colNum; c++) { + html += ''; + } + } + html += ''; + } + html += '
        


      '; + + // 执行命令 + var editor = this.editor; + editor.cmd.do('insertHTML', html); + + // 防止 firefox 下出现 resize 的控制点 + editor.cmd.do('enableObjectResizing', false); + editor.cmd.do('enableInlineTableEditing', false); + }, + + // 创建编辑表格的 panel + _createEditPanel: function _createEditPanel() { + var _this2 = this; + + // 可用的 id + var addRowBtnId = getRandom('add-row'); + var addColBtnId = getRandom('add-col'); + var delRowBtnId = getRandom('del-row'); + var delColBtnId = getRandom('del-col'); + var delTableBtnId = getRandom('del-table'); + + // 创建 panel 对象 + var panel = new Panel(this, { + width: 320, + // panel 包含多个 tab + tabs: [{ + // 标题 + title: '编辑表格', + // 模板 + tpl: '
      \n
      \n \n \n \n \n
      \n
      \n \n \n
      ', + // 事件绑定 + events: [{ + // 增加行 + selector: '#' + addRowBtnId, + type: 'click', + fn: function fn() { + _this2._addRow(); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + // 增加列 + selector: '#' + addColBtnId, + type: 'click', + fn: function fn() { + _this2._addCol(); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + // 删除行 + selector: '#' + delRowBtnId, + type: 'click', + fn: function fn() { + _this2._delRow(); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + // 删除列 + selector: '#' + delColBtnId, + type: 'click', + fn: function fn() { + _this2._delCol(); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + // 删除表格 + selector: '#' + delTableBtnId, + type: 'click', + fn: function fn() { + _this2._delTable(); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + }] + }); + // 显示 panel + panel.show(); + }, + + // 获取选中的单元格的位置信息 + _getLocationData: function _getLocationData() { + var result = {}; + var editor = this.editor; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + var nodeName = $selectionELem.getNodeName(); + if (nodeName !== 'TD' && nodeName !== 'TH') { + return; + } + + // 获取 td index + var $tr = $selectionELem.parent(); + var $tds = $tr.children(); + var tdLength = $tds.length; + $tds.forEach(function (td, index) { + if (td === $selectionELem[0]) { + // 记录并跳出循环 + result.td = { + index: index, + elem: td, + length: tdLength + }; + return false; + } + }); + + // 获取 tr index + var $tbody = $tr.parent(); + var $trs = $tbody.children(); + var trLength = $trs.length; + $trs.forEach(function (tr, index) { + if (tr === $tr[0]) { + // 记录并跳出循环 + result.tr = { + index: index, + elem: tr, + length: trLength + }; + return false; + } + }); + + // 返回结果 + return result; + }, + + // 增加行 + _addRow: function _addRow() { + // 获取当前单元格的位置信息 + var locationData = this._getLocationData(); + if (!locationData) { + return; + } + var trData = locationData.tr; + var $currentTr = $(trData.elem); + var tdData = locationData.td; + var tdLength = tdData.length; + + // 拼接即将插入的字符串 + var newTr = document.createElement('tr'); + var tpl = '', + i = void 0; + for (i = 0; i < tdLength; i++) { + tpl += ' '; + } + newTr.innerHTML = tpl; + // 插入 + $(newTr).insertAfter($currentTr); + }, + + // 增加列 + _addCol: function _addCol() { + // 获取当前单元格的位置信息 + var locationData = this._getLocationData(); + if (!locationData) { + return; + } + var trData = locationData.tr; + var tdData = locationData.td; + var tdIndex = tdData.index; + var $currentTr = $(trData.elem); + var $trParent = $currentTr.parent(); + var $trs = $trParent.children(); + + // 遍历所有行 + $trs.forEach(function (tr) { + var $tr = $(tr); + var $tds = $tr.children(); + var $currentTd = $tds.get(tdIndex); + var name = $currentTd.getNodeName().toLowerCase(); + + // new 一个 td,并插入 + var newTd = document.createElement(name); + $(newTd).insertAfter($currentTd); + }); + }, + + // 删除行 + _delRow: function _delRow() { + // 获取当前单元格的位置信息 + var locationData = this._getLocationData(); + if (!locationData) { + return; + } + var trData = locationData.tr; + var $currentTr = $(trData.elem); + $currentTr.remove(); + }, + + // 删除列 + _delCol: function _delCol() { + // 获取当前单元格的位置信息 + var locationData = this._getLocationData(); + if (!locationData) { + return; + } + var trData = locationData.tr; + var tdData = locationData.td; + var tdIndex = tdData.index; + var $currentTr = $(trData.elem); + var $trParent = $currentTr.parent(); + var $trs = $trParent.children(); + + // 遍历所有行 + $trs.forEach(function (tr) { + var $tr = $(tr); + var $tds = $tr.children(); + var $currentTd = $tds.get(tdIndex); + // 删除 + $currentTd.remove(); + }); + }, + + // 删除表格 + _delTable: function _delTable() { + var editor = this.editor; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + var $table = $selectionELem.parentUntil('table'); + if (!$table) { + return; + } + $table.remove(); + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + var nodeName = $selectionELem.getNodeName(); + if (nodeName === 'TD' || nodeName === 'TH') { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + menu - video +*/ +// 构造函数 +function Video(editor) { + this.editor = editor; + this.$elem = $('
      '); + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Video.prototype = { + constructor: Video, + + onClick: function onClick() { + this._createPanel(); + }, + + _createPanel: function _createPanel() { + var _this = this; + + // 创建 id + var textValId = getRandom('text-val'); + var btnId = getRandom('btn'); + + // 创建 panel + var panel = new Panel(this, { + width: 350, + // 一个 panel 多个 tab + tabs: [{ + // 标题 + title: '插入视频', + // 模板 + tpl: '
      \n \n
      \n \n
      \n
      ', + // 事件绑定 + events: [{ + selector: '#' + btnId, + type: 'click', + fn: function fn() { + var $text = $('#' + textValId); + var val = $text.val().trim(); + + // 测试用视频地址 + // + + if (val) { + // 插入视频 + _this._insert(val); + } + + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + } // first tab end + ] // tabs end + }); // panel end + + // 显示 panel + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 插入视频 + _insert: function _insert(val) { + var editor = this.editor; + editor.cmd.do('insertHTML', val + '


      '); + } +}; + +/* + menu - img +*/ +// 构造函数 +function Image(editor) { + this.editor = editor; + var imgMenuId = getRandom('w-e-img'); + this.$elem = $('
      '); + editor.imgMenuId = imgMenuId; + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Image.prototype = { + constructor: Image, + + onClick: function onClick() { + var editor = this.editor; + var config = editor.config; + if (config.qiniu) { + return; + } + if (this._active) { + this._createEditPanel(); + } else { + this._createInsertPanel(); + } + }, + + _createEditPanel: function _createEditPanel() { + var editor = this.editor; + + // id + var width30 = getRandom('width-30'); + var width50 = getRandom('width-50'); + var width100 = getRandom('width-100'); + var delBtn = getRandom('del-btn'); + + // tab 配置 + var tabsConfig = [{ + title: '编辑图片', + tpl: '
      \n
      \n \u6700\u5927\u5BBD\u5EA6\uFF1A\n \n \n \n
      \n
      \n \n \n
      ', + events: [{ + selector: '#' + width30, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.css('max-width', '30%'); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + selector: '#' + width50, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.css('max-width', '50%'); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + selector: '#' + width100, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.css('max-width', '100%'); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + selector: '#' + delBtn, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.remove(); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + }]; + + // 创建 panel 并显示 + var panel = new Panel(this, { + width: 300, + tabs: tabsConfig + }); + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + _createInsertPanel: function _createInsertPanel() { + var editor = this.editor; + var uploadImg = editor.uploadImg; + var config = editor.config; + + // id + var upTriggerId = getRandom('up-trigger'); + var upFileId = getRandom('up-file'); + var linkUrlId = getRandom('link-url'); + var linkBtnId = getRandom('link-btn'); + + // tabs 的配置 + var tabsConfig = [{ + title: '上传图片', + tpl: '
      \n
      \n \n
      \n
      \n \n
      \n
      ', + events: [{ + // 触发选择图片 + selector: '#' + upTriggerId, + type: 'click', + fn: function fn() { + var $file = $('#' + upFileId); + var fileElem = $file[0]; + if (fileElem) { + fileElem.click(); + } else { + // 返回 true 可关闭 panel + return true; + } + } + }, { + // 选择图片完毕 + selector: '#' + upFileId, + type: 'change', + fn: function fn() { + var $file = $('#' + upFileId); + var fileElem = $file[0]; + if (!fileElem) { + // 返回 true 可关闭 panel + return true; + } + + // 获取选中的 file 对象列表 + var fileList = fileElem.files; + if (fileList.length) { + uploadImg.uploadImg(fileList); + } + + // 返回 true 可关闭 panel + return true; + } + }] + }, // first tab end + { + title: '网络图片', + tpl: '
      \n \n
      \n \n
      \n
      ', + events: [{ + selector: '#' + linkBtnId, + type: 'click', + fn: function fn() { + var $linkUrl = $('#' + linkUrlId); + var url = $linkUrl.val().trim(); + + if (url) { + uploadImg.insertLinkImg(url); + } + + // 返回 true 表示函数执行结束之后关闭 panel + return true; + } + }] + } // second tab end + ]; // tabs end + + // 判断 tabs 的显示 + var tabsConfigResult = []; + if ((config.uploadImgShowBase64 || config.uploadImgServer || config.customUploadImg) && window.FileReader) { + // 显示“上传图片” + tabsConfigResult.push(tabsConfig[0]); + } + if (config.showLinkImg) { + // 显示“网络图片” + tabsConfigResult.push(tabsConfig[1]); + } + + // 创建 panel 并显示 + var panel = new Panel(this, { + width: 300, + tabs: tabsConfigResult + }); + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + if (editor._selectedImg) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + 所有菜单的汇总 +*/ + +// 存储菜单的构造函数 +var MenuConstructors = {}; + +MenuConstructors.bold = Bold; + +MenuConstructors.head = Head; + +MenuConstructors.link = Link; + +MenuConstructors.italic = Italic; + +MenuConstructors.redo = Redo; + +MenuConstructors.strikeThrough = StrikeThrough; + +MenuConstructors.underline = Underline; + +MenuConstructors.undo = Undo; + +MenuConstructors.list = List; + +MenuConstructors.justify = Justify; + +MenuConstructors.foreColor = ForeColor; + +MenuConstructors.backColor = BackColor; + +MenuConstructors.quote = Quote; + +MenuConstructors.code = Code; + +MenuConstructors.emoticon = Emoticon; + +MenuConstructors.table = Table; + +MenuConstructors.video = Video; + +MenuConstructors.image = Image; + +/* + 菜单集合 +*/ +// 构造函数 +function Menus(editor) { + this.editor = editor; + this.menus = {}; +} + +// 修改原型 +Menus.prototype = { + constructor: Menus, + + // 初始化菜单 + init: function init() { + var _this = this; + + var editor = this.editor; + var config = editor.config || {}; + var configMenus = config.menus || []; // 获取配置中的菜单 + + // 根据配置信息,创建菜单 + configMenus.forEach(function (menuKey) { + var MenuConstructor = MenuConstructors[menuKey]; + if (MenuConstructor && typeof MenuConstructor === 'function') { + // 创建单个菜单 + _this.menus[menuKey] = new MenuConstructor(editor); + } + }); + + // 添加到菜单栏 + this._addToToolbar(); + + // 绑定事件 + this._bindEvent(); + }, + + // 添加到菜单栏 + _addToToolbar: function _addToToolbar() { + var editor = this.editor; + var $toolbarElem = editor.$toolbarElem; + var menus = this.menus; + var config = editor.config; + // config.zIndex 是配置的编辑区域的 z-index,菜单的 z-index 得在其基础上 +1 + var zIndex = config.zIndex + 1; + objForEach(menus, function (key, menu) { + var $elem = menu.$elem; + if ($elem) { + // 设置 z-index + $elem.css('z-index', zIndex); + $toolbarElem.append($elem); + } + }); + }, + + // 绑定菜单 click mouseenter 事件 + _bindEvent: function _bindEvent() { + var menus = this.menus; + var editor = this.editor; + objForEach(menus, function (key, menu) { + var type = menu.type; + if (!type) { + return; + } + var $elem = menu.$elem; + var droplist = menu.droplist; + var panel = menu.panel; + + // 点击类型,例如 bold + if (type === 'click' && menu.onClick) { + $elem.on('click', function (e) { + if (editor.selection.getRange() == null) { + return; + } + menu.onClick(e); + }); + } + + // 下拉框,例如 head + if (type === 'droplist' && droplist) { + $elem.on('mouseenter', function (e) { + if (editor.selection.getRange() == null) { + return; + } + // 显示 + droplist.showTimeoutId = setTimeout(function () { + droplist.show(); + }, 200); + }).on('mouseleave', function (e) { + // 隐藏 + droplist.hideTimeoutId = setTimeout(function () { + droplist.hide(); + }, 0); + }); + } + + // 弹框类型,例如 link + if (type === 'panel' && menu.onClick) { + $elem.on('click', function (e) { + e.stopPropagation(); + if (editor.selection.getRange() == null) { + return; + } + // 在自定义事件中显示 panel + menu.onClick(e); + }); + } + }); + }, + + // 尝试修改菜单状态 + changeActive: function changeActive() { + var menus = this.menus; + objForEach(menus, function (key, menu) { + if (menu.tryChangeActive) { + setTimeout(function () { + menu.tryChangeActive(); + }, 100); + } + }); + } +}; + +/* + 粘贴信息的处理 +*/ + +// 获取粘贴的纯文本 +function getPasteText(e) { + var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData; + var pasteText = void 0; + if (clipboardData == null) { + pasteText = window.clipboardData && window.clipboardData.getData('text'); + } else { + pasteText = clipboardData.getData('text/plain'); + } + + return replaceHtmlSymbol(pasteText); +} + +// 获取粘贴的html +function getPasteHtml(e, filterStyle) { + var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData; + var pasteText = void 0, + pasteHtml = void 0; + if (clipboardData == null) { + pasteText = window.clipboardData && window.clipboardData.getData('text'); + } else { + pasteText = clipboardData.getData('text/plain'); + pasteHtml = clipboardData.getData('text/html'); + } + if (!pasteHtml && pasteText) { + pasteHtml = '

      ' + replaceHtmlSymbol(pasteText) + '

      '; + } + if (!pasteHtml) { + return; + } + + // 过滤word中状态过来的无用字符 + var docSplitHtml = pasteHtml.split(''); + if (docSplitHtml.length === 2) { + pasteHtml = docSplitHtml[0]; + } + + // 过滤无用标签 + pasteHtml = pasteHtml.replace(/<(meta|script|link).+?>/igm, ''); + // 去掉注释 + pasteHtml = pasteHtml.replace(//mg, ''); + + if (filterStyle) { + // 过滤样式 + pasteHtml = pasteHtml.replace(/\s?(class|style)=('|").+?('|")/igm, ''); + } else { + // 保留样式 + pasteHtml = pasteHtml.replace(/\s?class=('|").+?('|")/igm, ''); + } + + return pasteHtml; +} + +// 获取粘贴的图片文件 +function getPasteImgs(e) { + var result = []; + var txt = getPasteText(e); + if (txt) { + // 有文字,就忽略图片 + return result; + } + + var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData || {}; + var items = clipboardData.items; + if (!items) { + return result; + } + + objForEach(items, function (key, value) { + var type = value.type; + if (/image/i.test(type)) { + result.push(value.getAsFile()); + } + }); + + return result; +} + +/* + 编辑区域 +*/ + +// 构造函数 +function Text(editor) { + this.editor = editor; +} + +// 修改原型 +Text.prototype = { + constructor: Text, + + // 初始化 + init: function init() { + // 绑定事件 + this._bindEvent(); + }, + + // 清空内容 + clear: function clear() { + this.html('


      '); + }, + + // 获取 设置 html + html: function html(val) { + var editor = this.editor; + var $textElem = editor.$textElem; + if (val == null) { + return $textElem.html(); + } else { + $textElem.html(val); + + // 初始化选取,将光标定位到内容尾部 + editor.initSelection(); + } + }, + + // 获取 设置 text + text: function text(val) { + var editor = this.editor; + var $textElem = editor.$textElem; + if (val == null) { + return $textElem.text(); + } else { + $textElem.text('

      ' + val + '

      '); + + // 初始化选取,将光标定位到内容尾部 + editor.initSelection(); + } + }, + + // 追加内容 + append: function append(html) { + var editor = this.editor; + var $textElem = editor.$textElem; + $textElem.append($(html)); + + // 初始化选取,将光标定位到内容尾部 + editor.initSelection(); + }, + + // 绑定事件 + _bindEvent: function _bindEvent() { + // 实时保存选取 + this._saveRangeRealTime(); + + // 按回车建时的特殊处理 + this._enterKeyHandle(); + + // 清空时保留


      + this._clearHandle(); + + // 粘贴事件(粘贴文字,粘贴图片) + this._pasteHandle(); + + // tab 特殊处理 + this._tabHandle(); + + // img 点击 + this._imgHandle(); + + // 拖拽事件 + this._dragHandle(); + }, + + // 实时保存选取 + _saveRangeRealTime: function _saveRangeRealTime() { + var editor = this.editor; + var $textElem = editor.$textElem; + + // 保存当前的选区 + function saveRange(e) { + // 随时保存选区 + editor.selection.saveRange(); + // 更新按钮 ative 状态 + editor.menus.changeActive(); + } + // 按键后保存 + $textElem.on('keyup', saveRange); + $textElem.on('mousedown', function (e) { + // mousedown 状态下,鼠标滑动到编辑区域外面,也需要保存选区 + $textElem.on('mouseleave', saveRange); + }); + $textElem.on('mouseup', function (e) { + saveRange(); + // 在编辑器区域之内完成点击,取消鼠标滑动到编辑区外面的事件 + $textElem.off('mouseleave', saveRange); + }); + }, + + // 按回车键时的特殊处理 + _enterKeyHandle: function _enterKeyHandle() { + var editor = this.editor; + var $textElem = editor.$textElem; + + // 将回车之后生成的非

      的顶级标签,改为

      + function pHandle(e) { + var $selectionElem = editor.selection.getSelectionContainerElem(); + var $parentElem = $selectionElem.parent(); + if (!$parentElem.equal($textElem)) { + // 不是顶级标签 + return; + } + var nodeName = $selectionElem.getNodeName(); + if (nodeName === 'P') { + // 当前的标签是 P ,不用做处理 + return; + } + + if ($selectionElem.text()) { + // 有内容,不做处理 + return; + } + + // 插入

      ,并将选取定位到

      ,删除当前标签 + var $p = $('


      '); + $p.insertBefore($selectionElem); + editor.selection.createRangeByElem($p, true); + editor.selection.restoreSelection(); + $selectionElem.remove(); + } + + $textElem.on('keyup', function (e) { + if (e.keyCode !== 13) { + // 不是回车键 + return; + } + // 将回车之后生成的非

      的顶级标签,改为

      + pHandle(e); + }); + + //

      回车时 特殊处理 + function codeHandle(e) { + var $selectionElem = editor.selection.getSelectionContainerElem(); + if (!$selectionElem) { + return; + } + var $parentElem = $selectionElem.parent(); + var selectionNodeName = $selectionElem.getNodeName(); + var parentNodeName = $parentElem.getNodeName(); + + if (selectionNodeName !== 'CODE' || parentNodeName !== 'PRE') { + // 不符合要求 忽略 + return; + } + + if (!editor.cmd.queryCommandSupported('insertHTML')) { + // 必须原生支持 insertHTML 命令 + return; + } + + // 处理:光标定位到代码末尾,联系点击两次回车,即跳出代码块 + if (editor._willBreakCode === true) { + // 此时可以跳出代码块 + // 插入

      ,并将选取定位到

      + var $p = $('


      '); + $p.insertAfter($parentElem); + editor.selection.createRangeByElem($p, true); + editor.selection.restoreSelection(); + + // 修改状态 + editor._willBreakCode = false; + + e.preventDefault(); + return; + } + + var _startOffset = editor.selection.getRange().startOffset; + + // 处理:回车时,不能插入
      而是插入 \n ,因为是在 pre 标签里面 + editor.cmd.do('insertHTML', '\n'); + editor.selection.saveRange(); + if (editor.selection.getRange().startOffset === _startOffset) { + // 没起作用,再来一遍 + editor.cmd.do('insertHTML', '\n'); + } + + var codeLength = $selectionElem.html().length; + if (editor.selection.getRange().startOffset + 1 === codeLength) { + // 说明光标在代码最后的位置,执行了回车操作 + // 记录下来,以便下次回车时候跳出 code + editor._willBreakCode = true; + } + + // 阻止默认行为 + e.preventDefault(); + } + + $textElem.on('keydown', function (e) { + if (e.keyCode !== 13) { + // 不是回车键 + // 取消即将跳转代码块的记录 + editor._willBreakCode = false; + return; + } + //
      回车时 特殊处理 + codeHandle(e); + }); + }, + + // 清空时保留


      + _clearHandle: function _clearHandle() { + var editor = this.editor; + var $textElem = editor.$textElem; + + $textElem.on('keydown', function (e) { + if (e.keyCode !== 8) { + return; + } + var txtHtml = $textElem.html().toLowerCase().trim(); + if (txtHtml === '


      ') { + // 最后剩下一个空行,就不再删除了 + e.preventDefault(); + return; + } + }); + + $textElem.on('keyup', function (e) { + if (e.keyCode !== 8) { + return; + } + var $p = void 0; + var txtHtml = $textElem.html().toLowerCase().trim(); + + // firefox 时用 txtHtml === '
      ' 判断,其他用 !txtHtml 判断 + if (!txtHtml || txtHtml === '
      ') { + // 内容空了 + $p = $('


      '); + $textElem.html(''); // 一定要先清空,否则在 firefox 下有问题 + $textElem.append($p); + editor.selection.createRangeByElem($p, false, true); + editor.selection.restoreSelection(); + } + }); + }, + + // 粘贴事件(粘贴文字 粘贴图片) + _pasteHandle: function _pasteHandle() { + var editor = this.editor; + var config = editor.config; + var pasteFilterStyle = config.pasteFilterStyle; + var pasteTextHandle = config.pasteTextHandle; + var $textElem = editor.$textElem; + + // 粘贴图片、文本的事件,每次只能执行一个 + // 判断该次粘贴事件是否可以执行 + var pasteTime = 0; + function canDo() { + var now = Date.now(); + var flag = false; + if (now - pasteTime >= 500) { + // 间隔大于 500 ms ,可以执行 + flag = true; + } + pasteTime = now; + return flag; + } + + // 粘贴文字 + $textElem.on('paste', function (e) { + if (UA.isIE()) { + return; + } else { + // 阻止默认行为,使用 execCommand 的粘贴命令 + e.preventDefault(); + } + + // 粘贴图片和文本,只能同时使用一个 + if (!canDo()) { + return; + } + + // 获取粘贴的文字 + var pasteHtml = getPasteHtml(e, pasteFilterStyle); + var pasteText = getPasteText(e); + pasteText = pasteText.replace(/\n/gm, '
      '); + + // 自定义处理粘贴的内容 + if (pasteTextHandle && typeof pasteTextHandle === 'function') { + pasteHtml = '' + (pasteTextHandle(pasteHtml) || ''); + pasteText = '' + (pasteTextHandle(pasteText) || ''); + } + + var $selectionElem = editor.selection.getSelectionContainerElem(); + if (!$selectionElem) { + return; + } + var nodeName = $selectionElem.getNodeName(); + + // code 中只能粘贴纯文本 + if (nodeName === 'CODE' || nodeName === 'PRE') { + editor.cmd.do('insertHTML', '

      ' + pasteText + '

      '); + return; + } + + // 先放开注释,有问题再追查 ———— + // // 表格中忽略,可能会出现异常问题 + // if (nodeName === 'TD' || nodeName === 'TH') { + // return + // } + + if (!pasteHtml) { + return; + } + try { + // firefox 中,获取的 pasteHtml 可能是没有
        包裹的
      • + // 因此执行 insertHTML 会报错 + editor.cmd.do('insertHTML', pasteHtml); + } catch (ex) { + // 此时使用 pasteText 来兼容一下 + editor.cmd.do('insertHTML', '

        ' + pasteText + '

        '); + } + }); + + // 粘贴图片 + $textElem.on('paste', function (e) { + if (UA.isIE()) { + return; + } else { + e.preventDefault(); + } + + // 粘贴图片和文本,只能同时使用一个 + if (!canDo()) { + return; + } + + // 获取粘贴的图片 + var pasteFiles = getPasteImgs(e); + if (!pasteFiles || !pasteFiles.length) { + return; + } + + // 获取当前的元素 + var $selectionElem = editor.selection.getSelectionContainerElem(); + if (!$selectionElem) { + return; + } + var nodeName = $selectionElem.getNodeName(); + + // code 中粘贴忽略 + if (nodeName === 'CODE' || nodeName === 'PRE') { + return; + } + + // 上传图片 + var uploadImg = editor.uploadImg; + uploadImg.uploadImg(pasteFiles); + }); + }, + + // tab 特殊处理 + _tabHandle: function _tabHandle() { + var editor = this.editor; + var $textElem = editor.$textElem; + + $textElem.on('keydown', function (e) { + if (e.keyCode !== 9) { + return; + } + if (!editor.cmd.queryCommandSupported('insertHTML')) { + // 必须原生支持 insertHTML 命令 + return; + } + var $selectionElem = editor.selection.getSelectionContainerElem(); + if (!$selectionElem) { + return; + } + var $parentElem = $selectionElem.parent(); + var selectionNodeName = $selectionElem.getNodeName(); + var parentNodeName = $parentElem.getNodeName(); + + if (selectionNodeName === 'CODE' && parentNodeName === 'PRE') { + //
         里面
        +                editor.cmd.do('insertHTML', '    ');
        +            } else {
        +                // 普通文字
        +                editor.cmd.do('insertHTML', '    ');
        +            }
        +
        +            e.preventDefault();
        +        });
        +    },
        +
        +    // img 点击
        +    _imgHandle: function _imgHandle() {
        +        var editor = this.editor;
        +        var $textElem = editor.$textElem;
        +
        +        // 为图片增加 selected 样式
        +        $textElem.on('click', 'img', function (e) {
        +            var img = this;
        +            var $img = $(img);
        +
        +            // 记录当前点击过的图片
        +            editor._selectedImg = $img;
        +
        +            // 修改选区并 restore ,防止用户此时点击退格键,会删除其他内容
        +            editor.selection.createRangeByElem($img);
        +            editor.selection.restoreSelection();
        +        });
        +
        +        // 去掉图片的 selected 样式
        +        $textElem.on('click  keyup', function (e) {
        +            if (e.target.matches('img')) {
        +                // 点击的是图片,忽略
        +                return;
        +            }
        +            // 删除记录
        +            editor._selectedImg = null;
        +        });
        +    },
        +
        +    // 拖拽事件
        +    _dragHandle: function _dragHandle() {
        +        var editor = this.editor;
        +
        +        // 禁用 document 拖拽事件
        +        var $document = $(document);
        +        $document.on('dragleave drop dragenter dragover', function (e) {
        +            e.preventDefault();
        +        });
        +
        +        // 添加编辑区域拖拽事件
        +        var $textElem = editor.$textElem;
        +        $textElem.on('drop', function (e) {
        +            e.preventDefault();
        +            var files = e.dataTransfer && e.dataTransfer.files;
        +            if (!files || !files.length) {
        +                return;
        +            }
        +
        +            // 上传图片
        +            var uploadImg = editor.uploadImg;
        +            uploadImg.uploadImg(files);
        +        });
        +    }
        +};
        +
        +/*
        +    命令,封装 document.execCommand
        +*/
        +
        +// 构造函数
        +function Command(editor) {
        +    this.editor = editor;
        +}
        +
        +// 修改原型
        +Command.prototype = {
        +    constructor: Command,
        +
        +    // 执行命令
        +    do: function _do(name, value) {
        +        var editor = this.editor;
        +
        +        // 如果无选区,忽略
        +        if (!editor.selection.getRange()) {
        +            return;
        +        }
        +
        +        // 恢复选取
        +        editor.selection.restoreSelection();
        +
        +        // 执行
        +        var _name = '_' + name;
        +        if (this[_name]) {
        +            // 有自定义事件
        +            this[_name](value);
        +        } else {
        +            // 默认 command
        +            this._execCommand(name, value);
        +        }
        +
        +        // 修改菜单状态
        +        editor.menus.changeActive();
        +
        +        // 最后,恢复选取保证光标在原来的位置闪烁
        +        editor.selection.saveRange();
        +        editor.selection.restoreSelection();
        +
        +        // 触发 onchange
        +        editor.change && editor.change();
        +    },
        +
        +    // 自定义 insertHTML 事件
        +    _insertHTML: function _insertHTML(html) {
        +        var editor = this.editor;
        +        var range = editor.selection.getRange();
        +
        +        if (this.queryCommandSupported('insertHTML')) {
        +            // W3C
        +            this._execCommand('insertHTML', html);
        +        } else if (range.insertNode) {
        +            // IE
        +            range.deleteContents();
        +            range.insertNode($(html)[0]);
        +        } else if (range.pasteHTML) {
        +            // IE <= 10
        +            range.pasteHTML(html);
        +        }
        +    },
        +
        +    // 插入 elem
        +    _insertElem: function _insertElem($elem) {
        +        var editor = this.editor;
        +        var range = editor.selection.getRange();
        +
        +        if (range.insertNode) {
        +            range.deleteContents();
        +            range.insertNode($elem[0]);
        +        }
        +    },
        +
        +    // 封装 execCommand
        +    _execCommand: function _execCommand(name, value) {
        +        document.execCommand(name, false, value);
        +    },
        +
        +    // 封装 document.queryCommandValue
        +    queryCommandValue: function queryCommandValue(name) {
        +        return document.queryCommandValue(name);
        +    },
        +
        +    // 封装 document.queryCommandState
        +    queryCommandState: function queryCommandState(name) {
        +        return document.queryCommandState(name);
        +    },
        +
        +    // 封装 document.queryCommandSupported
        +    queryCommandSupported: function queryCommandSupported(name) {
        +        return document.queryCommandSupported(name);
        +    }
        +};
        +
        +/*
        +    selection range API
        +*/
        +
        +// 构造函数
        +function API(editor) {
        +    this.editor = editor;
        +    this._currentRange = null;
        +}
        +
        +// 修改原型
        +API.prototype = {
        +    constructor: API,
        +
        +    // 获取 range 对象
        +    getRange: function getRange() {
        +        return this._currentRange;
        +    },
        +
        +    // 保存选区
        +    saveRange: function saveRange(_range) {
        +        if (_range) {
        +            // 保存已有选区
        +            this._currentRange = _range;
        +            return;
        +        }
        +
        +        // 获取当前的选区
        +        var selection = window.getSelection();
        +        if (selection.rangeCount === 0) {
        +            return;
        +        }
        +        var range = selection.getRangeAt(0);
        +
        +        // 判断选区内容是否在编辑内容之内
        +        var $containerElem = this.getSelectionContainerElem(range);
        +        if (!$containerElem) {
        +            return;
        +        }
        +        var editor = this.editor;
        +        var $textElem = editor.$textElem;
        +        if ($textElem.isContain($containerElem)) {
        +            // 是编辑内容之内的
        +            this._currentRange = range;
        +        }
        +    },
        +
        +    // 折叠选区
        +    collapseRange: function collapseRange(toStart) {
        +        if (toStart == null) {
        +            // 默认为 false
        +            toStart = false;
        +        }
        +        var range = this._currentRange;
        +        if (range) {
        +            range.collapse(toStart);
        +        }
        +    },
        +
        +    // 选中区域的文字
        +    getSelectionText: function getSelectionText() {
        +        var range = this._currentRange;
        +        if (range) {
        +            return this._currentRange.toString();
        +        } else {
        +            return '';
        +        }
        +    },
        +
        +    // 选区的 $Elem
        +    getSelectionContainerElem: function getSelectionContainerElem(range) {
        +        range = range || this._currentRange;
        +        var elem = void 0;
        +        if (range) {
        +            elem = range.commonAncestorContainer;
        +            return $(elem.nodeType === 1 ? elem : elem.parentNode);
        +        }
        +    },
        +    getSelectionStartElem: function getSelectionStartElem(range) {
        +        range = range || this._currentRange;
        +        var elem = void 0;
        +        if (range) {
        +            elem = range.startContainer;
        +            return $(elem.nodeType === 1 ? elem : elem.parentNode);
        +        }
        +    },
        +    getSelectionEndElem: function getSelectionEndElem(range) {
        +        range = range || this._currentRange;
        +        var elem = void 0;
        +        if (range) {
        +            elem = range.endContainer;
        +            return $(elem.nodeType === 1 ? elem : elem.parentNode);
        +        }
        +    },
        +
        +    // 选区是否为空
        +    isSelectionEmpty: function isSelectionEmpty() {
        +        var range = this._currentRange;
        +        if (range && range.startContainer) {
        +            if (range.startContainer === range.endContainer) {
        +                if (range.startOffset === range.endOffset) {
        +                    return true;
        +                }
        +            }
        +        }
        +        return false;
        +    },
        +
        +    // 恢复选区
        +    restoreSelection: function restoreSelection() {
        +        var selection = window.getSelection();
        +        selection.removeAllRanges();
        +        selection.addRange(this._currentRange);
        +    },
        +
        +    // 创建一个空白(即 ​ 字符)选区
        +    createEmptyRange: function createEmptyRange() {
        +        var editor = this.editor;
        +        var range = this.getRange();
        +        var $elem = void 0;
        +
        +        if (!range) {
        +            // 当前无 range
        +            return;
        +        }
        +        if (!this.isSelectionEmpty()) {
        +            // 当前选区必须没有内容才可以
        +            return;
        +        }
        +
        +        try {
        +            // 目前只支持 webkit 内核
        +            if (UA.isWebkit()) {
        +                // 插入 ​
        +                editor.cmd.do('insertHTML', '​');
        +                // 修改 offset 位置
        +                range.setEnd(range.endContainer, range.endOffset + 1);
        +                // 存储
        +                this.saveRange(range);
        +            } else {
        +                $elem = $('');
        +                editor.cmd.do('insertElem', $elem);
        +                this.createRangeByElem($elem, true);
        +            }
        +        } catch (ex) {
        +            // 部分情况下会报错,兼容一下
        +        }
        +    },
        +
        +    // 根据 $Elem 设置选区
        +    createRangeByElem: function createRangeByElem($elem, toStart, isContent) {
        +        // $elem - 经过封装的 elem
        +        // toStart - true 开始位置,false 结束位置
        +        // isContent - 是否选中Elem的内容
        +        if (!$elem.length) {
        +            return;
        +        }
        +
        +        var elem = $elem[0];
        +        var range = document.createRange();
        +
        +        if (isContent) {
        +            range.selectNodeContents(elem);
        +        } else {
        +            range.selectNode(elem);
        +        }
        +
        +        if (typeof toStart === 'boolean') {
        +            range.collapse(toStart);
        +        }
        +
        +        // 存储 range
        +        this.saveRange(range);
        +    }
        +};
        +
        +/*
        +    上传进度条
        +*/
        +
        +function Progress(editor) {
        +    this.editor = editor;
        +    this._time = 0;
        +    this._isShow = false;
        +    this._isRender = false;
        +    this._timeoutId = 0;
        +    this.$textContainer = editor.$textContainerElem;
        +    this.$bar = $('
        '); +} + +Progress.prototype = { + constructor: Progress, + + show: function show(progress) { + var _this = this; + + // 状态处理 + if (this._isShow) { + return; + } + this._isShow = true; + + // 渲染 + var $bar = this.$bar; + if (!this._isRender) { + var $textContainer = this.$textContainer; + $textContainer.append($bar); + } else { + this._isRender = true; + } + + // 改变进度(节流,100ms 渲染一次) + if (Date.now() - this._time > 100) { + if (progress <= 1) { + $bar.css('width', progress * 100 + '%'); + this._time = Date.now(); + } + } + + // 隐藏 + var timeoutId = this._timeoutId; + if (timeoutId) { + clearTimeout(timeoutId); + } + timeoutId = setTimeout(function () { + _this._hide(); + }, 500); + }, + + _hide: function _hide() { + var $bar = this.$bar; + $bar.remove(); + + // 修改状态 + this._time = 0; + this._isShow = false; + this._isRender = false; + } +}; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; +} : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; +}; + +/* + 上传图片 +*/ + +// 构造函数 +function UploadImg(editor) { + this.editor = editor; +} + +// 原型 +UploadImg.prototype = { + constructor: UploadImg, + + // 根据 debug 弹出不同的信息 + _alert: function _alert(alertInfo, debugInfo) { + var editor = this.editor; + var debug = editor.config.debug; + var customAlert = editor.config.customAlert; + + if (debug) { + throw new Error('wangEditor: ' + (debugInfo || alertInfo)); + } else { + if (customAlert && typeof customAlert === 'function') { + customAlert(alertInfo); + } else { + alert(alertInfo); + } + } + }, + + // 根据链接插入图片 + insertLinkImg: function insertLinkImg(link) { + var _this2 = this; + + if (!link) { + return; + } + var editor = this.editor; + var config = editor.config; + + // 校验格式 + var linkImgCheck = config.linkImgCheck; + var checkResult = void 0; + if (linkImgCheck && typeof linkImgCheck === 'function') { + checkResult = linkImgCheck(link); + if (typeof checkResult === 'string') { + // 校验失败,提示信息 + alert(checkResult); + return; + } + } + + editor.cmd.do('insertHTML', ''); + + // 验证图片 url 是否有效,无效的话给出提示 + var img = document.createElement('img'); + img.onload = function () { + var callback = config.linkImgCallback; + if (callback && typeof callback === 'function') { + callback(link); + } + + img = null; + }; + img.onerror = function () { + img = null; + // 无法成功下载图片 + _this2._alert('插入图片错误', 'wangEditor: \u63D2\u5165\u56FE\u7247\u51FA\u9519\uFF0C\u56FE\u7247\u94FE\u63A5\u662F "' + link + '"\uFF0C\u4E0B\u8F7D\u8BE5\u94FE\u63A5\u5931\u8D25'); + return; + }; + img.onabort = function () { + img = null; + }; + img.src = link; + }, + + // 上传图片 + uploadImg: function uploadImg(files) { + var _this3 = this; + + if (!files || !files.length) { + return; + } + + // ------------------------------ 获取配置信息 ------------------------------ + var editor = this.editor; + var config = editor.config; + var uploadImgServer = config.uploadImgServer; + var uploadImgShowBase64 = config.uploadImgShowBase64; + + var maxSize = config.uploadImgMaxSize; + var maxSizeM = maxSize / 1000 / 1000; + var maxLength = config.uploadImgMaxLength || 10000; + var uploadFileName = config.uploadFileName || ''; + var uploadImgParams = config.uploadImgParams || {}; + var uploadImgParamsWithUrl = config.uploadImgParamsWithUrl; + var uploadImgHeaders = config.uploadImgHeaders || {}; + var hooks = config.uploadImgHooks || {}; + var timeout = config.uploadImgTimeout || 3000; + var withCredentials = config.withCredentials; + if (withCredentials == null) { + withCredentials = false; + } + var customUploadImg = config.customUploadImg; + + if (!customUploadImg) { + // 没有 customUploadImg 的情况下,需要如下两个配置才能继续进行图片上传 + if (!uploadImgServer && !uploadImgShowBase64) { + return; + } + } + + // ------------------------------ 验证文件信息 ------------------------------ + var resultFiles = []; + var errInfo = []; + arrForEach(files, function (file) { + var name = file.name; + var size = file.size; + + // chrome 低版本 name === undefined + if (!name || !size) { + return; + } + + if (/\.(jpg|jpeg|png|bmp|gif)$/i.test(name) === false) { + // 后缀名不合法,不是图片 + errInfo.push('\u3010' + name + '\u3011\u4E0D\u662F\u56FE\u7247'); + return; + } + if (maxSize < size) { + // 上传图片过大 + errInfo.push('\u3010' + name + '\u3011\u5927\u4E8E ' + maxSizeM + 'M'); + return; + } + + // 验证通过的加入结果列表 + resultFiles.push(file); + }); + // 抛出验证信息 + if (errInfo.length) { + this._alert('图片验证未通过: \n' + errInfo.join('\n')); + return; + } + if (resultFiles.length > maxLength) { + this._alert('一次最多上传' + maxLength + '张图片'); + return; + } + + // ------------------------------ 自定义上传 ------------------------------ + if (customUploadImg && typeof customUploadImg === 'function') { + customUploadImg(resultFiles, this.insertLinkImg.bind(this)); + + // 阻止以下代码执行 + return; + } + + // 添加图片数据 + var formdata = new FormData(); + arrForEach(resultFiles, function (file) { + var name = uploadFileName || file.name; + formdata.append(name, file); + }); + + // ------------------------------ 上传图片 ------------------------------ + if (uploadImgServer && typeof uploadImgServer === 'string') { + // 添加参数 + var uploadImgServerArr = uploadImgServer.split('#'); + uploadImgServer = uploadImgServerArr[0]; + var uploadImgServerHash = uploadImgServerArr[1] || ''; + objForEach(uploadImgParams, function (key, val) { + val = encodeURIComponent(val); + + // 第一,将参数拼接到 url 中 + if (uploadImgParamsWithUrl) { + if (uploadImgServer.indexOf('?') > 0) { + uploadImgServer += '&'; + } else { + uploadImgServer += '?'; + } + uploadImgServer = uploadImgServer + key + '=' + val; + } + + // 第二,将参数添加到 formdata 中 + formdata.append(key, val); + }); + if (uploadImgServerHash) { + uploadImgServer += '#' + uploadImgServerHash; + } + + // 定义 xhr + var xhr = new XMLHttpRequest(); + xhr.open('POST', uploadImgServer); + + // 设置超时 + xhr.timeout = timeout; + xhr.ontimeout = function () { + // hook - timeout + if (hooks.timeout && typeof hooks.timeout === 'function') { + hooks.timeout(xhr, editor); + } + + _this3._alert('上传图片超时'); + }; + + // 监控 progress + if (xhr.upload) { + xhr.upload.onprogress = function (e) { + var percent = void 0; + // 进度条 + var progressBar = new Progress(editor); + if (e.lengthComputable) { + percent = e.loaded / e.total; + progressBar.show(percent); + } + }; + } + + // 返回数据 + xhr.onreadystatechange = function () { + var result = void 0; + if (xhr.readyState === 4) { + if (xhr.status < 200 || xhr.status >= 300) { + // hook - error + if (hooks.error && typeof hooks.error === 'function') { + hooks.error(xhr, editor); + } + + // xhr 返回状态错误 + _this3._alert('上传图片发生错误', '\u4E0A\u4F20\u56FE\u7247\u53D1\u751F\u9519\u8BEF\uFF0C\u670D\u52A1\u5668\u8FD4\u56DE\u72B6\u6001\u662F ' + xhr.status); + return; + } + + result = xhr.responseText; + if ((typeof result === 'undefined' ? 'undefined' : _typeof(result)) !== 'object') { + try { + result = JSON.parse(result); + } catch (ex) { + // hook - fail + if (hooks.fail && typeof hooks.fail === 'function') { + hooks.fail(xhr, editor, result); + } + + _this3._alert('上传图片失败', '上传图片返回结果错误,返回结果是: ' + result); + return; + } + } + if (!hooks.customInsert && result.errno != '0') { + // hook - fail + if (hooks.fail && typeof hooks.fail === 'function') { + hooks.fail(xhr, editor, result); + } + + // 数据错误 + _this3._alert('上传图片失败', '上传图片返回结果错误,返回结果 errno=' + result.errno); + } else { + if (hooks.customInsert && typeof hooks.customInsert === 'function') { + // 使用者自定义插入方法 + hooks.customInsert(_this3.insertLinkImg.bind(_this3), result, editor); + } else { + // 将图片插入编辑器 + var data = result.data || []; + data.forEach(function (link) { + _this3.insertLinkImg(link); + }); + } + + // hook - success + if (hooks.success && typeof hooks.success === 'function') { + hooks.success(xhr, editor, result); + } + } + } + }; + + // hook - before + if (hooks.before && typeof hooks.before === 'function') { + var beforeResult = hooks.before(xhr, editor, resultFiles); + if (beforeResult && (typeof beforeResult === 'undefined' ? 'undefined' : _typeof(beforeResult)) === 'object') { + if (beforeResult.prevent) { + // 如果返回的结果是 {prevent: true, msg: 'xxxx'} 则表示用户放弃上传 + this._alert(beforeResult.msg); + return; + } + } + } + + // 自定义 headers + objForEach(uploadImgHeaders, function (key, val) { + xhr.setRequestHeader(key, val); + }); + + // 跨域传 cookie + xhr.withCredentials = withCredentials; + + // 发送请求 + xhr.send(formdata); + + // 注意,要 return 。不去操作接下来的 base64 显示方式 + return; + } + + // ------------------------------ 显示 base64 格式 ------------------------------ + if (uploadImgShowBase64) { + arrForEach(files, function (file) { + var _this = _this3; + var reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = function () { + _this.insertLinkImg(this.result); + }; + }); + } + } +}; + +/* + 编辑器构造函数 +*/ + +// id,累加 +var editorId = 1; + +// 构造函数 +function Editor(toolbarSelector, textSelector) { + if (toolbarSelector == null) { + // 没有传入任何参数,报错 + throw new Error('错误:初始化编辑器时候未传入任何参数,请查阅文档'); + } + // id,用以区分单个页面不同的编辑器对象 + this.id = 'wangEditor-' + editorId++; + + this.toolbarSelector = toolbarSelector; + this.textSelector = textSelector; + + // 自定义配置 + this.customConfig = {}; +} + +// 修改原型 +Editor.prototype = { + constructor: Editor, + + // 初始化配置 + _initConfig: function _initConfig() { + // _config 是默认配置,this.customConfig 是用户自定义配置,将它们 merge 之后再赋值 + var target = {}; + this.config = Object.assign(target, config, this.customConfig); + + // 将语言配置,生成正则表达式 + var langConfig = this.config.lang || {}; + var langArgs = []; + objForEach(langConfig, function (key, val) { + // key 即需要生成正则表达式的规则,如“插入链接” + // val 即需要被替换成的语言,如“insert link” + langArgs.push({ + reg: new RegExp(key, 'img'), + val: val + + }); + }); + this.config.langArgs = langArgs; + }, + + // 初始化 DOM + _initDom: function _initDom() { + var _this = this; + + var toolbarSelector = this.toolbarSelector; + var $toolbarSelector = $(toolbarSelector); + var textSelector = this.textSelector; + + var config$$1 = this.config; + var zIndex = config$$1.zIndex; + + // 定义变量 + var $toolbarElem = void 0, + $textContainerElem = void 0, + $textElem = void 0, + $children = void 0; + + if (textSelector == null) { + // 只传入一个参数,即是容器的选择器或元素,toolbar 和 text 的元素自行创建 + $toolbarElem = $('
        '); + $textContainerElem = $('
        '); + + // 将编辑器区域原有的内容,暂存起来 + $children = $toolbarSelector.children(); + + // 添加到 DOM 结构中 + $toolbarSelector.append($toolbarElem).append($textContainerElem); + + // 自行创建的,需要配置默认的样式 + $toolbarElem.css('background-color', '#f1f1f1').css('border', '1px solid #ccc'); + $textContainerElem.css('border', '1px solid #ccc').css('border-top', 'none').css('height', '300px'); + } else { + // toolbar 和 text 的选择器都有值,记录属性 + $toolbarElem = $toolbarSelector; + $textContainerElem = $(textSelector); + // 将编辑器区域原有的内容,暂存起来 + $children = $textContainerElem.children(); + } + + // 编辑区域 + $textElem = $('
        '); + $textElem.attr('contenteditable', 'true').css('width', '100%').css('height', '100%'); + + // 初始化编辑区域内容 + if ($children && $children.length) { + $textElem.append($children); + } else { + $textElem.append($('


        ')); + } + + // 编辑区域加入DOM + $textContainerElem.append($textElem); + + // 设置通用的 class + $toolbarElem.addClass('w-e-toolbar'); + $textContainerElem.addClass('w-e-text-container'); + $textContainerElem.css('z-index', zIndex); + $textElem.addClass('w-e-text'); + + // 添加 ID + var toolbarElemId = getRandom('toolbar-elem'); + $toolbarElem.attr('id', toolbarElemId); + var textElemId = getRandom('text-elem'); + $textElem.attr('id', textElemId); + + // 记录属性 + this.$toolbarElem = $toolbarElem; + this.$textContainerElem = $textContainerElem; + this.$textElem = $textElem; + this.toolbarElemId = toolbarElemId; + this.textElemId = textElemId; + + // 绑定 onchange + $textContainerElem.on('click keyup', function () { + _this.change && _this.change(); + }); + $toolbarElem.on('click', function () { + this.change && this.change(); + }); + + //绑定 onfocus 与 onblur 事件 + if (config$$1.onfocus || config$$1.onblur) { + // 当前编辑器是否是焦点状态 + this.isFocus = false; + + $(document).on('click', function (e) { + //判断当前点击元素是否在编辑器内 + var isChild = $toolbarSelector.isContain($(e.target)); + + if (!isChild) { + if (_this.isFocus) { + _this.onblur && _this.onblur(); + } + _this.isFocus = false; + } else { + if (!_this.isFocus) { + _this.onfocus && _this.onfocus(); + } + _this.isFocus = true; + } + }); + } + }, + + // 封装 command + _initCommand: function _initCommand() { + this.cmd = new Command(this); + }, + + // 封装 selection range API + _initSelectionAPI: function _initSelectionAPI() { + this.selection = new API(this); + }, + + // 添加图片上传 + _initUploadImg: function _initUploadImg() { + this.uploadImg = new UploadImg(this); + }, + + // 初始化菜单 + _initMenus: function _initMenus() { + this.menus = new Menus(this); + this.menus.init(); + }, + + // 添加 text 区域 + _initText: function _initText() { + this.txt = new Text(this); + this.txt.init(); + }, + + // 初始化选区,将光标定位到内容尾部 + initSelection: function initSelection(newLine) { + var $textElem = this.$textElem; + var $children = $textElem.children(); + if (!$children.length) { + // 如果编辑器区域无内容,添加一个空行,重新设置选区 + $textElem.append($('


        ')); + this.initSelection(); + return; + } + + var $last = $children.last(); + + if (newLine) { + // 新增一个空行 + var html = $last.html().toLowerCase(); + var nodeName = $last.getNodeName(); + if (html !== '
        ' && html !== '' || nodeName !== 'P') { + // 最后一个元素不是


        ,添加一个空行,重新设置选区 + $textElem.append($('


        ')); + this.initSelection(); + return; + } + } + + this.selection.createRangeByElem($last, false, true); + this.selection.restoreSelection(); + }, + + // 绑定事件 + _bindEvent: function _bindEvent() { + // -------- 绑定 onchange 事件 -------- + var onChangeTimeoutId = 0; + var beforeChangeHtml = this.txt.html(); + var config$$1 = this.config; + + // onchange 触发延迟时间 + var onchangeTimeout = config$$1.onchangeTimeout; + onchangeTimeout = parseInt(onchangeTimeout, 10); + if (!onchangeTimeout || onchangeTimeout <= 0) { + onchangeTimeout = 200; + } + + var onchange = config$$1.onchange; + if (onchange && typeof onchange === 'function') { + // 触发 change 的有三个场景: + // 1. $textContainerElem.on('click keyup') + // 2. $toolbarElem.on('click') + // 3. editor.cmd.do() + this.change = function () { + // 判断是否有变化 + var currentHtml = this.txt.html(); + + if (currentHtml.length === beforeChangeHtml.length) { + // 需要比较每一个字符 + if (currentHtml === beforeChangeHtml) { + return; + } + } + + // 执行,使用节流 + if (onChangeTimeoutId) { + clearTimeout(onChangeTimeoutId); + } + onChangeTimeoutId = setTimeout(function () { + // 触发配置的 onchange 函数 + onchange(currentHtml); + beforeChangeHtml = currentHtml; + }, onchangeTimeout); + }; + } + + // -------- 绑定 onblur 事件 -------- + var onblur = config$$1.onblur; + if (onblur && typeof onblur === 'function') { + this.onblur = function () { + var currentHtml = this.txt.html(); + onblur(currentHtml); + }; + } + + // -------- 绑定 onfocus 事件 -------- + var onfocus = config$$1.onfocus; + if (onfocus && typeof onfocus === 'function') { + this.onfocus = function () { + onfocus(); + }; + } + }, + + // 创建编辑器 + create: function create() { + // 初始化配置信息 + this._initConfig(); + + // 初始化 DOM + this._initDom(); + + // 封装 command API + this._initCommand(); + + // 封装 selection range API + this._initSelectionAPI(); + + // 添加 text + this._initText(); + + // 初始化菜单 + this._initMenus(); + + // 添加 图片上传 + this._initUploadImg(); + + // 初始化选区,将光标定位到内容尾部 + this.initSelection(true); + + // 绑定事件 + this._bindEvent(); + } +}; + +// 检验是否浏览器环境 +try { + document; +} catch (ex) { + throw new Error('请在浏览器环境下运行'); +} + +// polyfill +polyfill(); + +// 这里的 `inlinecss` 将被替换成 css 代码的内容,详情可去 ./gulpfile.js 中搜索 `inlinecss` 关键字 +var inlinecss = '.w-e-toolbar,.w-e-text-container,.w-e-menu-panel { padding: 0; margin: 0; box-sizing: border-box;}.w-e-toolbar *,.w-e-text-container *,.w-e-menu-panel * { padding: 0; margin: 0; box-sizing: border-box;}.w-e-clear-fix:after { content: ""; display: table; clear: both;}.w-e-toolbar .w-e-droplist { position: absolute; left: 0; top: 0; background-color: #fff; border: 1px solid #f1f1f1; border-right-color: #ccc; border-bottom-color: #ccc;}.w-e-toolbar .w-e-droplist .w-e-dp-title { text-align: center; color: #999; line-height: 2; border-bottom: 1px solid #f1f1f1; font-size: 13px;}.w-e-toolbar .w-e-droplist ul.w-e-list { list-style: none; line-height: 1;}.w-e-toolbar .w-e-droplist ul.w-e-list li.w-e-item { color: #333; padding: 5px 0;}.w-e-toolbar .w-e-droplist ul.w-e-list li.w-e-item:hover { background-color: #f1f1f1;}.w-e-toolbar .w-e-droplist ul.w-e-block { list-style: none; text-align: left; padding: 5px;}.w-e-toolbar .w-e-droplist ul.w-e-block li.w-e-item { display: inline-block; *display: inline; *zoom: 1; padding: 3px 5px;}.w-e-toolbar .w-e-droplist ul.w-e-block li.w-e-item:hover { background-color: #f1f1f1;}@font-face { font-family: \'w-e-icon\'; src: url(data:application/x-font-woff;charset=utf-8;base64,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) format(\'truetype\'); font-weight: normal; font-style: normal;}[class^="w-e-icon-"],[class*=" w-e-icon-"] { /* use !important to prevent issues with browser extensions that change fonts */ font-family: \'w-e-icon\' !important; speak: none; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;}.w-e-icon-close:before { content: "\\f00d";}.w-e-icon-upload2:before { content: "\\e9c6";}.w-e-icon-trash-o:before { content: "\\f014";}.w-e-icon-header:before { content: "\\f1dc";}.w-e-icon-pencil2:before { content: "\\e906";}.w-e-icon-paint-brush:before { content: "\\f1fc";}.w-e-icon-image:before { content: "\\e90d";}.w-e-icon-play:before { content: "\\e912";}.w-e-icon-location:before { content: "\\e947";}.w-e-icon-undo:before { content: "\\e965";}.w-e-icon-redo:before { content: "\\e966";}.w-e-icon-quotes-left:before { content: "\\e977";}.w-e-icon-list-numbered:before { content: "\\e9b9";}.w-e-icon-list2:before { content: "\\e9bb";}.w-e-icon-link:before { content: "\\e9cb";}.w-e-icon-happy:before { content: "\\e9df";}.w-e-icon-bold:before { content: "\\ea62";}.w-e-icon-underline:before { content: "\\ea63";}.w-e-icon-italic:before { content: "\\ea64";}.w-e-icon-strikethrough:before { content: "\\ea65";}.w-e-icon-table2:before { content: "\\ea71";}.w-e-icon-paragraph-left:before { content: "\\ea77";}.w-e-icon-paragraph-center:before { content: "\\ea78";}.w-e-icon-paragraph-right:before { content: "\\ea79";}.w-e-icon-terminal:before { content: "\\f120";}.w-e-icon-page-break:before { content: "\\ea68";}.w-e-icon-cancel-circle:before { content: "\\ea0d";}.w-e-toolbar { display: -webkit-box; display: -ms-flexbox; display: flex; padding: 0 5px; /* 单个菜单 */}.w-e-toolbar .w-e-menu { position: relative; text-align: center; padding: 5px 10px; cursor: pointer;}.w-e-toolbar .w-e-menu i { color: #999;}.w-e-toolbar .w-e-menu:hover i { color: #333;}.w-e-toolbar .w-e-active i { color: #1e88e5;}.w-e-toolbar .w-e-active:hover i { color: #1e88e5;}.w-e-text-container .w-e-panel-container { position: absolute; top: 0; left: 50%; border: 1px solid #ccc; border-top: 0; box-shadow: 1px 1px 2px #ccc; color: #333; background-color: #fff; /* 为 emotion panel 定制的样式 */ /* 上传图片的 panel 定制样式 */}.w-e-text-container .w-e-panel-container .w-e-panel-close { position: absolute; right: 0; top: 0; padding: 5px; margin: 2px 5px 0 0; cursor: pointer; color: #999;}.w-e-text-container .w-e-panel-container .w-e-panel-close:hover { color: #333;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-title { list-style: none; display: -webkit-box; display: -ms-flexbox; display: flex; font-size: 14px; margin: 2px 10px 0 10px; border-bottom: 1px solid #f1f1f1;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-title .w-e-item { padding: 3px 5px; color: #999; cursor: pointer; margin: 0 3px; position: relative; top: 1px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-title .w-e-active { color: #333; border-bottom: 1px solid #333; cursor: default; font-weight: 700;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content { padding: 10px 15px 10px 15px; font-size: 16px; /* 输入框的样式 */ /* 按钮的样式 */}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input:focus,.w-e-text-container .w-e-panel-container .w-e-panel-tab-content textarea:focus,.w-e-text-container .w-e-panel-container .w-e-panel-tab-content button:focus { outline: none;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content textarea { width: 100%; border: 1px solid #ccc; padding: 5px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content textarea:focus { border-color: #1e88e5;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text] { border: none; border-bottom: 1px solid #ccc; font-size: 14px; height: 20px; color: #333; text-align: left;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text].small { width: 30px; text-align: center;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text].block { display: block; width: 100%; margin: 10px 0;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text]:focus { border-bottom: 2px solid #1e88e5;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button { font-size: 14px; color: #1e88e5; border: none; padding: 5px 10px; background-color: #fff; cursor: pointer; border-radius: 3px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.left { float: left; margin-right: 10px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.right { float: right; margin-left: 10px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.gray { color: #999;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.red { color: #c24f4a;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button:hover { background-color: #f1f1f1;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container:after { content: ""; display: table; clear: both;}.w-e-text-container .w-e-panel-container .w-e-emoticon-container .w-e-item { cursor: pointer; font-size: 18px; padding: 0 3px; display: inline-block; *display: inline; *zoom: 1;}.w-e-text-container .w-e-panel-container .w-e-up-img-container { text-align: center;}.w-e-text-container .w-e-panel-container .w-e-up-img-container .w-e-up-btn { display: inline-block; *display: inline; *zoom: 1; color: #999; cursor: pointer; font-size: 60px; line-height: 1;}.w-e-text-container .w-e-panel-container .w-e-up-img-container .w-e-up-btn:hover { color: #333;}.w-e-text-container { position: relative;}.w-e-text-container .w-e-progress { position: absolute; background-color: #1e88e5; bottom: 0; left: 0; height: 1px;}.w-e-text { padding: 0 10px; overflow-y: scroll;}.w-e-text p,.w-e-text h1,.w-e-text h2,.w-e-text h3,.w-e-text h4,.w-e-text h5,.w-e-text table,.w-e-text pre { margin: 10px 0; line-height: 1.5;}.w-e-text ul,.w-e-text ol { margin: 10px 0 10px 20px;}.w-e-text blockquote { display: block; border-left: 8px solid #d0e5f2; padding: 5px 10px; margin: 10px 0; line-height: 1.4; font-size: 100%; background-color: #f1f1f1;}.w-e-text code { display: inline-block; *display: inline; *zoom: 1; background-color: #f1f1f1; border-radius: 3px; padding: 3px 5px; margin: 0 3px;}.w-e-text pre code { display: block;}.w-e-text table { border-top: 1px solid #ccc; border-left: 1px solid #ccc;}.w-e-text table td,.w-e-text table th { border-bottom: 1px solid #ccc; border-right: 1px solid #ccc; padding: 3px 5px;}.w-e-text table th { border-bottom: 2px solid #ccc; text-align: center;}.w-e-text:focus { outline: none;}.w-e-text img { cursor: pointer;}.w-e-text img:hover { box-shadow: 0 0 5px #333;}'; + +// 将 css 代码添加到