add application attachments

This commit is contained in:
东皇 2018-05-17 22:06:23 +08:00
parent 135f3362bb
commit 4986ccd6c7
30 changed files with 753 additions and 188 deletions

View File

@ -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 <br>
@ -129,6 +128,26 @@ public class ApplicationController
((ApplicationService) queryService).updateImage(form);
}
@RequestMapping(value = "/add-attachment", method = RequestMethod.POST)
public ViewModel<ApplicationAttachment> 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) {

View File

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

View File

@ -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 <br>
@ -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<Application> findViewModel(final String id) {
final List<ApplicationImage> 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 <T extends ApplicationFile> List<T> uploadFiles(
final String applicationId,
int sort,
final MultipartFile[] files,
final String[] titles,
final String createdBy,
Supplier<T> constructor,
Consumer<T> insert) {
final List<T> 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 <T extends ApplicationFile> void updateFile(
final ApplicationFileFormForUpdate form,
final Function<String, T> finder,
final Function<String, List<T>> listSort,
final Consumer<T> 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 <T extends ApplicationFile> void removeFile(
final String id,
final String userId,
final boolean admin,
final Function<String, T> finder,
final Function<String, Integer> deleter,
final Function<String, Integer> 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<ApplicationImage> uploadImages(
final String applicationId,
int sort,
final MultipartFile[] images,
final String[] titles,
final String createdBy) {
final List<ApplicationImage> 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;
}

View File

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

View File

@ -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 <br>
* chyxion@163.com <br>
* Mar 11, 2018 11:39:49
*/
public interface ApplicationAttachmentMapper
extends ApplicationFileMapper<ApplicationAttachment>,
BaseMapper<String, ApplicationAttachment> {
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* @author Donghuang <br>
* donghuang@wacai.com <br>
* May 17, 2018 17:09:24
*/
-->
<!DOCTYPE mapper PUBLIC
"-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pudonghot.ambition.crm.mapper.ApplicationAttachmentMapper">
<select id="nextSort" resultType="int">
<include refid="com.pudonghot.ambition.crm.mapper.ApplicationFileMapper.nextSort" />
</select>
<update id="updateSort">
<include refid="com.pudonghot.ambition.crm.mapper.ApplicationFileMapper.updateSort" />
</update>
<select id="listSort" resultType="com.pudonghot.ambition.crm.model.ApplicationAttachment">
<include refid="com.pudonghot.ambition.crm.mapper.ApplicationFileMapper.listSort" />
</select>
</mapper>

View File

@ -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 <br>
* chyxion@163.com <br>
* Mar 11, 2018 11:39:49
*/
interface ApplicationFileMapper<T extends ApplicationFile> {
/**
* 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<T> listSort(
@NotBlank
@Param("applicationId")
String applicationId);
}

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* @author Shaun Chyxion <br>
* chyxion@163.com <br>
* May 17, 2018 17:29:10
*/
-->
<!DOCTYPE mapper PUBLIC
"-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pudonghot.ambition.crm.mapper.ApplicationFileMapper">
<sql id="nextSort">
select if (application_id, max(sort) + 1, 1)
from <include refid="table" />
where application_id = #{applicationId}
</sql>
<sql id="updateSort">
update <include refid="table" /> a
join (
select id, @cur_row := @cur_row + 1 sort
from <include refid="table" />
join (select @cur_row := 0) r
where application_id = #{applicationId}
order by <include refid="table" />.sort) s
on a.id = s.id
set a.sort = s.sort
</sql>
<sql id="listSort">
select id, file_id,
url, application_id,
created_by, date_created,
updated_by, date_updated,
enabled, note,
@cur_row := @cur_row + 1 sort
from <include refid="table" />
join (select @cur_row := 0) r
where application_id = #{applicationId}
order by <include refid="table" />.sort
</sql>
</mapper>

View File

@ -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 <br>
* Mar 11, 2018 11:39:49
*/
public interface ApplicationImageMapper extends BaseMapper<String, ApplicationImage> {
/**
* 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<ApplicationImage> listSort(
@NotBlank
@Param("applicationId")
String applicationId);
public interface ApplicationImageMapper
extends ApplicationFileMapper<ApplicationImage>,
BaseMapper<String, ApplicationImage> {
}

View File

@ -12,33 +12,14 @@
<mapper namespace="com.pudonghot.ambition.crm.mapper.ApplicationImageMapper">
<select id="nextSort" resultType="int">
select if (application_id, max(sort) + 1, 1)
from <include refid="table" />
where application_id = #{applicationId}
<include refid="com.pudonghot.ambition.crm.mapper.ApplicationFileMapper.nextSort" />
</select>
<update id="updateSort">
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
<include refid="com.pudonghot.ambition.crm.mapper.ApplicationFileMapper.updateSort" />
</update>
<select id="listSort" resultType="com.pudonghot.ambition.crm.model.ApplicationImage">
select id, file_id,
url, application_id,
created_by, date_created,
updated_by, date_updated,
enabled, note,
@cur_row := @cur_row + 1 sort
from <include refid="table" />
join (select @cur_row := 0) r
where application_id = #{applicationId}
order by crm_application_image.sort
<include refid="com.pudonghot.ambition.crm.mapper.ApplicationFileMapper.listSort" />
</select>
</mapper>

View File

@ -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
<include refid="table" /> a
<include refid="Tigon.search" />

View File

@ -1,10 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--<context:component-scan base-package="cn.com.flaginfo.umsapp.ota" /> -->
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>

View File

@ -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 <br>
* donghuang@wacai.com <br>
* May 17, 2018 17:12:56
*/
@Getter
@Setter
public class ApplicationAttachmentFormForCreate extends FC2<String> {
private static final long serialVersionUID = 1L;
// Properties
// current user is admin
private boolean admin;
@NotBlank
private String applicationId;
@NotNull
private MultipartFile attachment;
}

View File

@ -30,4 +30,6 @@ public class ApplicationFormForCreate extends FC2<String> {
private String owner;
private String[] imageTitles;
private MultipartFile[] images;
private String[] attachmentTitles;
private MultipartFile[] attachments;
}

View File

@ -0,0 +1,15 @@
package com.pudonghot.ambition.crm.form.update;
import lombok.Getter;
import lombok.Setter;
/**
* @author Donghuang <br>
* donghuang@wacai.com <br>
* May 17, 2018 17:12:46
*/
@Getter
@Setter
public class ApplicationAttachmentFormForUpdate extends ApplicationFileFormForUpdate {
private static final long serialVersionUID = 1L;
}

View File

@ -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 <br>
* chyxion@163.com <br>
* May 17, 2018 17:50:22
*/
@Getter
@Setter
public class ApplicationFileFormForUpdate extends FU2<String, String> {
private static final long serialVersionUID = 1L;
// current user is admin
private boolean admin;
private float sort;
}

View File

@ -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<String, String> {
public class ApplicationImageFormForUpdate extends ApplicationFileFormForUpdate {
private static final long serialVersionUID = 1L;
// current user is admin
private boolean admin;
private float sort;
}

View File

@ -32,4 +32,8 @@ public class Application extends M3<String, String> {
private String images;
@Transient
private String imageTitles;
@Transient
private String attachments;
@Transient
private String attachmentTitles;
}

View File

@ -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 <br>
* chyxion@163.com <br>
* May 17, 2018 17:02:51
*/
@Getter
@Setter
@Table("crm_application_attachment")
public class ApplicationAttachment extends ApplicationFile {
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,28 @@
package com.pudonghot.ambition.crm.model;
import lombok.Getter;
import lombok.Setter;
import me.chyxion.tigon.model.M3;
/**
* @author Donghuang <br>
* donghuang@wacai.com <br>
* May 17, 2018 17:23:39
*/
@Getter
@Setter
public class ApplicationFile extends M3<String, String> {
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;
}

View File

@ -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<String, String> {
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;
}

View File

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

View File

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

View File

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

View File

@ -38,6 +38,22 @@
<div class="space-12"></div>
{{/each}}
{{/with}}
<hr />
{{#with (customer-application-images model.attachments model.attachmentTitles) as |attachments|}}
{{#if attachments.length}}
<div class="row">
{{#each attachments as |attachment index|}}
<div class="col-xs-12">
<a href="{{attachment.image}}" title="{{attachment.title}}" target="_blank">
<i class="ace-icon fa fa-paperclip bigger-120 grey"></i>
{{if attachment.title attachment.title 'Attachment'}}
</a>
<div class="space-2"></div>
</div>
{{/each}}
</div>
{{/if}}
{{/with}}
</div>
{{/modal-dialog}}
{{/if}}

View File

@ -0,0 +1 @@
{{input type='file' name=name}}

View File

@ -1,5 +1,5 @@
<label for="{{if name name idField}}" class="{{get this 'label-class'}} control-label no-padding-right"> {{label}} </label>
<div class="{{get this 'input-class'}}">
<label for="{{if name name idField}}" class="{{label-class}} control-label no-padding-right"> {{label}} </label>
<div class="{{input-class}}">
{{#if hasBlock}}
{{yield}}

View File

@ -40,6 +40,34 @@
</div>
{{/form-input}}
{{#form-input name='attachment' label='Attachments'}}
<div class="widget-box transparent">
<div class="widget-header widget-header-small">
<div class="widget-toolbar action-buttons">
<a href="#" data-rel="tooltip" title="Add Attachment" {{action (route-action 'addAttachment')}}>
<i class="ace-icon fa fa-plus-circle green"></i>
</a>
</div>
</div>
<div class="widget-body">
<div class="widget-main padding-4">
{{#each model.attachments as |attachment i|}}
<div class="col-xs-6 no-padding-left">
<div class="space-4"></div>
{{input name=(concat 'attachmentTitles[' i ']') class='width-80' placeholder='Attachment name' value=image.title}}
&nbsp;
<a href="#" class="red" data-rel="tooltip" title="Remove Attachment" {{action (route-action 'removeAttachment' attachment)}}>
<i class="ace-icon fa fa-times bigger-125"></i>
</a>
<div class="space-2"></div>
{{!input type='file'}}
{{file-input name=(concat 'attachments[' i ']') file=attachment}}
</div>
{{/each}}
</div>
</div>
</div>
{{/form-input}}
<hr />
{{form-footer-buttons}}
{{/form-content}}

View File

@ -71,12 +71,83 @@
<i class="ace-icon fa fa-trash-o"></i>
</a>
{{#if (not-eq model.images.firstObject.id image.id)}}
<a class="btn btn-xs btn-info" data-rel="tooltip" title="Move Up" {{action (route-action 'moveUp' image)}}>
<a class="btn btn-xs btn-info" data-rel="tooltip" title="Move Up" {{action (route-action 'moveImageUp' image)}}>
<i class="ace-icon fa fa-arrow-up bigger-90"></i>
</a>
{{/if}}
{{#if (not-eq model.images.lastObject.id image.id)}}
<a class="btn btn-xs btn-success" data-rel="tooltip" title="Move Down" {{action (route-action 'moveDown' image)}}>
<a class="btn btn-xs btn-success" data-rel="tooltip" title="Move Down" {{action (route-action 'moveImageDown' image)}}>
<i class="ace-icon fa fa-arrow-down bigger-90"></i>
</a>
{{/if}}
</div>
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</div>
</div>
{{/form-input}}
{{#form-input label='Attachments' name='attachments'}}
<div class="widget-box transparent" style="opacity: 1;">
<!-- #section:custom/widget-box.options -->
<div class="widget-header">
<div class="widget-toolbar action-buttons">
<a href="#" data-rel="tooltip" title="Add Attachment" {{action (route-action 'addAttachment')}}>
<i class="ace-icon fa fa-plus-circle green"></i>
</a>
</div>
</div>
<!-- /section:custom/widget-box.options -->
<div class="widget-body">
<div class="widget-main no-padding">
<table class="table table-striped table-bordered table-hover">
<thead class="thin-border-bottom">
<tr>
<th style="width: 86px;">
<i class="ace-icon fa fa-paperclip bigger-110 hidden-480"></i>
Attachment
</th>
<th>
<i class="ace-icon fa fa-sticky-note-o bigger-110 hidden-480"></i>
Description
</th>
<th style="width: 106px;">
<i class="ace-icon fa fa-cogs bigger-110 hidden-480"></i>
Settings
</th>
</tr>
</thead>
<tbody>
{{#each model.attachments as |attachment|}}
<tr>
<td>
{{!image-previews images=image.url}}
<a href="{{attachment.url}}" target="_blank">
<i class="ace-icon fa fa-download bigger-110 grey"></i>
Download
</a>
</td>
<td>
{{editable-cell model=attachment field='note' post-url='application/update-attachment'}}
</td>
<td>
<div class="btn-group">
<a class="btn btn-xs btn-danger" data-rel="tooltip" title="Remove" {{action (route-action 'removeAttachment' attachment)}}>
<i class="ace-icon fa fa-trash-o"></i>
</a>
{{#if (not-eq model.images.firstObject.id attachment.id)}}
<a class="btn btn-xs btn-info" data-rel="tooltip" title="Move Up" {{action (route-action 'moveAttachmentUp' attachment)}}>
<i class="ace-icon fa fa-arrow-up bigger-90"></i>
</a>
{{/if}}
{{#if (not-eq model.images.lastObject.id attachment.id)}}
<a class="btn btn-xs btn-success" data-rel="tooltip" title="Move Down" {{action (route-action 'moveAttachmentDown' attachment)}}>
<i class="ace-icon fa fa-arrow-down bigger-90"></i>
</a>
{{/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}}

View File

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