diff --git a/base-fast/Word模板标签文本.txt b/base-fast/Word模板标签文本.txt new file mode 100644 index 0000000..46f6375 --- /dev/null +++ b/base-fast/Word模板标签文本.txt @@ -0,0 +1,38 @@ +# Word模板标签文本(直接复制使用) + +## 循环开始标签 +{{#rows}} + +## 循环结束标签 +{{/rows}} + +## 数据字段标签 +{{seq}} +{{materialName}} +{{materialSpec}} +{{qty}} +{{unitName}} +{{salePrice}} +{{amount}} +{{remark}} + +## 其他字段标签 +{{totalPrice}} +{{clientName}} +{{contractCode}} +{{effectiveDate}} + +--- + +## 使用说明 + +1. 从本文件复制标签文本 +2. 粘贴到Word模板的对应位置 +3. 确保使用"无格式文本"粘贴 + +## 注意事项 + +- 标签必须完全匹配,包括大小写 +- 不要有多余的空格 +- 使用英文字符,不要用中文字符 +- {{#rows}} 中的 # 是英文井号 diff --git a/base-fast/Word模板配置说明.md b/base-fast/Word模板配置说明.md new file mode 100644 index 0000000..b37ee20 --- /dev/null +++ b/base-fast/Word模板配置说明.md @@ -0,0 +1,243 @@ +# Word合同模板配置说明 + +## 📋 问题原因 + +Word模板中的动态列表数据渲染失败,主要原因是: +1. 没有配置 `LoopRowTableRenderPolicy` 循环行渲染策略 +2. 数据格式不匹配(直接传JSONArray而不是List) +3. 模板语法使用不正确 + +## ✅ 解决方案 + +### 1. Word模板正确语法 + +在Word模板的表格中,需要使用以下格式: + +``` +┌─────────┬──────────┬──────────┬──────┬──────┬──────────┬──────────┬──────┐ +│ 序号 │ 产品名称 │ 型号 │ 数量 │ 单位 │ 单价(元) │ 总价(元) │ 备注 │ +├─────────┼──────────┼──────────┼──────┼──────┼──────────┼──────────┼──────┤ +│{{#rows}}│ │ │ │ │ │ │ │ +├─────────┼──────────┼──────────┼──────┼──────┼──────────┼──────────┼──────┤ +│{{seq}} │{{materialName}}│{{materialSpec}}│{{qty}}│{{unitName}}│{{salePrice}}│{{amount}}│{{remark}}│ +├─────────┼──────────┼──────────┼──────┼──────┼──────────┼──────────┼──────┤ +│{{/rows}}│ │ │ │ │ │ │ │ +└─────────┴──────────┴──────────┴──────┴──────┴──────────┴──────────┴──────┘ +``` + +**重要说明**: +- `{{#rows}}` 必须单独占一行(表格的一行) +- 数据行包含所有字段:`{{seq}}`, `{{materialName}}` 等 +- `{{/rows}}` 必须单独占一行(表格的一行) +- 这三行构成一个完整的循环结构 + +### 2. 模板文件位置 + +将模板文件放在以下位置之一: + +**方式1:放在resources目录(推荐)** +``` +src/main/resources/templates/contract_template.docx +``` + +**方式2:使用绝对路径** +```java +/Users/mima0000/Desktop/合同.docx +``` + +### 3. 后端代码关键点 + +#### 3.1 配置循环行策略 +```java +LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy(); +Configure config = Configure.builder() + .bind("rows", policy) // 绑定rows标签 + .build(); +``` + +#### 3.2 数据格式转换 +```java +// 错误方式 ❌ +dataMap.put("rows", JSONArray.parseArray(materialJson)); + +// 正确方式 ✅ +List> rowsList = new ArrayList<>(); +for (int i = 0; i < materialArray.size(); i++) { + JSONObject item = materialArray.getJSONObject(i); + Map rowData = new HashMap<>(); + rowData.put("seq", i + 1); + rowData.put("materialName", item.getString("materialName")); + // ... 其他字段 + rowsList.add(rowData); +} +dataMap.put("rows", rowsList); +``` + +#### 3.3 使用配置渲染 +```java +XWPFTemplate template = XWPFTemplate.compile(templateStream, config) + .render(dataMap); +``` + +## 🎯 完整的Word模板示例 + +### 模板结构 + +``` +服务合同 + +需方:{{clientName}} 合同编号:{{contractCode}} +供方:上海诺力智能科技有限公司 签订时间:{{effectiveDate}} + +一、产品明细单 + +┌─────────┬──────────┬──────────┬──────┬──────┬──────────┬──────────┬──────┐ +│ 序号 │ 产品名称 │ 型号 │ 数量 │ 单位 │ 单价(元) │ 总价(元) │ 备注 │ +├─────────┼──────────┼──────────┼──────┼──────┼──────────┼──────────┼──────┤ +│{{#rows}}│ │ │ │ │ │ │ │ +├─────────┼──────────┼──────────┼──────┼──────┼──────────┼──────────┼──────┤ +│{{seq}} │{{materialName}}│{{materialSpec}}│{{qty}}│{{unitName}}│{{salePrice}}│{{amount}}│{{remark}}│ +├─────────┼──────────┼──────────┼──────┼──────┼──────────┼──────────┼──────┤ +│{{/rows}}│ │ │ │ │ │ │ │ +├─────────┴──────────┴──────────┴──────┴──────┴──────────┼──────────┼──────┤ +│ 共计: │{{totalPrice}}│ │ +└──────────────────────────────────────────────┴──────────┴──────┘ + +二、质量要求:{{qc}} +三、交货时间、地点:货期:{{delivery}},交货地:{{place}} +四、运输方式:{{transport}} +五、包装标准:{{packaging}} +六、结算方式:{{pay}},付款方式:{{payment}} +七、违约责任:{{breach}} +八、解决合同纠纷的方式:{{solve_dispute}} +九、其它约定事项:{{supplement}} + +┌──────────────────────────────┬──────────────────────────────┐ +│ 需方: │ 供方: │ +├──────────────────────────────┼──────────────────────────────┤ +│单位名称:{{clientName}} │单位名称:上海诺力智能科技有限公司│ +│地址:{{clientAdd}} │地址:上海青浦区徐泾镇... │ +│委托代理人:{{juridicalPerson}}│委托代理人: │ +│电话:{{clientTel}} │电话: │ +│传真:{{clientFax}} │传真: │ +│开户银行:{{clientBank}} │开户银行:招商银行虹桥支行 │ +│帐号:{{clientCard}} │帐号:12191702501091 │ +└──────────────────────────────┴──────────────────────────────┘ +``` + +## 📝 字段说明 + +### 基本信息字段 +| 字段名 | 说明 | 示例 | +|--------|------|------| +| clientName | 客户名称 | XX公司 | +| contractCode | 合同编号 | HT20250101 | +| effectiveDate | 生效日期 | 2025年01月01日 | +| totalPrice | 总价 | 100000.00 | + +### 列表字段(rows) +| 字段名 | 说明 | 示例 | +|--------|------|------| +| seq | 序号 | 1, 2, 3... | +| materialName | 产品名称 | 叉车 | +| materialSpec | 型号 | CPD15 | +| qty | 数量 | 10 | +| unitName | 单位 | 台 | +| salePrice | 单价 | 50000.00 | +| amount | 总价 | 500000.00 | +| remark | 备注 | 含税 | + +### 合同条款字段 +| 字段名 | 说明 | +|--------|------| +| qc | 质量要求 | +| delivery | 交货时间 | +| place | 交货地点 | +| transport | 运输方式 | +| packaging | 包装标准 | +| pay | 结算方式 | +| payment | 付款方式 | +| breach | 违约责任 | +| solve_dispute | 解决纠纷方式 | +| supplement | 其它约定 | + +### 客户信息字段 +| 字段名 | 说明 | +|--------|------| +| clientAdd | 客户地址 | +| juridicalPerson | 法人代表 | +| clientTel | 客户电话 | +| clientFax | 客户传真 | +| clientBank | 客户开户行 | +| clientCard | 客户账号 | + +## 🔧 常见问题 + +### 1. 列表数据不显示 +**原因**:没有配置 `LoopRowTableRenderPolicy` +**解决**:在代码中添加配置 +```java +Configure config = Configure.builder() + .bind("rows", policy) + .build(); +``` + +### 2. 列表只显示一行 +**原因**:模板中 `{{#rows}}` 和 `{{/rows}}` 没有单独占一行 +**解决**:确保循环标签单独占表格的一行 + +### 3. 数据格式错误 +**原因**:直接传JSONArray而不是List +**解决**:转换为List>格式 + +### 4. 中文乱码 +**原因**:文件名编码问题 +**解决**:使用URLEncoder编码文件名 +```java +String fileName = java.net.URLEncoder.encode("合同.docx", "UTF-8"); +``` + +## 🎉 测试步骤 + +1. **准备模板文件** + - 按照上述格式创建Word模板 + - 保存为 `contract_template.docx` + - 放到 `src/main/resources/templates/` 目录 + +2. **测试导出** + - 访问:`GET /flow/contract/export?contractId=1` + - 检查下载的Word文档 + - 验证列表数据是否正确显示 + +3. **验证数据** + - 打开导出的Word文档 + - 检查产品明细表是否有多行数据 + - 检查所有字段是否正确填充 + +## 💡 最佳实践 + +1. **模板管理** + - 将模板文件放在resources目录 + - 使用版本控制管理模板 + - 为不同类型合同创建不同模板 + +2. **错误处理** + - 添加try-catch捕获异常 + - 记录详细的错误日志 + - 返回友好的错误提示 + +3. **性能优化** + - 缓存模板对象 + - 使用流式处理大文件 + - 异步处理导出任务 + +## 📚 参考资料 + +- poi-tl官方文档:http://deepoove.com/poi-tl/ +- 循环行表格:http://deepoove.com/poi-tl/#_loop-row-table +- GitHub示例:https://github.com/Sayi/poi-tl + +--- + +**更新时间**: 2026-01-30 +**版本**: v1.0 diff --git a/base-fast/pom.xml b/base-fast/pom.xml index f9aa8ef..8616d80 100644 --- a/base-fast/pom.xml +++ b/base-fast/pom.xml @@ -71,6 +71,42 @@ org.springframework.boot spring-boot-starter-aop + + com.alibaba + easyexcel + 2.2.6 + + + + org.apache.poi + poi + 4.1.2 + + + com.deepoove + poi-tl + 1.10.2 + + + org.apache.poi + poi-ooxml + 4.1.2 + + + org.apache.poi + poi-ooxml-schemas + 4.1.2 + + + org.apache.commons + commons-compress + 1.21 + + + commons-io + commons-io + 2.11.0 + org.springframework.boot spring-boot-starter-data-elasticsearch diff --git a/base-fast/src/main/java/com/boge/config/ShiroConfig.java b/base-fast/src/main/java/com/boge/config/ShiroConfig.java index 832fe0d..64906cb 100644 --- a/base-fast/src/main/java/com/boge/config/ShiroConfig.java +++ b/base-fast/src/main/java/com/boge/config/ShiroConfig.java @@ -67,6 +67,8 @@ public class ShiroConfig { filterMap.put("/js/**", "anon"); filterMap.put("/img/**", "anon"); filterMap.put("/fonts/**", "anon"); + filterMap.put("/fonts/**", "anon"); + filterMap.put("/flow/contract/export", "anon"); filterMap.put("/**", "oauth2"); shiroFilter.setFilterChainDefinitionMap(filterMap); diff --git a/base-fast/src/main/java/com/boge/modules/contract/controller/ContractController.java b/base-fast/src/main/java/com/boge/modules/contract/controller/ContractController.java index 86dff5b..0c5612d 100644 --- a/base-fast/src/main/java/com/boge/modules/contract/controller/ContractController.java +++ b/base-fast/src/main/java/com/boge/modules/contract/controller/ContractController.java @@ -1,5 +1,7 @@ package com.boge.modules.contract.controller; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -10,26 +12,31 @@ import com.boge.common.utils.CodeUtil; import com.boge.common.utils.PageUtils; import com.boge.common.utils.R; import com.boge.common.utils.ShiroUtils; -import com.boge.modules.client.entity.ClientQuery; -import com.boge.modules.contract.dao.ContractDao; +import com.boge.modules.client.entity.ClientEntity; +import com.boge.modules.client.service.ClientService; import com.boge.modules.contract.entity.ContractEntity; import com.boge.modules.contract.entity.ContractQuery; import com.boge.modules.contract.service.ContractService; import com.boge.modules.orderRecord.record.RecordEvent; -import com.boge.modules.price.entity.PriceEntity; import com.boge.modules.price.enums.PriceApprovalEnum; -import com.boge.modules.tickets.entity.LocalStorage; -import com.boge.modules.tickets.service.LocalStorageService; -import lombok.val; +import com.boge.modules.sys.localStorage.service.entity.LocalStorage; +import com.boge.modules.sys.localStorage.service.LocalStorageService; +import com.deepoove.poi.XWPFTemplate; +import com.deepoove.poi.data.RowRenderData; +import com.deepoove.poi.data.Rows; +import com.deepoove.poi.data.TableRenderData; +import com.deepoove.poi.data.Tables; +import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Map; +import javax.servlet.http.HttpServletResponse; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.text.SimpleDateFormat; +import java.util.*; import java.util.stream.Collectors; @@ -46,6 +53,8 @@ public class ContractController { @Autowired private ContractService contractService; @Autowired + private ClientService clientService; + @Autowired private LocalStorageService localStorageService; @Autowired private EventPublisher eventPublisher; @@ -132,4 +141,77 @@ public class ContractController { return R.ok(); } + /** + * 导出合同 + */ + @RequestMapping("/export") + //@RequiresPermissions("flow:contract:delete") + public void export(Integer contractId, HttpServletResponse response){ + try { + // 1. 查询合同数据 + ContractEntity contract = contractService.selectDtlById(contractId); + String materialJson = contract.getMaterialJson(); + ClientEntity clientEntity = clientService.getById(contract.getClientId()); + + // 2. 构建数据Map + Map dataMap = new HashMap<>(); + dataMap.put("clientName", clientEntity.getClientName()); + dataMap.put("contractCode", contract.getContractCode()); + dataMap.put("effectiveDate", new SimpleDateFormat("yyyy年MM月dd日").format(contract.getEffectiveTime())); + dataMap.put("clientAdd", clientEntity.getAddress()); + dataMap.put("juridicalPerson", clientEntity.getJuridicalPerson()); + dataMap.put("clientTel", clientEntity.getTel()); + dataMap.put("clientFax", clientEntity.getFax()); + dataMap.put("clientBank", clientEntity.getBank()); + dataMap.put("clientCard", clientEntity.getCard()); + dataMap.put("totalPrice", contract.getTotalPrice()); + dataMap.put("qc", contract.getQc()); + dataMap.put("delivery", contract.getDelivery()); + dataMap.put("place", contract.getPlace()); + dataMap.put("transport", contract.getTransport()); + dataMap.put("packaging", contract.getPackaging()); + dataMap.put("pay", contract.getPay()); + dataMap.put("payment", contract.getPayment()); + dataMap.put("breach", contract.getBreach()); + dataMap.put("solve_dispute", contract.getSolveDispute()); + dataMap.put("supplement", contract.getSupplement()); + + // 3. 处理产品明细列表数据 + JSONArray materialArray = JSONArray.parseArray(materialJson); + List> rowsList = new ArrayList<>(); + + for (int i = 0; i < materialArray.size(); i++) { + JSONObject item = materialArray.getJSONObject(i); + Map rowData = new HashMap<>(); + rowData.put("seq", i + 1); // 序号 + rowData.put("materialName", item.getString("materialName")); + rowData.put("materialSpec", item.getString("materialSpec")); + rowData.put("qty", item.getString("qty")); + rowData.put("unitName", item.getString("unitName")); + rowData.put("salePrice", item.getString("salePrice")); + rowData.put("amount", item.getString("amount")); + rowData.put("remark", item.getString("remark")); + rowsList.add(rowData); + } + dataMap.put("goods", rowsList); + // 4. 配置循环行渲染策略 + com.deepoove.poi.config.Configure config = com.deepoove.poi.config.Configure.builder() + .bind("goods", new LoopRowTableRenderPolicy()) // 绑定rows标签使用循环行策略 + .build(); + InputStream templateStream = new java.io.FileInputStream("/Users/mima0000/Desktop/合同.docx"); + XWPFTemplate template = XWPFTemplate.compile(templateStream, config).render(dataMap); + // 6. 设置响应头 + String fileName = "合同_" + contract.getContractCode() + ".docx"; + response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + + java.net.URLEncoder.encode(fileName, "UTF-8")); + // 7. 输出到响应流 + template.write(response.getOutputStream()); + template.close(); + } catch (Exception ex) { + ex.printStackTrace(); + throw new RuntimeException("导出合同失败: " + ex.getMessage()); + } + } + } diff --git a/base-fast/src/main/java/com/boge/modules/flow/controller/FlwInstanceController.java b/base-fast/src/main/java/com/boge/modules/flow/controller/FlwInstanceController.java index 626c46b..6619d56 100644 --- a/base-fast/src/main/java/com/boge/modules/flow/controller/FlwInstanceController.java +++ b/base-fast/src/main/java/com/boge/modules/flow/controller/FlwInstanceController.java @@ -13,8 +13,8 @@ import com.boge.modules.flow.entity.FlwHiTaskEntity; import com.boge.modules.flow.service.ActHiProcessinfoService; import com.boge.modules.flow.service.FlwInstanceService; import com.boge.modules.sys.entity.SysUserEntity; -import com.boge.modules.tickets.entity.LocalStorage; -import com.boge.modules.tickets.service.LocalStorageService; +import com.boge.modules.sys.localStorage.service.entity.LocalStorage; +import com.boge.modules.sys.localStorage.service.LocalStorageService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.web.bind.annotation.*; diff --git a/base-fast/src/main/java/com/boge/modules/price/controller/PriceController.java b/base-fast/src/main/java/com/boge/modules/price/controller/PriceController.java index 947cd7d..666c67a 100644 --- a/base-fast/src/main/java/com/boge/modules/price/controller/PriceController.java +++ b/base-fast/src/main/java/com/boge/modules/price/controller/PriceController.java @@ -1,8 +1,8 @@ package com.boge.modules.price.controller; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.boge.common.publisher.EventPublisher; import com.boge.common.publisher.EventType; @@ -11,18 +11,18 @@ import com.boge.common.utils.PageUtils; import com.boge.common.utils.R; import com.boge.common.utils.ShiroUtils; import com.boge.modules.orderRecord.record.RecordEvent; -import com.boge.modules.orderRecord.record.service.PmOrderRecordService; -import com.boge.modules.price.entity.PriceDto; import com.boge.modules.price.entity.PriceEntity; import com.boge.modules.price.enums.PriceApprovalEnum; import com.boge.modules.price.service.PriceService; -import com.boge.modules.tickets.entity.LocalStorage; -import com.boge.modules.tickets.service.LocalStorageService; +import com.boge.modules.sys.localStorage.service.entity.LocalStorage; +import com.boge.modules.sys.localStorage.service.LocalStorageService; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import javax.servlet.http.HttpServletResponse; import java.util.Arrays; import java.util.Date; import java.util.List; @@ -128,4 +128,16 @@ public class PriceController { return R.ok(); } + /** + * 审核 + */ + @RequestMapping("/export") + //@RequiresPermissions("flow:contract:delete") + public R export(Integer priceId, HttpServletResponse response){ + PriceEntity contract = priceService.getById(priceId); + ClassPathResource pathResource = new ClassPathResource("static/model/报价单样式.xls");// 根目录 + String materialJson = contract.getMaterialJson(); + localStorageService.downloadExcelModel(pathResource.getPath(),response,(JSONObject)JSON.toJSON(contract),JSONArray.parseArray(materialJson)); + return R.ok(); + } } diff --git a/base-fast/src/main/java/com/boge/modules/sys/LocalStorageController.java b/base-fast/src/main/java/com/boge/modules/sys/localStorage/controller/LocalStorageController.java similarity index 93% rename from base-fast/src/main/java/com/boge/modules/sys/LocalStorageController.java rename to base-fast/src/main/java/com/boge/modules/sys/localStorage/controller/LocalStorageController.java index 228988c..4551901 100644 --- a/base-fast/src/main/java/com/boge/modules/sys/LocalStorageController.java +++ b/base-fast/src/main/java/com/boge/modules/sys/localStorage/controller/LocalStorageController.java @@ -2,12 +2,12 @@ // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // -package com.boge.modules.sys; +package com.boge.modules.sys.localStorage.controller; import com.boge.common.exception.RRException; import com.boge.common.utils.FileUtil; -import com.boge.modules.tickets.entity.LocalStorage; -import com.boge.modules.tickets.service.LocalStorageService; +import com.boge.modules.sys.localStorage.service.LocalStorageService; +import com.boge.modules.sys.localStorage.service.entity.LocalStorage; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.apache.commons.lang3.StringUtils; diff --git a/base-fast/src/main/java/com/boge/modules/tickets/service/LocalStorageService.java b/base-fast/src/main/java/com/boge/modules/sys/localStorage/service/LocalStorageService.java similarity index 58% rename from base-fast/src/main/java/com/boge/modules/tickets/service/LocalStorageService.java rename to base-fast/src/main/java/com/boge/modules/sys/localStorage/service/LocalStorageService.java index 8e2dbb8..38c5ab4 100644 --- a/base-fast/src/main/java/com/boge/modules/tickets/service/LocalStorageService.java +++ b/base-fast/src/main/java/com/boge/modules/sys/localStorage/service/LocalStorageService.java @@ -1,14 +1,14 @@ -package com.boge.modules.tickets.service; +package com.boge.modules.sys.localStorage.service; import com.baomidou.mybatisplus.extension.service.IService; -import com.boge.modules.tickets.entity.LocalStorage; -import com.boge.modules.tickets.entity.TicketsEntity; +import com.boge.modules.sys.localStorage.service.entity.LocalStorage; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; +import java.util.Map; public interface LocalStorageService extends IService { @@ -21,4 +21,14 @@ public interface LocalStorageService extends IService { void download(List localStorageDtos, HttpServletResponse response) throws IOException; void downloadFile(LocalStorage localStorage, HttpServletRequest request, HttpServletResponse response) throws IOException; + + /** + * + * @param fileUrl + * @param response + * @param mapParam:模版中json的数据{xxx} + * @param listParam:模版中列表的数据{date.xxx} + */ + void downloadExcelModel(String fileUrl, HttpServletResponse response, Map mapParam, List listParam); + void downloadWordModel(String fileUrl, HttpServletResponse response, Map mapParam); } diff --git a/base-fast/src/main/java/com/boge/modules/tickets/dao/LocalStorageMapper.java b/base-fast/src/main/java/com/boge/modules/sys/localStorage/service/dao/LocalStorageMapper.java similarity index 55% rename from base-fast/src/main/java/com/boge/modules/tickets/dao/LocalStorageMapper.java rename to base-fast/src/main/java/com/boge/modules/sys/localStorage/service/dao/LocalStorageMapper.java index fb8f290..5bd2294 100644 --- a/base-fast/src/main/java/com/boge/modules/tickets/dao/LocalStorageMapper.java +++ b/base-fast/src/main/java/com/boge/modules/sys/localStorage/service/dao/LocalStorageMapper.java @@ -1,8 +1,7 @@ -package com.boge.modules.tickets.dao; +package com.boge.modules.sys.localStorage.service.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; -import com.boge.modules.tickets.entity.LocalStorage; -import com.boge.modules.tickets.entity.TicketsEntity; +import com.boge.modules.sys.localStorage.service.entity.LocalStorage; import org.apache.ibatis.annotations.Mapper; @Mapper diff --git a/base-fast/src/main/java/com/boge/modules/sys/localStorage/service/dto/LocalStorageDto.java b/base-fast/src/main/java/com/boge/modules/sys/localStorage/service/dto/LocalStorageDto.java new file mode 100644 index 0000000..eb60a81 --- /dev/null +++ b/base-fast/src/main/java/com/boge/modules/sys/localStorage/service/dto/LocalStorageDto.java @@ -0,0 +1,15 @@ +package com.boge.modules.sys.localStorage.service.dto; + +import lombok.Data; + +import java.io.Serializable; +@Data +public class LocalStorageDto implements Serializable { + private Long id; + private String realName; + private String name; + private String suffix; + private String type; + private String size; + +} diff --git a/base-fast/src/main/java/com/boge/modules/tickets/entity/LocalStorage.java b/base-fast/src/main/java/com/boge/modules/sys/localStorage/service/entity/LocalStorage.java similarity index 69% rename from base-fast/src/main/java/com/boge/modules/tickets/entity/LocalStorage.java rename to base-fast/src/main/java/com/boge/modules/sys/localStorage/service/entity/LocalStorage.java index df4c8ea..c03f935 100644 --- a/base-fast/src/main/java/com/boge/modules/tickets/entity/LocalStorage.java +++ b/base-fast/src/main/java/com/boge/modules/sys/localStorage/service/entity/LocalStorage.java @@ -1,19 +1,11 @@ -package com.boge.modules.tickets.entity; +package com.boge.modules.sys.localStorage.service.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import io.swagger.annotations.ApiModelProperty; import lombok.Data; -import org.hibernate.annotations.CreationTimestamp; -import org.hibernate.annotations.UpdateTimestamp; -import org.springframework.data.annotation.CreatedBy; -import org.springframework.data.annotation.LastModifiedBy; -import org.springframework.data.elasticsearch.annotations.Document; -import javax.persistence.*; import java.io.Serializable; -import java.sql.Timestamp; import java.util.Date; diff --git a/base-fast/src/main/java/com/boge/modules/tickets/entity/LocalStorageQueryCriteria.java b/base-fast/src/main/java/com/boge/modules/sys/localStorage/service/entity/LocalStorageQueryCriteria.java similarity index 97% rename from base-fast/src/main/java/com/boge/modules/tickets/entity/LocalStorageQueryCriteria.java rename to base-fast/src/main/java/com/boge/modules/sys/localStorage/service/entity/LocalStorageQueryCriteria.java index 6a7f995..c90a6c9 100644 --- a/base-fast/src/main/java/com/boge/modules/tickets/entity/LocalStorageQueryCriteria.java +++ b/base-fast/src/main/java/com/boge/modules/sys/localStorage/service/entity/LocalStorageQueryCriteria.java @@ -1,4 +1,4 @@ -package com.boge.modules.tickets.entity; +package com.boge.modules.sys.localStorage.service.entity; import java.sql.Timestamp; import java.util.List; diff --git a/base-fast/src/main/java/com/boge/modules/tickets/service/impl/LocalStorageServiceImpl.java b/base-fast/src/main/java/com/boge/modules/sys/localStorage/service/impl/LocalStorageServiceImpl.java similarity index 54% rename from base-fast/src/main/java/com/boge/modules/tickets/service/impl/LocalStorageServiceImpl.java rename to base-fast/src/main/java/com/boge/modules/sys/localStorage/service/impl/LocalStorageServiceImpl.java index f044da9..645803c 100644 --- a/base-fast/src/main/java/com/boge/modules/tickets/service/impl/LocalStorageServiceImpl.java +++ b/base-fast/src/main/java/com/boge/modules/sys/localStorage/service/impl/LocalStorageServiceImpl.java @@ -1,29 +1,41 @@ -package com.boge.modules.tickets.service.impl; +package com.boge.modules.sys.localStorage.service.impl; + import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; -import java.io.File; -import java.io.IOException; -import java.util.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.metadata.fill.FillWrapper; +import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.boge.common.exception.RRException; +import com.boge.common.query.MapOf; import com.boge.common.utils.FileProperties; import com.boge.common.utils.FileUtil; import com.boge.modules.contract.entity.ContractEntity; import com.boge.modules.contract.service.ContractService; -import com.boge.modules.tickets.dao.LocalStorageMapper; -import com.boge.modules.tickets.entity.LocalStorage; - -import com.boge.modules.tickets.service.LocalStorageService; - +import com.boge.modules.sys.localStorage.service.LocalStorageService; +import com.boge.modules.sys.localStorage.service.dao.LocalStorageMapper; +import com.boge.modules.sys.localStorage.service.entity.LocalStorage; +import com.deepoove.poi.XWPFTemplate; +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.apache.poi.xwpf.usermodel.XWPFParagraph; +import org.apache.poi.xwpf.usermodel.XWPFRun; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; +import org.springframework.validation.BindException; import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.URLEncoder; +import java.util.*; @Service public class LocalStorageServiceImpl extends ServiceImpl implements LocalStorageService { @@ -101,4 +113,56 @@ public class LocalStorageServiceImpl extends ServiceImpl query(@RequestParam String annex) { - List list = new ArrayList<>(); - if (StringUtils.isNotEmpty(annex)){ - String[] split = annex.split(","); - list = localStorageService.listByIds(Arrays.asList(split)); - } - return new ResponseEntity(list, HttpStatus.OK); - } - - @ApiOperation("导出数据") - @GetMapping({"/download"}) - public void download(@RequestParam Long storageId , HttpServletResponse response, HttpServletRequest request) throws IOException { - this.localStorageService.downloadFile(this.localStorageService.getById(storageId),request, response); - } - - @ApiOperation("上传文件") - @PostMapping - public ResponseEntity create(@RequestParam String name, @RequestParam("file") MultipartFile file) { - LocalStorage localStorage = this.localStorageService.create(name, file); - return new ResponseEntity(localStorage, HttpStatus.CREATED); - } - - @PostMapping({"/pictures"}) - @ApiOperation("上传图片") - public ResponseEntity upload(@RequestParam MultipartFile file) { - String suffix = FileUtil.getExtensionName(file.getOriginalFilename()); - if (!"图片".equals(FileUtil.getFileType(suffix))) { - throw new RRException("只能上传图片"); - } else { - LocalStorage localStorage = this.localStorageService.create((String)null, file); - return new ResponseEntity(localStorage, HttpStatus.OK); - } - } - - - - @DeleteMapping - @ApiOperation("多选删除") - public ResponseEntity delete(@RequestBody Long[] ids) { - localStorageService.removeByIds(Arrays.asList(ids)); - return new ResponseEntity(HttpStatus.OK); - } - - public LocalStorageController(final LocalStorageService localStorageService) { - this.localStorageService = localStorageService; - } -} diff --git a/base-fast/src/main/java/com/boge/modules/tickets/controller/TicketsController.java b/base-fast/src/main/java/com/boge/modules/tickets/controller/TicketsController.java index 16ba182..684d937 100644 --- a/base-fast/src/main/java/com/boge/modules/tickets/controller/TicketsController.java +++ b/base-fast/src/main/java/com/boge/modules/tickets/controller/TicketsController.java @@ -8,11 +8,7 @@ import com.boge.common.utils.ShiroUtils; import com.boge.modules.sys.entity.SysUserEntity; import com.boge.modules.tickets.dto.TicketsDTO; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PathVariable; -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.RestController; +import org.springframework.web.bind.annotation.*; import com.boge.modules.tickets.entity.TicketsEntity; import com.boge.modules.tickets.service.TicketsService; @@ -78,6 +74,16 @@ public class TicketsController { ticketsService.saveTicket(tickets); return R.ok(); } + /** + * 发起 + */ + @GetMapping("/initiate") + //@RequiresPermissions("tickets:tickets:save") + public R initiate( String ticketsId){ + ticketsService.initiate(ticketsId); + return R.ok(); + } + /** * 修改 diff --git a/base-fast/src/main/java/com/boge/modules/tickets/enums/TicketsStatusEnums.java b/base-fast/src/main/java/com/boge/modules/tickets/enums/TicketsStatusEnums.java index 947af40..c3c91ba 100644 --- a/base-fast/src/main/java/com/boge/modules/tickets/enums/TicketsStatusEnums.java +++ b/base-fast/src/main/java/com/boge/modules/tickets/enums/TicketsStatusEnums.java @@ -10,7 +10,8 @@ import lombok.Getter; UNCHECK(0, "未开始"), CHECKED(1, "已指派"), REJECT(2, "处理中"), - FINISH(3, "已完成"); + FINISH(3, "已完成"), + REFUSE(4, "拒绝"); private Integer code; private String msg; diff --git a/base-fast/src/main/java/com/boge/modules/tickets/service/TicketsService.java b/base-fast/src/main/java/com/boge/modules/tickets/service/TicketsService.java index 3845e5d..4ac6222 100644 --- a/base-fast/src/main/java/com/boge/modules/tickets/service/TicketsService.java +++ b/base-fast/src/main/java/com/boge/modules/tickets/service/TicketsService.java @@ -23,5 +23,7 @@ public interface TicketsService extends IService { PageUtils queryPageByType(Map params); void saveTicket(TicketsEntity tickets); + + void initiate(String ticketsId); } diff --git a/base-fast/src/main/java/com/boge/modules/tickets/service/impl/TicketsServiceImpl.java b/base-fast/src/main/java/com/boge/modules/tickets/service/impl/TicketsServiceImpl.java index 8a1292d..bece410 100644 --- a/base-fast/src/main/java/com/boge/modules/tickets/service/impl/TicketsServiceImpl.java +++ b/base-fast/src/main/java/com/boge/modules/tickets/service/impl/TicketsServiceImpl.java @@ -3,6 +3,8 @@ package com.boge.modules.tickets.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.boge.common.exception.RRException; import com.boge.common.utils.ShiroUtils; import com.boge.modules.car.entity.CarEntity; import com.boge.modules.car.service.CarService; @@ -13,6 +15,7 @@ import com.boge.modules.sys.service.SysUserRoleService; import com.boge.modules.sys.service.impl.SysUserServiceImpl; import com.boge.modules.tickets.dto.TicketsDTO; import com.boge.modules.tickets.enums.TicketUserEnums; +import org.apache.commons.lang3.StringUtils; import org.flowable.common.engine.api.FlowableException; import org.flowable.engine.IdentityService; import org.flowable.engine.RuntimeService; @@ -115,22 +118,27 @@ public class TicketsServiceImpl extends ServiceImpl i @Override public void saveTicket(TicketsEntity tickets) { - // 启动流程 - try { - // 记录流程的发起人 - identityService.setAuthenticatedUserId(ShiroUtils.getUserId().toString()); - Map startVars = new HashMap<>(); - ProcessInstance processInstance = runtimeService.startProcessInstanceById(defId, startVars); - SysUserEntity loginUser = ShiroUtils.getUserEntity(); - tickets.setCreateTime(new Date()); - tickets.setCreateUser(loginUser.getNickname()); - tickets.setStatus(TicketsStatusEnums.REJECT.getCode()); - tickets.setAssignUserId(TicketUserEnums.SPECIALIST.getCode()); - tickets.setProcessInstance(processInstance.getProcessInstanceId()); - ticketsDao.insert(tickets); - } catch (Exception e) { - e.printStackTrace(); + SysUserEntity loginUser = ShiroUtils.getUserEntity(); + tickets.setCreateTime(new Date()); + tickets.setCreateUser(loginUser.getNickname()); + tickets.setStatus(TicketsStatusEnums.UNCHECK.getCode()); + ticketsDao.insert(tickets); + } + + @Override + public void initiate(String ticketsId) { + if (StringUtils.isEmpty(ticketsId)){ + throw new RRException("工单ID不能为空"); } + // 记录流程的发起人 + identityService.setAuthenticatedUserId(ShiroUtils.getUserId().toString()); + Map startVars = new HashMap<>(); + ProcessInstance processInstance = runtimeService.startProcessInstanceById(defId, startVars); + this.update(new LambdaUpdateWrapper() + .set(TicketsEntity::getProcessInstance,processInstance.getProcessInstanceId()) + .set(TicketsEntity::getStatus,TicketsStatusEnums.REJECT.getCode()) + .set(TicketsEntity::getAssignUserId,ShiroUtils.getUserId()) + .eq(TicketsEntity::getTicketsId,ticketsId)); } @Override diff --git a/base-fast/src/main/resources/templates/合同.docx b/base-fast/src/main/resources/templates/合同.docx new file mode 100644 index 0000000..4916ed4 Binary files /dev/null and b/base-fast/src/main/resources/templates/合同.docx differ diff --git a/base-fast/src/main/resources/templates/报价单.xlsx b/base-fast/src/main/resources/templates/报价单.xlsx new file mode 100644 index 0000000..a390ca7 Binary files /dev/null and b/base-fast/src/main/resources/templates/报价单.xlsx differ diff --git a/base-vue/src/views/modules/contract/temp-add-or-update.vue b/base-vue/src/views/modules/contract/temp-add-or-update.vue index 4bb6530..a8888af 100644 --- a/base-vue/src/views/modules/contract/temp-add-or-update.vue +++ b/base-vue/src/views/modules/contract/temp-add-or-update.vue @@ -52,19 +52,14 @@
- 三、 售后服务: - - -
-
- 四、 交货时间、地点: + 三、 服务时间、地点: 货期: 。交货地:
- 五、 运输方式及到达站和费用负担: + 四、 运输方式及到达站和费用负担:
@@ -285,193 +280,33 @@ export default { return result }, exportToExcel () { - // 获取客户名称 - let clientName = '' - if (this.dictData && this.dictData[1]) { - const client = this.dictData[1].find(item => item.value === this.dataForm.clientId) - clientName = client ? client.label : '' + // 检查contractId是否存在 + if (!this.dataForm.contractId) { + this.$message.error('合同ID不存在,无法导出') + return } - - // 构建Excel HTML内容 - let excelHtml = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ` - // 添加产品明细 - this.materData.forEach((item, index) => { - excelHtml += ` - - - - - - - - - - - ` + // 调用后端接口导出Excel + const url = this.$http.adornUrl('/flow/contract/export') + const params = this.$http.adornParams({ + contractId: this.dataForm.contractId }) - // 添加合计行 - excelHtml += ` - - - - - - - - - ` + // 构建完整的URL + const fullUrl = `${url}?${new URLSearchParams(params).toString()}` - // 添加合同条款 - excelHtml += ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - ` + // 创建隐藏的iframe来下载文件 + const iframe = document.createElement('iframe') + iframe.style.display = 'none' + iframe.src = fullUrl + document.body.appendChild(iframe) - // 添加双方信息 - excelHtml += ` - - - - -
产品购销合同
需方:${clientName}合同编号:${this.dataForm.contractCode || ''}
供方:上海诺力智能科技有限公司生效日期:${this.dataForm.effectiveTime || ''}
一、产品明细单
序号产品名称订货代码型号数量单位单价(元)总价(元)
${index + 1}${item.materialName || ''}${item.materialCode || ''}${item.materialSpec || ''}${item.qty || ''}${item.unitName || ''}${item.salePrice || ''}${item.amount || ''}
共计:${this.dataForm.totalPrice || ''}
共计人民币金额:(大写)${this.toChineseCurrency(this.dataForm.totalPrice)}含13%增值税
二、质量要求、技术标准、供方对质量负责的条件和期限:${this.dataForm.qc || ''}
三、售后服务:${this.dataForm.afterSales || ''}
四、交货时间、地点:货期:${this.dataForm.delivery || ''},交货地:${this.dataForm.place || ''}
五、运输方式及到达站和费用负担:${this.dataForm.transport || ''}
六、包装标准:${this.dataForm.packaging || ''}
七、结算方式:${this.dataForm.pay || ''},付款方式:${this.dataForm.payment || ''}
八、违约责任:${this.dataForm.breach || ''}
九、解决合同纠纷的方式:${this.dataForm.solveDispute || ''}
十、其它约定事项:${this.dataForm.supplement || ''}
-
需方:
-
单位名称:${this.client.clientName || ''}
-
地址:${this.client.address || ''}
-
委托代理电话:${this.client.tel || ''}
-
传真:${this.client.fax || ''}
-
开户银行:${this.client.bank || ''}
-
帐号:${this.client.card || ''}
-
-
供方:
-
单位名称:上海诺力智能科技有限公司(盖章)
-
地址:上海青浦区徐泾镇高光路215弄99号4号楼302室
-
委托代理电话:
-
传真:
-
开户银行:招商银行虹桥支行
-
帐号:12191702501091
-
- - - ` + // 延迟移除iframe + setTimeout(() => { + document.body.removeChild(iframe) + }, 3000) - // 创建Blob对象 - const blob = new Blob(['\ufeff', excelHtml], { - type: 'application/vnd.ms-excel;charset=utf-8' - }) - - // 生成文件名 - const fileName = `产品购销合同_${this.dataForm.contractCode || new Date().getTime()}.xls` - - // 创建下载链接 - const link = document.createElement('a') - link.href = URL.createObjectURL(blob) - link.download = fileName - - // 触发下载 - document.body.appendChild(link) - link.click() - - // 清理 - document.body.removeChild(link) - URL.revokeObjectURL(link.href) - - this.$message.success('Excel文件导出成功') + this.$message.success('正在导出Excel文件,请稍候...') } } } diff --git a/base-vue/src/views/modules/tickets/tickets.vue b/base-vue/src/views/modules/tickets/tickets.vue index 77f63e3..85e9fbf 100644 --- a/base-vue/src/views/modules/tickets/tickets.vue +++ b/base-vue/src/views/modules/tickets/tickets.vue @@ -151,12 +151,26 @@ fixed="right" header-align="center" align="center" - width="170" + width="220" label="操作"> @@ -389,6 +403,37 @@ this.$message.error(data.msg) } }) + }, + // 发起流程 + initiateFlowHandle(ticketsId) { + this.$confirm('确定要发起流程吗?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + this.$http({ + url: this.$http.adornUrl('/tickets/tickets/initiate'), + method: 'get', + params: this.$http.adornParams({ + ticketsId: ticketsId + }) + }).then(({data}) => { + if (data && data.code === 200) { + this.$message({ + message: '流程发起成功', + type: 'success', + duration: 1500, + onClose: () => { + this.getDataList() + } + }) + } else { + this.$message.error(data.msg || '流程发起失败') + } + }).catch(() => { + this.$message.error('流程发起失败,请稍后重试') + }) + }).catch(() => {}) } } }