Merge branch 'dev-yhj' into 1.2.0

master
Harry Yang 2022-12-21 10:43:34 +08:00
commit 969b044daa
29 changed files with 2116 additions and 378 deletions

View File

@ -32,6 +32,7 @@ repositories {
}
dependencies {
implementation 'org.hibernate:hibernate-java8:5.1.0.Final'
compile "org.springframework.boot:spring-boot-starter:${springBootVersion}"
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'org.springframework.boot:spring-boot-starter-freemarker'

View File

@ -0,0 +1,260 @@
package cn.palmte.work.controller.backend;
import org.springframework.beans.BeanUtils;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import cn.palmte.work.model.Admin;
import cn.palmte.work.model.DeptRepository;
import cn.palmte.work.model.Project;
import cn.palmte.work.model.ProjectBudgetIncomeDetail;
import cn.palmte.work.model.ProjectRepository;
import cn.palmte.work.model.enums.CooperationType;
import cn.palmte.work.model.enums.Enumerable;
import cn.palmte.work.model.enums.ProcessStatus;
import cn.palmte.work.model.enums.ProcessType;
import cn.palmte.work.model.enums.ProjectType;
import cn.palmte.work.model.enums.SealType;
import cn.palmte.work.model.process.SaleContractProcess;
import cn.palmte.work.model.process.SealTypeArray;
import cn.palmte.work.model.process.form.SaleContractDetailForm;
import cn.palmte.work.model.process.form.SaleContractProcessForm;
import cn.palmte.work.service.ProjectBudgetService;
import cn.palmte.work.utils.InterfaceUtil;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;
/**
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 1.0 2022/12/8 11:03
*/
@Controller
@RequiredArgsConstructor
@RequestMapping("/process")
public class ProcessController {
static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private final DeptRepository deptRepository;
private final ProjectRepository projectRepository;
private final ProjectBudgetService projectBudgetService;
private final JdbcTemplate jdbcTemplate;
private final EntityManager entityManager;
static class FormMetadata {
// 部门
// 印章类型
public Map<String, String> sealTypes;
// 税率
public List<Integer> taxRate;
}
@ResponseBody
@GetMapping("/form/metadata")
public FormMetadata metadata() {
FormMetadata metadata = new FormMetadata();
metadata.sealTypes = Arrays.stream(SealType.values())
.collect(Collectors.toMap(SealType::getDescription, Enum::name));
metadata.taxRate = Arrays.asList(0, 1, 3, 4, 5, 6, 9, 10, 13);
return metadata;
}
/**
*
*/
@GetMapping("/new")
public String newProcess(Model model) {
model.addAttribute("sealTypes", SealType.values());
model.addAttribute("taxRate", Arrays.asList(0, 1, 3, 4, 5, 6, 9, 10, 13));
return "/admin/business/process-new";
}
/**
*
*/
@GetMapping("/completed")
public String completed() {
return "/admin/business/process-completed";
}
/**
*
*/
@GetMapping("/review")
public String review(Model model) {
model.addAttribute("processTypes", ProcessType.values());
return "/admin/business/process-review";
}
@ResponseBody
@GetMapping("/projects")
public List<Map<String, Object>> query(@RequestParam String q) {
return projectRepository.findByProjectNoOrName(q)
.stream()
.map(project -> {
HashMap<String, Object> map = new HashMap<>();
map.put("id", project.getId());
map.put("name", project.getName());
return map;
})
.collect(Collectors.toList());
}
@Builder
public static class ProjectReturnValue {
public final String applyDate = LocalDate.now().format(formatter);
public String projectNo;
public Integer projectId;
public String projectName;
// 申请人
public String applyPersonName;
// 项目类型
public String projectType;
// 合作类型
public String cooperationType;
// 合同金额
public BigDecimal contractAmount;
public List<ProjectBudgetIncomeDetail> incomeDetails;
// FIXME 垫资
// 是否垫资
public final String isPrepaid = "是";
// 垫资金额
public final String repaidAmount = "50000元";
// 预算毛利率
public final String budgetGrossMargin = "3.9%";
}
@ResponseBody
@GetMapping("/projects/{id}")
public ProjectReturnValue query(@PathVariable int id) {
Project project = projectRepository.findById(id);
Admin admin = InterfaceUtil.getAdmin();
// 可以在对应表数据查询 是否存在再启用
List<ProjectBudgetIncomeDetail> incomeDetails = projectBudgetService.getBudgetIncomeDetail(project);
return ProjectReturnValue.builder()
.projectId(project.getId())
.incomeDetails(incomeDetails)
.projectName(project.getName())
.projectNo(project.getProjectNo())
.applyPersonName(admin.getRealName())
.contractAmount(project.getContractAmount())
.projectType(Enumerable.of(ProjectType.class, project.getType()).getDescription())
.cooperationType(Enumerable.of(CooperationType.class, project.getCooperateType()).getDescription())
.build();
}
// 销售合同流程
@ResponseBody
@PostMapping
@Transactional
public void post(@RequestBody @Valid SaleContractProcessForm form) {
SaleContractProcess entity = new SaleContractProcess();
BeanUtils.copyProperties(form, entity, "sealTypes", "applyDate", "applyDept");
entity.setApplyDate(LocalDate.parse(form.getApplyDate(), formatter));
entity.setSealTypes(SealTypeArray.of(form.getSealTypes()));
entity.setApplyDept(String.join(",", form.getApplyDept()));
entityManager.persist(entity);
List<SaleContractDetailForm> incomeDetails = form.getIncomeDetails();
if (!CollectionUtils.isEmpty(incomeDetails)) {
jdbcTemplate.batchUpdate("update project_budget_income_detail set expiration_date =? where id =? ",
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
SaleContractDetailForm detailForm = incomeDetails.get(i);
ps.setString(1, detailForm.getExpirationDate());
ps.setInt(2, detailForm.getId());
}
@Override
public int getBatchSize() {
return incomeDetails.size();
}
});
}
}
@Data
static class ProcessQueryForm {
private String projectNo;
private String projectTitle;
private String applyPersonName;
private ProcessType processType;
private ProcessStatus processStatus;
}
@ResponseBody
@PostMapping("/query")
public List<SaleContractProcess> list(@RequestBody ProcessQueryForm form) {
TypedQuery<SaleContractProcess> query = entityManager.createQuery(
"from SaleContractProcess ", SaleContractProcess.class);
// query.setMaxResults();
return query.getResultList();
}
// 审核
@Data
static class AuditForm {
private Integer processId;
@NotNull
private String auditOpinion;
private ProcessStatus processStatus;
}
@ResponseBody
@PostMapping("/audit")
public void audit(@RequestBody @Valid AuditForm form) {
}
}

View File

