From 4986ccd6c798230b7a2f3d6a0caeb247ab4801e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=9C=E7=9A=87?= Date: Thu, 17 May 2018 22:06:23 +0800 Subject: [PATCH] add application attachments --- .../crm/controller/ApplicationController.java | 27 +- .../crm/service/ApplicationService.java | 26 +- .../support/ApplicationServiceSupport.java | 240 ++++++++++++------ .../main/resources/application_dev.properties | 3 +- .../mapper/ApplicationAttachmentMapper.java | 14 + .../mapper/ApplicationAttachmentMapper.xml | 25 ++ .../crm/mapper/ApplicationFileMapper.java | 38 +++ .../crm/mapper/ApplicationFileMapper.xml | 44 ++++ .../crm/mapper/ApplicationImageMapper.java | 31 +-- .../crm/mapper/ApplicationImageMapper.xml | 25 +- .../ambition/crm/mapper/ApplicationMapper.xml | 13 +- .../src/main/resources/spring/spring-app.xml | 6 +- .../ApplicationAttachmentFormForCreate.java | 27 ++ .../form/create/ApplicationFormForCreate.java | 2 + .../ApplicationAttachmentFormForUpdate.java | 15 ++ .../update/ApplicationFileFormForUpdate.java | 20 ++ .../update/ApplicationImageFormForUpdate.java | 7 +- .../ambition/crm/model/Application.java | 4 + .../crm/model/ApplicationAttachment.java | 17 ++ .../ambition/crm/model/ApplicationFile.java | 28 ++ .../ambition/crm/model/ApplicationImage.java | 15 +- web/app/components/file-input.js | 51 ++++ web/app/routes/customer-application/create.js | 13 +- web/app/routes/customer-application/edit.js | 88 +++++-- .../customer-application/preview-btn.hbs | 16 ++ web/app/templates/components/file-input.hbs | 1 + web/app/templates/components/form-input.hbs | 4 +- .../templates/customer-application/create.hbs | 28 ++ .../templates/customer-application/edit.hbs | 89 ++++++- .../integration/components/file-input-test.js | 24 ++ 30 files changed, 753 insertions(+), 188 deletions(-) create mode 100644 server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationAttachmentMapper.java create mode 100644 server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationAttachmentMapper.xml create mode 100644 server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationFileMapper.java create mode 100644 server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationFileMapper.xml create mode 100644 server/model/src/main/java/com/pudonghot/ambition/crm/form/create/ApplicationAttachmentFormForCreate.java create mode 100644 server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationAttachmentFormForUpdate.java create mode 100644 server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationFileFormForUpdate.java create mode 100644 server/model/src/main/java/com/pudonghot/ambition/crm/model/ApplicationAttachment.java create mode 100644 server/model/src/main/java/com/pudonghot/ambition/crm/model/ApplicationFile.java create mode 100644 web/app/components/file-input.js create mode 100644 web/app/templates/components/file-input.hbs create mode 100644 web/tests/integration/components/file-input-test.js diff --git a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/ApplicationController.java b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/ApplicationController.java index b3125d3..81d6151 100644 --- a/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/ApplicationController.java +++ b/server/crm/src/main/java/com/pudonghot/ambition/crm/controller/ApplicationController.java @@ -9,16 +9,13 @@ import javax.validation.constraints.Max; import javax.validation.constraints.Min; import me.chyxion.tigon.model.ViewModel; import me.chyxion.tigon.model.ListResult; +import com.pudonghot.ambition.crm.model.*; import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.StringUtils; -import com.pudonghot.ambition.crm.model.User; import org.springframework.stereotype.Controller; -import com.pudonghot.ambition.crm.model.Application; import org.hibernate.validator.constraints.NotBlank; import com.pudonghot.ambition.crm.service.UserService; import org.apache.shiro.authz.annotation.RequiresRoles; -import com.pudonghot.ambition.crm.model.CustomerProperty; -import com.pudonghot.ambition.crm.model.ApplicationImage; import org.springframework.web.bind.annotation.RequestParam; import com.pudonghot.ambition.crm.service.ApplicationService; import org.springframework.web.bind.annotation.RequestMethod; @@ -28,6 +25,8 @@ import com.pudonghot.ambition.crm.form.create.ApplicationFormForCreate; import com.pudonghot.ambition.crm.form.update.ApplicationFormForUpdate; import com.pudonghot.ambition.crm.form.create.ApplicationImageFormForCreate; import com.pudonghot.ambition.crm.form.update.ApplicationImageFormForUpdate; +import com.pudonghot.ambition.crm.form.create.ApplicationAttachmentFormForCreate; +import com.pudonghot.ambition.crm.form.update.ApplicationAttachmentFormForUpdate; /** * @author Shaun Chyxion
@@ -129,6 +128,26 @@ public class ApplicationController ((ApplicationService) queryService).updateImage(form); } + @RequestMapping(value = "/add-attachment", method = RequestMethod.POST) + public ViewModel addAttachment( + @Valid ApplicationAttachmentFormForCreate form) { + Assert.state(!form.getAttachment().isEmpty(), "Image content is empty"); + form.setAdmin(getAuthUser().getUser().getData().isAdmin()); + return new ViewModel<>(((ApplicationService) queryService).addAttachment(form)); + } + + @RequestMapping(value = "/remove-attachment", method = RequestMethod.POST) + public void removeAttachment(@NotBlank @RequestParam("id") String id) { + final User user = getAuthUser().getUser().getData(); + ((ApplicationService) queryService).removeAttachment(id, user.getId(), user.isAdmin()); + } + + @RequestMapping(value = "/update-attachment", method = RequestMethod.POST) + public void updateImage(@Valid ApplicationAttachmentFormForUpdate form) { + form.setAdmin(getAuthUser().getUser().getData().isAdmin()); + ((ApplicationService) queryService).updateAttachment(form); + } + @RequiresRoles(User.ROLE_ADMIN) @RequestMapping(value = "/delete", method = RequestMethod.POST) public void delete(@NotBlank @RequestParam("id") String id) { diff --git a/server/crm/src/main/java/com/pudonghot/ambition/crm/service/ApplicationService.java b/server/crm/src/main/java/com/pudonghot/ambition/crm/service/ApplicationService.java index 0eb8bd5..5c47523 100644 --- a/server/crm/src/main/java/com/pudonghot/ambition/crm/service/ApplicationService.java +++ b/server/crm/src/main/java/com/pudonghot/ambition/crm/service/ApplicationService.java @@ -2,13 +2,16 @@ package com.pudonghot.ambition.crm.service; import javax.validation.Valid; import com.pudonghot.ambition.crm.model.Application; -import com.pudonghot.ambition.crm.model.ApplicationImage; import org.hibernate.validator.constraints.NotBlank; import me.chyxion.tigon.service.BaseCrudByFormService; +import com.pudonghot.ambition.crm.model.ApplicationImage; +import com.pudonghot.ambition.crm.model.ApplicationAttachment; import com.pudonghot.ambition.crm.form.update.ApplicationFormForUpdate; import com.pudonghot.ambition.crm.form.create.ApplicationFormForCreate; import com.pudonghot.ambition.crm.form.create.ApplicationImageFormForCreate; import com.pudonghot.ambition.crm.form.update.ApplicationImageFormForUpdate; +import com.pudonghot.ambition.crm.form.create.ApplicationAttachmentFormForCreate; +import com.pudonghot.ambition.crm.form.update.ApplicationAttachmentFormForUpdate; /** * @author Shaun Chyxion
@@ -37,5 +40,26 @@ public interface ApplicationService * @param form form */ void updateImage(@Valid ApplicationImageFormForUpdate form); + + + /** + * add attachment + * @param form form + */ + ApplicationAttachment addAttachment(@Valid ApplicationAttachmentFormForCreate form); + + /** + * remove attachment + * @param id attachment id + * @param userId user id + * @param admin admin + */ + void removeAttachment(@NotBlank String id, String userId, boolean admin); + + /** + * update attachment + * @param form form + */ + void updateAttachment(@Valid ApplicationAttachmentFormForUpdate form); } diff --git a/server/crm/src/main/java/com/pudonghot/ambition/crm/service/support/ApplicationServiceSupport.java b/server/crm/src/main/java/com/pudonghot/ambition/crm/service/support/ApplicationServiceSupport.java index ac82cfc..c47562f 100644 --- a/server/crm/src/main/java/com/pudonghot/ambition/crm/service/support/ApplicationServiceSupport.java +++ b/server/crm/src/main/java/com/pudonghot/ambition/crm/service/support/ApplicationServiceSupport.java @@ -7,10 +7,13 @@ import java.io.IOException; import java.io.InputStream; import lombok.extern.slf4j.Slf4j; import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; import me.chyxion.tigon.mybatis.Search; import org.springframework.util.Assert; import me.chyxion.tigon.model.ViewModel; import com.pudonghot.ambition.crm.model.*; +import org.apache.commons.io.FilenameUtils; import com.pudonghot.ambition.crm.mapper.*; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @@ -24,8 +27,11 @@ import org.springframework.transaction.annotation.Transactional; import com.pudonghot.ambition.crm.service.CustomerPermissionService; import com.pudonghot.ambition.crm.form.update.ApplicationFormForUpdate; import com.pudonghot.ambition.crm.form.create.ApplicationFormForCreate; +import com.pudonghot.ambition.crm.form.update.ApplicationFileFormForUpdate; import com.pudonghot.ambition.crm.form.create.ApplicationImageFormForCreate; import com.pudonghot.ambition.crm.form.update.ApplicationImageFormForUpdate; +import com.pudonghot.ambition.crm.form.create.ApplicationAttachmentFormForCreate; +import com.pudonghot.ambition.crm.form.update.ApplicationAttachmentFormForUpdate; /** * @author Shaun Chyxion
@@ -41,6 +47,8 @@ public class ApplicationServiceSupport @Autowired private ApplicationImageMapper imageMapper; @Autowired + private ApplicationAttachmentMapper attachmentMapper; + @Autowired private DiskFileApi fileApi; @Autowired private CustomerMapper customerMapper; @@ -62,7 +70,8 @@ public class ApplicationServiceSupport final Date now = new Date(); application.setDateUpdated(now); - uploadImages(id, 1, form.getImages(), form.getImageTitles(), form.getCreatedBy()); + uploadFiles(id, 1, form.getImages(), form.getImageTitles(), form.getCreatedBy(), ApplicationImage::new, imageMapper::insert); + uploadFiles(id, 1, form.getAttachments(), form.getAttachmentTitles(), form.getCreatedBy(), ApplicationAttachment::new, attachmentMapper::insert); mapper.insert(application); return toViewModel(application); } @@ -117,11 +126,13 @@ public class ApplicationServiceSupport */ @Override public ViewModel findViewModel(final String id) { - final List images = imageMapper.list( + final Search search = new Search(ApplicationImage.APPLICATION_ID, id) - .asc(ApplicationImage.SORT)); + .asc(ApplicationImage.SORT); + return super.findViewModel(id) - .setAttr("images", images); + .setAttr("images", imageMapper.list(search)) + .setAttr("attachments", attachmentMapper.list(search)); } /** @@ -132,11 +143,21 @@ public class ApplicationServiceSupport final String applicationId = form.getApplicationId(); final String createdBy = form.getCreatedBy(); validatePerm(applicationId, createdBy, form.isAdmin()); - return uploadImages(applicationId, + return uploadFiles(applicationId, imageMapper.nextSort(applicationId), new MultipartFile[] {form.getImage()}, new String[] {form.getNote()}, - createdBy).iterator().next(); + createdBy, + ApplicationImage::new, + imageMapper::insert).iterator().next(); + } + + /** + * {@inheritDoc} + */ + @Override + public void updateImage(final ApplicationImageFormForUpdate form) { + updateFile(form, imageMapper::find, imageMapper::listSort, imageMapper::update); } /** @@ -145,23 +166,128 @@ public class ApplicationServiceSupport @Override @Transactional(rollbackFor = Throwable.class) public void removeImage(final String id, final String userId, final boolean admin) { - final ApplicationImage appImage = imageMapper.find(id); - Assert.state(appImage != null, "No application image [" + id + "] found"); - final String applicationId = appImage.getApplicationId(); - validatePerm(applicationId, userId, admin); - fileApi.delete(imageFolder(applicationId) + "/" + id); - imageMapper.delete(id); - imageMapper.updateSort(applicationId); + removeFile(id, userId, admin, imageMapper::find, imageMapper::delete, imageMapper::updateSort); } /** * {@inheritDoc} */ @Override - public void updateImage(final ApplicationImageFormForUpdate form) { + public ApplicationAttachment addAttachment(final ApplicationAttachmentFormForCreate form) { + final String applicationId = form.getApplicationId(); + final String createdBy = form.getCreatedBy(); + validatePerm(applicationId, createdBy, form.isAdmin()); + return uploadFiles(applicationId, + attachmentMapper.nextSort(applicationId), + new MultipartFile[] {form.getAttachment()}, + new String[] {form.getNote()}, + createdBy, + ApplicationAttachment::new, + attachmentMapper::insert).iterator().next(); + } + + /** + * {@inheritDoc} + */ + @Override + public void updateAttachment(final ApplicationAttachmentFormForUpdate form) { + updateFile(form, attachmentMapper::find, attachmentMapper::listSort, attachmentMapper::update); + } + + /** + * {@inheritDoc} + */ + @Override + @Transactional(rollbackFor = Throwable.class) + public void removeAttachment(final String id, final String userId, final boolean admin) { + removeFile(id, userId, admin, attachmentMapper::find, attachmentMapper::delete, attachmentMapper::updateSort); + } + + /** + * {@inheritDoc} + */ + @Override + @Transactional(rollbackFor = Throwable.class) + public int delete(final String id) { + log.info("Delete application [{}].", id); + final Application app = find(id); + Assert.state(app != null, "No application [" + id + "] found"); + Assert.state(!app.isEnabled(), "Application [" + id + "] is enabled"); + Assert.state(customerApplicationMapper.count( + new Search(CustomerApplication.APPLICATION_ID, id)) == 0, + "Application [" + id + "] is in using"); + final Search appFileSearch = new Search(ApplicationImage.APPLICATION_ID, id); + imageMapper.list(appFileSearch).forEach( + image -> fileApi.deleteById(image.getFileId())); + imageMapper.delete(appFileSearch); + + attachmentMapper.list(appFileSearch).forEach( + attachment -> fileApi.deleteById(attachment.getFileId())); + attachmentMapper.delete(appFileSearch); + return mapper.delete(id); + } + + private List uploadFiles( + final String applicationId, + int sort, + final MultipartFile[] files, + final String[] titles, + final String createdBy, + Supplier constructor, + Consumer insert) { + + final List appFiles = new ArrayList<>(); + final Date now = new Date(); + final String fileFolder = fileFolder(applicationId); + int i = 0; + for (final MultipartFile file : files) { + if (!file.isEmpty()) { + final String originalFilename = file.getOriginalFilename(); + try (final InputStream ins = file.getInputStream()) { + final String fileId = idSeq.get(); + final T appFile = constructor.get(); + final FileInfo fileInfo = fileApi.upload(ins, + file.getSize(), + fileFolder, + fileId, + file.getContentType(), + FilenameUtils.getExtension(originalFilename), + originalFilename); + appFile.setId(fileId); + appFile.setApplicationId(applicationId); + appFile.setFileId(fileInfo.getId()); + appFile.setUrl(fileInfo.getUrl()); + appFile.setSort(sort++); + appFile.setEnabled(true); + appFile.setNote(StringUtils.defaultIfBlank(titles[i], originalFilename)); + appFile.setCreatedBy(createdBy); + appFile.setDateCreated(now); + // insert + insert.accept(appFile); + appFiles.add(appFile); + } + catch (final IOException e) { + throw new IllegalStateException( + "Read upload file [" + originalFilename + "] error cased", e); + } + } + else { + log.info("Application file [{}] is empty, ignore.", file); + } + ++i; + } + return appFiles; + } + + private void updateFile( + final ApplicationFileFormForUpdate form, + final Function finder, + final Function> listSort, + final Consumer updater) { + final String id = form.getId(); - final ApplicationImage appImage = imageMapper.find(id); - Assert.state(appImage != null, "No application image [" + id + "] found"); + final T appImage = finder.apply(id); + Assert.state(appImage != null, "No application file [" + id + "] found"); final String applicationId = appImage.getApplicationId(); final String updatedBy = form.getUpdatedBy(); validatePerm(applicationId, updatedBy, form.isAdmin()); @@ -180,75 +306,31 @@ public class ApplicationServiceSupport appImage.setNote(form.getNote()); appImage.setDateUpdated(new Date()); appImage.setUpdatedBy(updatedBy); - imageMapper.update(appImage); + + updater.accept(appImage); + if (sortUpdated) { - imageMapper.listSort(applicationId).forEach(imageMapper::update); + listSort.apply(applicationId).forEach(updater); } } - /** - * {@inheritDoc} - */ - @Override - @Transactional(rollbackFor = Throwable.class) - public int delete(final String id) { - log.info("Delete application [{}].", id); - final Application app = find(id); - Assert.state(app != null, "No application [" + id + "] found"); - Assert.state(!app.isEnabled(), "Application [" + id + "] is enabled"); - Assert.state(customerApplicationMapper.count( - new Search(CustomerApplication.APPLICATION_ID, id)) == 0, - "Application [" + id + "] is in using"); - final Search appImageSearch = new Search(ApplicationImage.APPLICATION_ID, id); - imageMapper.list(appImageSearch).forEach( - image -> fileApi.deleteById(image.getFileId())); - imageMapper.delete(appImageSearch); - return mapper.delete(id); + private void removeFile( + final String id, + final String userId, + final boolean admin, + final Function finder, + final Function deleter, + final Function sortUpdater) { + final T appFile = finder.apply(id); + Assert.state(appFile != null, "No application file [" + id + "] found"); + final String applicationId = appFile.getApplicationId(); + validatePerm(applicationId, userId, admin); + fileApi.delete(fileFolder(applicationId) + "/" + id); + deleter.apply(id); + sortUpdater.apply(applicationId); } - private List uploadImages( - final String applicationId, - int sort, - final MultipartFile[] images, - final String[] titles, - final String createdBy) { - - final List imagesRtn = new ArrayList<>(); - final Date now = new Date(); - final String imageFolder = imageFolder(applicationId); - int i = 0; - for (final MultipartFile image : images) { - if (!image.isEmpty()) { - try (final InputStream ins = image.getInputStream()) { - final String imageId = idSeq.get(); - final ApplicationImage appImage = new ApplicationImage(); - final FileInfo fileInfo = fileApi.uploadImage(ins, imageFolder, imageId); - appImage.setId(imageId); - appImage.setApplicationId(applicationId); - appImage.setFileId(fileInfo.getId()); - appImage.setUrl(fileInfo.getUrl()); - appImage.setSort(sort++); - appImage.setEnabled(true); - appImage.setNote(titles[i]); - appImage.setCreatedBy(createdBy); - appImage.setDateCreated(now); - imageMapper.insert(appImage); - imagesRtn.add(appImage); - } - catch (final IOException e) { - throw new IllegalStateException( - "Read upload image [" + image.getOriginalFilename() + "] error cased", e); - } - } - else { - log.info("Application image [{}] is empty, ignore.", image); - } - ++i; - } - return imagesRtn; - } - - private String imageFolder(final String appId) { + private String fileFolder(final String appId) { return "app/" + appId; } diff --git a/server/crm/src/main/resources/application_dev.properties b/server/crm/src/main/resources/application_dev.properties index 1223477..6331826 100644 --- a/server/crm/src/main/resources/application_dev.properties +++ b/server/crm/src/main/resources/application_dev.properties @@ -19,4 +19,5 @@ spring.http.multipart.max-request-size=1024MB # File file.base-path=http://127.0.0.1:1217/lm-f/ -file.base-dir=/Users/chyxion/Workspaces/ambition-crm/files/ +# file.base-dir=/Users/chyxion/Workspaces/ambition-crm/files/ +file.base-dir=/Users/donghuang/Documents/Workspaces/ambition-crm/files/ diff --git a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationAttachmentMapper.java b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationAttachmentMapper.java new file mode 100644 index 0000000..0c360f8 --- /dev/null +++ b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationAttachmentMapper.java @@ -0,0 +1,14 @@ +package com.pudonghot.ambition.crm.mapper; + +import me.chyxion.tigon.mybatis.BaseMapper; +import com.pudonghot.ambition.crm.model.ApplicationAttachment; + +/** + * @author Shaun Chyxion
+ * chyxion@163.com
+ * Mar 11, 2018 11:39:49 + */ +public interface ApplicationAttachmentMapper + extends ApplicationFileMapper, + BaseMapper { +} diff --git a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationAttachmentMapper.xml b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationAttachmentMapper.xml new file mode 100644 index 0000000..7883827 --- /dev/null +++ b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationAttachmentMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + diff --git a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationFileMapper.java b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationFileMapper.java new file mode 100644 index 0000000..3b75811 --- /dev/null +++ b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationFileMapper.java @@ -0,0 +1,38 @@ +package com.pudonghot.ambition.crm.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import org.hibernate.validator.constraints.NotBlank; +import com.pudonghot.ambition.crm.model.ApplicationFile; + +/** + * @author Shaun Chyxion
+ * chyxion@163.com
+ * Mar 11, 2018 11:39:49 + */ +interface ApplicationFileMapper { + + /** + * find next sort + * @param applicationId application id + * @return next sort + */ + int nextSort(@NotBlank @Param("applicationId") String applicationId); + + /** + * update sort + * @param applicationId application id + * @return effected rows + */ + int updateSort(@NotBlank @Param("applicationId") String applicationId); + + /** + * list application files + * @param applicationId + * @return images + */ + List listSort( + @NotBlank + @Param("applicationId") + String applicationId); +} diff --git a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationFileMapper.xml b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationFileMapper.xml new file mode 100644 index 0000000..8d6a876 --- /dev/null +++ b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationFileMapper.xml @@ -0,0 +1,44 @@ + + + + + + + select if (application_id, max(sort) + 1, 1) + from + where application_id = #{applicationId} + + + + update a + join ( + select id, @cur_row := @cur_row + 1 sort + from + join (select @cur_row := 0) r + where application_id = #{applicationId} + order by .sort) s + on a.id = s.id + set a.sort = s.sort + + + + select id, file_id, + url, application_id, + created_by, date_created, + updated_by, date_updated, + enabled, note, + @cur_row := @cur_row + 1 sort + from + join (select @cur_row := 0) r + where application_id = #{applicationId} + order by .sort + + diff --git a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.java b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.java index 53ac673..a807e36 100644 --- a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.java +++ b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.java @@ -1,9 +1,6 @@ package com.pudonghot.ambition.crm.mapper; -import java.util.List; import me.chyxion.tigon.mybatis.BaseMapper; -import org.apache.ibatis.annotations.Param; -import org.hibernate.validator.constraints.NotBlank; import com.pudonghot.ambition.crm.model.ApplicationImage; /** @@ -11,29 +8,7 @@ import com.pudonghot.ambition.crm.model.ApplicationImage; * chyxion@163.com
* Mar 11, 2018 11:39:49 */ -public interface ApplicationImageMapper extends BaseMapper { - - /** - * find next sort - * @param applicationId application id - * @return next sort - */ - int nextSort(@NotBlank @Param("applicationId") String applicationId); - - /** - * update sort - * @param applicationId application id - * @return effected rows - */ - int updateSort(@NotBlank @Param("applicationId") String applicationId); - - /** - * list application images - * @param applicationId - * @return images - */ - List listSort( - @NotBlank - @Param("applicationId") - String applicationId); +public interface ApplicationImageMapper + extends ApplicationFileMapper, + BaseMapper { } diff --git a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.xml b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.xml index 97676a9..e8c0ff5 100644 --- a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.xml +++ b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationImageMapper.xml @@ -12,33 +12,14 @@ - update crm_application_image a - join ( - select id, @cur_row := @cur_row + 1 sort - from crm_application_image - join (select @cur_row := 0) r - where application_id = #{applicationId} - order by crm_application_image.sort) s - on a.id = s.id - set a.sort = s.sort + diff --git a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationMapper.xml b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationMapper.xml index a83723c..bb11e73 100644 --- a/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationMapper.xml +++ b/server/mapper/src/main/java/com/pudonghot/ambition/crm/mapper/ApplicationMapper.xml @@ -22,7 +22,18 @@ (select group_concat(note order by sort separator 0x1d) from crm_application_image where application_id = a.id - group by application_id) image_titles + group by application_id) image_titles, + + (select group_concat(url order by sort separator 0x1d) from + crm_application_attachment + where application_id = a.id + group by application_id) attachments, + + (select group_concat(note order by sort separator 0x1d) from + crm_application_attachment + where application_id = a.id + group by application_id) attachment_titles + from a diff --git a/server/mapper/src/main/resources/spring/spring-app.xml b/server/mapper/src/main/resources/spring/spring-app.xml index d0effba..230464e 100644 --- a/server/mapper/src/main/resources/spring/spring-app.xml +++ b/server/mapper/src/main/resources/spring/spring-app.xml @@ -1,10 +1,6 @@ - + http://www.springframework.org/schema/beans/spring-beans.xsd"> diff --git a/server/model/src/main/java/com/pudonghot/ambition/crm/form/create/ApplicationAttachmentFormForCreate.java b/server/model/src/main/java/com/pudonghot/ambition/crm/form/create/ApplicationAttachmentFormForCreate.java new file mode 100644 index 0000000..4508f5c --- /dev/null +++ b/server/model/src/main/java/com/pudonghot/ambition/crm/form/create/ApplicationAttachmentFormForCreate.java @@ -0,0 +1,27 @@ +package com.pudonghot.ambition.crm.form.create; + +import lombok.Getter; +import lombok.Setter; +import me.chyxion.tigon.form.FC2; +import javax.validation.constraints.NotNull; +import org.hibernate.validator.constraints.NotBlank; +import org.springframework.web.multipart.MultipartFile; + +/** + * @author Donghuang
+ * donghuang@wacai.com
+ * May 17, 2018 17:12:56 + */ +@Getter +@Setter +public class ApplicationAttachmentFormForCreate extends FC2 { + private static final long serialVersionUID = 1L; + + // Properties + // current user is admin + private boolean admin; + @NotBlank + private String applicationId; + @NotNull + private MultipartFile attachment; +} diff --git a/server/model/src/main/java/com/pudonghot/ambition/crm/form/create/ApplicationFormForCreate.java b/server/model/src/main/java/com/pudonghot/ambition/crm/form/create/ApplicationFormForCreate.java index e6d91d3..6f6f621 100644 --- a/server/model/src/main/java/com/pudonghot/ambition/crm/form/create/ApplicationFormForCreate.java +++ b/server/model/src/main/java/com/pudonghot/ambition/crm/form/create/ApplicationFormForCreate.java @@ -30,4 +30,6 @@ public class ApplicationFormForCreate extends FC2 { private String owner; private String[] imageTitles; private MultipartFile[] images; + private String[] attachmentTitles; + private MultipartFile[] attachments; } diff --git a/server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationAttachmentFormForUpdate.java b/server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationAttachmentFormForUpdate.java new file mode 100644 index 0000000..16c5ebb --- /dev/null +++ b/server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationAttachmentFormForUpdate.java @@ -0,0 +1,15 @@ +package com.pudonghot.ambition.crm.form.update; + +import lombok.Getter; +import lombok.Setter; + +/** + * @author Donghuang
+ * donghuang@wacai.com
+ * May 17, 2018 17:12:46 + */ +@Getter +@Setter +public class ApplicationAttachmentFormForUpdate extends ApplicationFileFormForUpdate { + private static final long serialVersionUID = 1L; +} diff --git a/server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationFileFormForUpdate.java b/server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationFileFormForUpdate.java new file mode 100644 index 0000000..2c9c36b --- /dev/null +++ b/server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationFileFormForUpdate.java @@ -0,0 +1,20 @@ +package com.pudonghot.ambition.crm.form.update; + +import lombok.Getter; +import lombok.Setter; +import me.chyxion.tigon.form.FU2; + +/** + * @author Shaun Chyxion
+ * chyxion@163.com
+ * May 17, 2018 17:50:22 + */ +@Getter +@Setter +public class ApplicationFileFormForUpdate extends FU2 { + private static final long serialVersionUID = 1L; + + // current user is admin + private boolean admin; + private float sort; +} diff --git a/server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationImageFormForUpdate.java b/server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationImageFormForUpdate.java index fe524d9..4ecbf09 100644 --- a/server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationImageFormForUpdate.java +++ b/server/model/src/main/java/com/pudonghot/ambition/crm/form/update/ApplicationImageFormForUpdate.java @@ -2,7 +2,6 @@ package com.pudonghot.ambition.crm.form.update; import lombok.Getter; import lombok.Setter; -import me.chyxion.tigon.form.FU2; /** * @version 0.0.1 @@ -13,10 +12,6 @@ import me.chyxion.tigon.form.FU2; */ @Getter @Setter -public class ApplicationImageFormForUpdate extends FU2 { +public class ApplicationImageFormForUpdate extends ApplicationFileFormForUpdate { private static final long serialVersionUID = 1L; - - // current user is admin - private boolean admin; - private float sort; } diff --git a/server/model/src/main/java/com/pudonghot/ambition/crm/model/Application.java b/server/model/src/main/java/com/pudonghot/ambition/crm/model/Application.java index 7627298..7671a59 100644 --- a/server/model/src/main/java/com/pudonghot/ambition/crm/model/Application.java +++ b/server/model/src/main/java/com/pudonghot/ambition/crm/model/Application.java @@ -32,4 +32,8 @@ public class Application extends M3 { private String images; @Transient private String imageTitles; + @Transient + private String attachments; + @Transient + private String attachmentTitles; } diff --git a/server/model/src/main/java/com/pudonghot/ambition/crm/model/ApplicationAttachment.java b/server/model/src/main/java/com/pudonghot/ambition/crm/model/ApplicationAttachment.java new file mode 100644 index 0000000..7b04be5 --- /dev/null +++ b/server/model/src/main/java/com/pudonghot/ambition/crm/model/ApplicationAttachment.java @@ -0,0 +1,17 @@ +package com.pudonghot.ambition.crm.model; + +import lombok.Getter; +import lombok.Setter; +import me.chyxion.tigon.mybatis.Table; + +/** + * @author Shaun Chyxion
+ * chyxion@163.com
+ * May 17, 2018 17:02:51 + */ +@Getter +@Setter +@Table("crm_application_attachment") +public class ApplicationAttachment extends ApplicationFile { + private static final long serialVersionUID = 1L; +} diff --git a/server/model/src/main/java/com/pudonghot/ambition/crm/model/ApplicationFile.java b/server/model/src/main/java/com/pudonghot/ambition/crm/model/ApplicationFile.java new file mode 100644 index 0000000..1eaeb8b --- /dev/null +++ b/server/model/src/main/java/com/pudonghot/ambition/crm/model/ApplicationFile.java @@ -0,0 +1,28 @@ +package com.pudonghot.ambition.crm.model; + +import lombok.Getter; +import lombok.Setter; +import me.chyxion.tigon.model.M3; + +/** + * @author Donghuang
+ * donghuang@wacai.com
+ * May 17, 2018 17:23:39 + */ +@Getter +@Setter +public class ApplicationFile extends M3 { + private static final long serialVersionUID = 1L; + + // Column Names + public static final String FILE_ID = "file_id"; + public static final String URL = "url"; + public static final String APPLICATION_ID = "application_id"; + public static final String SORT = "sort"; + + // Properties + private String fileId; + private String url; + private String applicationId; + private float sort; +} diff --git a/server/model/src/main/java/com/pudonghot/ambition/crm/model/ApplicationImage.java b/server/model/src/main/java/com/pudonghot/ambition/crm/model/ApplicationImage.java index b8bad03..74cfd32 100644 --- a/server/model/src/main/java/com/pudonghot/ambition/crm/model/ApplicationImage.java +++ b/server/model/src/main/java/com/pudonghot/ambition/crm/model/ApplicationImage.java @@ -2,7 +2,6 @@ package com.pudonghot.ambition.crm.model; import lombok.Getter; import lombok.Setter; -import me.chyxion.tigon.model.M3; import me.chyxion.tigon.mybatis.Table; /** @@ -13,18 +12,6 @@ import me.chyxion.tigon.mybatis.Table; @Getter @Setter @Table("crm_application_image") -public class ApplicationImage extends M3 { +public class ApplicationImage extends ApplicationFile { private static final long serialVersionUID = 1L; - - // Column Names - public static final String FILE_ID = "file_id"; - public static final String URL = "url"; - public static final String APPLICATION_ID = "application_id"; - public static final String SORT = "sort"; - - // Properties - private String fileId; - private String url; - private String applicationId; - private float sort; } diff --git a/web/app/components/file-input.js b/web/app/components/file-input.js new file mode 100644 index 0000000..6e1391e --- /dev/null +++ b/web/app/components/file-input.js @@ -0,0 +1,51 @@ +import Ember from 'ember'; +import Component from '@ember/component'; +import { computed } from '@ember/object'; + +export default Component.extend({ + file: {}, + value: computed.alias('file.file'), + 'no-file': 'Choose file', + 'btn-choose': 'Choose file', + didReceiveAttrs() { + const me = this; + me._super(...arguments); + }, + didInsertElement() { + let me = this; + me._super(...arguments); + + me.$('input[type=file]').ace_file_input({ + no_file: me.get('no-file'), + btn_choose: me.get('btn-choose'), + btn_change: null, + droppable: true, + style: 'well', + no_icon: 'ace-icon fa fa-files-o', + + // thumbnail: 'large', + // allowExt: ['jpg', 'jpeg', 'png', 'gif'], + // allowMime: ['image/jpg', 'image/jpeg', 'image/png', 'image/gif'], + + before_change: function() { + let filename = me.$(this).val(); + me.set('value', filename); + return true; + }, + before_remove: function() { + let filename = me.$(this).val(); + me.set('value', null); + return true; + } + }).on('file.error.ace', function(e, info) { + if (me.$(this)[0] === e.target) { + if (info.error_count['ext'] > 0 || info.error_count['mime'] > 0) { + me.get('message').warn('Invalid file'); + } + else if(info.error_count['size'] > 0) { + me.get('message').warn('File size too large'); + } + } + }); + } +}); diff --git a/web/app/routes/customer-application/create.js b/web/app/routes/customer-application/create.js index f3a4d9b..d7c75f7 100644 --- a/web/app/routes/customer-application/create.js +++ b/web/app/routes/customer-application/create.js @@ -15,17 +15,22 @@ export default BaseRoute.extend({ return RSVP.hash({ enabled: true, images: [{}], + attachments: [{}], users: this.get('store').ajaxGet('user/list-for-select') }); }, actions: { addImage() { - const me = this; - me.get('controller.model.images').pushObject({}); + this.get('controller.model.images').pushObject({}); }, removeImage(image) { - const me = this; - me.get('controller.model.images').removeObject(image); + this.get('controller.model.images').removeObject(image); }, + addAttachment() { + this.get('controller.model.attachments').pushObject({}); + }, + removeAttachment(attachment) { + this.get('controller.model.attachments').removeObject(attachment); + } } }); diff --git a/web/app/routes/customer-application/edit.js b/web/app/routes/customer-application/edit.js index f122bfc..bd222b6 100644 --- a/web/app/routes/customer-application/edit.js +++ b/web/app/routes/customer-application/edit.js @@ -23,6 +23,7 @@ export default BaseEditRoute.extend({ }, submitAddImage() { const me = this; + me.set('controller.errors', {}); if (me.get('controller.model.image.file')) { me.set('controller.errors', {}); me.get('store').ajaxPost('application/add-image', @@ -49,30 +50,79 @@ export default BaseEditRoute.extend({ }); }); }, - moveUp(image) { - let me = this; - let images = me.get('controller.model.images'); - if (images && images.length > 1) { - let index = images.indexOf(image); - images.removeObject(image); - images.insertAt(index - 1, image); - --image.sort; - me.updateImage(image); + moveImageUp(image) { + const me = this; + me.moveUp(me.get('controller.model.images'), image, 'updateImage'); + }, + moveImageDown(image) { + const me = this; + me.moveDown(me.get('controller.model.images'), image, 'updateImage'); + }, + // attachments + addAttachment() { + const me = this; + me.set('controller.model.addAttachment', true); + me.set('controller.model.attachment', {}); + }, + submitAddAttachment() { + const me = this; + me.set('controller.errors', {}); + if (me.get('controller.model.attachment.file')) { + me.get('store').ajaxPost('application/add-attachment', + new FormData($('#form_' + me.get('controller.model.id'))[0])) + .then(attachment => { + me.get('controller.model.attachments').pushObject(attachment); + me.set('controller.model.addAttachment', false); + }); + } + else { + me.set('controller.errors.attachment', ['No attachment file selected']); } }, - moveDown(image) { - let me = this; - let images = me.get('controller.model.images'); - if (images && images.length > 1) { - let index = images.indexOf(image); - images.removeObject(image); - images.insertAt(index + 1, image); - ++image.sort; - me.updateImage(image); - } + closeAddAttachment() { + const me = this; + me.set('controller.model.addAttachment', false); + }, + removeAttachment(attachment) { + const me = this; + me.get('dialog').confirm('Are you sure to remove attachment?', () => { + me.get('store').ajaxPost('application/remove-attachment', attachment).then(() => { + me.get('message').alert('Attachment removed'); + me.get('controller.model.attachments').removeObject(attachment); + }); + }); + }, + moveAttachmentUp(attachment) { + const me = this; + me.moveUp(me.get('controller.model.attachments'), attachment, 'updateAttachment'); + }, + moveAttachmentDown(attachment) { + const me = this; + me.moveDown(me.get('controller.model.attachments'), attachment, 'updateAttachment'); } }, updateImage(image) { this.get('ajax').doPost('application/update-image', image, false); + }, + updateAttachment(attachment) { + this.get('ajax').doPost('application/update-attachment', attachment, false); + }, + moveUp(files, file, update) { + if (files && files.length > 1) { + let index = files.indexOf(file); + files.removeObject(file); + files.insertAt(index - 1, file); + --file.sort; + this[update](file); + } + }, + moveDown(files, file, update) { + if (files && files.length > 1) { + let index = files.indexOf(file); + files.removeObject(file); + files.insertAt(index + 1, file); + ++file.sort; + this[update](file); + } } }); diff --git a/web/app/templates/components/customer-application/preview-btn.hbs b/web/app/templates/components/customer-application/preview-btn.hbs index 3523eac..c67df79 100644 --- a/web/app/templates/components/customer-application/preview-btn.hbs +++ b/web/app/templates/components/customer-application/preview-btn.hbs @@ -38,6 +38,22 @@
{{/each}} {{/with}} +
+ {{#with (customer-application-images model.attachments model.attachmentTitles) as |attachments|}} + {{#if attachments.length}} +
+ {{#each attachments as |attachment index|}} + + {{/each}} +
+ {{/if}} + {{/with}} {{/modal-dialog}} {{/if}} diff --git a/web/app/templates/components/file-input.hbs b/web/app/templates/components/file-input.hbs new file mode 100644 index 0000000..d9dd2be --- /dev/null +++ b/web/app/templates/components/file-input.hbs @@ -0,0 +1 @@ +{{input type='file' name=name}} \ No newline at end of file diff --git a/web/app/templates/components/form-input.hbs b/web/app/templates/components/form-input.hbs index 10cfa84..38317de 100644 --- a/web/app/templates/components/form-input.hbs +++ b/web/app/templates/components/form-input.hbs @@ -1,5 +1,5 @@ - -
+ +
{{#if hasBlock}} {{yield}} diff --git a/web/app/templates/customer-application/create.hbs b/web/app/templates/customer-application/create.hbs index 19a006b..de68087 100644 --- a/web/app/templates/customer-application/create.hbs +++ b/web/app/templates/customer-application/create.hbs @@ -40,6 +40,34 @@
{{/form-input}} + {{#form-input name='attachment' label='Attachments'}} +
+
+
+ + + +
+
+
+
+ {{#each model.attachments as |attachment i|}} +
+
+ {{input name=(concat 'attachmentTitles[' i ']') class='width-80' placeholder='Attachment name' value=image.title}} +   + + + +
+ {{!input type='file'}} + {{file-input name=(concat 'attachments[' i ']') file=attachment}} +
+ {{/each}} +
+
+
+ {{/form-input}}
{{form-footer-buttons}} {{/form-content}} diff --git a/web/app/templates/customer-application/edit.hbs b/web/app/templates/customer-application/edit.hbs index c45e804..1a6ff36 100644 --- a/web/app/templates/customer-application/edit.hbs +++ b/web/app/templates/customer-application/edit.hbs @@ -71,12 +71,83 @@ {{#if (not-eq model.images.firstObject.id image.id)}} - + {{/if}} {{#if (not-eq model.images.lastObject.id image.id)}} - + + + + {{/if}} +
+ + + {{/each}} + + + + + + {{/form-input}} + + {{#form-input label='Attachments' name='attachments'}} +
+ +
+
+ + + +
+
+ + +
+
+ + + + + + + + + + + {{#each model.attachments as |attachment|}} + + + +
+ + Attachment + + + Description + + + Settings +
+ {{!image-previews images=image.url}} + + + Download + + + {{editable-cell model=attachment field='note' post-url='application/update-attachment'}} + +
+ + + + {{#if (not-eq model.images.firstObject.id attachment.id)}} + + + + {{/if}} + {{#if (not-eq model.images.lastObject.id attachment.id)}} + {{/if}} @@ -109,3 +180,17 @@ {{/form-content}} {{/modal-dialog}} {{/if}} + +{{#if model.addAttachment}} + {{#modal-dialog title='Add Attachment' no-cancel=true submit=(route-action 'submitAddAttachment') on-close=(route-action 'closeAddAttachment') close-to-parent=false}} + {{#form-content form-id=(concat 'form_' model.id)}} + {{input type='hidden' name='applicationId' value=model.id}} + {{#form-input name='note' label='Description'}} + {{input name='note' value=model.attachment.note class='col-xs-12'}} + {{/form-input}} + {{#form-input name='attachment' label='Attachment'}} + {{file-input name='attachment' file=model.attachment}} + {{/form-input}} + {{/form-content}} + {{/modal-dialog}} +{{/if}} diff --git a/web/tests/integration/components/file-input-test.js b/web/tests/integration/components/file-input-test.js new file mode 100644 index 0000000..598579e --- /dev/null +++ b/web/tests/integration/components/file-input-test.js @@ -0,0 +1,24 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; + +moduleForComponent('file-input', 'Integration | Component | file input', { + integration: true +}); + +test('it renders', function(assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.on('myAction', function(val) { ... }); + + this.render(hbs`{{file-input}}`); + + assert.equal(this.$().text().trim(), ''); + + // Template block usage: + this.render(hbs` + {{#file-input}} + template block text + {{/file-input}} + `); + + assert.equal(this.$().text().trim(), 'template block text'); +});