@ -37,6 +37,10 @@ public class ProjectBudgetIncomeDetailBase {
@Column(name = "tax_rate")
private BigDecimal taxRate;
// 质保期 5 个字符
@Column(name = "expiration_date")
public String expirationDate;
public Integer getId() {
return id;
}
@ -117,6 +121,14 @@ public class ProjectBudgetIncomeDetailBase {
this.taxRate = taxRate;
}
public String getExpirationDate() {
return expirationDate;
}
public void setExpirationDate(String expirationDate) {
this.expirationDate = expirationDate;
}
public BigDecimal getTotalTaxInclude(){
if(null == price){
return null;

View File

@ -3,6 +3,7 @@ package cn.palmte.work.model;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import javax.transaction.Transactional;
import java.util.Date;
@ -38,4 +39,9 @@ public interface ProjectRepository extends JpaRepository<Project,Integer> {
@Transactional(rollbackOn = Exception.class)
@Query(value = "update project set approve_id=?, approve_name=? where approve_id = ?", nativeQuery = true)
int batchUpdateApprove(int targetAdminId, String targetAdminName, int adminId);
@Query(value = "select * from project where `project_no` like concat('%', :q, '%') or `name` like concat('%', :q, '%')",
nativeQuery = true)
List<Project> findByProjectNoOrName(@Param("q") String query);
}

View File

@ -0,0 +1,30 @@
package cn.palmte.work.model.enums;
/**
* 12
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 15:20
*/
public enum CooperationType implements Enumerable<Integer> {
STRATEGIC_COOPERATION(1, "战略合作"),
NOT_STRATEGIC_COOPERATION(2, "非战略合作");
private final int value;
private final String description;
CooperationType(int value, String description) {
this.value = value;
this.description = description;
}
public String getDescription() {
return description;
}
@Override
public Integer getValue() {
return value;
}
}

View File

@ -0,0 +1,13 @@
package cn.palmte.work.model.enums;
/**
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0
*/
public interface Descriptive {
/**
* Return a description
*/
String getDescription();
}

View File

@ -0,0 +1,107 @@
package cn.palmte.work.model.enums;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
/**
* Enumerable for {@link Enum}
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0
*/
public interface Enumerable<V> extends Descriptive {
@SuppressWarnings("unchecked")
default V getValue() {
return (V) name();
}
@Override
default String getDescription() {
return name();
}
/**
* The default name of the enumeration, this method does not need
* to be implemented, the enumeration class is automatically inherited
*/
String name();
/**
* Returns Enumerable by {@link Enumerable#getValue() enum value}
*
* @param enumerable enum
* @param value enumeration value
* @param <T> enumeration type
* @param <V> enumeration value type
* @return enumeration instance
* @throws NullPointerException if enumerable is {@code null}
* @see Enumerable#getValue()
*/
// @Nullable
static <T extends Enumerable<V>, V> T of(Class<T> enumerable, /*@Nullable*/ V value) {
if (value != null) {
T[] enumConstants = enumerable.getEnumConstants();
if (enumConstants != null) {
for (T constant : enumConstants) {
if (Objects.equals(value, constant.getValue())) {
return constant;
}
}
}
}
return null;
}
/**
* Get the value corresponding to the name
*
* @param enumerable enumeration class
* @param name enumeration name
* @param <T> enum type
* @param <V> enumeration value type
* @return enumeration value
* @see Enumerable#getValue()
*/
// @Nullable
static <T extends Enumerable<V>, V> V getValue(Class<T> enumerable, String name) {
T[] enumConstants = enumerable.getEnumConstants();
if (enumConstants != null) {
for (T constant : enumConstants) {
if (Objects.equals(name, constant.name())) {
return constant.getValue();
}
}
}
return null;
}
/**
* @param <T> enum type
* @param <V> enumeration value type
* @param defaultValue default value
* @see #of(Class, V)
*/
static <T extends Enumerable<V>, V> T of(Class<T> enumerable, V value, Supplier<T> defaultValue) {
return find(enumerable, value).orElseGet(defaultValue);
}
/**
* @param defaultValue default value
* @param <T> enum type
* @param <V> enumeration value type
* @see #of(Class, V)
*/
static <T extends Enumerable<V>, V> T of(Class<T> enumerable, V value, T defaultValue) {
return find(enumerable, value).orElse(defaultValue);
}
/**
* @return Optional of T
*/
static <T extends Enumerable<V>, V> Optional<T> find(Class<T> enumerable, V value) {
return Optional.ofNullable(of(enumerable, value));
}
}

View File

@ -0,0 +1,28 @@
package cn.palmte.work.model.enums;
/**
*
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/13 16:12
*/
public enum ProcessStatus implements Enumerable<String> {
draft("草稿"),
to_be_audit("待审核"),
audit_passed("审核通过"),
audit_not_passed("审核不通过"),
completed("完成");
private final String description;
ProcessStatus(String description) {
this.description = description;
}
@Override
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,23 @@
package cn.palmte.work.model.enums;
/**
*
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/13 15:41
*/
public enum ProcessTaxRate {
;
private final String description;
ProcessTaxRate(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,22 @@
package cn.palmte.work.model.enums;
/**
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/19 16:47
*/
public enum ProcessType implements Enumerable<String> {
sale_contract("销售合同流程"),
business_procurement("业务采购流程");
private final String description;
ProcessType(String description) {
this.description = description;
}
@Override
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,32 @@
package cn.palmte.work.model.enums;
/**
* 1 23
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 15:10
*/
public enum ProjectType implements Enumerable<Integer> {
ENGINEERING_INTEGRATION(1, "工程集成"),
DEVICE_INTEGRATION(2, "设备集成"),
STRATEGIC_COOPERATION(3, "战略合作");
private final int value;
private final String description;
ProjectType(int value, String description) {
this.value = value;
this.description = description;
}
public String getDescription() {
return description;
}
@Override
public Integer getValue() {
return value;
}
}

View File

@ -0,0 +1,42 @@
package cn.palmte.work.model.enums;
/**
*
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/13 15:25
*/
public enum SealType implements Enumerable<String> {
// "公章"
// "法人章"
// "合同专用章"
// "项目章"
// "法人签名章"
// "总经理签名章"
// "财务专用章"
// "电子合同章"
// "其他"
official("公章"),
legal("法人章"),
contract_special("合同专用章"),
project("项目章"),
legal_person_signature("法人签名章"),
general_manager_signature("总经理签名章"),
financial("财务专用章"),
electronic_contract("电子合同章"),
other("其他");
private final String description;
SealType(String description) {
this.description = description;
}
@Override
public String getDescription() {
return description;
}
}

View File

@ -0,0 +1,96 @@
package cn.palmte.work.model.process;
import org.hibernate.annotations.GenericGenerator;
import java.time.LocalDate;
import java.time.LocalDateTime;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import cn.palmte.work.model.enums.ProcessStatus;
import lombok.Data;
/**
*
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 16:11
*/
@Data
@Entity
@Table(name = "sale_contract_process")
public class SaleContractProcess {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@GenericGenerator(name = "persistenceGenerator", strategy = "increment")
private Integer id;
private Integer projectId;
// 项目编号
private String projectNo;
private LocalDate applyDate;
// 项目标题
private String projectTitle;
// 申请人
private String applyPersonName;
// 申请部门
private String applyDept;
// 申请部门领导
private String applyDeptLeaderName;
// 申请人电话
private String applyPersonPhone;
// 合同编号
private String contractNo;
// 合同名称
private String contractName;
// 客户名称
private String clientName;
// 用印类型
@Convert(converter = SealTypeArrayConverter.class)
private SealTypeArray sealTypes;
// 税率
private String taxRate;
// 收款条件
private String paymentTerms;
// 状态
@Enumerated(EnumType.STRING)
private ProcessStatus status;
// 当前审核人
private String currentAudit;
// 最后更新时间
private LocalDateTime lastUpdateAt;
private LocalDateTime createAt;
// 项目类型
// @Enumerated(EnumType.STRING)
// private ProjectType projectType;
// 合作类型
// @Enumerated(EnumType.STRING)
// private CooperationType cooperationType;
}

View File

@ -0,0 +1,11 @@
package cn.palmte.work.model.process;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 17:13
*/
public interface SaleContractProcessRepository extends JpaRepository<SaleContractProcess, Integer> {
}

View File

@ -0,0 +1,58 @@
package cn.palmte.work.model.process;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.persistence.Convert;
import cn.palmte.work.model.enums.Enumerable;
import cn.palmte.work.model.enums.SealType;
/**
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 16:35
*/
//@JsonSerialize
@Convert(converter = SealTypeArrayConverter.class)
public class SealTypeArray {
public static final SealTypeArray EMPTY = new SealTypeArray();
private final List<SealType> sealTypes = new ArrayList<>();
public SealTypeArray() { }
public SealTypeArray(SealType... sealTypes) {
Collections.addAll(this.sealTypes, sealTypes);
}
public List<SealType> getSealTypes() {
return sealTypes;
}
public void setSealTypes(List<SealType> sealTypes) {
this.sealTypes.clear();
this.sealTypes.addAll(sealTypes);
}
//
public static SealTypeArray of(SealType... sealTypes) {
return new SealTypeArray(sealTypes);
}
public static SealTypeArray of(String... sealTypes) {
return of(Arrays.stream(sealTypes)
.map(type -> Enumerable.of(SealType.class, type))
.toArray(SealType[]::new));
}
public static SealTypeArray of(Collection<SealType> sealTypes) {
SealTypeArray sealTypeArray = new SealTypeArray();
sealTypeArray.sealTypes.addAll(sealTypes);
return sealTypeArray;
}
}

View File

@ -0,0 +1,44 @@
package cn.palmte.work.model.process;
import org.springframework.util.StringUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.persistence.AttributeConverter;
import cn.palmte.work.model.enums.Enumerable;
import cn.palmte.work.model.enums.SealType;
/**
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 16:36
*/
public class SealTypeArrayConverter implements AttributeConverter<SealTypeArray, String> {
@Override
public String convertToDatabaseColumn(SealTypeArray attribute) {
List<SealType> sealTypes = attribute.getSealTypes();
if (sealTypes.isEmpty()) {
return null;
}
return sealTypes.stream()
.map(SealType::getValue)
.collect(Collectors.joining(","));
}
@Override
public SealTypeArray convertToEntityAttribute(String dbData) {
return Optional.ofNullable(dbData)
.filter(StringUtils::hasText)
.map(data -> data.split(","))
.map(Arrays::stream)
.map(stream -> stream.map(element -> Enumerable.of(SealType.class, element))
.toArray(SealType[]::new))
.map(SealTypeArray::of)
.orElse(SealTypeArray.EMPTY);
}
}

View File

@ -0,0 +1,17 @@
package cn.palmte.work.model.process.form;
import lombok.Data;
/**
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 17:17
*/
@Data
public class SaleContractDetailForm {
private int id;
// 5 个字符
private String expirationDate;
}

View File

@ -0,0 +1,71 @@
package cn.palmte.work.model.process.form;
import java.time.LocalDate;
import java.util.List;
import javax.validation.constraints.NotNull;
import cn.palmte.work.model.enums.ProcessStatus;
import lombok.Data;
/**
*
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 17:05
*/
@Data
public class SaleContractProcessForm {
@NotNull
private Integer projectId;
// 项目编号
private String projectNo;
private String applyDate;
// 项目标题
private String projectTitle;
// 项目类型
// private String projectType;
// 合作类型
// private String cooperationType;
// 申请人
private String applyPersonName;
private String[] applyDept;
// 申请部门领导
private String applyDeptLeaderName;
// 申请人电话
private String applyPersonPhone;
// 合同编号
private String contractNo;
// 合同名称
private String contractName;
// 客户名称
private String clientName;
// 用印类型
private String[] sealTypes;
// 税率
private String taxRate;
// 收款条件
private String paymentTerms;
private ProcessStatus status;
private List<SaleContractDetailForm> incomeDetails;
}

View File

@ -0,0 +1,7 @@
/**
* Model
*
* @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
* @since 2.0 2022/12/14 16:32
*/
package cn.palmte.work.model.process;

View File

@ -1,73 +0,0 @@
package cn.palmte.work.service;
import cn.palmte.work.config.UploadProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import sun.misc.BASE64Decoder;
import top.jfunc.common.datetime.DatetimeUtils;
import top.jfunc.common.utils.CommonUtil;
import top.jfunc.common.utils.FileUtil;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Date;
/**
* @author xiongshiyan at 2020/10/13 , contact me with email yanshixiong@126.com or phone 15208384257
*/
@Service
public class UploadService {
private static final Logger logger = LoggerFactory.getLogger(UploadService.class);
public static final String DATA_IMAGE_PNG_BASE64 = "data:image/png;base64,";
public static final String DATA_IMAGE_JPEG_BASE64 = "data:image/jpeg;base64,";
public static final String DATA_IMAGE_JPG_BASE64 = "data:image/jpg;base64,";
@Autowired
private UploadProperties uploadProperties;
public String upload(String base64Str){
String suffix = "png";
if (base64Str.contains(DATA_IMAGE_PNG_BASE64)) {
base64Str = base64Str.replaceAll(DATA_IMAGE_PNG_BASE64, "");
suffix = "png";
} else if (base64Str.contains(DATA_IMAGE_JPEG_BASE64)) {
base64Str = base64Str.replaceAll(DATA_IMAGE_JPEG_BASE64, "");
suffix = "jpeg";
}else if (base64Str.contains(DATA_IMAGE_JPG_BASE64)) {
base64Str = base64Str.replaceAll(DATA_IMAGE_JPG_BASE64, "");
suffix = "jpg";
}
String uploadPath = uploadProperties.getPath();
String uploadPrefix = uploadProperties.getPrefix();
String yyyyMMdd = DatetimeUtils.toStr(new Date(), "yyyyMMdd");
String saveDir = uploadPath + "/" + yyyyMMdd;
FileUtil.makeSureExistDir(saveDir);
String fileName = CommonUtil.randomString(32) + "." + suffix;
String absolutePath = saveDir + "/" + fileName;
File file = new File(absolutePath);
decodeAndWrite(base64Str , file);
return uploadPrefix + "/" + yyyyMMdd + "/" + fileName;
}
private void decodeAndWrite(String base64Str, File destFile) {
try {
// Base64解码
byte[] bytes = new BASE64Decoder().decodeBuffer(base64Str);
for (int i = 0; i < bytes.length; ++i) {
if (bytes[i] < 0) {
// 调整异常数据
bytes[i] += 256;
}
}
Files.write(Paths.get(destFile.getAbsolutePath()), bytes);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
}

View File

@ -1,305 +0,0 @@
package cn.palmte.work.utils.image;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Decoder;
import top.jfunc.common.utils.ArrayUtil;
import top.jfunc.common.utils.RandomUtil;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Random;
/**
*
*/
public class VerifyImageUtil {
private static final Logger logger = LoggerFactory.getLogger(VerifyImageUtil.class);
/**
*
*/
private static int CUT_WIDTH = 50;
/**
*
*/
private static int CUT_HEIGHT = 50;
/**
*
*/
private static int CIRCLE_R = 5;
/**
*
*/
private static int RECTANGLE_PADDING = 8;
/**
*
*/
private static int SLIDER_IMG_OUT_PADDING = 1;
private static int CENTER_INDICATOR = 1;
private static int BORDER_INDICATOR = 2;
private static int OTHER_INDICATOR = 0;
/**
* file
*/
public static VerifyImage getVerifyImage(File file) throws IOException {
BufferedImage srcImage = ImageIO.read(file);
return getVerifyImage(srcImage);
}
/**
*
*/
public static VerifyImage getVerifyImage(InputStream inputStream) throws IOException {
BufferedImage srcImage = ImageIO.read(inputStream);
return getVerifyImage(srcImage);
}
/**
*
*/
public static VerifyImage getVerifyImage(BufferedImage srcImage) throws IOException {
//1/4=75处最右减1个方块儿-5是保守的防止超界
int locationX = RandomUtil.randomInt(srcImage.getWidth()/4, srcImage.getWidth()-CUT_WIDTH-5);
//距离最下方一个方块,-5是保守的防止超界
int locationY = RandomUtil.randomInt(5, srcImage.getHeight()-CUT_HEIGHT-5);
BufferedImage markImage = new BufferedImage(CUT_WIDTH,CUT_HEIGHT,BufferedImage.TYPE_4BYTE_ABGR);
cutImgByTemplate(srcImage, markImage, getBlockData(), locationX, locationY);
return new VerifyImage(getImageBASE64(srcImage),
getImageBASE64(markImage),
locationX,
locationY,
srcImage.getWidth(),
srcImage.getHeight());
}
/**
*
* <p>
* 0
* 1
* 2
* @return int[][]
*/
private static int[][] getBlockData() {
int[][] data = new int[CUT_WIDTH][CUT_HEIGHT];
SecureRandom random = new SecureRandom();
//Random random = new Random();
//(x-a)²+(y-b)²=r²
//x中心位置左右5像素随机
double x1 = RECTANGLE_PADDING + (CUT_WIDTH - 2 * RECTANGLE_PADDING) / 2.0/* - 5 + random.nextInt(10)*/;
//y 矩形上边界半径-1像素移动
double y1_top = RECTANGLE_PADDING - random.nextInt(3);
double y1_bottom = CUT_HEIGHT - RECTANGLE_PADDING + random.nextInt(3);
double y1 = random.nextInt(2) == 1 ? y1_top : y1_bottom;
double x2_right = CUT_WIDTH - RECTANGLE_PADDING - CIRCLE_R + random.nextInt(2 * CIRCLE_R - 4);
double x2_left = RECTANGLE_PADDING + CIRCLE_R - 2 - random.nextInt(2 * CIRCLE_R - 4);
double x2 = random.nextInt(2) == 1 ? x2_right : x2_left;
double y2 = RECTANGLE_PADDING + (CUT_HEIGHT - 2 * RECTANGLE_PADDING) / 2.0 - 4 + random.nextInt(10);
double po = Math.pow(CIRCLE_R, 2);
for (int i = 0; i < CUT_WIDTH; i++) {
for (int j = 0; j < CUT_HEIGHT; j++) {
//矩形区域
boolean fill;
if ((i >= RECTANGLE_PADDING && i < CUT_WIDTH - RECTANGLE_PADDING)
&& (j >= RECTANGLE_PADDING && j < CUT_HEIGHT - RECTANGLE_PADDING)) {
data[i][j] = CENTER_INDICATOR;
fill = true;
} else {
data[i][j] = OTHER_INDICATOR;
fill = false;
}
//凸出区域
double d3 = Math.pow(i - x1, 2) + Math.pow(j - y1, 2);
if (d3 < po) {
data[i][j] = CENTER_INDICATOR;
} else {
if (!fill) {
data[i][j] = OTHER_INDICATOR;
}
}
//凹进区域
double d4 = Math.pow(i - x2, 2) + Math.pow(j - y2, 2);
if (d4 < po) {
data[i][j] = OTHER_INDICATOR;
}
}
}
//边界阴影
for (int i = 0; i < CUT_WIDTH; i++) {
for (int j = 0; j < CUT_HEIGHT; j++) {
//四个正方形边角处理
for (int k = 1; k <= SLIDER_IMG_OUT_PADDING; k++) {
//左上、右上
if (i >= RECTANGLE_PADDING - k && i < RECTANGLE_PADDING
&& ((j >= RECTANGLE_PADDING - k && j < RECTANGLE_PADDING)
|| (j >= CUT_HEIGHT - RECTANGLE_PADDING - k && j < CUT_HEIGHT - RECTANGLE_PADDING +1))) {
data[i][j] = BORDER_INDICATOR;
}
//左下、右下
if (i >= CUT_WIDTH - RECTANGLE_PADDING + k - 1 && i < CUT_WIDTH - RECTANGLE_PADDING + 1) {
for (int n = 1; n <= SLIDER_IMG_OUT_PADDING; n++) {
if (((j >= RECTANGLE_PADDING - n && j < RECTANGLE_PADDING)
|| (j >= CUT_HEIGHT - RECTANGLE_PADDING - n && j <= CUT_HEIGHT - RECTANGLE_PADDING ))) {
data[i][j] = BORDER_INDICATOR;
}
}
}
}
if (data[i][j] == 1 && j - SLIDER_IMG_OUT_PADDING > 0 && data[i][j - SLIDER_IMG_OUT_PADDING] == 0) {
data[i][j - SLIDER_IMG_OUT_PADDING] = BORDER_INDICATOR;
}
if (data[i][j] == 1 && j + SLIDER_IMG_OUT_PADDING > 0 && j + SLIDER_IMG_OUT_PADDING < CUT_HEIGHT && data[i][j + SLIDER_IMG_OUT_PADDING] == 0) {
data[i][j + SLIDER_IMG_OUT_PADDING] = BORDER_INDICATOR;
}
if (data[i][j] == 1 && i - SLIDER_IMG_OUT_PADDING > 0 && data[i - SLIDER_IMG_OUT_PADDING][j] == 0) {
data[i - SLIDER_IMG_OUT_PADDING][j] = BORDER_INDICATOR;
}
if (data[i][j] == 1 && i + SLIDER_IMG_OUT_PADDING > 0 && i + SLIDER_IMG_OUT_PADDING < CUT_WIDTH && data[i + SLIDER_IMG_OUT_PADDING][j] == 0) {
data[i + SLIDER_IMG_OUT_PADDING][j] = BORDER_INDICATOR;
}
}
}
//最外边界
/*for (int i = 0; i < CUT_WIDTH; i++) {
data[0][i] = data[CUT_WIDTH-1][i] = 3;
}
for (int i = 0; i < CUT_HEIGHT; i++) {
data[i][0] = data[i][CUT_HEIGHT-1] = 3;
}*/
return data;
}
/**
*
*
* @param oriImage
* @param targetImage
* @param blockImage
* @param x x
* @param y y
*/
private static void cutImgByTemplate(BufferedImage oriImage, BufferedImage targetImage, int[][] blockImage, int x, int y) {
print(blockImage);
for (int i = 0; i < CUT_WIDTH; i++) {
for (int j = 0; j < CUT_HEIGHT; j++) {
int xx = x + i;
int yy = y + j;
int rgbFlg = blockImage[i][j];
int rgb_ori = oriImage.getRGB(xx, yy);
// 原图中对应位置变色处理
if (rgbFlg == CENTER_INDICATOR) {
//中心
//抠图上复制对应颜色值
targetImage.setRGB(i,j, rgb_ori);
//原图对应位置颜色变化[透明50%]
oriImage.setRGB(xx, yy, rgb_ori & 0x80ffffff);
} else if (rgbFlg == BORDER_INDICATOR) {
//边框
targetImage.setRGB(i, j, Color.WHITE.getRGB());
oriImage.setRGB(xx, yy, Color.GRAY.getRGB());
}else if(rgbFlg == OTHER_INDICATOR){
//int alpha = 0;
targetImage.setRGB(i, j, rgb_ori & 0x00ffffff);
}
}
}
}
private static void print(int[][] blockData){
StringBuilder builder = new StringBuilder("\r\n");
for (int i = 0; i < blockData.length; i++) {
for (int j = 0; j < blockData[i].length; j++) {
builder.append(blockData[i][j]).append(" ");
}
builder.append("\r\n");
}
logger.debug(builder.toString());
}
/**
*
*/
public static File getRandomFile(File dir) throws IOException {
File[] fileList = dir.listFiles();
if(ArrayUtil.isEmpty(fileList)){
return null;
}
List<String> fileNameList = new ArrayList<>(fileList.length);
for (File tempFile: fileList){
if (tempFile.isDirectory()) {
continue;
}
if (tempFile.getName().endsWith(".png") || tempFile.getName().endsWith(".jpg")){
fileNameList.add(tempFile.getAbsolutePath().trim());
}
}
int randomIndex = new SecureRandom().nextInt(fileNameList.size());
//int randomIndex = new Random().nextInt(fileNameList.size());
return new File(fileNameList.get(randomIndex));
}
/**
* IMG
*/
public static void writeImg(BufferedImage image, File file) throws IOException {
ByteArrayOutputStream bao=new ByteArrayOutputStream();
ImageIO.write(image,"png",bao);
try (FileOutputStream out = new FileOutputStream(file)){
out.write(bao.toByteArray());
}
}
/**
* BASE64
*/
public static String getImageBASE64(BufferedImage image) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(image,"png",out);
//转成byte数组
byte[] bytes = out.toByteArray();
return Base64.getEncoder().encodeToString(bytes);
}
/**
* BASE64
*/
public static BufferedImage base64String2Image(String base64String) throws IOException {
BASE64Decoder decoder=new BASE64Decoder();
byte[] bytes1 = decoder.decodeBuffer(base64String);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes1);
return ImageIO.read(byteArrayInputStream);
}
/*public static void main(String[] args) throws IOException {
VerifyImage verifyImage = VerifyImageUtil.getVerifyImage(new File("C:\\Users\\xiongshiyan\\Desktop\\ssss.png"));
System.out.println(verifyImage);
VerifyImageUtil.writeImg(VerifyImageUtil.base64String2Image(verifyImage.getSrcImage()), new File("C:\\Users\\xiongshiyan\\Desktop\\src.png"));
VerifyImageUtil.writeImg(VerifyImageUtil.base64String2Image(verifyImage.getCutImage()), new File("C:\\Users\\xiongshiyan\\Desktop\\cut.png"));
}*/
}

View File

@ -0,0 +1,38 @@
# 创建 流程表
create table sale_contract_process
(
id int auto_increment primary key,
apply_date date null,
apply_dept varchar(255) null,
apply_dept_leader_name varchar(255) null,
apply_person_name varchar(255) null,
apply_person_phone varchar(255) null,
client_name varchar(255) null,
contract_name varchar(255) null,
contract_no varchar(255) null,
payment_terms varchar(255) null,
project_id int null,
project_no varchar(255) null,
project_title varchar(255) null comment '标题',
seal_types varchar(255) null comment '印章类型',
`status` varchar(255) null,
tax_rate varchar(255) null,
current_audit varchar(255) null comment '当前审核人',
create_at datetime default CURRENT_TIMESTAMP comment '创建时间',
last_update_at datetime on update CURRENT_TIMESTAMP comment '最后更新时间'
);
alter table sale_contract_process
add current_audit varchar(255) null comment '当前审核人';
alter table sale_contract_process
add create_at datetime default CURRENT_TIMESTAMP comment '创建时间';
alter table sale_contract_process
add last_update_at datetime on update CURRENT_TIMESTAMP comment '最后更新时间';

Binary file not shown.

After

Width:  |  Height:  |  Size: 991 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 993 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,44 @@
<#assign base=request.contextPath />
<#import "../../common/defaultLayout.ftl" as defaultLayout>
<@defaultLayout.layout>
<style>
</style>
<div id="app">{{ message }}</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const {createApp} = Vue
createApp({
data() {
return {
message: 'Hello Vue!'
}
}
}).mount('#app')
</script>
<script type="text/javascript">
$(function () {
})
</script>
</@defaultLayout.layout>

View File

@ -0,0 +1,855 @@
<#assign base=request.contextPath />
<#import "../../common/defaultLayout.ftl" as defaultLayout>
<@defaultLayout.layout>
<#-- <link rel="stylesheet" href="../assets/css/amazeui.switch.css"/>-->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<style>
#businessPurchaseDetailsModal {
overflow: auto;
}
#businessPurchaseDetailsModal > table {
}
#newBusinessProcurementContractProcess {
overflow: auto;
}
.el-upload__input {
display: none !important;
}
.el-textarea .el-input__count {
line-height: 15px;
}
.admin-content-body {
margin-bottom: 100px;
}
.el-table__empty-block {
height: 60px !important;
}
</style>
<div class="admin-content" id="app">
<div class="admin-content-body">
<div class="am-cf am-padding">
<div class="am-fl am-cf"><strong class="am-text-primary am-text-lg">业务应用</strong> /
<small>{{subTitle}}</small></div>
</div>
<div class="am-g">
<div class="am-u-sm-12 am-u-md-12" v-if="isButtonMode">
<button type="button" class="am-btn" @click="goToSaleContractProcess">
<img src="${base}/assets/process/销售合同流程@3x.png" width="36"/>
销售合同流程
</button>
<button type="button" class="am-btn" @click="businessProcurementProcessClick">
<img src="${base}/assets/process/业务采购流程@3x.png" width="39"/>
业务采购流程
</button>
</div>
<#-- 业务采购流程 -->
<div class="am-modal am-modal-prompt" tabindex="-1" id="businessProcurementProcessModal">
<div class="am-modal-dialog">
<div class="am-modal-hd"><strong>项目编号</strong></div>
<div class="am-modal-bd">
<input type="text" class="am-modal-prompt-input" placeholder="请输入项目编号或名称,支持模糊查询"/>
</div>
<div class="am-modal-footer">
<span class="am-modal-btn" data-am-modal-cancel>取消</span>
<span class="am-modal-btn" data-am-modal-confirm>查询</span>
</div>
</div>
</div>
<#-- 新增销售合同流程 -->
<div class="am-u-sm-12 am-u-md-12" v-if="isSalesContractProcessMode">
<el-form :inline="true" ref="saleContractProcessForm" :model="processForm" label-position="right" label-width="100px">
<div class="am-form-inline">
<el-tooltip :disabled="projectSelected" effect="light" content="项目编号或名称,支持模糊查询" placement="top-end">
<el-form-item label="项目编号">
<el-autocomplete v-model="processForm.projectNo" :fetch-suggestions="queryProject" clearable
value-key="name" placeholder="请输入项目编号或名称" @select="handleSelectProject"
@clear="clearProjectProcess">
</el-autocomplete>
</el-form-item>
</el-tooltip>
<el-tooltip :disabled="!projectSelected" effect="light" :content="projectTitle" placement="top-start">
<el-form-item label="项目标题">
<span v-if="projectSelected">{{projectTitle}}</span>
<span v-else>未选择项目</span>
</el-form-item>
</el-tooltip>
<el-form-item label="申请时间">
<span v-if="projectSelected">{{processForm.applyDate}}</span>
<span v-else>未选择项目</span>
</el-form-item>
<el-form-item label="项目类型">
<span v-if="projectSelected">{{processForm.projectType}}</span>
<span v-else>未选择项目</span>
</el-form-item>
<el-form-item label="合作类型">
<span v-if="projectSelected">{{processForm.cooperationType}}</span>
<span v-else>未选择项目</span>
</el-form-item>
</div>
<div>
<el-form-item label="申请部门" :rules="[{ required: true, message: '申请部门电话不能为空'}]" prop="applyDept">
<el-cascader :options="applySectorOptions" clearable v-model="processForm.applyDept"></el-cascader>
</el-form-item>
<el-form-item label="申请人">
<span v-if="projectSelected">{{processForm.applyPersonName}}</span>
<span v-else>未选择项目</span>
</el-form-item>
<el-form-item label="申请部门领导">
<span v-if="projectSelected">{{processForm.applyDeptLeaderName}}</span>
<span v-else>未选择部门</span>
</el-form-item>
<el-form-item label="申请人电话" :rules="[{ required: true, message: '申请人电话不能为空'}]" prop="applyPersonPhone">
<el-input placeholder="请输入内容" v-model="processForm.applyPersonPhone"></el-input>
</el-form-item>
</div>
<div>
<el-form-item label="合同编号" :rules="[{ required: true, message: '合同编号不能为空'}]" prop="contractNo">
<el-input placeholder="请输入合同编号" v-model="processForm.contractNo"></el-input>
</el-form-item>
<el-form-item label="合同名称" :rules="[{ required: true, message: '合同名称不能为空'}]" prop="contractName">
<el-input placeholder="请输入合同名称" v-model="processForm.contractName"></el-input>
</el-form-item>
<el-form-item label="合同金额">
<span v-if="projectSelected">{{processForm.contractAmount}}元</span>
<span v-else>未选择项目</span>
</el-form-item>
</div>
<div>
<el-form-item label="客户名称" :rules="[{ required: true, message: '客户名称不能为空'}]">
<el-input placeholder="请输入客户名称" v-model="processForm.clientName"></el-input>
</el-form-item>
<el-form-item label="最终用户名称">
<#--TODO 最终用户名称-->
<span v-if="projectSelected">{{processForm.finalUserName}}</span>
<span v-else>未选择项目</span>
</el-form-item>
</div>
<div>
<el-form-item label="用印类型" :rules="[{ required: true, message: '用印类型不能为空'}]">
<el-checkbox-group v-model="processForm.sealTypes">
<#list sealTypes as sealType>
<el-checkbox label="${sealType.name()}" key="key-${sealType.name()}">${sealType.description}</el-checkbox>
</#list>
</el-checkbox-group>
</el-form-item>
</div>
<div>
<el-form-item label="税率" :rules="[{ required: true, message: '税率不能为空'}]" prop="taxRate">
<el-select v-model="processForm.taxRate" placeholder="请选择税率">
<#list taxRate as rate>
<el-option label="${rate}%" value="${rate}"></el-option>
</#list>
</el-select>
</el-form-item>
<el-form-item label="是否垫资">
<span v-if="projectSelected">{{processForm.isPrepaid}}</span>
<span v-else>未选择项目</span>
</el-form-item>
<el-form-item label="垫资金额">
<span v-if="projectSelected">{{processForm.repaidAmount}}</span>
<span v-else>未选择项目</span>
</el-form-item>
<el-form-item label="预算毛利率">
<span v-if="projectSelected">{{processForm.budgetGrossMargin}}</span>
<span v-else>未选择项目</span>
</el-form-item>
</div>
<div>
<el-form-item label="收款条件" :rules="[{ required: true, message: '收款条件不能为空'}]" prop="paymentTerms">
<el-input type="textarea" :autosize="{ minRows: 3, maxRows: 10}" cols="90" maxlength="5000" show-word-limit
v-model="processForm.paymentTerms" placeholder="请输入收款条件限制5000字"></el-input>
</el-form-item>
</div>
<div>
<el-form-item label="备注">
<el-input type="textarea" :autosize="{ minRows: 3, maxRows: 10}" maxlength="5000" show-word-limit
v-model="processForm.remark" placeholder="请输入备注限制5000字" cols="90"></el-input>
</el-form-item>
</div>
<div>
<el-form-item label="上传附件">
<el-upload class="upload-demo"
action="https://jsonplaceholder.typicode.com/posts/"
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:limit="1"
:on-exceed="handleExceed"
:file-list="fileList">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传PDF、excel、word、图片、压缩包且不超过50MB</div>
</el-upload>
</el-form-item>
</div>
<div>
<el-form-item label="合同清单明细">
<el-button type="text" @click="goToSaleContractDetail">详细清单</el-button>
</el-form-item>
</div>
</el-form>
<#-- <el-row justify="space-around" type="flex" class="row-bg">-->
<el-row>
<el-button type="info" @click="goToHome">返回上一级</el-button>
<el-button type="primary" @click="saveDraft">保存草稿</el-button>
<el-button type="success" @click="submit">提交</el-button>
</el-row>
</div>
<#-- 销售合同清单明细 -->
<div class="am-u-sm-12 am-u-md-12" v-if="isSaleContractDetailMode">
<el-table border :data="incomeDetails" @row-dblclick="dbclick">
<el-table-column type="index" :index="1" label="序号" fixed></el-table-column>
<el-table-column prop="name" label="名称" fixed width="120"></el-table-column>
<el-table-column prop="type" label="类别"></el-table-column>
<el-table-column prop="spec" label="规格型号"></el-table-column>
<el-table-column prop="param" label="参数"></el-table-column>
<el-table-column prop="unit" label="单位"></el-table-column>
<el-table-column prop="amount" label="数量"></el-table-column>
<el-table-column prop="price" label="单价" width="120"></el-table-column>
<el-table-column prop="taxRate" label="税率"></el-table-column>
<el-table-column prop="totalTaxInclude" label="含税金额" width="120"></el-table-column>
<el-table-column prop="totalTaxExclude" label="不含税金额" width="120"></el-table-column>
<el-table-column prop="totalTax" label="税金"></el-table-column>
<el-table-column prop="expirationDate" label="质保期" fixed="right" width="150">
<template slot-scope="scope">
<el-input maxlength="5" size="mini" placeholder="请输入质保期"
v-model="scope.row.expirationDate"></el-input>
</template>
</el-table-column>
</el-table>
<el-row style="margin: 20px 0">
<el-button type="info" @click="goToSaleContractProcess">返回上一级</el-button>
<el-button type="primary" @click="submitToSaleContractProcess">保存并返回上一级</el-button>
</el-row>
</div>
<#-- 新增业务采购合同流程 -->
<div class="am-u-sm-12 am-u-md-12" id="newBusinessProcurementContractProcess" v-if="isBusinessProcurementContractProcessMode">
<form role="form" id="newBusinessProcurementContractProcessForm">
<div class="am-form-inline">
<div class="am-form-group">
<label class="am-u-md-3 am-form-label">项目编号</label>
<el-autocomplete v-model="processForm.serialNumber" :fetch-suggestions="queryProject"
placeholder="请输入内容" @select="handleSelectProject"></el-autocomplete>
</div>
<div class="am-form-group">
<label class="am-u-md-3 am-form-label">项目标题</label>
<el-input placeholder="请输入内容" v-model="processForm.title" clearable></el-input>
</div>
<div class="am-form-group">
<label class="am-u-md-3 am-form-label">申请时间</label>
<input type="text" class="am-form-field am-u-sm-2" :value="processForm.time"/>
</div>
<div class="am-form-group">
<label class="am-u-sm-3 am-form-label">采购模式</label>
<input type="text" class="am-form-field" :value="processForm.time"/>
</div>
<div class="am-form-group">
<label class="am-u-md-3 am-form-label">合作类型</label>
<input type="text" class="am-form-field" :value="processForm.time"/>
</div>
</div>
<div class="am-form-inline">
<div class="am-form-group am-u-md-4">
<label class="am-form-label">申请部门</label>
<input type="text" class="am-form-field" :value="processForm.title"/>
<input type="text" class="am-form-field" :value="processForm.time"/>
<input type="text" class="am-form-field" :value="processForm.time"/>
</div>
<div class="am-form-group am-u-md-4">
<label class="am-form-label">申请人</label>
<input type="text" class="am-form-field" value="周瑾"/>
</div>
<div class="am-form-group am-u-md-4">
<label class="am-form-label">申请部门领导</label>
<input type="text" class="am-form-field" value="尹浩"/>
</div>
</div>
<div class="am-form-inline">
</div>
<div class="am-form-inline">
<button type="submit" class="am-btn am-btn-default">登录</button>
</div>
</form>
</div>
<#-- 选择 业务采购清单明细 -->
<div class="am-u-sm-12 am-u-md-12" v-if="isBusinessProcurementContractProcessMode">
<el-table style="width: 100%" border>
<el-table-column prop="fee" label="费用项目" width="180"></el-table-column>
<el-table-column prop="fee" label="采购类别" width="180"></el-table-column>
<el-table-column prop="fee" label="产品名称" width="180"></el-table-column>
<el-table-column prop="fee" label="单位" width="180"></el-table-column>
<el-table-column prop="fee" label="数量" width="180"></el-table-column>
<el-table-column prop="fee" label="预算单价" width="180"></el-table-column>
<el-table-column prop="fee" label="税率(%" width="180"></el-table-column>
<el-table-column prop="fee" label="含税总金额(元)" width="180"></el-table-column>
<el-table-column prop="fee" label="不含税金额(元)" width="180"></el-table-column>
<el-table-column prop="fee" label="税金(元)" width="180"></el-table-column>
<el-table-column prop="fee" label="是否垫资" width="180"></el-table-column>
<el-table-column prop="fee" label="支出时间" width="180"></el-table-column>
<el-table-column prop="fee" label="支出金额(元)" width="180"></el-table-column>
<el-table-column prop="fee" label="已采购数量" width="180"></el-table-column>
<el-table-column prop="fee" label="本次采购数量" width="180"></el-table-column>
<el-table-column prop="fee" label="未采购数量" width="180"></el-table-column>
<el-table-column prop="fee" label="供应商名称"></el-table-column>
<el-table-column prop="fee" label="设备厂商名称"></el-table-column>
<el-table-column prop="fee" label="对应采购清单"></el-table-column>
<el-table-column prop="fee" label="规格型号"></el-table-column>
<el-table-column prop="fee" label="对应采购数目"></el-table-column>
<el-table-column prop="fee" label="采购单价"></el-table-column>
<el-table-column prop="fee" label="含税总金额(元)"></el-table-column>
</el-table>
</div>
</div>
</div>
</div>
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script>
const newBusinessProcurementContractProcess = "newBusinessProcurementContractProcess"
const saleContractProcess = "saleContractProcess"
const saleContractDetail = "saleContractDetail"
const BUTTON = "btn"
const isEmpty = (obj) => {
return !obj || (obj.length && obj.length === 0)
}
const isNotEmpty = (obj) => {
return !isEmpty(obj)
}
const isBlank = (obj) => {
return isEmpty(obj) || (obj.trim && isEmpty(obj.trim()))
}
const hasText = (obj) => {
return !isBlank(obj)
}
const data = () => {
return {
mode: "btn", // btn , newBusinessProcurementContractProcess
processForm: {
sealTypes: [],
},
projectSelected: false,
applySectorOptions: [
{
value: 'zhinan',
label: '指南',
children: [{
value: 'shejiyuanze',
label: '设计原则',
children: [{
value: 'yizhi',
label: '一致'
}, {
value: 'fankui',
label: '反馈'
}, {
value: 'xiaolv',
label: '效率'
}, {
value: 'kekong',
label: '可控'
}]
}, {
value: 'daohang',
label: '导航',
children: [{
value: 'cexiangdaohang',
label: '侧向导航'
}, {
value: 'dingbudaohang',
label: '顶部导航'
}]
}]
},
{
value: 'zujian',
label: '组件',
children: [{
value: 'basic',
label: 'Basic',
children: [{
value: 'layout',
label: 'Layout 布局'
}, {
value: 'color',
label: 'Color 色彩'
}, {
value: 'typography',
label: 'Typography 字体'
}, {
value: 'icon',
label: 'Icon 图标'
}, {
value: 'button',
label: 'Button 按钮'
}]
}, {
value: 'form',
label: 'Form',
children: [{
value: 'radio',
label: 'Radio 单选框'
}, {
value: 'checkbox',
label: 'Checkbox 多选框'
}, {
value: 'input',
label: 'Input 输入框'
}, {
value: 'input-number',
label: 'InputNumber 计数器'
}, {
value: 'select',
label: 'Select 选择器'
}, {
value: 'cascader',
label: 'Cascader 级联选择器'
}, {
value: 'switch',
label: 'Switch 开关'
}, {
value: 'slider',
label: 'Slider 滑块'
}, {
value: 'time-picker',
label: 'TimePicker 时间选择器'
}, {
value: 'date-picker',
label: 'DatePicker 日期选择器'
}, {
value: 'datetime-picker',
label: 'DateTimePicker 日期时间选择器'
}, {
value: 'upload',
label: 'Upload 上传'
}, {
value: 'rate',
label: 'Rate 评分'
}, {
value: 'form',
label: 'Form 表单'
}]
}, {
value: 'data',
label: 'Data',
children: [{
value: 'table',
label: 'Table 表格'
}, {
value: 'tag',
label: 'Tag 标签'
}, {
value: 'progress',
label: 'Progress 进度条'
}, {
value: 'tree',
label: 'Tree 树形控件'
}, {
value: 'pagination',
label: 'Pagination 分页'
}, {
value: 'badge',
label: 'Badge 标记'
}]
}, {
value: 'notice',
label: 'Notice',
children: [{
value: 'alert',
label: 'Alert 警告'
}, {
value: 'loading',
label: 'Loading 加载'
}, {
value: 'message',
label: 'Message 消息提示'
}, {
value: 'message-box',
label: 'MessageBox 弹框'
}, {
value: 'notification',
label: 'Notification 通知'
}]
}, {
value: 'navigation',
label: 'Navigation',
children: [{
value: 'menu',
label: 'NavMenu 导航菜单'
}, {
value: 'tabs',
label: 'Tabs 标签页'
}, {
value: 'breadcrumb',
label: 'Breadcrumb 面包屑'
}, {
value: 'dropdown',
label: 'Dropdown 下拉菜单'
}, {
value: 'steps',
label: 'Steps 步骤条'
}]
}, {
value: 'others',
label: 'Others',
children: [{
value: 'dialog',
label: 'Dialog 对话框'
}, {
value: 'tooltip',
label: 'Tooltip 文字提示'
}, {
value: 'popover',
label: 'Popover 弹出框'
}, {
value: 'card',
label: 'Card 卡片'
}, {
value: 'carousel',
label: 'Carousel 走马灯'
}, {
value: 'collapse',
label: 'Collapse 折叠面板'
}]
}]
},
{
value: 'ziyuan',
label: '资源',
children: [{
value: 'axure',
label: 'Axure Components'
}, {
value: 'sketch',
label: 'Sketch Templates'
}, {
value: 'jiaohu',
label: '组件交互文档'
}]
}
],
fileList: [],
// 销售合同收入明细
incomeDetails: [],
}
}
const methods = {
changeMode(mode) {
this.mode = mode
},
businessProcurementProcessClick() {
const that = this
},
goToHome() {
this.changeMode(BUTTON)
},
goToSaleContractProcess() {
this.changeMode(saleContractProcess)
},
goToSaleContractDetail() {
const { projectId } = this.processForm
if (projectId) {
this.changeMode(saleContractDetail)
}
else {
this.$message.warning("项目还未选择")
}
},
dbclick(row, event, column) {
row.input = !row.input
},
render(obj) {
console.log(obj)
},
initForm(form) {
this.processForm = { ...form, sealTypes: [] }
},
queryProject(q, callback) {
if (isBlank(q)) {
return
}
fetch("${base}/process/projects?q=" + q)
.then(res => res.json())
.then(data => {
if (data.length === 0) {
callback([{
name: '未搜索到结果'
}])
}
else {
callback(data)
}
})
.catch(err => {
this.$message.error('项目搜索失败');
})
},
handleSelectProject(selected) {
const { id, name } = selected
if (!id) {
this.initForm({})
return
}
const loading = this.$loading({
lock: true,
text: '正在加载项目',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
fetch("${base}/process/projects/" + id)
.then(res => res.json())
.then(data => {
const { incomeDetails, ...form } = data
// 转换数据
// @formatter:off
const computeType = (type) => {
switch (type) {
case 1: return '设备'
case 2: return '工程'
case 3: return '服务'
default: return '未知'
}
}
// @formatter:on
this.initForm(form)
this.projectSelected = true
this.incomeDetails = incomeDetails.map(detail => ({
...detail, type: computeType(detail.type)
}))
})
.catch(err => {
this.$message.error("项目'" + name + "'加载失败");
})
.finally(() => loading.close())
},
clearProjectProcess() {
this.projectSelected = false
this.initForm({})
this.incomeDetails = []
},
saveDraft() {
this.processForm.status = 'draft'
this.submit()
},
submit() {
this.$refs["saleContractProcessForm"].validate((valid) => {
if (valid) {
const loading = this.$loading({
lock: true,
text: '正在提交',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
const form = {
...this.processForm,
projectTitle: this.projectTitle,
incomeDetails: this.incomeDetails.map(detail => ({
id: detail.id, expirationDate: detail.expirationDate
}))
}
fetch("${base}/process", {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(form),
}).then(response => {
this.$message({
showClose: true,
message: '提交成功',
type: 'success'
})
}).catch(err => {
this.$message.error("项目提交失败");
}).finally(() => loading.close())
}
else {
return false;
}
})
},
submitToSaleContractProcess() {
this.goToSaleContractProcess()
},
handleRemove(file, fileList) {
console.log(file, fileList);
},
handlePreview(file) {
console.log(file);
},
handleExceed(files, fileList) {
this.$message.warning("当前限制选择 3 个文件,本次选择了 " + files.length + " 个文件,共选择了 +" + files.length + fileList.length + " 个文件");
},
beforeRemove(file, fileList) {
return this.$confirm("确定移除 " + file.name + "");
},
indexMethod(index) {
return index * 1;
}
}
new Vue({
el: '#app',
data,
computed: {
projectTitle() {
const { projectNo, projectName, applyPersonName, applyDate } = this.processForm
if (projectNo && projectName) {
return projectNo.trim() + "-" + projectName.trim() + "-" + applyPersonName + "-" + applyDate
}
return ""
},
isButtonMode() {
return this.mode === BUTTON
},
isBusinessProcurementContractProcessMode() {
return this.mode === newBusinessProcurementContractProcess
},
isSalesContractProcessMode() {
return this.mode === saleContractProcess
},
isSaleContractDetailMode() {
return this.mode === saleContractDetail
},
subTitle() {
switch (this.mode) {
case BUTTON:
return "新增流程"
case saleContractProcess:
return "新增销售合同流程"
case saleContractDetail:
return "销售合同清单明细"
case newBusinessProcurementContractProcess:
return "新增业务采购合同流程"
}
}
},
methods,
mounted() {
this.handleSelectProject({ id: 135, name: '' })
},
})
</script>
</@defaultLayout.layout>

View File

@ -0,0 +1,299 @@
<#assign base=request.contextPath />
<#import "../../common/defaultLayout.ftl" as defaultLayout>
<@defaultLayout.layout>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<style>
#businessPurchaseDetailsModal {
overflow: auto;
}
#businessPurchaseDetailsModal > table {
}
#newBusinessProcurementContractProcess {
overflow: auto;
}
.el-upload__input {
display: none !important;
}
.el-textarea .el-input__count {
line-height: 15px;
}
.admin-content-body {
margin-bottom: 100px;
}
.el-table__empty-block {
height: 60px !important;
}
.el-radio-button__inner, .el-radio-group {
line-height: unset;
}
</style>
<div class="admin-content" id="app">
<div class="admin-content-body">
<div class="am-cf am-padding">
<div class="am-fl am-cf"><strong class="am-text-primary am-text-lg">业务应用</strong> /
<small>待我审核</small></div>
</div>
<div class="am-g">
<div class="am-u-sm-12 am-u-md-12">
<el-form :inline="true" ref="queryForm" :model="queryForm" label-position="right">
<div>
<el-form-item label="标题">
<el-input placeholder="请输入标题" v-model="queryForm.projectTitle"></el-input>
</el-form-item>
<el-form-item label="项目编号">
<el-input placeholder="请输入项目编号" v-model="queryForm.projectNo"></el-input>
</el-form-item>
<el-form-item label="流程类型">
<el-select v-model="queryForm.processType" placeholder="请选择">
<el-option label="全部" value=""></el-option>
<#list processTypes as processType>
<el-option label="${processType.description}"
value="${processType.name()}"></el-option>
</#list>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="申请人">
<el-input placeholder="请输入申请人" v-model="queryForm.applyPersonName"></el-input>
</el-form-item>
<el-button type="primary" @click="queryTable">查询</el-button>
</div>
</el-form>
<el-table border :data="tableData">
<el-table-column type="index" :index="1" label="序号" fixed></el-table-column>
<el-table-column prop="projectNo" label="项目编号" fixed></el-table-column>
<el-table-column prop="projectTitle" label="标题"></el-table-column>
<el-table-column prop="processType" label="流程类型"></el-table-column>
<el-table-column prop="applyPersonName" label="申请人"></el-table-column>
<el-table-column prop="status" label="审核状态"></el-table-column>
<el-table-column prop="amount" label="当前审核人"></el-table-column>
<el-table-column prop="lastUpdateAt" label="最后更新时间"></el-table-column>
<el-table-column label="操作" fixed="right" width="180">
<template slot-scope="scope">
<el-button type="text" @click="showDetail(scope.row, scope)">查看详情</el-button>
<el-button type="text" @click="auditProcess(scope.row, scope)">审核</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination style="text-align: center; margin-top: 10px"
@current-change="handlePageChange"
layout="prev, pager, next" :total="50"></el-pagination>
</div>
</div>
<el-dialog title="审核" :visible.sync="auditFormVisible">
<el-form ref="auditForm" :model="auditForm" label-width="80px">
<el-form-item prop="processStatus" label="审核" :rules="[{ required: true, message: '还没审核'}]">
<el-radio-group v-model="auditForm.processStatus">
<el-radio label="audit_passed">审核通过</el-radio>
<el-radio label="audit_not_passed">审核不通过</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item prop="auditOpinion" label="审核意见" :rules="[{ required: true, message: '审核意见不能为空'}]">
<el-input type="textarea" :autosize="{ minRows: 4, maxRows: 10}"
maxlength="5000" v-model="auditForm.auditOpinion"
placeholder="请输入审核意见" cols="90"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitAudit">提交</el-button>
</div>
</el-dialog>
</div>
</div>
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script>
const isEmpty = (obj) => {
return !obj || (obj.length && obj.length === 0)
}
const isNotEmpty = (obj) => {
return !isEmpty(obj)
}
const isBlank = (obj) => {
return isEmpty(obj) || (obj.trim && isEmpty(obj.trim()))
}
const hasText = (obj) => {
return !isBlank(obj)
}
const data = () => {
return {
auditForm: {
processStatus: null
},
auditFormVisible: false,
tableData: [],
queryForm: {},
projectSelected: false,
fileList: [],
// 销售合同收入明细
incomeDetails: [],
}
}
const methods = {
showDetail(row, scope) {
console.log(row)
console.log(scope)
},
auditProcess(row, scope) {
this.auditForm = {
processId: row.id,
processStatus: null
}
this.auditFormVisible = true
},
submitAudit() {
this.$refs["auditForm"].validate((valid) => {
if (valid) {
const loading = this.$loading({
lock: true,
text: '正在审核',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
const form = this.auditForm
fetch("${base}/process/audit", {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(form),
}).then(response => {
this.$message({
showClose: true,
message: '审核成功',
type: 'success'
})
// 关闭对话框
this.auditFormVisible = false
}).catch(err => {
this.$message.error("审核失败");
}).finally(() => loading.close())
}
else {
return false;
}
})
},
render(obj) {
console.log(obj)
},
queryTable() {
const form = {
...this.queryForm,
}
fetch("${base}/process/query", {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(form),
}).then(res => res.json())
.then(data => {
this.tableData = data
})
.catch(err => {
this.$message.error('项目搜索失败');
})
},
handlePageChange(val) {
console.log(`当前页:` + val)
},
}
new Vue({
el: '#app',
data,
computed: {
projectTitle() {
const { projectNo, projectName, applyPersonName, applyDate } = this.processForm
if (projectNo && projectName) {
return projectNo.trim() + "-" + projectName.trim() + "-" + applyPersonName + "-" + applyDate
}
return ""
},
isButtonMode() {
return this.mode === BUTTON
},
isBusinessProcurementContractProcessMode() {
return this.mode === newBusinessProcurementContractProcess
},
isSalesContractProcessMode() {
return this.mode === saleContractProcess
},
isSaleContractDetailMode() {
return this.mode === saleContractDetail
},
subTitle() {
switch (this.mode) {
case BUTTON:
return "新增流程"
case saleContractProcess:
return "新增销售合同流程"
case saleContractDetail:
return "销售合同清单明细"
case newBusinessProcurementContractProcess:
return "新增业务采购合同流程"
}
}
},
methods,
mounted() {
this.queryTable()
},
})
</script>
</@defaultLayout.layout>