Browse Source

提交所有未提交的业务代码

wangshuangpan 2 months ago
parent
commit
a2163a2cc4
100 changed files with 23568 additions and 1684 deletions
  1. 5 1
      health-admin/pom.xml
  2. 4 3
      health-admin/src/main/java/com/bzd/web/controller/common/CommonController.java
  3. 126 0
      health-admin/src/main/java/com/bzd/web/controller/common/recipeOCR.java
  4. 1 1
      health-admin/src/main/java/com/bzd/web/controller/daas/SDaasChannelManagementController.java
  5. 39 1
      health-admin/src/main/java/com/bzd/web/controller/ddgl/SDdglOrderStoreController.java
  6. 6 5
      health-admin/src/main/java/com/bzd/web/controller/dtp/ColdDistributionController.java
  7. 89 53
      health-admin/src/main/java/com/bzd/web/controller/dtp/PharmaceuticalServiceController.java
  8. 4 4
      health-admin/src/main/java/com/bzd/web/controller/dtp/PrintController.java
  9. 19 3
      health-admin/src/main/java/com/bzd/web/controller/dtp/RecipeRegisterController.java
  10. 3 3
      health-admin/src/main/java/com/bzd/web/controller/dtp/RecipeReportController.java
  11. 16 14
      health-admin/src/main/java/com/bzd/web/controller/dtp/SDtpYypzFollowUpSopController.java
  12. 1 0
      health-admin/src/main/java/com/bzd/web/controller/dtp/SDtpZskcxDrugInstructionsController.java
  13. 23 17
      health-admin/src/main/java/com/bzd/web/controller/gxhpz/DrugConfigController.java
  14. 11 7
      health-admin/src/main/java/com/bzd/web/controller/gxhpz/DvalueConfigController.java
  15. 190 106
      health-admin/src/main/java/com/bzd/web/controller/gxhpz/FollowTaskController.java
  16. 192 0
      health-admin/src/main/java/com/bzd/web/controller/gxhpz/FormTemplateController.java
  17. 7 5
      health-admin/src/main/java/com/bzd/web/controller/gxhpz/HospitalController.java
  18. 8 6
      health-admin/src/main/java/com/bzd/web/controller/gxhpz/PharmacistsController.java
  19. 1534 0
      health-admin/src/main/java/com/bzd/web/controller/report/KPIController.java
  20. 5 12
      health-admin/src/main/java/com/bzd/web/controller/spgl/SPConfigInfoController.java
  21. 4 0
      health-admin/src/main/java/com/bzd/web/controller/system/SysPostController.java
  22. 11 12
      health-admin/src/main/java/com/bzd/web/controller/zlgl/SZlglCfdjSaleprescriptioninfoController.java
  23. 9 1
      health-admin/src/main/resources/application.yml
  24. 36 1
      health-admin/src/main/resources/static/health/js/common.js
  25. 39 0
      health-admin/src/main/resources/static/health/js/ry-ui.js
  26. 5 5
      health-admin/src/main/resources/templates/daas/SDaasChannelManagementList.html
  27. 32 69
      health-admin/src/main/resources/templates/dtp/archives/archivesAdd.html
  28. 2 2
      health-admin/src/main/resources/templates/dtp/archives/archivesAddCallback.html
  29. 29 10
      health-admin/src/main/resources/templates/dtp/archives/archivesEdit.html
  30. 202 24
      health-admin/src/main/resources/templates/dtp/archives/archivesList.html
  31. 1 1
      health-admin/src/main/resources/templates/dtp/cold/cold.html
  32. 1 1
      health-admin/src/main/resources/templates/dtp/followUp/closePlanPage.html
  33. 216 0
      health-admin/src/main/resources/templates/dtp/followUp/createPlanPage.html
  34. 1088 0
      health-admin/src/main/resources/templates/dtp/followUp/followUp.html
  35. 998 273
      health-admin/src/main/resources/templates/dtp/followUp/followUpEdit.html
  36. 660 397
      health-admin/src/main/resources/templates/dtp/followUp/followUpEditAll.html
  37. 118 63
      health-admin/src/main/resources/templates/dtp/followUp/followUpList.html
  38. 4951 0
      health-admin/src/main/resources/templates/dtp/followUp/follwUpEditAllDetail.html
  39. 10 0
      health-admin/src/main/resources/templates/dtp/followUp/follwUpEditDetail.html
  40. 1 1
      health-admin/src/main/resources/templates/dtp/followUpAssign/followUpAssignAdd.html
  41. 35 130
      health-admin/src/main/resources/templates/dtp/followUpAssign/followUpAssignList.html
  42. 1 1
      health-admin/src/main/resources/templates/dtp/followUpAssign/followUpInformationPage.html
  43. 2 2
      health-admin/src/main/resources/templates/dtp/patientCounseling/patientCounselingEdit.html
  44. 1 1
      health-admin/src/main/resources/templates/dtp/print/detail.html
  45. 4 4
      health-admin/src/main/resources/templates/dtp/print/print.html
  46. 36 2
      health-admin/src/main/resources/templates/dtp/recipe/drugInfo.html
  47. 28 10
      health-admin/src/main/resources/templates/dtp/recipe/edit.html
  48. 1 1
      health-admin/src/main/resources/templates/dtp/recipe/huanzheBanding.html
  49. 179 15
      health-admin/src/main/resources/templates/dtp/recipe/newRecipe.html
  50. 24 17
      health-admin/src/main/resources/templates/dtp/recipe/recipe.html
  51. 101 17
      health-admin/src/main/resources/templates/dtp/recipe/salesRegistration.html
  52. 3 3
      health-admin/src/main/resources/templates/dtp/recipe/view.html
  53. 486 152
      health-admin/src/main/resources/templates/dtp/sfrw/SDtpYypzFollowUpSopAdd.html
  54. 494 121
      health-admin/src/main/resources/templates/dtp/sfrw/SDtpYypzFollowUpSopPageEdit.html
  55. 4 21
      health-admin/src/main/resources/templates/gxhpz/addRepurchasedGoods.html
  56. 1 1
      health-admin/src/main/resources/templates/gxhpz/allDrugsInfo.html
  57. 37 2
      health-admin/src/main/resources/templates/gxhpz/allProduct.html
  58. 773 0
      health-admin/src/main/resources/templates/gxhpz/design.html
  59. 2 0
      health-admin/src/main/resources/templates/gxhpz/drugconfigAdd.html
  60. 1 1
      health-admin/src/main/resources/templates/gxhpz/drugconfigDetail.html
  61. 5 4
      health-admin/src/main/resources/templates/gxhpz/drugconfigList.html
  62. 1 1
      health-admin/src/main/resources/templates/gxhpz/dvalueconfigDetail.html
  63. 15 14
      health-admin/src/main/resources/templates/gxhpz/dvalueconfigList.html
  64. 37 5
      health-admin/src/main/resources/templates/gxhpz/followUpTaskAdd.html
  65. 1248 0
      health-admin/src/main/resources/templates/gxhpz/formEdit.html
  66. 1183 0
      health-admin/src/main/resources/templates/gxhpz/formTemplateEdit.html
  67. 38 2
      health-admin/src/main/resources/templates/gxhpz/hospitalAdd.html
  68. 1 1
      health-admin/src/main/resources/templates/gxhpz/hospitalEdit.html
  69. 41 4
      health-admin/src/main/resources/templates/gxhpz/hospitalList.html
  70. 41 5
      health-admin/src/main/resources/templates/gxhpz/pharmacistsAdd.html
  71. 1 1
      health-admin/src/main/resources/templates/gxhpz/pharmacistsEdit.html
  72. 15 4
      health-admin/src/main/resources/templates/gxhpz/pharmacistsList.html
  73. 282 0
      health-admin/src/main/resources/templates/gxhpz/preview.html
  74. 163 0
      health-admin/src/main/resources/templates/gxhpz/template.html
  75. 2 2
      health-admin/src/main/resources/templates/index.html
  76. 224 0
      health-admin/src/main/resources/templates/report/kpi/OverviewKPIList.html
  77. 446 0
      health-admin/src/main/resources/templates/report/kpi/OverviewKPIListView.html
  78. 239 0
      health-admin/src/main/resources/templates/report/kpi/TrkPilotMktTbl.html
  79. 139 0
      health-admin/src/main/resources/templates/report/kpi/TrkPilotMktTblView.html
  80. 871 0
      health-admin/src/main/resources/templates/report/kpi/prescriptionAnalysis.html
  81. 257 0
      health-admin/src/main/resources/templates/report/kpi/prescriptionDetail.html
  82. 218 0
      health-admin/src/main/resources/templates/report/kpi/storeDValProd4Rates.html
  83. 366 0
      health-admin/src/main/resources/templates/report/kpi/storeDValProd4RatesView.html
  84. 1190 0
      health-admin/src/main/resources/templates/report/kpi/storeDValProd4RatesVisualize.html
  85. 523 0
      health-admin/src/main/resources/templates/report/kpi/visualization-patient-detail.html
  86. 691 0
      health-admin/src/main/resources/templates/report/kpi/visualization-patient.html
  87. 771 0
      health-admin/src/main/resources/templates/report/kpi/visualization.html
  88. 37 3
      health-admin/src/main/resources/templates/spgl/SPProductinfoList.html
  89. 5 5
      health-admin/src/main/resources/templates/zlgl/SZlglCfdjSaleprescriptioninfoList.html
  90. 11 3
      health-common/pom.xml
  91. 104 0
      health-common/src/main/java/com/bzd/common/core/domain/entity/StoreDrugPerformanceExport.java
  92. 137 0
      health-common/src/main/java/com/bzd/common/core/domain/entity/StorePatientDrugStatisticExport.java
  93. 130 0
      health-common/src/main/java/com/bzd/common/core/domain/entity/TotalPerformanceExport.java
  94. 46 0
      health-common/src/main/java/com/bzd/common/utils/IDCardValidator.java
  95. 107 0
      health-common/src/main/java/com/bzd/common/utils/submail/MessageXsend.java
  96. 53 0
      health-common/src/main/java/com/bzd/common/utils/submail/RequestEncoder.java
  97. 84 0
      health-common/src/main/java/com/bzd/common/utils/submail/SubMailConfig.java
  98. 31 0
      health-quartz/src/main/java/com/bzd/quartz/domain/SysJob.java
  99. 50 0
      health-quartz/src/main/java/com/bzd/quartz/task/FilterCondition.java
  100. 856 28
      health-quartz/src/main/java/com/bzd/quartz/task/RyTask.java

+ 5 - 1
health-admin/pom.xml

@@ -16,7 +16,11 @@
     </description>
 
     <dependencies>
-
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.9.3</version>
+        </dependency>
         <!-- SpringBoot集成thymeleaf模板 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>

+ 4 - 3
health-admin/src/main/java/com/bzd/web/controller/common/CommonController.java

@@ -25,12 +25,12 @@ import java.util.List;
 /**
  * 通用请求处理
  *
- * @author LiXiagnFei
+ * @author wsp
  */
 @Controller
 @RequestMapping("/common")
-public class CommonController
-{
+public class CommonController {
+
     private static final Logger log = LoggerFactory.getLogger(CommonController.class);
 
     @Autowired
@@ -60,6 +60,7 @@ public class CommonController
             return AjaxResult.error();
         }
     }
+
     /**
      * 通用下载请求
      *

+ 126 - 0
health-admin/src/main/java/com/bzd/web/controller/common/recipeOCR.java

@@ -0,0 +1,126 @@
+package com.bzd.web.controller.common;
+
+import okhttp3.*;
+import org.json.JSONObject;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 通用请求处理
+ *
+ * @author wsp
+ */
+@Controller
+@RequestMapping("/ocr/recipe")
+public class recipeOCR {
+    static final OkHttpClient HTTP_CLIENT = new OkHttpClient().newBuilder().build();
+
+    @PostMapping("/uploadInvoice")
+    public ResponseEntity<String> uploadInvoice(@RequestParam("image") MultipartFile file,@RequestParam("flag") String flag) throws Exception {
+        Map<String, Object> errorResponse = new HashMap<>();
+        try {
+            byte[] bytes = file.getBytes();
+            String apiKey="kCf9zQbSFkHmqKlo2bewI7Fe";
+            String secretKey="IHaSX3X76k0vLBTcOT3Z8SKJ1q6SF07i";
+            String result = medicalPrescription(apiKey, secretKey, bytes,flag);
+            return ResponseEntity.ok(result); // 返回识别结果
+        } catch (Exception e) {
+            // 捕获异常并返回适当的错误响应
+            errorResponse.put("error", e.getMessage());
+            if (e instanceof IOException && e.getMessage().contains("No permission")) {
+                errorResponse.put("error_code", 6);
+                errorResponse.put("error_msg", "No permission to access data");
+            }
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse.toString());
+        }
+
+
+    }
+    /**
+     * 处方笺识别
+     */
+    public static String medicalPrescription(String apiKey, String secretKey, byte[] imgData, String flag) throws Exception {
+        // 请求URL
+        String urlFP = "https://aip.baidubce.com/rest/2.0/ocr/v1/medical_invoice";
+        String urlCF = "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate_basic";
+        String url = flag.equals("FP")?urlFP:urlCF;
+        //webimage 网络图片文字识别
+        //idcard 身份证识别
+        //medical_prescription 处方笺识别
+        //medical_invoice 医疗发票识别
+        //accurate_basic 通用文字识别(高精度版)
+        // 图片进行Base64编码
+        String imgStr = Base64.getEncoder().encodeToString(imgData);
+        String imgParam = URLEncoder.encode(imgStr, "UTF-8");
+
+        String param = "image=" + imgParam;
+
+        // 获取access token
+        String accessToken = getAccessToken(apiKey, secretKey);
+        // 发送请求
+        RequestBody body = RequestBody.create(param, MediaType.get("application/x-www-form-urlencoded"));
+        Request request = new Request.Builder()
+                .url(url + "?access_token=" + accessToken)
+                .post(body)
+                .build();
+
+        try (Response response = HTTP_CLIENT.newCall(request).execute()) {
+            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
+
+            String result = response.body().string();
+            System.out.println(result);
+            return result;
+        }
+    }
+    // 假设这里有一个方法用于读取文件并返回字节数组
+    public static byte[] readFileByBytes(String filePath) throws IOException {
+        return java.nio.file.Files.readAllBytes(new File(filePath).toPath());
+    }
+    // 假设这里有一个方法用于将字节数组编码为Base64字符串
+    public static String encode(byte[] data) {
+        return java.util.Base64.getEncoder().encodeToString(data);
+    }
+    //获取token
+    public static String getAccessToken(String apiKey, String secretKey) throws IOException {
+        // 构建请求URL
+        HttpUrl url = new HttpUrl.Builder()
+                .scheme("https")
+                .host("aip.baidubce.com")
+                .addPathSegment("oauth")
+                .addPathSegment("2.0")
+                .addPathSegment("token")
+                .addQueryParameter("grant_type", "client_credentials")
+                .addQueryParameter("client_id", apiKey)
+                .addQueryParameter("client_secret", secretKey)
+                .build();
+
+        // 创建请求
+        Request request = new Request.Builder()
+                .url(url)
+                .get() // 使用GET方法
+                .build();
+
+        // 发送请求并处理响应
+        try (Response response = HTTP_CLIENT.newCall(request).execute()) {
+            if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
+
+            // 解析响应
+            String responseBody = response.body().string();
+            JSONObject jsonObject = new JSONObject(responseBody);
+            return jsonObject.getString("access_token");
+        }
+    }
+
+}

+ 1 - 1
health-admin/src/main/java/com/bzd/web/controller/daas/SDaasChannelManagementController.java

@@ -88,7 +88,7 @@ public class SDaasChannelManagementController extends BaseController {
     * @return
     * @throws Exception
     */
-    @RequiresPermissions("daas:ds:list")
+    @RequiresPermissions("daas:ds:query")
     @PostMapping("/sDaasChannelManagementList")
     @ResponseBody
     public TableDataInfo sdaaschannelmanagementList() throws Exception {

+ 39 - 1
health-admin/src/main/java/com/bzd/web/controller/ddgl/SDdglOrderStoreController.java

@@ -27,7 +27,7 @@ import java.util.List;
 * @since 2024-10-15
 */
 @Controller
-@RequestMapping(value = "ddgl/sddglorderstore")
+@RequestMapping(value = "ddgl/ddglorderstore")
 public class SDdglOrderStoreController extends BaseController {
 
     // 页面跳转前缀
@@ -109,6 +109,44 @@ public class SDdglOrderStoreController extends BaseController {
     List<PageData> pageData = sDdglOrderStoreService.findSDdglOrderStoreList(pd);
         return getDataTable(pageData);
     }
+    /**
+     * 门店订单表 模拟生成1
+     *
+     * @return
+     * @throws Exception
+     */
+    @RequiresPermissions("ddgl:dd:add")
+    @PostMapping("/getRandomOrdersWithMedications")
+    @ResponseBody
+    public AjaxResult getRandomOrdersWithMedications() throws Exception {
+        PageData pd = this.getPageData();
+        String limitNumber= (String) pd.get("limitNumber");
+        pd.put("limitNumber",Integer.parseInt(limitNumber));
+        int a = sDdglOrderStoreService.getRandomOrdersWithMedications(pd);
+        if(a>0){
+            return AjaxResult.success("模拟生成成功");
+        }else {
+            return AjaxResult.error("模拟生成失败");
+        }
+    }
+    /**
+     * 门店订单表 模拟生成2
+     *
+     * @return
+     * @throws Exception
+     */
+    @RequiresPermissions("ddgl:dd:add")
+    @PostMapping("/getFilteredOrdersWithMedications")
+    @ResponseBody
+    public AjaxResult getFilteredOrdersWithMedications() throws Exception {
+        PageData pd = this.getPageData();
+        int a = sDdglOrderStoreService.getFilteredOrdersWithMedications(pd);
+        if(a>0){
+            return AjaxResult.success("模拟生成成功");
+        }else {
+            return AjaxResult.error("模拟生成失败");
+        }
+    }
 
     /**
     * 门店订单表 数据删除 根据id

+ 6 - 5
health-admin/src/main/java/com/bzd/web/controller/dtp/ColdDistributionController.java

@@ -38,7 +38,7 @@ public class ColdDistributionController extends BaseController {
      *
      * 冷链配送订单查询
      */
-    @RequiresPermissions("dtp:cold:list")
+    @RequiresPermissions("dtp:cold:query")
     @PostMapping("/list")
     @ResponseBody
     public TableDataInfo list() throws Exception {
@@ -53,6 +53,7 @@ public class ColdDistributionController extends BaseController {
     /**
      * 冷链配送订单新增页面
      */
+    @RequiresPermissions("dtp:cold:add")
     @GetMapping("/add")
     public String add(ModelMap mmap)
     {
@@ -62,19 +63,19 @@ public class ColdDistributionController extends BaseController {
     /**
      * 冷链配送订单新增保存
      */
-    //@RequiresPermissions("server:serv:add")
     @Log(title = "配送订单新增", businessType = BusinessType.INSERT)
     @PostMapping("/add")
     @ResponseBody
     public AjaxResult addSave() throws Exception {
         PageData pd = new PageData();
         pd = this.getPageData();
-
+        pd.put("storeId",getSysUser().getDeptId());
         Integer integer = dtpService.saveCold(pd);
         return toAjax(integer);
     }
 
     @Log(title = "订单删除", businessType = BusinessType.DELETE)
+    @RequiresPermissions("dtp:cold:remove")
     @PostMapping("/remove")
     @ResponseBody
     public AjaxResult remove() throws Exception
@@ -99,18 +100,18 @@ public class ColdDistributionController extends BaseController {
     /**
      * 保存訂單修改信息
      */
-    @RequiresPermissions("dtp:cold:edit")
     @Log(title = "冷链配送订单管理", businessType = BusinessType.UPDATE)
     @PostMapping("/edit")
     @ResponseBody
     public AjaxResult editSave() throws Exception
     {
         PageData pd = this.getPageData();
+        pd.put("storeId",getSysUser().getDeptId());
         Integer update = dtpService.editColdOrder(pd);
         if(update!=1){
             return error("修改失败");
         }
-        return toAjax(dtpService.editColdOrder(pd));
+        return toAjax(update);
     }
 
 

+ 89 - 53
health-admin/src/main/java/com/bzd/web/controller/dtp/PharmaceuticalServiceController.java

@@ -6,13 +6,16 @@ import com.bzd.common.config.HealthConfig;
 import com.bzd.common.config.dao.PageData;
 import com.bzd.common.core.controller.BaseController;
 import com.bzd.common.core.domain.AjaxResult;
+import com.bzd.common.core.domain.entity.SysDept;
 import com.bzd.common.core.page.TableDataInfo;
 import com.bzd.common.enums.BusinessType;
 import com.bzd.common.utils.DateUtils;
+import com.bzd.common.utils.IDCardValidator;
 import com.bzd.common.utils.ServletUtils;
 import com.bzd.common.utils.StringUtils;
 import com.bzd.common.utils.file.FileUploadUtils;
 import com.bzd.common.utils.file.MimeTypeUtils;
+import com.bzd.system.service.ISysDeptService;
 import com.bzd.system.service.PharmaceuticalService;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.json.JSONObject;
@@ -26,6 +29,7 @@ import java.util.HashMap;
 import java.util.List;
 
 import static com.bzd.common.config.datasource.DynamicDataSourceContextHolder.log;
+import static com.bzd.common.utils.ShiroUtils.getSysUser;
 import static oshi.util.UserGroupInfo.getUser;
 
 /**
@@ -53,14 +57,15 @@ public class PharmaceuticalServiceController extends BaseController {
 
     @Autowired
     private PharmaceuticalService pharmaceuticalService;
-
+    @Autowired
+    private ISysDeptService deptService;
     private final AipOcr client;
     /**
      * 档案管理页
      *
      * @return
      */
-    @RequiresPermissions("dtp:pmService:view")
+    @RequiresPermissions("dtp:pmService:danganguan")
     @GetMapping("/archives")
     public String archives() {
         return prefix_archives + "/archivesList";
@@ -72,16 +77,20 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:archivesList")
+    @RequiresPermissions("dtp:pmService:query")
     @PostMapping("/archivesList")
     @ResponseBody
-    public TableDataInfo archivesList() throws Exception {
+    public TableDataInfo archivesList(ModelMap mmap) throws Exception {
         PageData pd = this.getPageData();
         startPage();
           String storeId = (String) pd.get("storeId");
           if(StringUtils.isNull(storeId)){
               pd.put("storeId",getSysUser().getDeptId());
           }
+//        Long deptId=getSysUser().getDeptId();
+//        mmap.put("dept", deptService.selectDeptByDeptId(deptId));
+        //mmap.put("dept", deptService.selectDeptByDeptId(deptId));
+        //mmap.put("dept", deptService.selectDeptTree(dept));
         List<PageData> pageData = pharmaceuticalService.findArchivesList(pd);
         return getDataTable(pageData);
     }
@@ -91,30 +100,33 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:archivesList")
+    @RequiresPermissions("dtp:pmService:query")
     @PostMapping("/slectPatientByNameOrPhone")
     @ResponseBody
     public TableDataInfo slectPatientByNameOrPhone() throws Exception {
         PageData pd = this.getPageData();
         startPage();
+        pd.put("storeId",getSysUser().getDeptId());//只查询出当前店铺的数据
         List<PageData> pageData = pharmaceuticalService.slectPatientByNameOrPhone(pd);
         return getDataTable(pageData);
     }
-    @RequiresPermissions("dtp:pmService:archivesList")
+    @RequiresPermissions("dtp:pmService:query")
     @PostMapping("/slectPatientByNameOrPhoneOrIdCard")
     @ResponseBody
     public AjaxResult slectPatientByNameOrPhoneOrIdCard() throws Exception {
         PageData pd = this.getPageData();
         startPage();
+        pd.put("storeId",getSysUser().getDeptId());//只查询出当前店铺的数据
         List<PageData>  pageData = pharmaceuticalService.slectPatientByNameOrPhoneOrIdCard(pd);
 
         return AjaxResult.success(pageData);
     }
-    @RequiresPermissions("dtp:pmService:archivesList")
+    @RequiresPermissions("dtp:pmService:query")
     @PostMapping("/slectPatientByNameAndPhone")
     @ResponseBody
     public AjaxResult slectPatientByNameAndPhone() throws Exception {
         PageData pd = this.getPageData();
+        pd.put("storeId",getSysUser().getDeptId());//只查询出当前店铺的数据
         PageData pageData = pharmaceuticalService.slectPatientByNameAndPhone(pd);
         if(StringUtils.isNotNull(pageData)){
             return AjaxResult.success(pageData);
@@ -123,7 +135,7 @@ public class PharmaceuticalServiceController extends BaseController {
         }
 }
 
-    @RequiresPermissions("dtp:pmService:archivesList")
+    @RequiresPermissions("dtp:pmService:query")
     @PostMapping("/slectPatientById")
     @ResponseBody
     public AjaxResult slectPatientById() throws Exception {
@@ -210,6 +222,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
+    @RequiresPermissions("dtp:pmService:add")
     @GetMapping("/archivesAdd")
     public String archivesAdd(ModelMap mmap) {
         mmap.put("posts", 1);
@@ -220,6 +233,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
+    @RequiresPermissions("dtp:pmService:add")
     @GetMapping("/addArchivesCallback")
     public String addArchivesCallback(ModelMap mmap) {
         mmap.put("posts", 1);
@@ -252,7 +266,6 @@ public class PharmaceuticalServiceController extends BaseController {
     public PharmaceuticalServiceController() {
         this.client = new AipOcr("116175151", "kCf9zQbSFkHmqKlo2bewI7Fe", "IHaSX3X76k0vLBTcOT3Z8SKJ1q6SF07i");
     }
-    @RequiresPermissions("dtp:pmService:add")
     @PostMapping("/idCard")
     @ResponseBody
     public AjaxResult recognizeIdCard(@RequestParam("image") MultipartFile file) {
@@ -276,11 +289,15 @@ public class PharmaceuticalServiceController extends BaseController {
     @PostMapping("/archivesAdd")
     @ResponseBody
     public AjaxResult addSave() throws Exception {
-        PageData pd = new PageData();
-        pd = this.getPageData();
-       Object dateBirth= pd.get("dateBirth");
-       if(StringUtils.isNotNull(dateBirth)){
-          Integer age = ServletUtils.calculateAge(dateBirth.toString());//根据出生日期字符串计算年龄
+        PageData pd = this.getPageData();
+        pd.put("storeId", getSysUser().getDeptId());
+        String documentNumber= (String) pd.get("documentNumber");
+        if (!IDCardValidator.validateIDCard(documentNumber)) {
+            return error("身份证号码不正确"+documentNumber+"请重新输入");
+        }
+        String dateBirth= (String) pd.get("dateBirth");
+       if(StringUtils.isNotEmpty(dateBirth)){
+          Integer age = ServletUtils.calculateAge(dateBirth);//根据出生日期字符串计算年龄
            pd.put("age",age);
        }
         if (pharmaceuticalService.checkPatientIsExist(pd))
@@ -299,6 +316,11 @@ public class PharmaceuticalServiceController extends BaseController {
     public AjaxResult addArchivesCallback2() {
         PageData pd = this.getPageData();
         try {
+            pd.put("storeId", getSysUser().getDeptId());
+            String documentNumber= (String) pd.get("documentNumber");
+            if (!IDCardValidator.validateIDCard(documentNumber)) {
+                return error("身份证号码不正确"+documentNumber+"请重新输入");
+            }
             Object dateBirth= pd.get("dateBirth");
             if(StringUtils.isNotNull(dateBirth)){
                 Integer age = ServletUtils.calculateAge(dateBirth.toString());//根据出生日期字符串计算年龄
@@ -308,7 +330,6 @@ public class PharmaceuticalServiceController extends BaseController {
             {
                 return error("患者建档新增保存: '" + pd.getString("name") +"+"+ pd.getString("documentNumber") +"+" +pd.getString("phoneNumber")+"'失败,患者姓,身份证名或电话已存在");
             }
-
             PageData pdc = pharmaceuticalService.addArchivesCallback(pd);
             return AjaxResult.success(pdc);
         }catch (Exception e) {
@@ -321,7 +342,6 @@ public class PharmaceuticalServiceController extends BaseController {
     /**
      * 扫码上翻
      */
-    @RequiresPermissions("dtp:pmService:edit")
     @Log(title = "档案管理修改", businessType = BusinessType.UPDATE)
     @PostMapping("/ShaoMaShangFan")
     @ResponseBody
@@ -421,7 +441,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:archivesList")
+    @RequiresPermissions("dtp:pmService:query")
     @PostMapping("/getDrugPurchaseList")
     @ResponseBody
     public TableDataInfo getDrugPurchaseList() throws Exception {
@@ -435,7 +455,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * 随访任务页
      * @return
      */
-    @RequiresPermissions("dtp:pmService:view")
+    @RequiresPermissions("gxhpz:task:list")
     @GetMapping("/followUp")
     public String followUp() {
         return prefix_followUp + "/followUpList";
@@ -446,7 +466,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:archivesList")
+    @RequiresPermissions("gxhpz:task:query")
     @PostMapping("/followUpList")
     @ResponseBody
     public TableDataInfo followUpList() throws Exception {
@@ -462,7 +482,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:remove")
+    @RequiresPermissions("gxhpz:task:remove")
     @Log(title = "随访任务数据删除", businessType = BusinessType.DELETE)
     @PostMapping("/followUpRemove")
     @ResponseBody
@@ -491,7 +511,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:edit")
+    @RequiresPermissions("task:follow:followTaskEdit")
     @GetMapping("/followUpEdit/{followUpId}")
     public String followUpView(@PathVariable("followUpId") Long followUpId, ModelMap mmap) throws Exception {
         PageData pd = this.getPageData();
@@ -504,7 +524,7 @@ public class PharmaceuticalServiceController extends BaseController {
     /**
      * 保存随访任务数据修改
      */
-    @RequiresPermissions("dtp:pmService:edit")
+    @RequiresPermissions("task:follow:followTaskEdit")
     @Log(title = "访任务数据修改", businessType = BusinessType.UPDATE)
     @PostMapping("/followUpEdit")
     @ResponseBody
@@ -540,7 +560,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * 随访任务页
      * @return
      */
-    @RequiresPermissions("dtp:pmService:view")
+    @RequiresPermissions("task:follow:renfenpei")
     @GetMapping("/followUpAssign")
     public String followUpAssign() {
         return prefix_followUp_assign + "/followUpAssignList";
@@ -551,7 +571,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:add")
+    @RequiresPermissions("task:followrwfp:add")
     @GetMapping("/followUpAssignAdd")
     public String followUpAssignAdd(ModelMap mmap) throws Exception {
         PageData pd = this.getPageData();
@@ -563,7 +583,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:followUpAssignList")
+    @RequiresPermissions("task:followrwfp:query")
     @PostMapping("/followUpAssignList")
     @ResponseBody
     public TableDataInfo followUpAssignList() throws Exception {
@@ -614,7 +634,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return selectPharmacistsByStoreId position position phone status
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:followUpAssignList")
+    @RequiresPermissions("task:followrwfp:query")
     @PostMapping("/selectPharmacistsByStoreId")
     @ResponseBody
     public TableDataInfo selectPharmacistsByStoreId() throws Exception {
@@ -631,7 +651,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:edit")
+    @RequiresPermissions("task:followrwfp:edit")
     @PostMapping("/editFollowUpAssign")
     @ResponseBody
     public AjaxResult editFollowUpAssignById() throws Exception {
@@ -655,7 +675,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:edit")
+    @RequiresPermissions("task:followrwfp:edit")
     @PostMapping("/editFollowTaskAssignById")
     @ResponseBody
     public AjaxResult editFollowTaskAssignById() throws Exception {
@@ -680,8 +700,8 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:remove")
-    @Log(title = "档案管理删除", businessType = BusinessType.DELETE)
+    @RequiresPermissions("task:followrwfp:remove")
+    @Log(title = "随访跟进人分配表", businessType = BusinessType.DELETE)
     @PostMapping("/followUpAssignRemove")
     @ResponseBody
     public AjaxResult followUpAssignRemove() throws Exception {
@@ -709,7 +729,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:edit")
+    @RequiresPermissions("task:followrwfp:edit")
     @GetMapping("/followUpAssignEdit/{followUpAssignId}")
     public String followUpAssignView(@PathVariable("followUpAssignId") Long followUpAssignId, ModelMap mmap) throws Exception {
         PageData pd = this.getPageData();
@@ -722,8 +742,8 @@ public class PharmaceuticalServiceController extends BaseController {
     /**
      * 保存随访任务数据修改
      */
-    @RequiresPermissions("dtp:pmService:edit")
-    @Log(title = "档案管理修改", businessType = BusinessType.UPDATE)
+    @RequiresPermissions("task:followrwfp:edit")
+    @Log(title = "随访跟进人分配表修改", businessType = BusinessType.UPDATE)
     @PostMapping("/followUpAssignEdit")
     @ResponseBody
     public AjaxResult followUpAssignEditSave() {
@@ -750,7 +770,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * 患者咨询 页面
      * @return
      */
-    @RequiresPermissions("dtp:pmService:view")
+    @RequiresPermissions("hzgl:hzzx:view")
     @GetMapping("/patientCounseling")
     public String patientCounseling() {
         return prefix_patient_counseling + "/patientCounselingList";
@@ -761,7 +781,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:patientCounselingList")
+    @RequiresPermissions("hzgl:hzzx:query")
     @PostMapping("/patientCounselingList")
     @ResponseBody
     public TableDataInfo patientCounselingList() throws Exception {
@@ -777,7 +797,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:remove")
+    @RequiresPermissions("hzgl:hzzx:query")
     @Log(title = "患者咨询删除", businessType = BusinessType.DELETE)
     @PostMapping("/patientCounselingRemove")
     @ResponseBody
@@ -806,7 +826,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:edit")
+    @RequiresPermissions("hzgl:hzzx:edit")
     @GetMapping("/patientCounselingEdit/{id}")
     public String patientCounselingView(@PathVariable("id") Long id, ModelMap mmap) throws Exception {
         PageData pd = this.getPageData();
@@ -823,7 +843,7 @@ public class PharmaceuticalServiceController extends BaseController {
     /**
      * 患者咨询添加跳转页面
      */
-    @RequiresPermissions("dtp:pmService:add")
+    @RequiresPermissions("hzgl:hzzx:query")
     @GetMapping("/patientCounselingAdd")
     public String patientCounselingAdd(ModelMap mmap) throws Exception {
         mmap.put("userid", getSysUser().getUserId());
@@ -840,7 +860,7 @@ public class PharmaceuticalServiceController extends BaseController {
     /**
      * 患者咨询信息保存
      */
-    @RequiresPermissions("dtp:pmService:add")
+    @RequiresPermissions("hzgl:hzzx:add")
     @Log(title = "患者咨询信息新增", businessType = BusinessType.INSERT)
     @PostMapping("/patientCounselingAdd")
     @ResponseBody
@@ -864,8 +884,8 @@ public class PharmaceuticalServiceController extends BaseController {
     /**
      * 患者咨询数据修改
      */
-    @RequiresPermissions("dtp:pmService:edit")
-    @Log(title = "档案管理修改", businessType = BusinessType.UPDATE)
+    @RequiresPermissions("hzgl:hzzx:edit")
+    @Log(title = "患者咨询数据修改", businessType = BusinessType.UPDATE)
     @PostMapping("/patientCounselingEdit")
     @ResponseBody
     public AjaxResult patientCounselingEditSave() {
@@ -893,7 +913,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * 随访满意度评价 页面
      * @return
      */
-    @RequiresPermissions("dtp:pmService:view")
+    @RequiresPermissions("hzgl:sfmydpj:view")
     @GetMapping("/followUpEvaluation")
     public String followUpEvaluation() {
         return prefix_followUp_evaluation + "/followUpEvaluationList";
@@ -904,7 +924,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:patientCounselingList")
+    @RequiresPermissions("hzgl:sfmydpj:query")
     @PostMapping("/followUpEvaluationList")
     @ResponseBody
     public TableDataInfo followUpEvaluationList() throws Exception {
@@ -920,7 +940,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:remove")
+    @RequiresPermissions("hzgl:sfmydpj:remove")
     @Log(title = "随访满意度评价删除", businessType = BusinessType.DELETE)
     @PostMapping("/followUpEvaluationRemove")
     @ResponseBody
@@ -949,7 +969,7 @@ public class PharmaceuticalServiceController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("dtp:pmService:edit")
+    @RequiresPermissions("hzgl:sfmydpj:edit")
     @GetMapping("/followUpEvaluationEdit/{followUpEvaluationId}")
     public String followUpEvaluationView(@PathVariable("followUpEvaluationId") Long followUpEvaluationId, ModelMap mmap) throws Exception {
         PageData pd = this.getPageData();
@@ -962,7 +982,7 @@ public class PharmaceuticalServiceController extends BaseController {
     /**
      * 保存随访满意度评价数据修改
      */
-    @RequiresPermissions("dtp:pmService:edit")
+    @RequiresPermissions("hzgl:sfmydpj:add")
     @Log(title = "随访满意度评价修改", businessType = BusinessType.UPDATE)
     @PostMapping("/followUpEvaluationEdit")
     @ResponseBody
@@ -988,24 +1008,40 @@ public class PharmaceuticalServiceController extends BaseController {
     /**
      * 保存购药记录药品的单次计量单位和单次剂量的数据修改
      */
-    @RequiresPermissions("dtp:pmService:edit")
+    @RequiresPermissions("dtp:recipe:edit")
     @Log(title = "购药记录药修改", businessType = BusinessType.UPDATE)
     @PostMapping("/updateDrugPurchaseRecord")
     @ResponseBody
-    public AjaxResult updateDrugPurchaseRecord() {
+    public AjaxResult updateDrugPurchaseRecord(ModelMap mmap) {
         PageData pd = this.getPageData();
         pd.put("up", "up");
+
+        PageData pageData=new PageData();
         try {
             pd.put("updatedTime", DateUtils.getTime());
             pd.put("updatedBy", getSysUser().getLoginName());
-            Integer updateResult = pharmaceuticalService.updateDrugPurchaseRecord(pd);
-            if (updateResult == 1) {
-                // 成功更新
-                return AjaxResult.success("修改成功");
-            } else {
+            int updateResult = pharmaceuticalService.updateDrugPurchaseRecord(pd);
+            PageData pd6=new PageData();
+            if (StringUtils.isNull(updateResult)) {
+
+
                 // 更新失败
                 logger.error("Failed to update DrugRecord with ID: {}", pd.get("id"));
                 return AjaxResult.error("修改失败");
+            } else{
+                pd6.put("patientId", updateResult);
+                Long storeId= (Long) pd.get("storeId");
+                if(StringUtils.isNotNull(storeId)){
+                    pd6.put("storeId", storeId);
+                }else{
+                    pd6.put("storeId", getSysUser().getDeptId());
+                }
+
+                List<PageData>  dValueList =pharmaceuticalService.getPatienDvalueList(pd6);//查询患者购药D值用药天数
+                pageData.put("data",dValueList);
+                mmap.putAll(pageData);
+                // 成功更新
+                return AjaxResult.success("修改成功", com.alibaba.fastjson.JSONObject.toJSONString(mmap));
             }
         } catch (Exception e) {
             // 异常处理

+ 4 - 4
health-admin/src/main/java/com/bzd/web/controller/dtp/PrintController.java

@@ -41,7 +41,7 @@ public class PrintController extends BaseController {
      *
      * 配送单据打印查询
      */
-    @RequiresPermissions("dtp:print:list")
+    @RequiresPermissions("dtp:print:query")
     @PostMapping("/list")
     @ResponseBody
     public TableDataInfo list() throws Exception {
@@ -63,7 +63,7 @@ public class PrintController extends BaseController {
     /**
      * 配送单据详情页面
      */
-    @RequiresPermissions("dtp:print:view")
+    @RequiresPermissions("dtp:print:detail")
     @GetMapping("/detail/{id}")
     public String detail(@PathVariable("id") Long id, ModelMap mmap) throws Exception {
         PageData pd = this.getPageData();
@@ -88,6 +88,7 @@ public class PrintController extends BaseController {
     }
 
     @Log(title = "配送单据打印删除", businessType = BusinessType.DELETE)
+    @RequiresPermissions("dtp:print:remove")
     @PostMapping("/remove")
     @ResponseBody
     public AjaxResult remove() throws Exception
@@ -112,7 +113,6 @@ public class PrintController extends BaseController {
     /**
      * 配送单据打印修改信息
      */
-    @RequiresPermissions("dtp:print:edit")
     @Log(title = "配送单据打印", businessType = BusinessType.UPDATE)
     @PostMapping("/edit")
     @ResponseBody
@@ -126,7 +126,7 @@ public class PrintController extends BaseController {
         return toAjax(dtpService.editPrint(pd));
     }
 
-    @Log(title = "配送单据信息", businessType = BusinessType.EXPORT)
+    @Log(title = "配送单据导出", businessType = BusinessType.EXPORT)
     @RequiresPermissions("dtp:print:export")
     @PostMapping("/export")
     @ResponseBody

+ 19 - 3
health-admin/src/main/java/com/bzd/web/controller/dtp/RecipeRegisterController.java

@@ -46,7 +46,19 @@ public class RecipeRegisterController extends BaseController {
         return prefix + "/recipe";
     }
 
-/**
+    /**
+     * 处方登记入口2  菜单DTP管理入口
+     * creator wsp
+     */
+    @RequiresPermissions("dtp:recipe:view")
+    @GetMapping("/cfdj")
+    public String recipe2()
+    {
+        return prefix + "/recipe";
+    }
+
+
+    /**
  *
  * 处方登记查询
  */
@@ -57,7 +69,8 @@ public class RecipeRegisterController extends BaseController {
         PageData pd = this.getPageData();
 
         startPage();
-        pd.put("sales_is", 4);//4: "待处方登记";\r\n5: "待订单销售"
+        pd.put("sales_is", 4);//4: "待处方登记";\r\n5: "待订单销售""
+        pd.put("storeId", getSysUser().getDeptId());
         List<PageData> pageData = dtpService.findForList(pd);
         return  getDataTable(pageData);
     }
@@ -73,6 +86,7 @@ public class RecipeRegisterController extends BaseController {
         startPage();
         pd.put("status", 4);
         pd.put("sales_yes", 4);//4: "待处方登记";\r\n5: "待订单销售"
+        pd.put("storeId", getSysUser().getDeptId());
         List<PageData> pageData = dtpService.findForList(pd);
         return  getDataTable(pageData);
     }
@@ -95,7 +109,9 @@ public class RecipeRegisterController extends BaseController {
     @ResponseBody
     public AjaxResult getLastRecipeInfo( ModelMap mmap) throws Exception {
         PageData pd = this.getPageData();
-        startPage();
+
+        pd.put("storeId", getSysUser().getDeptId());
+        pd.put("registered_item", 1);//只查询 为登记品的数据
         PageData pageData =dtpService.getLastRecipeInfo(pd);
         return  AjaxResult.success(pageData);
     }

+ 3 - 3
health-admin/src/main/java/com/bzd/web/controller/dtp/RecipeReportController.java

@@ -35,7 +35,7 @@ public class RecipeReportController extends BaseController {
      * DTP处方登记概览详情
      * @return
      */
-    @RequiresPermissions("dtp:report:list")
+    @RequiresPermissions("dtp:report:query")
     @GetMapping("/viewDetail/{id}")
     @ResponseBody
     public AjaxResult viewDetail(@PathVariable("id") Long id)
@@ -84,7 +84,7 @@ public class RecipeReportController extends BaseController {
      *
      * DTP处方登记概 查询报表柱状图
      */
-    @RequiresPermissions("dtp:report:list")
+    @RequiresPermissions("dtp:report:query")
     @PostMapping("/listReport")
     @ResponseBody
     public AjaxResult listReport() throws Exception {
@@ -191,7 +191,7 @@ public class RecipeReportController extends BaseController {
      *
      * DTP处方登记概 查询报表
      */
-    @RequiresPermissions("dtp:report:list")
+    @RequiresPermissions("dtp:report:query")
     @PostMapping("/list")
     @ResponseBody
     public TableDataInfo list() throws Exception {

+ 16 - 14
health-admin/src/main/java/com/bzd/web/controller/dtp/SDtpYypzFollowUpSopController.java

@@ -38,7 +38,7 @@ public class SDtpYypzFollowUpSopController extends BaseController {
     *
     * @return
     */
-    @RequiresPermissions("dtp:dtp:view")
+    @RequiresPermissions("gxhpz:jhpz:index")
     @GetMapping("/sDtpYypzFollowUpSopAdd")
     public String add() {
         return prefix + "/sfrw/SDtpYypzFollowUpSopAdd";
@@ -59,7 +59,7 @@ public class SDtpYypzFollowUpSopController extends BaseController {
             if (result == 1) {
                 return AjaxResult.success("新增成功");
             } else {
-                logger.error("Failed to update 表skccrkckreceiptinfo with ID: {}", pd.get("id"));
+                logger.error("Failed to update 随访SOP表新增 with ID: {}", pd.get("id"));
                 return AjaxResult.error("新增失败");
             }
         } catch (Exception e) {
@@ -73,7 +73,7 @@ public class SDtpYypzFollowUpSopController extends BaseController {
     *
     * @return
     */
-    @RequiresPermissions("dtp:dtp:view")
+    @RequiresPermissions("gxhpz:jhpz:index")
     @GetMapping("/sDtpYypzFollowUpSopView")
     public String sdtpyypzfollowupsopListView() {
         return prefix + "/sfrw/SDtpYypzFollowUpSopList";
@@ -85,12 +85,13 @@ public class SDtpYypzFollowUpSopController extends BaseController {
     * @return
     * @throws Exception
     */
-    @RequiresPermissions("dtp:dtp:list")
+    @RequiresPermissions("gxhpz:jhpz:index")
     @PostMapping("/sDtpYypzFollowUpSopList")
     @ResponseBody
     public TableDataInfo sdtpyypzfollowupsopList() throws Exception {
     PageData pd = this.getPageData();
     startPage();
+        pd.put("storeId", getSysUser().getDeptId().toString());
     List<PageData> pageData = sDtpYypzFollowUpSopService.findSDtpYypzFollowUpSopList(pd);
         return getDataTable(pageData);
     }
@@ -101,7 +102,7 @@ public class SDtpYypzFollowUpSopController extends BaseController {
     * @return
     * @throws Exception
     */
-    @RequiresPermissions("dtp:dtp:remove")
+    @RequiresPermissions("gxhpz:jhpz:remove")
     @Log(title = "随访SOP表删除", businessType = BusinessType.DELETE)
     @PostMapping("/sDtpYypzFollowUpSopRemove")
     @ResponseBody
@@ -117,7 +118,7 @@ public class SDtpYypzFollowUpSopController extends BaseController {
     * @return
     * @throws Exception
     */
-    @RequiresPermissions("dtp:dtp:edit")
+    @RequiresPermissions("gxhpz:jhpz:edit")
     @GetMapping("/sDtpYypzFollowUpSopEdit/{id}")
     public String sDtpYypzFollowUpSopEditPage(@PathVariable("id") Long id, ModelMap mmap) throws Exception {
         PageData pd = this.getPageData();
@@ -132,16 +133,17 @@ public class SDtpYypzFollowUpSopController extends BaseController {
         }
         mmap.put("Template",Template);
 
-        if(null!=pd.getString("editTage")){
-            return prefix+ "/sfrw/SDtpYypzFollowUpSopPageEdit";
-        }
-        return prefix+ "/sfrw/SDtpYypzFollowUpSopEdit";
+//        if(pd.containsKey("editTage")){
+////            return prefix+ "/sfrw/SDtpYypzFollowUpSopPageEdit";
+////        }
+////        return prefix+ "/sfrw/SDtpYypzFollowUpSopEdit";
+        return prefix+ "/sfrw/SDtpYypzFollowUpSopPageEdit";
     }
 
     /**
-    * 随访SOP表保存修改的数据
-    */
-    @RequiresPermissions("dtp:dtp:edit")
+     * 随访SOP表保存修改的数据
+     */
+    @RequiresPermissions("gxhpz:jhpz:edit")
     @Log(title = "随访SOP表修改", businessType = BusinessType.UPDATE)
     @PostMapping("/sDtpYypzFollowUpSopEdit")
     @ResponseBody
@@ -168,7 +170,7 @@ public class SDtpYypzFollowUpSopController extends BaseController {
     /**
      * 随访SOP状态 状态: 0已创建 1启用,2禁用 默认为已创建0
      */
-    @RequiresPermissions("dtp:dtp:edit")
+    @RequiresPermissions("gxhpz:jhpz:edit")
     @Log(title = "随访SOP表修改", businessType = BusinessType.UPDATE)
     @PostMapping("/updateSopStatusByTemplateId")
     @ResponseBody

+ 1 - 0
health-admin/src/main/java/com/bzd/web/controller/dtp/SDtpZskcxDrugInstructionsController.java

@@ -49,6 +49,7 @@ public class SDtpZskcxDrugInstructionsController extends BaseController {
     * @return
     */
     @Log(title = "药品说明书表新增", businessType = BusinessType.INSERT)
+    @RequiresPermissions("dtp:dtp:add")
     @PostMapping("/sDtpZskcxDrugInstructionsAdd")
     @ResponseBody
     public AjaxResult addSave() throws Exception {

+ 23 - 17
health-admin/src/main/java/com/bzd/web/controller/gxhpz/DrugConfigController.java

@@ -27,7 +27,7 @@ public class DrugConfigController extends BaseController {
     private String prefix = "gxhpz";
     @Autowired
     private DrugConfigService drugConfigService;
-    @RequiresPermissions("gxhpz:yppz:view")
+    @RequiresPermissions("gxhpz:ypk:query")
     @GetMapping("/index")
     public String recipe()
     {
@@ -37,18 +37,20 @@ public class DrugConfigController extends BaseController {
      *
      * 查询
      */
-    @RequiresPermissions("gxhpz:yppz:list")
+    @RequiresPermissions("gxhpz:ypk:query")
     @PostMapping("/drugconfigList")
     @ResponseBody
     public TableDataInfo list() throws Exception {
         PageData pd = this.getPageData();
         startPage();
+        pd.put("storeId",getSysUser().getDeptId().toString());
         List<PageData> pageData = drugConfigService.findForList(pd);
         return  getDataTable(pageData);
     }
     /**
      * 新增页面
      */
+    @RequiresPermissions("gxhpz:ypk:add")
     @GetMapping("/add")
     public String add(ModelMap mmap)
     {
@@ -58,7 +60,6 @@ public class DrugConfigController extends BaseController {
     /**
      * 详情
      */
-    @RequiresPermissions("dtp:yppz:view")
     @GetMapping("/drugConfigDetail/{id}")
     public String drugConfigDetail(@PathVariable("id") Long id, ModelMap mmap) throws Exception {
         PageData pd = this.getPageData();
@@ -70,7 +71,7 @@ public class DrugConfigController extends BaseController {
     /**
      * 查询药品库s_gxhpz_product_info
      */
-    @RequiresPermissions("dtp:yppz:view")
+    @RequiresPermissions("gxhpz:yppz:query")
     @PostMapping("/searchDrugs")
     @ResponseBody
     public AjaxResult searchDrugs() throws Exception {
@@ -79,10 +80,10 @@ public class DrugConfigController extends BaseController {
         if(StringUtils.isNull(query)){
             return AjaxResult.success();
         }
+        pd.put("storeId",getSysUser().getDeptId().toString());
         List<PageData> pds =drugConfigService.selectproductByCodeAndName(pd);
             return  AjaxResult.success(pds);
     }
-    @RequiresPermissions("dtp:yppz:view")
     @PostMapping("/searchDrugsObject")
     @ResponseBody
     public AjaxResult searchDrugsObject() throws Exception {
@@ -91,6 +92,7 @@ public class DrugConfigController extends BaseController {
         if(StringUtils.isNull(query)){
             return AjaxResult.success();
         }
+        pd.put("storeId",getSysUser().getDeptId().toString());
         PageData pdo =drugConfigService.selectOneproductByCodeAndName(pd);
         return  AjaxResult.success(pdo);
     }
@@ -98,12 +100,13 @@ public class DrugConfigController extends BaseController {
     /**
      * 药品新增保存
      */
-    @RequiresPermissions("gxhpz:yppz:add")
     @Log(title = "药品新增", businessType = BusinessType.INSERT)
     @PostMapping("/savedrugConfig")
     @ResponseBody
     public AjaxResult savedrugConfig() throws Exception {
         PageData pd = this.getPageData();
+
+        pd.put("storeId",getSysUser().getDeptId().toString());
         Object pds = drugConfigService.findOne(pd);
         if(pds !=null){
             return AjaxResult.error("药品编码已存在,药品重复");
@@ -117,7 +120,7 @@ public class DrugConfigController extends BaseController {
 
     }
     @Log(title = "删除", businessType = BusinessType.DELETE)
-    @RequiresPermissions("gxhpz:yppz:remove")
+    @RequiresPermissions("gxhpz:ysregjrpz:remove")
     @PostMapping("/remove")
     @ResponseBody
     public AjaxResult remove() throws Exception
@@ -142,7 +145,7 @@ public class DrugConfigController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("gxhpz:yppz:edit")
+    @RequiresPermissions("gxhpz:ysregjrpz:edit")
     @GetMapping("/edit/{id}")
     public String view(@PathVariable("id") String id, ModelMap mmap)throws Exception
     {
@@ -156,13 +159,13 @@ public class DrugConfigController extends BaseController {
     /**
      * 保存修改信息
      */
-    @RequiresPermissions("gxhpz:yppz:edit")
     @Log(title = "药品管理", businessType = BusinessType.UPDATE)
     @PostMapping("/editDrugConfig")
     @ResponseBody
     public AjaxResult editSave() throws Exception
     {
         PageData pd = this.getPageData();
+        pd.put("storeId",getSysUser().getDeptId().toString());
         Integer update = drugConfigService.update(pd);
         if(update!=1){
             return  AjaxResult.error("失败");
@@ -173,7 +176,7 @@ public class DrugConfigController extends BaseController {
     /**
      * 药品配置-启用停用
      */
-    @RequiresPermissions("gxhpz:yppz:edit")
+    @RequiresPermissions("gxhpz:ysregjrpz:edit")
     @Log(title = "药品配置-启用停用", businessType = BusinessType.UPDATE)
     @PostMapping("/changeStatus")
     @ResponseBody
@@ -188,6 +191,7 @@ public class DrugConfigController extends BaseController {
        }else {
             str = "启用";
        }
+        pd.put("storeId",getSysUser().getDeptId().toString());
         Integer update = drugConfigService.changeStatus(pd);
         if(update!=1){
             return AjaxResult.error(str+"失败");
@@ -200,7 +204,7 @@ public class DrugConfigController extends BaseController {
     /**
      * 药品配置-启用停用
      */
-    @RequiresPermissions("gxhpz:yppz:edit")
+    @RequiresPermissions("gxhpz:ysregjrpz:edit")
     @Log(title = "药品配置-启用停用", businessType = BusinessType.UPDATE)
     @PostMapping("/changedvalueStatus")
     @ResponseBody
@@ -232,7 +236,7 @@ public class DrugConfigController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("gxhpz:yppz:edit")
+    @RequiresPermissions("gxhpz:ysregjrpz:query")
     @GetMapping("/toCorrelation/{id}")
     public String toCorrelation(@PathVariable("id") String id, ModelMap mmap)throws Exception
     {
@@ -244,7 +248,7 @@ public class DrugConfigController extends BaseController {
         System.out.println("drugData"+pageData);
         return prefix + "/addRepurchasedGoods";
     }
-    @RequiresPermissions("gxhpz:yppz:edit")
+    @RequiresPermissions("gxhpz:regjrpz:add")
     @GetMapping("/toCorrelationAdd")
     public String toCorrelationAdd(ModelMap mmap)throws Exception
     {
@@ -271,7 +275,7 @@ public class DrugConfigController extends BaseController {
     /**
      * 药品信息页面
      */
-    @RequiresPermissions("dtp:yppz:view")
+    @RequiresPermissions("dtp:recipe:query")
     @GetMapping("/allDrugsInfo")
     public String allDrugsInfo(ModelMap mmap)
     {
@@ -281,7 +285,6 @@ public class DrugConfigController extends BaseController {
     /**
      * 药品信息页面
      */
-    @RequiresPermissions("dtp:yppz:view")
     @GetMapping("/getAllProduct")
     public String getAllProduct(ModelMap mmap)
     {
@@ -289,22 +292,24 @@ public class DrugConfigController extends BaseController {
         return prefix + "/allProduct";
     }
     /**配置药**/
-    @RequiresPermissions("gxhpz:yppz:list")
+    @RequiresPermissions("gxhpz:yppz:query")
     @PostMapping("/getAllDrugs")
     @ResponseBody
     public TableDataInfo getAllDrugs() throws Exception {
         PageData pd = this.getPageData();
         startPage();
+        pd.put("storeId",getSysUser().getDeptId().toString());
         List<PageData> pageData = drugConfigService.getAllDrugs(pd);
         return  getDataTable(pageData);
     }
     /**配置药**/
-    @RequiresPermissions("gxhpz:yppz:list")
+    @RequiresPermissions("gxhpz:yppz:query")
     @PostMapping("/getAllProductInfo")
     @ResponseBody
     public TableDataInfo getAllProductInfo() throws Exception {
         PageData pd = this.getPageData();
         startPage();
+        pd.put("storeId",getSysUser().getDeptId().toString());
         List<PageData> pageData = drugConfigService.getAllProductInfo(pd);
         return  getDataTable(pageData);
     }
@@ -319,6 +324,7 @@ public class DrugConfigController extends BaseController {
     public AjaxResult changesdvalueStatus() throws Exception{
         String str="";
         PageData pd = this.getPageData();
+        pd.put("storeId",getSysUser().getDeptId().toString());
         String dvalueStatus = (String) pd.get("dvalueStatus");
         if(dvalueStatus.equals("0")){
             str = "停止";

+ 11 - 7
health-admin/src/main/java/com/bzd/web/controller/gxhpz/DvalueConfigController.java

@@ -29,7 +29,7 @@ public class DvalueConfigController extends BaseController {
     private String prefix = "gxhpz";
     @Autowired
     private DvalueConfigService dvalueConfigService;
-    @RequiresPermissions("gxhpz:dzpz:view")
+    @RequiresPermissions("gxhpz:dzpz:query")
     @GetMapping("/index")
     public String recipe()
     {
@@ -39,18 +39,20 @@ public class DvalueConfigController extends BaseController {
      *
      * D值查询
      */
-    @RequiresPermissions("gxhpz:dzpz:list")
+    @RequiresPermissions("gxhpz:dzpz:query")
     @PostMapping("/dvalueconfigList")
     @ResponseBody
     public TableDataInfo list() throws Exception {
         PageData pd = this.getPageData();
         startPage();
+        pd.put("storeId",getSysUser().getDeptId().toString());
         List<PageData> pageData = dvalueConfigService.findForList(pd);
         return  getDataTable(pageData);
     }
     /**
      * 新增页面
      */
+    @RequiresPermissions("gxhpz:dzpz:add")
     @GetMapping("/add")
     public String add(ModelMap mmap)
     {
@@ -60,7 +62,7 @@ public class DvalueConfigController extends BaseController {
     /**
      * D值详情
      */
-    @RequiresPermissions("gxhpz:dzpz:view")
+    @RequiresPermissions("gxhpz:dzpz:detail")
     @GetMapping("/dvalueconfigDetail/{id}")
     public String drugInfo(@PathVariable("id") Long id, ModelMap mmap) throws Exception {
         PageData pd = this.getPageData();
@@ -72,12 +74,12 @@ public class DvalueConfigController extends BaseController {
     /**
      * D值新增保存
      */
-    //@RequiresPermissions("server:serv:add")
     @Log(title = "D值新增", businessType = BusinessType.INSERT)
     @PostMapping("/savedvalueconfig")
     @ResponseBody
     public AjaxResult savedvalueconfig() throws Exception {
         PageData pd = this.getPageData();
+        pd.put("storeId",getSysUser().getDeptId().toString());
         int result = dvalueConfigService.save(pd);
         if(result>0){
             return AjaxResult.success("成功");
@@ -88,7 +90,7 @@ public class DvalueConfigController extends BaseController {
         }
 
     }
-    @Log(title = "D值删除", businessType = BusinessType.DELETE)
+    @RequiresPermissions("gxhpz:dzpz:remove")
     @PostMapping("/remove")
     @ResponseBody
     public AjaxResult remove() throws Exception
@@ -127,13 +129,13 @@ public class DvalueConfigController extends BaseController {
     /**
      * 保存D值修改信息
      */
-    @RequiresPermissions("gxhpz:dzpz:edit")
     @Log(title = "D值管理", businessType = BusinessType.UPDATE)
     @PostMapping("/dvalueconfigEdit")
     @ResponseBody
     public AjaxResult editSave() throws Exception
     {
         PageData pd = this.getPageData();
+        pd.put("storeId",getSysUser().getDeptId().toString());
         Integer update = dvalueConfigService.update(pd);
         if(update!=1){
             return error("修改失败");
@@ -170,7 +172,7 @@ public class DvalueConfigController extends BaseController {
     }
 
 
-
+    @RequiresPermissions("gxhpz:dzpz:query")
     @PostMapping("/searchDvalue")
     @ResponseBody
     public AjaxResult searchDvalue() throws Exception
@@ -182,6 +184,7 @@ public class DvalueConfigController extends BaseController {
         pd.put("query", query);
         pd.put("d_value_code", query);
         pd.put("dValueName", query);
+        pd.put("storeId",getSysUser().getDeptId().toString());
         try {
             Object result = dvalueConfigService.findOne(pd);
             if (result != null) {
@@ -206,6 +209,7 @@ public class DvalueConfigController extends BaseController {
         pd.put("createdTime", DateUtils.getTime());
         pd.put("d_value_code",s5id);
         pd.put("dValueName",nameValue);
+        pd.put("storeId",getSysUser().getDeptId().toString());
         try {
             Object result = dvalueConfigService.save(pd);
             if (result != null) {

+ 190 - 106
health-admin/src/main/java/com/bzd/web/controller/gxhpz/FollowTaskController.java

@@ -9,6 +9,7 @@ import com.bzd.common.enums.BusinessType;
 import com.bzd.common.utils.DateUtils;
 import com.bzd.common.utils.StringUtils;
 import com.bzd.system.service.PharmaceuticalService;
+import com.bzd.system.service.gxhpz.CosePlanService;
 import com.bzd.system.service.gxhpz.FollowPlanService;
 import com.bzd.system.service.gxhpz.FollowTaskService;
 import org.apache.shiro.authz.annotation.RequiresPermissions;
@@ -21,10 +22,9 @@ import java.math.BigDecimal;
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * 随访任务任务控制层
@@ -42,18 +42,25 @@ public class FollowTaskController extends BaseController {
     @Autowired
     private PharmaceuticalService pharmaceuticalService;
 
+    @Autowired
+    private CosePlanService cosePlanService;
+
     // 随访任务
     private String prefix_followUp = "dtp/followUp";
     /**
      *
      * 任务查询
      */
-    @RequiresPermissions("gxhpz:task:list")
+    //@RequiresPermissions("task:follow:query")
     @PostMapping("/followTaskList")
     @ResponseBody
     public TableDataInfo TaskList() throws Exception {
         PageData pd = this.getPageData();
         startPage();
+       String storeId= (String) pd.get("storeId");
+       if(StringUtils.isEmpty(storeId)){
+           pd.put("storeId", getSysUser().getDeptId());
+       }
         //随访任务表单展示信息:只展示任务动作为表单任务的数据,不显示触达任务的数据
         pd.put("taskActionTaskType","表单任务");//任务动作(任务类型) 触达任务 表单任务
         List<PageData> pageData = followTaskService.findForList(pd);
@@ -106,6 +113,30 @@ public class FollowTaskController extends BaseController {
         }
 
     }
+    // 关闭计划原因常量
+    private static final Set<String> AUTO_CLOSE_CAUSES;
+
+    static {
+        HashSet<String> tempSet = new HashSet<>();
+        tempSet.add("离世");
+        tempSet.add("患者因疾病进展/死亡永久停药");
+        tempSet.add("已结束该药治疗疗程");
+        tempSet.add("患者疾病康复好转");
+        tempSet.add("疾病康复好转");
+        tempSet.add("经济原因无购买可能");
+        tempSet.add("不良反应严重");
+        tempSet.add("疾病进展(耐药、效果不明显)更换治疗用药");
+        tempSet.add("回到地级市医院购药");
+        AUTO_CLOSE_CAUSES = Collections.unmodifiableSet(tempSet);
+    }
+    // 关闭任务常量
+    private static final Set<String> AUTO_CLOSE_ASK_CAUSES;
+    static {
+        HashSet<String> tempSet = new HashSet<>();
+        tempSet.add("永久停药");
+        AUTO_CLOSE_ASK_CAUSES = Collections.unmodifiableSet(tempSet);
+    }
+
     @Log(title = "任务删除", businessType = BusinessType.DELETE)
     @PostMapping("/removeTask")
     @ResponseBody
@@ -144,7 +175,7 @@ public class FollowTaskController extends BaseController {
     /**
      * 保存任务修改信息/保存随访表单信息/保存随访计划信息
      */
-    @RequiresPermissions("gxhpz:task:edit")
+    @RequiresPermissions("task:follow:followTaskEdit")
     @Log(title = "任务管理", businessType = BusinessType.UPDATE)
     @PostMapping("/followTaskEdit")
     @ResponseBody
@@ -153,13 +184,9 @@ public class FollowTaskController extends BaseController {
         PageData pd = this.getPageData();
         String id = (String) pd.get("id");
         String patientId = (String)pd.get("patientId");
-        String returnMethod = (String)pd.get("returnMethod");
-        String iscoordinate = (String)pd.get("iscoordinate");
-        String returnObject = (String)pd.get("returnObject");
-        String medicationStatus = (String)pd.get("medicationStatus");//用药状态
+        String confirmFlag = (String)pd.get("confirmFlag");
         boolean newTask = false;
-        if(StringUtils.isNotEmpty(returnMethod) && StringUtils.isNotEmpty(iscoordinate)
-         && StringUtils.isNotEmpty(returnObject) && StringUtils.isNotEmpty(patientId) && StringUtils.isNotEmpty(id)){
+        if(StringUtils.isNotEmpty(patientId) && StringUtils.isNotEmpty(id)){
             pd.put("taskStatus", "已完成");//任务状态:\r\n,0待执行,\r\n1已完成,\r\n2未完成,\r\n3已下发,\r\n4已取消
         }
         String next_follow_time = (String) pd.get("next_follow_time");
@@ -196,11 +223,7 @@ public class FollowTaskController extends BaseController {
                 pd.put("interval_this_time", null);
             }
         }
-//        else {
-//            // 如果下次随访时间为空,则默认任务状态为“未完成”
-//            pd.put("taskStatus", "未完成");
-//            pd.put("interval_this_time", null);
-//        }
+
         pd.put("storeId", getSysUser().getDeptId());//登记人
         pd.put("operator", getSysUser().getLoginName());//登记人
         pd.put("updatedAt", DateUtils.getTime());//更新时间
@@ -212,20 +235,20 @@ public class FollowTaskController extends BaseController {
             PageData pageDataTask = followTaskService.selectOneById(pdTask);//根据任务id查询随访任务数据
             if(StringUtils.isNotNull(pageDataTask)){
                 pd.put("useForm", pageDataTask.get("useForm"));
+                pdTask.put("drugsLinkId", pageDataTask.get("drugsLinkId"));
+                pdTask.put("status", 7);//'处方状态:1:订单已完成;2: "待上传处方";3: "待确认信息";4: "待处方登记";5: "待订单销售";6: "待绑定患者";7: "处方已完成";8: "订单已退款";默认 0 "待确认信息";'
+
             }
         }
 
         Integer update = followTaskService.update(pd);
-        if(StringUtils.isNotEmpty(medicationStatus)){
-          String perpetual_stopdrug_cause = (String)  pd.get("perpetual_stopdrug_cause");//永久停药原因
-            if(StringUtils.isNotEmpty(perpetual_stopdrug_cause)  && perpetual_stopdrug_cause.equals("离世")){
-                //=离世时 关闭该患者所有计划任务
-                String planId =(String) pd.get("bc_planId");
-                pd.put("planId", planId);
-                pd.put("storeId", getSysUser().getDeptId());
-                Integer closeResult = followPlanService.closeAllPlan(pd);//关闭该患者所有计划任务
-                System.out.println(pd+"<参数操作结果>"+closeResult);
-            }
+        if(isAutoCloseCondition(pd) && confirmFlag.equals("1")){
+            Integer ret = cosePlanService.closePlan(pd);
+            System.out.println(pd+"<关闭计划操作结果>"+ret);
+        }
+        if(isAutoCloseTaskCondition(pd) && confirmFlag.equals("1")){
+            Integer ret = cosePlanService.closeTask(pd);
+            System.out.println(pd+"<关闭任务操作结果>"+ret);
         }
 
         if(newTask){
@@ -235,11 +258,32 @@ public class FollowTaskController extends BaseController {
             int addResult = followTaskService.addNewFollowTask(pd);//生成下次随访任务
             System.out.println(pd+"生成新任务结果="+addResult);
         }
+
         if(update<=0){
             return error("保存失败");
+        }else{
+            String drugsLinkId = (String) pdTask.get("drugsLinkId").toString();
+            if(StringUtils.isNotNull(drugsLinkId)){
+                update += followTaskService.updateCFBStatus(pdTask);//修改处方状态
+            }
+
+            return toAjax(update);
         }
-        return toAjax(update);
+
+    }
+    // 自动关闭计划条件判断
+    private boolean isAutoCloseCondition(PageData pd) {
+        return "2".equals(pd.getString("iscoordinate")) &&
+                "永久停药".equals(pd.getString("medicationStatus")) &&
+                AUTO_CLOSE_CAUSES.contains(pd.getString("perpetual_stopdrug_cause"));
+    }
+    // 自动关闭任务条件判断
+    private boolean isAutoCloseTaskCondition(PageData pd) {
+        return "1".equals(pd.getString("iscoordinate")) &&
+
+                AUTO_CLOSE_ASK_CAUSES.contains(pd.getString("medicationStatus"));
     }
+
     /**
      * 关闭计划原因页面
      * @return
@@ -287,93 +331,22 @@ public class FollowTaskController extends BaseController {
             pd4.put("businessBelonging", "脱落召回");//去查计划的条件 脱落召回
 
             pd5.put("id", pageData.get("patientId"));//去查购药记录的条件
-            PageData Plan1 = followPlanService.selectPlanByCPB(pd3);//去查计划 常规随访的
+            List<PageData> Plan1 = followPlanService.selectPlanByCPBList(pd3);//去查计划 常规随访的
             //pd1 = pharmaceuticalService.selectPatientById(pd2);//查询患者信息
             pd1 = pharmaceuticalService.selectFollowUpPerson(pd2);//查询跟进人数据信息
             if(StringUtils.isNotNull(Plan1)){
 
-                pdt1.put("planId", Plan1.get("planId"));
-                pageData.put("planId_cg", Plan1.get("planId"));
-                pageData.put("mdmCode_cg", Plan1.get("mdmCode"));
-                pageData.put("Plan1",Plan1);//常规随访的 类型的计划
-                pageData.put("follow_up_person",Plan1.get("follow_up_person"));
-                pageData.put("follow_up_person_id",Plan1.get("follow_up_person_id"));
-                pageData.put("productName",Plan1.get("productName"));
-                pageData.put("specification",Plan1.get("specification"));
-                pageData.put("businessBelonging",Plan1.get("businessBelonging"));
-                pageData.put("createdBy",Plan1.get("createdBy"));
-                pageData.put("createdTime",  Plan1.get("createdTime1"));
-                pageData.put("updatedBy",Plan1.get("updatedBy"));
-                pageData.put("updatedTime",Plan1.get("updatedTime1"));
-                // 获取原始的状态值并进行处理
-                Integer status  = (Integer) Plan1.get("status");
-                if (status != null) {
-                    switch (status) {
-                        case 0:
-                            pageData.put("status","关闭");
-                            break;
-                        case 1:
-                            pageData.put("status","进行中");
-                            break;
-                        case 2:
-                            pageData.put("status","待开启");
-                            break;
-                        default:
-                            pageData.put("status","待开启");
-                            break;
-                    }
-                }
-                List<PageData> listTask1= followTaskService.findForList(pdt1);//去查任务 常规随访
-                if(listTask1.size()>0){
-                    pageData.put("listTask1",listTask1);
-                }
+                pageData.put("planListCG",Plan1);
             }
 
-            PageData Plan2 = followPlanService.selectPlanByCPB(pd4);//去查计划 脱落召回
+            List<PageData> Plan2 = followPlanService.selectPlanByCPBList(pd4);//去查计划 脱落召回
             if(StringUtils.isNotNull(Plan2)){
-               String planIdrw = (String) Plan2.get("planId");
-               if(StringUtils.isNotEmpty(planIdrw)){
-                   pdt2.put("planId", planIdrw);
-                   List<PageData> listTask2= followTaskService.findForList(pdt2);//去查任务 脱落召回
-                   if(listTask2.size()>0){
-                       pageData.put("listTask2",listTask2);
-                   }
-               }
-                pageData.put("planId_tl", Plan2.get("planId"));
-                pageData.put("mdmCode_tl", Plan2.get("mdmCode"));
-                pageData.put("follow_up_person2",Plan2.get("follow_up_person"));
-                pageData.put("productName2",Plan2.get("productName"));
-                pageData.put("specification2",Plan2.get("specification"));
-                pageData.put("businessBelonging2",Plan2.get("businessBelonging"));
-                pageData.put("createdBy2",Plan2.get("createdBy"));
-                pageData.put("createdTime2",Plan2.get("createdTime1"));
-                pageData.put("updatedBy2",Plan2.get("updatedBy"));
-                pageData.put("updatedTime2",Plan2.get("updatedTime1"));
-                pageData.put("Plan2",Plan2);//脱落召回 类型的计划
-                //pd1.put("status2",Plan2.get("status"));
-                // 获取原始的状态值并进行处理
-                Integer status2  = (Integer) Plan2.get("status");
-                if (status2 != null) {
-                    switch (status2) {
-                        case 0:
-                            pageData.put("status2","关闭");
-                            break;
-                        case 1:
-                            pageData.put("status2","进行中");
-                            break;
-                        case 2:
-                            pageData.put("status2","待开启");
-                            break;
-                        default:
-                            pageData.put("status2","待开启");
-                            break;
-                    }
-                }
-
+//
+                pageData.put("planListTL",Plan2);
             }
             pd6.put("patientId", pageData.get("patientId"));
-            pd6.put("storeId", getSysUser().getDeptId());
-            List<PageData>  dValueList =pharmaceuticalService.getPatienDvalueList(pd6);//查询患者购药D值用药天数
+            pd6.put("storeId", getSysUser().getDeptId().toString());
+            List<PageData>  dValueList =pharmaceuticalService.getPatienDvalueList2(pd6);//查询患者购药D值用药天数
             pageData.put("dValueList",dValueList);
             PageData  Degree =followPlanService.getPerfectionDegree(pd2);//查询档案完善度百分比
             List<PageData>  records =pharmaceuticalService.getDrugPurchaseList(pd5);//去查购药记录
@@ -390,6 +363,7 @@ public class FollowTaskController extends BaseController {
             }
            if(records.size()>0){
                pageData.put("recordsData",records);
+               pageData.put("dl",records.get(0).get("dl"));
            }
             //任务跟进人数据
             if(StringUtils.isNotNull(pd1)){
@@ -455,7 +429,7 @@ public class FollowTaskController extends BaseController {
     {
         PageData pd = this.getPageData();
         System.out.println("关闭计划"+pd);
-        Integer update = followPlanService.closePlan(pd);
+        Integer update = cosePlanService.closePlan(pd);
         if(update>0){
             return AjaxResult.success("关闭计划成功");
         }else{
@@ -480,7 +454,117 @@ public class FollowTaskController extends BaseController {
         }
 
     }
+    /**
+     * 重启计划
+     */
+    @RequiresPermissions("gxhpz:task:edit")
+    @Log(title = "重启计划", businessType = BusinessType.UPDATE)
+    @PostMapping("/RestartPlan")
+    @ResponseBody
+    public AjaxResult RestartPlan() throws Exception
+    {
+        PageData pd = this.getPageData();
+        Integer update = followTaskService.RestartPlan(pd);
+        if(update>0){
+            return AjaxResult.success("重启计划成功");
+        }else{
+            return AjaxResult.error("重启计划失败");
+        }
 
+    }
+
+    /**
+     *
+     * 任务跟进人查询
+     */
+    @RequiresPermissions("gxhpz:task:list")
+    @PostMapping("/findTaskFollowerList")
+    @ResponseBody
+    public AjaxResult findTaskFollowerList() throws Exception {
+        PageData pd = this.getPageData();
+        startPage();
+        pd.put("storeId",getSysUser().getDeptId());
+        pd.put("position","任务跟进人");
+        List<PageData> pageData = followTaskService.findTaskFollowerList(pd);
+        return  AjaxResult.success(pageData);
+    }
+    /**
+     *
+     * 查询门店
+     */
+    @RequiresPermissions("gxhpz:task:list")
+    @PostMapping("/findTaskStoreList")
+    @ResponseBody
+    public AjaxResult findTaskStoreList() throws Exception {
+        PageData pd = this.getPageData();
+        startPage();
+        pd.put("storeId",getSysUser().getDeptId());
+        pd.put("status",'0');//部门状态(0正常 1停用)(0: 正常营业, 1: 暂停营业)
+        List<PageData> pageData = followTaskService.findTaskStoreList(pd);
+        List<Map<String, Object>> formattedStores = pageData.stream()
+                .map(store -> {
+                    Map<String, Object> item = new HashMap<>();
+                    item.put("id", store.get("id"));  // 必须包含 id 字段
+                    item.put("text", store.get("dept_name"));  // 必须包含 text 字段
+                    item.put("dept_name", store.get("dept_name"));  // 必须包含 text 字段
+                    return item;
+                }).collect(Collectors.toList());
+        return  AjaxResult.success(formattedStores);
+    }
 
 
+    /**
+     * 创建计划页面弹框
+     *
+     * @return
+     * @throws Exception
+     */
+    @RequiresPermissions("gxhpz:task:add")
+    @GetMapping("/createPlanPage")
+    public String createPlanPage(ModelMap mmap) throws Exception {
+        PageData pd = this.getPageData();
+        mmap.put("posts", 1);
+        return prefix_followUp + "/createPlanPage";
+    }
+    /**
+     * 创建计划页面弹框 查询模版
+     * @return Template
+     * @throws Exception
+     */
+    @RequiresPermissions("gxhpz:task:list")
+    @PostMapping("/createPlanToGetTemplate")
+    @ResponseBody
+    public TableDataInfo createPlanToGetTemplate() throws Exception {
+        PageData pd = this.getPageData();
+        startPage();
+        pd.put("storeId", getSysUser().getDeptId());
+        pd.put("status", 1);//状态: 0已创建 1启用,2禁用 默认为已创建0
+        List<PageData> pageData = followTaskService.createPlanToGetTemplate(pd);
+        return getDataTable(pageData);
+    }
+
+//
+    /**
+     * 创建计划
+     * @return int
+     * @throws Exception
+     */
+    @RequiresPermissions("gxhpz:task:list")
+    @PostMapping("/createPlanAdd")
+    @ResponseBody
+    public AjaxResult createPlanAdd() throws Exception {
+        PageData pd = this.getPageData();
+        //已创建过该模版计划
+
+        PageData isCreated = followPlanService.selectPlanByTEMP(pd);
+        if(StringUtils.isNotNull(isCreated)){
+            return AjaxResult.error(isCreated.get("planName")+"-"+"已创建过该模版计划"+pd.get("templateId")+"-"+pd.get("templateName"));
+        }
+        Integer update = followTaskService.createPlanAdd(pd);
+        if(update>0){
+            return AjaxResult.success("成功");
+        }else{
+            return AjaxResult.error("失败");
+        }
+    }
 }

+ 192 - 0
health-admin/src/main/java/com/bzd/web/controller/gxhpz/FormTemplateController.java

@@ -0,0 +1,192 @@
+package com.bzd.web.controller.gxhpz;
+
+import com.bzd.common.annotation.Log;
+import com.bzd.common.config.dao.PageData;
+import com.bzd.common.core.controller.BaseController;
+import com.bzd.common.core.domain.AjaxResult;
+import com.bzd.common.core.page.TableDataInfo;
+import com.bzd.common.enums.BusinessType;
+import com.bzd.system.service.gxhpz.FormTemplateService;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 表单模板管理
+ */
+@Controller
+@RequestMapping("form/template")
+public class FormTemplateController extends BaseController {
+    private String prefix = "gxhpz";
+    
+    @Autowired
+    private FormTemplateService formTemplateService;
+    
+    @RequiresPermissions("form:template:view")
+    @GetMapping("/formAdd")
+    public String template() {
+        return prefix + "/template";
+    }
+    
+    /**
+     * 表单模板列表
+     */
+    @RequiresPermissions("form:template:list")
+    @PostMapping("/list")
+    @ResponseBody
+    public TableDataInfo list() throws Exception {
+        PageData pd = this.getPageData();
+        startPage();
+        pd.put("storeId", getSysUser().getDeptId().toString());
+        List<PageData> list = formTemplateService.selectTemplateList(pd);
+        return getDataTable(list);
+    }
+    
+    /**
+     * 新增表单模板
+     */
+    @RequiresPermissions("form:template:add")
+    @GetMapping("/add")
+    public String add(ModelMap mmap) {
+        return prefix + "/design";
+    }
+    
+    /**
+     * 新增保存表单模板
+     */
+    @RequiresPermissions("form:template:add")
+    @Log(title = "表单模板管理", businessType = BusinessType.INSERT)
+    @PostMapping("/add")
+    @ResponseBody
+    public AjaxResult addSave() throws Exception {
+        PageData pd = this.getPageData();
+        pd.put("storeId", getSysUser().getDeptId().toString());
+        pd.put("createdBy", getLoginName());
+        return toAjax(formTemplateService.insertTemplate(pd));
+    }
+    
+    /**
+     * 修改表单模板
+     */
+    @RequiresPermissions("form:template:edit")
+    @GetMapping("/edit/{id}")
+    public String edit(@PathVariable("id") Long id, ModelMap mmap) throws Exception {
+        PageData pd = new PageData();
+        pd.put("id", id);
+        PageData template = formTemplateService.selectTemplateById(pd);
+        mmap.put("formData",template);
+        return prefix + "/formTemplateEdit";
+    }
+    
+    /**
+     * 修改保存表单模板
+     */
+    @RequiresPermissions("form:template:edit")
+    @Log(title = "表单模板管理", businessType = BusinessType.UPDATE)
+    @PostMapping("/edit")
+    @ResponseBody
+    public AjaxResult editSave() throws Exception {
+        PageData pd = this.getPageData();
+        pd.put("storeId", getSysUser().getDeptId().toString());
+        pd.put("updatedBy", getLoginName());
+        return toAjax(formTemplateService.updateTemplate(pd));
+    }
+    
+    /**
+     * 删除表单模板
+     */
+    @RequiresPermissions("form:template:remove")
+    @Log(title = "表单模板管理", businessType = BusinessType.DELETE)
+    @PostMapping("/remove")
+    @ResponseBody
+    public AjaxResult remove(String ids) throws Exception {
+        return toAjax(formTemplateService.deleteTemplateByIds(ids));
+    }
+    
+    /**
+     * 表单设计器
+     */
+    @RequiresPermissions("form:template:design")
+    @GetMapping("/design/{id}")
+    public String design(@PathVariable("id") Long id, ModelMap mmap) throws Exception {
+        PageData pd = new PageData();
+        pd.put("id", id);
+        PageData template = formTemplateService.selectTemplateById(pd);
+        mmap.putAll(template);
+        return prefix + "/design";
+    }
+    
+    /**
+     * 保存表单设计
+     */
+    @RequiresPermissions("form:template:design")
+    @Log(title = "表单设计", businessType = BusinessType.UPDATE)
+    @PostMapping("/saveDesign")
+    @ResponseBody
+    public AjaxResult saveDesign() throws Exception {
+        PageData pd = this.getPageData();
+        pd.put("updatedBy", getLoginName());
+        return toAjax(formTemplateService.saveTemplateDesign(pd));
+    }
+    
+    /**
+     * 预览表单
+     */
+    @RequiresPermissions("form:template:preview")
+    @GetMapping("/preview/{id}")
+    public String preview(@PathVariable("id") Long id, ModelMap mmap) throws Exception {
+        PageData pd = new PageData();
+        pd.put("id", id);
+        pd.put("storeId", getSysUser().getDeptId().toString());
+        PageData data = formTemplateService.getTemplateForPreview(pd);
+        mmap.put("formData", data);
+        return prefix + "/preview";
+    }
+    
+    /**
+     * 发布表单模板
+     */
+    @RequiresPermissions("form:template:publish")
+    @Log(title = "表单模板管理", businessType = BusinessType.UPDATE)
+    @PostMapping("/publish/{id}")
+    @ResponseBody
+    public AjaxResult publish(@PathVariable("id") Long id) throws Exception {
+        PageData pd = new PageData();
+        pd.put("id", id);
+        pd.put("status", 1);
+        pd.put("updatedBy", getLoginName());
+        return toAjax(formTemplateService.publishTemplate(pd));
+    }
+    
+    /**
+     * 停用表单模板
+     */
+    @RequiresPermissions("form:template:disable")
+    @Log(title = "表单模板管理", businessType = BusinessType.UPDATE)
+    @PostMapping("/disable/{id}")
+    @ResponseBody
+    public AjaxResult disable(@PathVariable("id") Long id) throws Exception {
+        PageData pd = new PageData();
+        pd.put("id", id);
+        pd.put("status", 0);
+        pd.put("updatedBy", getLoginName());
+        return toAjax(formTemplateService.disableTemplate(pd));
+    }
+    
+    /**
+     * 获取可用表单列表(供选择)
+     */
+    @PostMapping("/availableTemplates")
+    @ResponseBody
+    public AjaxResult getAvailableTemplates() throws Exception {
+        PageData pd = this.getPageData();
+        pd.put("storeId", getSysUser().getDeptId().toString());
+        pd.put("status", 1);
+        List<PageData> list = formTemplateService.selectAvailableTemplates(pd);
+        return AjaxResult.success(list);
+    }
+}

+ 7 - 5
health-admin/src/main/java/com/bzd/web/controller/gxhpz/HospitalController.java

@@ -40,18 +40,20 @@ public class HospitalController extends BaseController {
      *
      * D值查询
      */
-    @RequiresPermissions("gxhpz:hospital:list")
+    @RequiresPermissions("gxhpz:yyk:query")
     @PostMapping("/hospitalList")
     @ResponseBody
     public TableDataInfo list() throws Exception {
         PageData pd = this.getPageData();
         startPage();
+        pd.put("storeId",getSysUser().getDeptId().toString());
         List<PageData> pageData = hospitalListService.findForList(pd);
         return  getDataTable(pageData);
     }
     /**
      * 新增页面
      */
+    @RequiresPermissions("gxhpz:yyk:add")
     @GetMapping("/add")
     public String add(ModelMap mmap)
     {
@@ -61,7 +63,7 @@ public class HospitalController extends BaseController {
     /**
      * D值详情
      */
-    @RequiresPermissions("gxhpz:hospital:view")
+    @RequiresPermissions("gxhpz:yyk:detail")
     @GetMapping("/hospitalDetail/{id}")
     public String drugInfo(@PathVariable("id") Long id, ModelMap mmap) throws Exception {
         PageData pd = this.getPageData();
@@ -73,12 +75,12 @@ public class HospitalController extends BaseController {
     /**
      * D值新增保存
      */
-    //@RequiresPermissions("server:serv:add")
     @Log(title = "新增医院", businessType = BusinessType.INSERT)
     @PostMapping("/saveHospital")
     @ResponseBody
     public AjaxResult saveHospital() throws Exception {
         PageData pd = this.getPageData();
+        pd.put("storeId",getSysUser().getDeptId().toString());
         int result = hospitalListService.save(pd);
         if(result>0){
             return AjaxResult.success("成功");
@@ -90,6 +92,7 @@ public class HospitalController extends BaseController {
 
     }
     @Log(title = "删除医院", businessType = BusinessType.DELETE)
+    @RequiresPermissions("gxhpz:yyk:remove")
     @PostMapping("/remove")
     @ResponseBody
     public AjaxResult remove() throws Exception
@@ -114,7 +117,7 @@ public class HospitalController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("gxhpz:hospital:edit")
+    @RequiresPermissions("gxhpz:yyk:edit")
     @GetMapping("/edit/{id}")
     public String view(@PathVariable("id") String id, ModelMap mmap)throws Exception
     {
@@ -128,7 +131,6 @@ public class HospitalController extends BaseController {
     /**
      * 修改医院信息
      */
-    @RequiresPermissions("gxhpz:hospital:edit")
     @Log(title = "医院修改", businessType = BusinessType.UPDATE)
     @PostMapping("/hospitalEdit")
     @ResponseBody

+ 8 - 6
health-admin/src/main/java/com/bzd/web/controller/gxhpz/PharmacistsController.java

@@ -21,7 +21,7 @@ import java.util.Arrays;
 import java.util.List;
 
 /**
- * D值配置管理
+ * 药师跟进人管理
  * creator wsp
  */
 @Controller
@@ -38,20 +38,22 @@ public class PharmacistsController extends BaseController {
     }
     /**
      *
-     * D值查询
+     * 查询
      */
-    @RequiresPermissions("gxhpz:pharmacists:list")
+    @RequiresPermissions("gxhpz:ysregjrpz:query")
     @PostMapping("/pharmacistsList")
     @ResponseBody
     public TableDataInfo list() throws Exception {
         PageData pd = this.getPageData();
         startPage();
+        pd.put("storeId",getSysUser().getDeptId().toString());
         List<PageData> pageData = pharmacistsService.findForList(pd);
         return  getDataTable(pageData);
     }
     /**
      * 新增药师页面
      */
+    @RequiresPermissions("gxhpz:yspz:add")
     @GetMapping("/add")
     public String add(ModelMap mmap)
     {
@@ -61,6 +63,7 @@ public class PharmacistsController extends BaseController {
     /**
      * 新增任务跟进人页面
      */
+    @RequiresPermissions("gxhpz:regjrpz:add")
     @GetMapping("/add2")
     public String add2(ModelMap mmap)
     {
@@ -71,7 +74,6 @@ public class PharmacistsController extends BaseController {
     /**
      * 审核药师新增
      */
-    //@RequiresPermissions("server:serv:add")
     @Log(title = "审核药师新增", businessType = BusinessType.INSERT)
     @PostMapping("/savePharmacists")
     @ResponseBody
@@ -86,6 +88,7 @@ public class PharmacistsController extends BaseController {
 
     }
     @Log(title = "审核药师删除", businessType = BusinessType.DELETE)
+    @RequiresPermissions("gxhpz:ysregjrpz:remove")
     @PostMapping("/remove")
     @ResponseBody
     public AjaxResult remove() throws Exception
@@ -110,7 +113,7 @@ public class PharmacistsController extends BaseController {
      * @return
      * @throws Exception
      */
-    @RequiresPermissions("gxhpz:pharmacists:edit")
+    @RequiresPermissions("gxhpz:ysregjrpz:edit")
     @GetMapping("/edit/{id}")
     public String view(@PathVariable("id") String id, ModelMap mmap)throws Exception
     {
@@ -124,7 +127,6 @@ public class PharmacistsController extends BaseController {
     /**
      * 审核药师修改
      */
-    @RequiresPermissions("gxhpz:pharmacists:edit")
     @Log(title = "审核药师修改", businessType = BusinessType.UPDATE)
     @PostMapping("/pharmacistsEdit")
     @ResponseBody

+ 1534 - 0
health-admin/src/main/java/com/bzd/web/controller/report/KPIController.java

@@ -0,0 +1,1534 @@
+package com.bzd.web.controller.report;
+
+import com.bzd.common.annotation.Log;
+import com.bzd.common.config.dao.PageData;
+import com.bzd.common.core.controller.BaseController;
+import com.bzd.common.core.domain.AjaxResult;
+import com.bzd.common.core.domain.entity.StoreDrugPerformanceExport;
+import com.bzd.common.core.domain.entity.StorePatientDrugStatisticExport;
+import com.bzd.common.core.page.TableDataInfo;
+import com.bzd.common.enums.BusinessType;
+import com.bzd.common.utils.StringUtils;
+import com.bzd.common.utils.poi.ExcelUtil;
+import com.bzd.system.service.report.KPIService;
+import com.google.common.collect.ImmutableMap;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.*;
+import com.bzd.common.core.domain.entity.TotalPerformanceExport;
+
+import java.lang.reflect.Field;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.*;
+import java.util.stream.Collectors;
+
+
+/**
+ *
+ * 总览KPI维度绩效数据 Controller
+ * @author wangshuangpan
+ * @since 2025-03-27
+ */
+@Controller
+@RequestMapping(value = "/report/kpi")
+public class KPIController extends BaseController {
+    private String prefix = "report/kpi";
+
+    @Autowired
+    private KPIService kpiService;
+    @RequiresPermissions("report:kpi:view")
+    @GetMapping()
+    public String view()
+    {
+        return prefix + "/OverviewKPIList";
+    }
+    @RequiresPermissions("report:kpi:view")
+    @GetMapping("/visualization")
+    public String visualization(ModelMap mmap)
+    {
+        return prefix + "/visualization";
+    }
+
+
+
+    @RequiresPermissions("report:kpi:view")
+    @GetMapping("/storeDValProd4RatesView/{id}")
+    public String storeDValProd4RatesView(@PathVariable("id") String id, ModelMap mmap) throws Exception {
+        PageData pd = this.getPageData();
+        pd.put("id", id);
+        PageData obj = kpiService.storeDValProd4RatesfindOne(pd);
+
+        mmap.putAll(obj);
+        return prefix + "/storeDValProd4RatesView";
+    }
+
+
+
+    /**
+     * 可视化数据详情页面
+     */
+    @RequiresPermissions("report:kpi:view")
+    @GetMapping("/visualizationDetail/{id}")
+    public String visualizationDetail(@PathVariable("id") String id, ModelMap mmap) throws Exception {
+        PageData pd = this.getPageData();
+        pd.put("storeId", id);
+        List<TotalPerformanceExport> list = kpiService.OverviewKPIListexport(pd);
+
+        if (list != null && !list.isEmpty()) {
+            Map<String, Object> detailData = entityToUnderscoreMap(list.get(0));
+            mmap.put("detail", detailData);
+        }
+
+        return prefix + "/OverviewKPIListView";
+    }
+
+
+
+    /**
+     * 获取可视化数据
+     */
+    @RequiresPermissions("report:kpi:view")
+    @PostMapping("/visualization/data")
+    @ResponseBody
+    public AjaxResult getVisualizationData() {
+        try {
+            PageData pd = getPageData();
+
+            // 根据需要处理查询参数,例如时间范围转换等
+            String monthType = pd.getString("month");
+            if ("B".equals(monthType) || "S".equals(monthType)) {
+                // 处理"本月"和"上月"的时间范围
+                // 这里可以添加额外的逻辑,如果需要的话
+            }
+
+            // 不分页获取所有符合条件的数据
+            List<TotalPerformanceExport> list = kpiService.OverviewKPIListexport(pd);
+
+            // 构建返回结果
+            Map<String, Object> result = new HashMap<>();
+
+            // 1. 计算总体指标
+            int totalPatients = 0;
+            int newPatients = 0;
+            double totalRepurchaseRate = 0;
+            double totalTaskCompletionRate = 0;
+            int rateCount = 0;
+
+            for (TotalPerformanceExport item : list) {
+                totalPatients += item.getMedicationPatients() != null ? item.getMedicationPatients() : 0;
+                newPatients += item.getNewPatients() != null ? item.getNewPatients() : 0;
+
+                if (item.getOnTimeRepurchaseRate() != null && !item.getOnTimeRepurchaseRate().isEmpty()) {
+                    String rateStr = item.getOnTimeRepurchaseRate().replace("%", "");
+                    try {
+                        totalRepurchaseRate += Double.parseDouble(rateStr) / 100.0;
+                        rateCount++;
+                    } catch (NumberFormatException e) {
+                        // 忽略格式错误
+                    }
+                }
+
+                if (item.getTaskCompletionRate() != null && !item.getTaskCompletionRate().isEmpty()) {
+                    String rateStr = item.getTaskCompletionRate().replace("%", "");
+                    try {
+                        totalTaskCompletionRate += Double.parseDouble(rateStr) / 100.0;
+                    } catch (NumberFormatException e) {
+                        // 忽略格式错误
+                    }
+                }
+            }
+
+            double avgRepurchaseRate = rateCount > 0 ? totalRepurchaseRate / rateCount : 0;
+            double avgTaskCompletionRate = rateCount > 0 ? totalTaskCompletionRate / rateCount : 0;
+
+            // 2. 构建返回数据
+            result.put("totalPatients", totalPatients);
+            result.put("newPatients", newPatients);
+            result.put("avgRepurchaseRate", String.format("%.2f", avgRepurchaseRate * 100) + "%");
+            result.put("avgTaskCompletionRate", String.format("%.2f", avgTaskCompletionRate * 100) + "%");
+            result.put("detailData", list);
+
+            // 3. 按月份分组统计数据
+            Map<String, List<TotalPerformanceExport>> monthlyData = list.stream()
+                    .filter(item -> item.getMonth() != null && !item.getMonth().isEmpty())
+                    .collect(Collectors.groupingBy(TotalPerformanceExport::getMonth));
+
+            Map<String, Object> monthlyStats = new HashMap<>();
+            for (Map.Entry<String, List<TotalPerformanceExport>> entry : monthlyData.entrySet()) {
+                String month = entry.getKey();
+                List<TotalPerformanceExport> monthItems = entry.getValue();
+
+                // 计算该月平均值
+                Map<String, Object> monthStat = calculateMonthlyStats(monthItems);
+                monthlyStats.put(month, monthStat);
+            }
+
+            result.put("monthlyStats", monthlyStats);
+
+            return success(result);
+        } catch (Exception e) {
+            logger.error("获取可视化数据失败", e);
+            return error("获取可视化数据失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 计算月度统计数据
+     */
+    private Map<String, Object> calculateMonthlyStats(List<TotalPerformanceExport> items) {
+        Map<String, Object> stats = new HashMap<>();
+
+        double totalRepurchaseRate = 0;
+        double totalChurnRate = 0;
+        double totalRecallRate = 0;
+        double totalLossRate = 0;
+        int count = 0;
+
+        int totalDueCount = 0;
+        int totalRepurchasedCount = 0;
+        int totalChurnedCount = 0;
+        int totalRecalledCount = 0;
+        int totalLostCount = 0;
+
+        for (TotalPerformanceExport item : items) {
+            // 处理率值
+            if (item.getOnTimeRepurchaseRate() != null && !item.getOnTimeRepurchaseRate().isEmpty()) {
+                try {
+                    String rateStr = item.getOnTimeRepurchaseRate().replace("%", "");
+                    totalRepurchaseRate += Double.parseDouble(rateStr) / 100.0;
+                } catch (NumberFormatException e) {
+                    // 忽略格式错误
+                }
+            }
+
+            if (item.getChurnRate() != null && !item.getChurnRate().isEmpty()) {
+                try {
+                    String rateStr = item.getChurnRate().replace("%", "");
+                    totalChurnRate += Double.parseDouble(rateStr) / 100.0;
+                } catch (NumberFormatException e) {
+                    // 忽略格式错误
+                }
+            }
+
+            if (item.getChurnRecallRate() != null && !item.getChurnRecallRate().isEmpty()) {
+                try {
+                    String rateStr = item.getChurnRecallRate().replace("%", "");
+                    totalRecallRate += Double.parseDouble(rateStr) / 100.0;
+                } catch (NumberFormatException e) {
+                    // 忽略格式错误
+                }
+            }
+
+            if (item.getLossRate() != null && !item.getLossRate().isEmpty()) {
+                try {
+                    String rateStr = item.getLossRate().replace("%", "");
+                    totalLossRate += Double.parseDouble(rateStr) / 100.0;
+                } catch (NumberFormatException e) {
+                    // 忽略格式错误
+                }
+            }
+
+            // 处理计数项
+            totalDueCount += item.getDueForRepurchaseCount() != null ? item.getDueForRepurchaseCount() : 0;
+            totalRepurchasedCount += item.getOnTimeRepurchasedCount() != null ? item.getOnTimeRepurchasedCount() : 0;
+            totalChurnedCount += item.getChurnedCount() != null ? item.getChurnedCount() : 0;
+            totalRecalledCount += item.getRecalledCount() != null ? item.getRecalledCount() : 0;
+            totalLostCount += item.getLostCount() != null ? item.getLostCount() : 0;
+
+            count++;
+        }
+
+        // 计算平均值
+        double avgRepurchaseRate = count > 0 ? totalRepurchaseRate / count : 0;
+        double avgChurnRate = count > 0 ? totalChurnRate / count : 0;
+        double avgRecallRate = count > 0 ? totalRecallRate / count : 0;
+        double avgLossRate = count > 0 ? totalLossRate / count : 0;
+
+        // 添加到结果
+        stats.put("avgRepurchaseRate", String.format("%.2f", avgRepurchaseRate * 100));
+        stats.put("avgChurnRate", String.format("%.2f", avgChurnRate * 100));
+        stats.put("avgRecallRate", String.format("%.2f", avgRecallRate * 100));
+        stats.put("avgLossRate", String.format("%.2f", avgLossRate * 100));
+
+        stats.put("totalDueCount", totalDueCount);
+        stats.put("totalRepurchasedCount", totalRepurchasedCount);
+        stats.put("totalChurnedCount", totalChurnedCount);
+        stats.put("totalRecalledCount", totalRecalledCount);
+        stats.put("totalLostCount", totalLostCount);
+
+        return stats;
+    }
+
+
+
+
+    /**
+     * 总览KPI门店维度绩效数据
+     * @return
+     */
+    @RequiresPermissions("report:kpi:query")
+    @PostMapping("/OverviewKPIList")
+    @ResponseBody
+    public TableDataInfo OverviewKPIList() throws Exception {
+        PageData pd = this.getPageData();
+        startPage();
+        String storeId = (String) pd.get("storeId");
+        if(StringUtils.isEmpty(storeId) && getSysUser().getDeptId()!=0){
+            pd.put("storeId",getSysUser().getDeptId().toString());
+        }
+            List<PageData> pd1 = kpiService.OverviewKPIList(pd);
+            return getDataTable(pd1);
+    }
+
+
+    /**
+     * 门店级D值品四率页面
+     * @return
+     */
+    @RequiresPermissions("report:kpi:view")
+    @GetMapping("/storeDValProd4Rates")
+    public String storeDValProd4Rates()
+    {
+        return prefix + "/storeDValProd4Rates";
+    }
+    @RequiresPermissions("report:kpi:view")
+    @GetMapping("/storeDValProd4RatesVisualize")
+    public String storeDValProd4RatesVisualize()
+    {
+        return prefix + "/storeDValProd4RatesVisualize";
+    }
+
+    /**
+     * 门店级D值品四率
+     * @return
+     */
+    @RequiresPermissions("report:kpi:query")
+    @PostMapping("/storeDValProd4RatesList")
+    @ResponseBody
+    public TableDataInfo storeDValProd4RatesList() throws Exception {
+        PageData pd = this.getPageData();
+        startPage();
+        String storeId = (String) pd.get("storeId");
+        if(StringUtils.isEmpty(storeId) && getSysUser().getDeptId()!=0){
+            pd.put("storeId",getSysUser().getDeptId().toString());
+        }
+        List<PageData> pd1 = kpiService.storeDValProd4Rates(pd);
+        return getDataTable(pd1);
+    }
+
+
+    /**
+     *患者维度
+     * @return
+     */
+    @RequiresPermissions("report:kpi:view")
+    @GetMapping("/TrkPilotMktTbl")
+    public String TrkPilotMktTbl()
+    {
+        return prefix + "/TrkPilotMktTbl";
+    }
+    /**
+     * 患者维度
+     * @return
+     */
+    @RequiresPermissions("report:kpi:query")
+    @PostMapping("/TrkPilotMktTblList")
+    @ResponseBody
+    public TableDataInfo TrkPilotMktTblList() throws Exception {
+        PageData pd = this.getPageData();
+        startPage();
+        String storeId = (String) pd.get("storeId");
+        if(StringUtils.isEmpty(storeId) && getSysUser().getDeptId()!=0){
+            pd.put("storeId",getSysUser().getDeptId().toString());
+        }
+        List<PageData> pd1 = kpiService.TrkPilotMktTbl(pd);
+        return getDataTable(pd1);
+    }
+
+
+
+    /**
+     * 导出总览绩效数据
+     */
+    @Log(title = "总览绩效数据导出", businessType = BusinessType.EXPORT)
+    @RequiresPermissions("report:kpi:export")
+    @PostMapping("/Total/export")
+    @ResponseBody
+    public AjaxResult export() throws Exception {
+        List<TotalPerformanceExport> list = kpiService.OverviewKPIListexport(getPageData());
+        ExcelUtil<TotalPerformanceExport> util = new ExcelUtil<>(TotalPerformanceExport.class);
+        return util.exportExcel(list, "总览绩效数据");
+    }
+
+    /**
+     * 导出品门店维度数据
+     */
+    @Log(title = "品门店维度数据导出", businessType = BusinessType.EXPORT)
+    @RequiresPermissions("report:kpi:export")
+    @PostMapping("/drugstore/export")
+    @ResponseBody
+    public AjaxResult exportDrugStore() throws Exception {
+        List<StoreDrugPerformanceExport> list = kpiService.storeDValProd4Ratesexport(getPageData());
+        ExcelUtil<StoreDrugPerformanceExport> util = new ExcelUtil<>(StoreDrugPerformanceExport.class);
+        return util.exportExcel(list, "品门店维度数据");
+    }
+
+    /**
+     * 导出门店+患者+D值品统计数据
+     */
+    @Log(title = "门店患者D值品统计数据导出", businessType = BusinessType.EXPORT)
+    @RequiresPermissions("report:kpi:export")
+    @PostMapping("/patient/export")
+    @ResponseBody
+    public AjaxResult exportPatient() throws Exception {
+        PageData pd = this.getPageData();
+        startPage();
+        String storeId = (String) pd.get("storeId");
+        if(StringUtils.isEmpty(storeId) && getSysUser().getDeptId()!=0){
+            pd.put("storeId",getSysUser().getDeptId().toString());
+        }
+        List<StorePatientDrugStatisticExport> lists  = kpiService.TrkPilotMktTblexport(pd);
+        ExcelUtil<StorePatientDrugStatisticExport> util = new ExcelUtil<>(StorePatientDrugStatisticExport.class);
+        return util.exportExcel(lists, "门店患者D值品统计数据");
+    }
+
+
+
+
+
+
+    /**
+     * 实体对象转下划线命名的Map
+     */
+    public static <T> Map<String, Object> entityToUnderscoreMap(T entity) {
+        Map<String, Object> map = new HashMap<>();
+        if (entity == null) {
+            return map;
+        }
+
+        Field[] fields = entity.getClass().getDeclaredFields();
+        for (Field field : fields) {
+            field.setAccessible(true);
+            String fieldName = field.getName();
+            // 驼峰转下划线
+            String underscoreFieldName = camelToUnderscore(fieldName);
+
+            try {
+                Object value = field.get(entity);
+                map.put(underscoreFieldName, value);
+            } catch (IllegalAccessException e) {
+                // 忽略无法访问的字段
+            }
+        }
+
+        return map;
+    }
+
+    /**
+     * 驼峰命名转下划线命名
+     */
+    public static String camelToUnderscore(String camelCaseName) {
+        if (camelCaseName == null || camelCaseName.isEmpty()) {
+            return "";
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < camelCaseName.length(); i++) {
+            char c = camelCaseName.charAt(i);
+            if (Character.isUpperCase(c)) {
+                sb.append('_').append(Character.toLowerCase(c));
+            } else {
+                sb.append(c);
+            }
+        }
+
+        return sb.toString();
+    }
+
+
+
+
+    /**
+     * 患者维度数据可视化页面
+     */
+    @RequiresPermissions("report:kpi:view")
+    @GetMapping("/visualization-patient")
+    public String visualizationPatient(ModelMap mmap) {
+        return prefix + "/visualization-patient";
+    }
+
+    /**
+     * 患者维度数据可视化详情页面
+     */
+    @RequiresPermissions("report:kpi:view")
+    @GetMapping("/visualization-patient-detail") // 改这里
+    public String visualizationPatientDetail(@RequestParam("patientId") String patientId,@RequestParam("drugId") String drugId,ModelMap mmap) throws Exception {
+        try {
+            PageData pd = this.getPageData();
+            pd.put("patientId", patientId);
+            pd.put("dValueCode", drugId);
+
+            // 获取患者详情数据
+            Map<String, Object> patientDetail =  kpiService.TrkPilotMktTblViewObj(pd);
+
+            // 处理日期数据,确保日期类型正确
+            convertDateFields(patientDetail);
+
+            mmap.put("detail", patientDetail);
+            return prefix + "/visualization-patient-detail";
+        } catch (Exception e) {
+            logger.error("加载患者详情失败", e);
+            return "error/500";
+        }
+    }
+
+
+
+    /**
+     * 处理日期数据,将 LocalDateTime 转换为 java.util.Date
+     * 解决 Thymeleaf #dates.format() 无法处理 LocalDateTime 的问题
+     */
+    private void convertDateFields(Map<String, Object> patientDetail) {
+        // 处理首次购药日期
+        convertDateField(patientDetail, "first_purchase_date");
+        // 处理最近购药日期
+        convertDateField(patientDetail, "last_purchase_date");
+        // 处理关闭日期
+        convertDateField(patientDetail, "close_date");
+    }
+
+    /**
+     * 将单个日期字段从 LocalDateTime 转换为 java.util.Date
+     */
+    private void convertDateField(Map<String, Object> data, String fieldName) {
+        if (data.containsKey(fieldName)) {
+            Object dateObj = data.get(fieldName);
+
+            if (dateObj instanceof LocalDateTime) {
+                LocalDateTime localDateTime = (LocalDateTime) dateObj;
+                Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
+                data.put(fieldName, date);
+            } else if (dateObj instanceof String) {
+                // 如果是字符串,可以尝试解析为日期
+                try {
+                    java.sql.Date sqlDate = java.sql.Date.valueOf((String) dateObj);
+                    data.put(fieldName, new Date(sqlDate.getTime()));
+                } catch (Exception e) {
+                    // 不做处理,保留原来的字符串
+                }
+            }
+            // 其他类型不处理
+        }
+    }
+
+
+
+
+
+
+
+
+
+    /**
+     * 获取患者维度可视化数据
+     */
+    @RequiresPermissions("report:kpi:view")
+    @PostMapping("/visualization-patient/data")
+    @ResponseBody
+    public AjaxResult getVisualizationPatientData() {
+        try {
+            PageData pd = getPageData();
+
+            // 不分页获取所有符合条件的数据
+            List<Map<String, Object>> list = (List<Map<String, Object>>) kpiService.TrkPilotMktTblView(pd);
+
+            // 构建返回结果
+            Map<String, Object> result = new HashMap<>();
+
+            // 1. 计算总体指标
+            int totalPatients = list.size();
+            int malePatients = 0;
+            int femalePatients = 0;
+            int remainingDaysLessThanZero = 0;  // 断药患者
+            int remainingDaysGreaterThanZero = 0; // 服药中患者
+
+            Map<String, Integer> medicationStatusMap = new HashMap<>();
+            Map<String, Integer> ageGroupMap = new HashMap<>();
+            Map<String, Integer> diseaseTypeMap = new HashMap<>();
+
+            for (Map<String, Object> item : list) {
+                // 性别统计
+                Object genderObj = item.get("gender");
+                if (genderObj != null) {
+                    String gender = String.valueOf(genderObj);
+                    if ("0".equals(gender)) {
+                        malePatients++;
+                    } else if ("1".equals(gender)) {
+                        femalePatients++;
+                    }
+                }
+
+                // 用药状态统计
+                Object statusObj = item.get("medication_status");
+                String medicationStatus = statusObj != null ? String.valueOf(statusObj) : "未知";
+                medicationStatusMap.put(medicationStatus, medicationStatusMap.getOrDefault(medicationStatus, 0) + 1);
+
+                // 剩余用药天数统计
+                Object remainingDaysObj = item.get("remaining_days");
+                if (remainingDaysObj != null) {
+                    try {
+                        Integer remainingDays = Integer.parseInt(String.valueOf(remainingDaysObj));
+                        if (remainingDays > 0) {
+                            remainingDaysGreaterThanZero++;
+                        } else {
+                            remainingDaysLessThanZero++;
+                        }
+                    } catch (NumberFormatException e) {
+                        // 忽略无法解析的数值
+                    }
+                }
+
+                // 年龄段统计
+                Object ageObj = item.get("age");
+                if (ageObj != null) {
+                    try {
+                        Integer age = Integer.parseInt(String.valueOf(ageObj));
+                        String ageGroup = getAgeGroup(age);
+                        ageGroupMap.put(ageGroup, ageGroupMap.getOrDefault(ageGroup, 0) + 1);
+                    } catch (NumberFormatException e) {
+                        // 忽略无法解析的数值
+                    }
+                }
+
+                // 疾病类型统计
+                Object diseaseObj = item.get("disease_type");
+                String diseaseType = diseaseObj != null ? String.valueOf(diseaseObj) : "未知";
+                diseaseTypeMap.put(diseaseType, diseaseTypeMap.getOrDefault(diseaseType, 0) + 1);
+            }
+
+            // 2. 构建返回数据
+            result.put("totalPatients", totalPatients);
+            result.put("malePatients", malePatients);
+            result.put("femalePatients", femalePatients);
+            result.put("medicationStatusDistribution", medicationStatusMap);
+            result.put("ageGroupDistribution", ageGroupMap);
+            result.put("diseaseTypeDistribution", diseaseTypeMap);
+            int finalRemainingDaysGreaterThanZero = remainingDaysGreaterThanZero;
+            int finalRemainingDaysLessThanZero = remainingDaysLessThanZero;
+            result.put("remainingDaysDistribution", new HashMap<String, Integer>() {{
+                put("服药中", finalRemainingDaysGreaterThanZero);
+                put("断药中", finalRemainingDaysLessThanZero);
+            }});
+            result.put("detailData", list);
+
+            return success(result);
+        } catch (Exception e) {
+            logger.error("获取患者维度可视化数据失败", e);
+            return error("获取患者维度可视化数据失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 根据年龄获取年龄段
+     */
+    private String getAgeGroup(Integer age) {
+        if (age < 18) {
+            return "未成年(<18岁)";
+        } else if (age < 30) {
+            return "青年(18-29岁)";
+        } else if (age < 45) {
+            return "中青年(30-44岁)";
+        } else if (age < 60) {
+            return "中年(45-59岁)";
+        } else if (age < 75) {
+            return "老年(60-74岁)";
+        } else {
+            return "高龄老年(≥75岁)";
+        }
+    }
+
+
+
+
+
+
+
+
+    /**
+     * 门店级D值品四率图表数据
+     * @return
+     * @throws Exception
+     */
+    @RequiresPermissions("report:kpi:view")
+    @PostMapping("/storeDValProd4RatesData")
+    @ResponseBody
+    public AjaxResult storeDValProd4RatesData() throws Exception {
+        PageData pd = this.getPageData();
+
+        // 处理登录用户门店权限
+        String storeId = (String) pd.get("storeId");
+        if(StringUtils.isEmpty(storeId) && getSysUser().getDeptId()!=0){
+            pd.put("storeId",getSysUser().getDeptId().toString());
+        }
+
+        // 获取可视化参数
+        String viewType = (String) pd.get("viewType");
+        String groupBy = (String) pd.get("groupBy");
+        String metric = (String) pd.get("metric");
+
+        // 调用相同的服务方法获取基础数据
+        List<Map<String, Object>> rawData = kpiService.storeDValProd4Rates2(pd);
+
+        // 根据可视化需求处理数据
+        Map<String, Object> result = new HashMap<>();
+
+        // 1. 添加原始数据
+        result.put("rawData", rawData);
+
+        // 2. 计算指标汇总数据
+        Map<String, Object> summary = calculateSummary(rawData);
+        result.put("summary", summary);
+
+        // 3. 如果需要按月份分组
+        if ("month".equals(groupBy)) {
+            List<Map<String, Object>> monthlyData = aggregateByMonth(rawData);
+            result.put("monthlyData", monthlyData);
+        }
+
+        // 4. 如果需要按门店分组
+        if ("store".equals(groupBy)) {
+            List<Map<String, Object>> storeData = aggregateByStore(rawData);
+            result.put("storeData", storeData);
+        }
+
+        // 5. 如果需要按产品分组
+        if ("product".equals(groupBy)) {
+            List<Map<String, Object>> productData = aggregateByProduct(rawData);
+            result.put("productData", productData);
+        }
+
+        return AjaxResult.success("获取图表数据成功", result);
+    }
+
+    /**
+     * 计算汇总指标
+     * @param rawData 原始数据
+     * @return 汇总指标
+     */
+    private Map<String, Object> calculateSummary(List<Map<String, Object>> rawData) {
+        Map<String, Object> summary = new HashMap<>();
+
+        if (rawData == null || rawData.isEmpty()) {
+            return summary;
+        }
+
+        // 计算平均值
+        double avgRepurchaseRate = 0.0;
+        double avgChurnRate = 0.0;
+        double avgRecallRate = 0.0;
+        double avgLossRate = 0.0;
+
+        int validCount = 0;
+
+        for (Map<String, Object> data : rawData) {
+            Object repurchaseRateObj = data.get("on_time_repurchase_rate");
+            Object churnRateObj = data.get("churn_rate");
+            Object recallRateObj = data.get("churn_recall_rate");
+            Object lossRateObj = data.get("loss_rate");
+
+            if (repurchaseRateObj != null) {
+                try {
+                    double repurchaseRate = Double.parseDouble(repurchaseRateObj.toString());
+                    avgRepurchaseRate += repurchaseRate;
+                    validCount++;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (churnRateObj != null) {
+                try {
+                    double churnRate = Double.parseDouble(churnRateObj.toString());
+                    avgChurnRate += churnRate;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (recallRateObj != null) {
+                try {
+                    double recallRate = Double.parseDouble(recallRateObj.toString());
+                    avgRecallRate += recallRate;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (lossRateObj != null) {
+                try {
+                    double lossRate = Double.parseDouble(lossRateObj.toString());
+                    avgLossRate += lossRate;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+        }
+
+        if (validCount > 0) {
+            summary.put("avgRepurchaseRate", Math.round(avgRepurchaseRate / validCount * 100) / 100.0);
+            summary.put("avgChurnRate", Math.round(avgChurnRate / validCount * 100) / 100.0);
+            summary.put("avgRecallRate", Math.round(avgRecallRate / validCount * 100) / 100.0);
+            summary.put("avgLossRate", Math.round(avgLossRate / validCount * 100) / 100.0);
+        }
+
+        // 计算总量
+        long totalDueForRepurchase = 0;
+        long totalOnTimeRepurchased = 0;
+        long totalChurned = 0;
+        long totalRecalled = 0;
+        long totalLost = 0;
+
+        for (Map<String, Object> data : rawData) {
+            Object dueForRepurchaseObj = data.get("due_for_repurchase_count");
+            Object onTimeRepurchasedObj = data.get("on_time_repurchased_count");
+            Object churnedObj = data.get("churned_count");
+            Object recalledObj = data.get("recalled_count");
+            Object lostObj = data.get("lost_count");
+
+            if (dueForRepurchaseObj != null) {
+                try {
+                    totalDueForRepurchase += Long.parseLong(dueForRepurchaseObj.toString());
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (onTimeRepurchasedObj != null) {
+                try {
+                    totalOnTimeRepurchased += Long.parseLong(onTimeRepurchasedObj.toString());
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (churnedObj != null) {
+                try {
+                    totalChurned += Long.parseLong(churnedObj.toString());
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (recalledObj != null) {
+                try {
+                    totalRecalled += Long.parseLong(recalledObj.toString());
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (lostObj != null) {
+                try {
+                    totalLost += Long.parseLong(lostObj.toString());
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+        }
+
+        summary.put("totalDueForRepurchase", totalDueForRepurchase);
+        summary.put("totalOnTimeRepurchased", totalOnTimeRepurchased);
+        summary.put("totalChurned", totalChurned);
+        summary.put("totalRecalled", totalRecalled);
+        summary.put("totalLost", totalLost);
+
+        return summary;
+    }
+
+    /**
+     * 按月份聚合数据
+     * @param rawData 原始数据
+     * @return 按月份聚合的数据
+     */
+    private List<Map<String, Object>> aggregateByMonth(List<Map<String, Object>> rawData) {
+        Map<String, Map<String, Object>> monthlyMap = new HashMap<>();
+
+        for (Map<String, Object> data : rawData) {
+            Object monthObj = data.get("month");
+            if (monthObj == null) continue;
+
+            String month = monthObj.toString();
+
+            if (!monthlyMap.containsKey(month)) {
+                Map<String, Object> monthData = new HashMap<>();
+                monthData.put("month", month);
+                monthData.put("repurchaseRateSum", 0.0);
+                monthData.put("churnRateSum", 0.0);
+                monthData.put("recallRateSum", 0.0);
+                monthData.put("lossRateSum", 0.0);
+                monthData.put("count", 0);
+                monthlyMap.put(month, monthData);
+            }
+
+            Map<String, Object> monthData = monthlyMap.get(month);
+            int count = (int) monthData.get("count");
+
+            Object repurchaseRateObj = data.get("on_time_repurchase_rate");
+            Object churnRateObj = data.get("churn_rate");
+            Object recallRateObj = data.get("churn_recall_rate");
+            Object lossRateObj = data.get("loss_rate");
+
+            boolean hasValidData = false;
+
+            if (repurchaseRateObj != null) {
+                try {
+                    double repurchaseRate = Double.parseDouble(repurchaseRateObj.toString());
+                    monthData.put("repurchaseRateSum", (double) monthData.get("repurchaseRateSum") + repurchaseRate);
+                    hasValidData = true;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (churnRateObj != null) {
+                try {
+                    double churnRate = Double.parseDouble(churnRateObj.toString());
+                    monthData.put("churnRateSum", (double) monthData.get("churnRateSum") + churnRate);
+                    hasValidData = true;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (recallRateObj != null) {
+                try {
+                    double recallRate = Double.parseDouble(recallRateObj.toString());
+                    monthData.put("recallRateSum", (double) monthData.get("recallRateSum") + recallRate);
+                    hasValidData = true;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (lossRateObj != null) {
+                try {
+                    double lossRate = Double.parseDouble(lossRateObj.toString());
+                    monthData.put("lossRateSum", (double) monthData.get("lossRateSum") + lossRate);
+                    hasValidData = true;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (hasValidData) {
+                monthData.put("count", count + 1);
+            }
+        }
+
+        // 计算平均值
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (Map.Entry<String, Map<String, Object>> entry : monthlyMap.entrySet()) {
+            Map<String, Object> monthData = entry.getValue();
+            int count = (int) monthData.get("count");
+
+            if (count > 0) {
+                Map<String, Object> avgData = new HashMap<>();
+                avgData.put("month", monthData.get("month"));
+                avgData.put("repurchaseRate", Math.round((double) monthData.get("repurchaseRateSum") / count * 100) / 100.0);
+                avgData.put("churnRate", Math.round((double) monthData.get("churnRateSum") / count * 100) / 100.0);
+                avgData.put("recallRate", Math.round((double) monthData.get("recallRateSum") / count * 100) / 100.0);
+                avgData.put("lossRate", Math.round((double) monthData.get("lossRateSum") / count * 100) / 100.0);
+                result.add(avgData);
+            }
+        }
+
+        // 按月份排序
+        result.sort((a, b) -> {
+            String monthA = (String) a.get("month");
+            String monthB = (String) b.get("month");
+            return monthA.compareTo(monthB);
+        });
+
+        return result;
+    }
+
+    /**
+     * 按门店聚合数据
+     * @param rawData 原始数据
+     * @return 按门店聚合的数据
+     */
+    private List<Map<String, Object>> aggregateByStore(List<Map<String, Object>> rawData) {
+        Map<String, Map<String, Object>> storeMap = new HashMap<>();
+
+        for (Map<String, Object> data : rawData) {
+            Object storeIdObj = data.get("store_cd");
+            Object storeNameObj = data.get("store_nm");
+
+            if (storeIdObj == null || storeNameObj == null) continue;
+
+            String storeId = storeIdObj.toString();
+            String storeName = storeNameObj.toString();
+
+            String storeKey = storeId + "_" + storeName;
+
+            if (!storeMap.containsKey(storeKey)) {
+                Map<String, Object> storeData = new HashMap<>();
+                storeData.put("storeId", storeId);
+                storeData.put("storeName", storeName);
+                storeData.put("repurchaseRateSum", 0.0);
+                storeData.put("churnRateSum", 0.0);
+                storeData.put("recallRateSum", 0.0);
+                storeData.put("lossRateSum", 0.0);
+                storeData.put("count", 0);
+                storeMap.put(storeKey, storeData);
+            }
+
+            Map<String, Object> storeData = storeMap.get(storeKey);
+            int count = (int) storeData.get("count");
+
+            Object repurchaseRateObj = data.get("on_time_repurchase_rate");
+            Object churnRateObj = data.get("churn_rate");
+            Object recallRateObj = data.get("churn_recall_rate");
+            Object lossRateObj = data.get("loss_rate");
+
+            boolean hasValidData = false;
+
+            if (repurchaseRateObj != null) {
+                try {
+                    double repurchaseRate = Double.parseDouble(repurchaseRateObj.toString());
+                    storeData.put("repurchaseRateSum", (double) storeData.get("repurchaseRateSum") + repurchaseRate);
+                    hasValidData = true;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (churnRateObj != null) {
+                try {
+                    double churnRate = Double.parseDouble(churnRateObj.toString());
+                    storeData.put("churnRateSum", (double) storeData.get("churnRateSum") + churnRate);
+                    hasValidData = true;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (recallRateObj != null) {
+                try {
+                    double recallRate = Double.parseDouble(recallRateObj.toString());
+                    storeData.put("recallRateSum", (double) storeData.get("recallRateSum") + recallRate);
+                    hasValidData = true;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (lossRateObj != null) {
+                try {
+                    double lossRate = Double.parseDouble(lossRateObj.toString());
+                    storeData.put("lossRateSum", (double) storeData.get("lossRateSum") + lossRate);
+                    hasValidData = true;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (hasValidData) {
+                storeData.put("count", count + 1);
+            }
+        }
+
+        // 计算平均值
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (Map.Entry<String, Map<String, Object>> entry : storeMap.entrySet()) {
+            Map<String, Object> storeData = entry.getValue();
+            int count = (int) storeData.get("count");
+
+            if (count > 0) {
+                Map<String, Object> avgData = new HashMap<>();
+                avgData.put("storeId", storeData.get("storeId"));
+                avgData.put("storeName", storeData.get("storeName"));
+                avgData.put("repurchaseRate", Math.round((double) storeData.get("repurchaseRateSum") / count * 100) / 100.0);
+                avgData.put("churnRate", Math.round((double) storeData.get("churnRateSum") / count * 100) / 100.0);
+                avgData.put("recallRate", Math.round((double) storeData.get("recallRateSum") / count * 100) / 100.0);
+                avgData.put("lossRate", Math.round((double) storeData.get("lossRateSum") / count * 100) / 100.0);
+                result.add(avgData);
+            }
+        }
+
+        // 按复购率排序(降序)
+        result.sort((a, b) -> {
+            Double rateA = (Double) a.get("repurchaseRate");
+            Double rateB = (Double) b.get("repurchaseRate");
+            return rateB.compareTo(rateA);
+        });
+
+        return result;
+    }
+
+    /**
+     * 按产品聚合数据
+     * @param rawData 原始数据
+     * @return 按产品聚合的数据
+     */
+    private List<Map<String, Object>> aggregateByProduct(List<Map<String, Object>> rawData) {
+        Map<String, Map<String, Object>> productMap = new HashMap<>();
+
+        for (Map<String, Object> data : rawData) {
+            Object productIdObj = data.get("drug_id");
+            Object productNameObj = data.get("drug_nm");
+
+            if (productIdObj == null || productNameObj == null) continue;
+
+            String productId = productIdObj.toString();
+            String productName = productNameObj.toString();
+
+            String productKey = productId + "_" + productName;
+
+            if (!productMap.containsKey(productKey)) {
+                Map<String, Object> productData = new HashMap<>();
+                productData.put("productId", productId);
+                productData.put("productName", productName);
+                productData.put("repurchaseRateSum", 0.0);
+                productData.put("churnRateSum", 0.0);
+                productData.put("recallRateSum", 0.0);
+                productData.put("lossRateSum", 0.0);
+                productData.put("count", 0);
+                productMap.put(productKey, productData);
+            }
+
+            Map<String, Object> productData = productMap.get(productKey);
+            int count = (int) productData.get("count");
+
+            Object repurchaseRateObj = data.get("on_time_repurchase_rate");
+            Object churnRateObj = data.get("churn_rate");
+            Object recallRateObj = data.get("churn_recall_rate");
+            Object lossRateObj = data.get("loss_rate");
+
+            boolean hasValidData = false;
+
+            if (repurchaseRateObj != null) {
+                try {
+                    double repurchaseRate = Double.parseDouble(repurchaseRateObj.toString());
+                    productData.put("repurchaseRateSum", (double) productData.get("repurchaseRateSum") + repurchaseRate);
+                    hasValidData = true;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (churnRateObj != null) {
+                try {
+                    double churnRate = Double.parseDouble(churnRateObj.toString());
+                    productData.put("churnRateSum", (double) productData.get("churnRateSum") + churnRate);
+                    hasValidData = true;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (recallRateObj != null) {
+                try {
+                    double recallRate = Double.parseDouble(recallRateObj.toString());
+                    productData.put("recallRateSum", (double) productData.get("recallRateSum") + recallRate);
+                    hasValidData = true;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (lossRateObj != null) {
+                try {
+                    double lossRate = Double.parseDouble(lossRateObj.toString());
+                    productData.put("lossRateSum", (double) productData.get("lossRateSum") + lossRate);
+                    hasValidData = true;
+                } catch (NumberFormatException e) {
+                    // 忽略无法解析的数据
+                }
+            }
+
+            if (hasValidData) {
+                productData.put("count", count + 1);
+            }
+        }
+
+        // 计算平均值
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (Map.Entry<String, Map<String, Object>> entry : productMap.entrySet()) {
+            Map<String, Object> productData = entry.getValue();
+            int count = (int) productData.get("count");
+
+            if (count > 0) {
+                Map<String, Object> avgData = new HashMap<>();
+                avgData.put("productId", productData.get("productId"));
+                avgData.put("productName", productData.get("productName"));
+                avgData.put("repurchaseRate", Math.round((double) productData.get("repurchaseRateSum") / count * 100) / 100.0);
+                avgData.put("churnRate", Math.round((double) productData.get("churnRateSum") / count * 100) / 100.0);
+                avgData.put("recallRate", Math.round((double) productData.get("recallRateSum") / count * 100) / 100.0);
+                avgData.put("lossRate", Math.round((double) productData.get("lossRateSum") / count * 100) / 100.0);
+                result.add(avgData);
+            }
+        }
+
+        // 按复购率排序(降序)
+        result.sort((a, b) -> {
+            Double rateA = (Double) a.get("repurchaseRate");
+            Double rateB = (Double) b.get("repurchaseRate");
+            return rateB.compareTo(rateA);
+        });
+
+        return result;
+    }
+
+
+
+
+
+    /**
+     * 处方登记情况分析页面
+     */
+    @RequiresPermissions("report:kpi:view")
+    @GetMapping("/prescriptionAnalysis")
+    public String prescriptionAnalysis() {
+        return prefix + "/prescriptionAnalysis";
+    }
+
+    /**
+     * 处方登记详情页面
+     */
+    @RequiresPermissions("report:kpi:view")
+    @GetMapping("/prescriptionDetail/{id}")
+    public String prescriptionDetail(@PathVariable("id") String id, ModelMap mmap) throws Exception {
+        PageData pd = this.getPageData();
+        pd.put("id", id);
+
+        // 获取处方基本信息
+        Map<String, Object> detail = kpiService.getPrescriptionDetail(pd);
+        mmap.put("detail", detail);
+
+        // 获取处方关联的药品列表
+        List<Map<String, Object>> drugList = kpiService.getPrescriptionDrugs(pd);
+        mmap.put("drugList", drugList);
+
+        return prefix + "/prescriptionDetail";
+    }
+
+
+    /**
+     * 获取处方登记分析数据
+     */
+    @RequiresPermissions("report:kpi:view")
+    @PostMapping("/prescriptionAnalysis/data")
+    @ResponseBody
+    public AjaxResult getPrescriptionAnalysisData() {
+        try {
+            PageData pd = getPageData();
+
+            // 处理登录用户门店权限
+            String storeId = (String) pd.get("storeId");
+            if(StringUtils.isEmpty(storeId) && getSysUser().getDeptId()!=0){
+                pd.put("storeId",getSysUser().getDeptId().toString());
+            }
+
+            // 获取处方数据
+            List<Map<String, Object>> prescriptionData = kpiService.getPrescriptionAnalysisData(pd);
+
+            // 构建返回结果
+            Map<String, Object> result = new HashMap<>();
+
+            // 计算关键指标
+            int totalPrescriptions = prescriptionData.size();
+            int completedPrescriptions = 0;
+            int pendingPrescriptions = 0;
+            int rejectedPrescriptions = 0;
+
+            Map<String, Integer> statusDistribution = new HashMap<>();
+            Map<String, Integer> hospitalDistribution = new HashMap<>();
+            Map<String, Integer> departmentDistribution = new HashMap<>();
+            Map<String, Integer> monthlyDistribution = new TreeMap<>();
+            Map<String, Integer> drugTypeDistribution = new HashMap<>();
+
+            for (Map<String, Object> item : prescriptionData) {
+                // 处方状态统计
+                Object statusObj = item.get("status");
+                if (statusObj != null) {
+                    int status = Integer.parseInt(statusObj.toString());
+                    String statusText = getPrescriptionStatusText(status);
+                    statusDistribution.put(statusText, statusDistribution.getOrDefault(statusText, 0) + 1);
+
+                    if (status == 1 || status == 7) {
+                        completedPrescriptions++;
+                    } else if (status == 8) {
+                        rejectedPrescriptions++;
+                    } else {
+                        pendingPrescriptions++;
+                    }
+                }
+
+                // 医院分布统计
+                Object hospitalObj = item.get("hospital");
+                if (hospitalObj != null) {
+                    String hospital = hospitalObj.toString();
+                    hospitalDistribution.put(hospital, hospitalDistribution.getOrDefault(hospital, 0) + 1);
+                }
+
+                // 科室分布统计
+                Object departmentObj = item.get("department");
+                if (departmentObj != null) {
+                    String department = departmentObj.toString();
+                    departmentDistribution.put(department, departmentDistribution.getOrDefault(department, 0) + 1);
+                }
+
+                // 月度处方量统计
+                Object dateObj = item.get("prescriptionIssueDate");
+                if (dateObj != null) {
+                    try {
+                        Date date = (Date) dateObj;
+                        Calendar cal = Calendar.getInstance();
+                        cal.setTime(date);
+                        String monthKey = cal.get(Calendar.YEAR) + "-" + (cal.get(Calendar.MONTH) + 1);
+                        monthlyDistribution.put(monthKey, monthlyDistribution.getOrDefault(monthKey, 0) + 1);
+                    } catch (Exception e) {
+                        // 忽略日期解析错误
+                    }
+                }
+
+                // 处方诊断
+                Object drugTypeObj = item.get("prescriptionDiagnosis");
+                if (drugTypeObj != null) {
+                    String drugType = drugTypeObj.toString();
+                    drugTypeDistribution.put(drugType, drugTypeDistribution.getOrDefault(drugType, 0) + 1);
+                }
+            }
+
+            // 构建返回数据
+            result.put("totalPrescriptions", totalPrescriptions);
+            result.put("completedPrescriptions", completedPrescriptions);
+            result.put("pendingPrescriptions", pendingPrescriptions);
+            result.put("rejectedPrescriptions", rejectedPrescriptions);
+
+            result.put("statusDistribution", statusDistribution);
+            result.put("hospitalDistribution", hospitalDistribution);
+            result.put("departmentDistribution", departmentDistribution);
+            result.put("monthlyDistribution", monthlyDistribution);
+            result.put("drugTypeDistribution", drugTypeDistribution);
+
+            // 获取最近处方数据(前10条)
+            List<Map<String, Object>> recentPrescriptions = prescriptionData.stream()
+                    .sorted((a, b) -> {
+                        Date dateA = (Date) a.getOrDefault("prescriptionIssueDate", new Date(0));
+                        Date dateB = (Date) b.getOrDefault("prescriptionIssueDate", new Date(0));
+                        return dateB.compareTo(dateA);
+                    })
+                    .limit(10)
+                    .collect(Collectors.toList());
+
+            result.put("recentPrescriptions", recentPrescriptions);
+
+            return success(result);
+        } catch (Exception e) {
+            logger.error("获取处方分析数据失败", e);
+            return error("获取处方分析数据失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取处方状态文本描述
+     */
+    private String getPrescriptionStatusText(int status) {
+        switch (status) {
+            case 1: return "订单已完成";
+            case 2: return "待上传处方";
+            case 3: return "待确认信息";
+            case 4: return "待处方登记";
+            case 5: return "待订单销售";
+            case 6: return "待绑定患者";
+            case 7: return "处方已完成";
+            case 8: return "订单已退款";
+            default: return "待确认信息";
+        }
+    }
+
+    /**
+     * 导出处方登记数据
+     */
+    @Log(title = "处方登记数据导出", businessType = BusinessType.EXPORT)
+    @RequiresPermissions("report:kpi:export")
+    @PostMapping("/prescription/export")
+    @ResponseBody
+    public AjaxResult exportPrescription() throws Exception {
+        PageData pd = this.getPageData();
+//        List<Map<String, Object>> list = kpiService.getPrescriptionAnalysisData(pd);
+//        ExcelUtil<Map<String, Object>> util = new ExcelUtil<>(Map.class);
+       // return util.exportExcel(list, "处方登记数据");
+        return null;
+    }
+    /**
+     * 获取门店处方量对比数据
+     */
+    @RequiresPermissions("report:kpi:view")
+    @PostMapping("/prescriptionAnalysis/storeComparison")
+    @ResponseBody
+    public AjaxResult getPrescriptionStoreComparison() {
+        try {
+            PageData pd = getPageData();
+
+            // 获取显示模式
+            String displayMode = pd.getString("displayMode");
+            boolean isPercentMode = "percent".equals(displayMode);
+
+            // 获取时间范围
+            String timeRange = pd.getString("timeRange");
+            if (StringUtils.isEmpty(timeRange)) {
+                timeRange = "week"; // 默认本周
+            }
+
+            // 设置时间范围
+            setTimeRange(pd, timeRange);
+
+            // 获取门店处方量对比数据
+            List<Map<String, Object>> storeData = kpiService.getPrescriptionStoreComparison(pd);
+
+            // 构建返回结果
+            Map<String, Object> result = new HashMap<>();
+            result.put("storeData", storeData);
+
+            // 计算汇总数据
+            int totalOrders = 0;
+            int totalPending = 0;
+            int totalCompleted = 0;
+
+            for (Map<String, Object> store : storeData) {
+                int orders = Integer.parseInt(store.get("orderCount").toString());
+                int pending = Integer.parseInt(store.get("pendingCount").toString());
+                int completed = Integer.parseInt(store.get("completedCount").toString());
+
+                totalOrders += orders;
+                totalPending += pending;
+                totalCompleted += completed;
+            }
+
+            result.put("totalOrders", totalOrders);
+            result.put("totalPending", totalPending);
+            result.put("totalCompleted", totalCompleted);
+            result.put("completionRate", totalOrders > 0 ? (double)totalCompleted / totalOrders * 100 : 0);
+
+            return success(result);
+        } catch (Exception e) {
+            logger.error("获取门店处方量对比数据失败", e);
+            return error("获取门店处方量对比数据失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 设置时间范围
+     */
+    private void setTimeRange(PageData pd, String timeRange) {
+        Calendar cal = Calendar.getInstance();
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+
+        switch (timeRange) {
+            case "week":
+                // 本周
+                cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
+                pd.put("startDate", sdf.format(cal.getTime()));
+                cal.add(Calendar.DAY_OF_WEEK, 6);
+                pd.put("endDate", sdf.format(cal.getTime()));
+                break;
+            case "month":
+                // 本月
+                cal.set(Calendar.DAY_OF_MONTH, 1);
+                pd.put("startDate", sdf.format(cal.getTime()));
+                cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
+                pd.put("endDate", sdf.format(cal.getTime()));
+                break;
+            case "lastMonth":
+                // 上月
+                cal.add(Calendar.MONTH, -1);
+                cal.set(Calendar.DAY_OF_MONTH, 1);
+                pd.put("startDate", sdf.format(cal.getTime()));
+                cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
+                pd.put("endDate", sdf.format(cal.getTime()));
+                break;
+            case "year":
+                // 本年
+                cal.set(Calendar.DAY_OF_YEAR, 1);
+                pd.put("startDate", sdf.format(cal.getTime()));
+                cal.set(Calendar.DAY_OF_YEAR, cal.getActualMaximum(Calendar.DAY_OF_YEAR));
+                pd.put("endDate", sdf.format(cal.getTime()));
+                break;
+            default:
+                // 使用表单中的日期范围
+                break;
+        }
+    }
+
+
+
+
+
+
+    /**
+     * 导出门店处方对比数据
+     */
+    @Log(title = "门店处方对比数据导出", businessType = BusinessType.EXPORT)
+    @RequiresPermissions("report:kpi:export")
+    @PostMapping("/prescription/storeComparison/export")
+    @ResponseBody
+    public AjaxResult exportStoreComparison() throws Exception {
+        PageData pd = this.getPageData();
+
+        // 设置时间范围
+        String timeRange = pd.getString("timeRange");
+        if (!StringUtils.isEmpty(timeRange)) {
+            setTimeRange(pd, timeRange);
+        }
+
+        List<Map<String, Object>> list = kpiService.getPrescriptionStoreComparison(pd);
+
+        // 创建自定义导出列
+        List<String> headerList = new ArrayList<>();
+        headerList.add("连锁店");
+        headerList.add("订单量");
+        headerList.add("待完成订单数量");
+        headerList.add("已完成订单数量");
+        headerList.add("订单完成率(%)");
+
+        List<String> keyList = new ArrayList<>();
+        keyList.add("storeName");
+        keyList.add("orderCount");
+        keyList.add("pendingCount");
+        keyList.add("completedCount");
+        keyList.add("completionRate");
+
+        // 获取时间范围描述
+        String timeDesc = getTimeRangeDesc(pd);
+
+        ExcelUtil<Map> util = new ExcelUtil<>(Map.class);
+       // return util.exportExcel(list, "DTP处方登记概览(" + timeDesc + ")", headerList, keyList);
+        return null;
+    }
+
+    /**
+     * 获取时间范围描述
+     */
+    private String getTimeRangeDesc(PageData pd) {
+        String startDate = pd.getString("startDate");
+        String endDate = pd.getString("endDate");
+
+        if (!StringUtils.isEmpty(startDate) && !StringUtils.isEmpty(endDate)) {
+            return startDate + "至" + endDate;
+        } else if (!StringUtils.isEmpty(startDate)) {
+            return startDate + "起";
+        } else if (!StringUtils.isEmpty(endDate)) {
+            return "至" + endDate;
+        } else {
+            return "全部";
+        }
+    }
+
+}

+ 5 - 12
health-admin/src/main/java/com/bzd/web/controller/spgl/SPConfigInfoController.java

@@ -65,13 +65,13 @@ public class SPConfigInfoController extends BaseController {
     }
 
 
-    @RequiresPermissions("sp:sp:view")
+    @RequiresPermissions("gxhpz:ypk:view")
     @GetMapping("/SPProductinfoListView")
     public String SPProductinfoListView() {
         return prefix + "/SPProductinfoList";
     }
 
-    @RequiresPermissions("sp:sp:list")
+    @RequiresPermissions("gxhpz:ypk:query")
     @PostMapping("/SPProductinfoList")
     @ResponseBody
     public TableDataInfo SPProductinfoList() throws Exception {
@@ -81,13 +81,13 @@ public class SPConfigInfoController extends BaseController {
         return getDataTable(pageData);
     }
 
-    @RequiresPermissions("sp:sp:add")
+    @RequiresPermissions("gxhpz:ypk:add")
     @GetMapping("/SPProductAdd")
     public String SPProductAdd() throws Exception {
         return prefix + "/SPProductAdd";
     }
 
-    @RequiresPermissions("sp:sp:add")
+    @RequiresPermissions("gxhpz:ypk:edit")
     @GetMapping("/SPProductEdit/{id}")
     public String SPProductEdit(@PathVariable("id") Long id, ModelMap mmap) throws Exception {
         PageData pd = this.getPageData();
@@ -97,7 +97,6 @@ public class SPConfigInfoController extends BaseController {
         return prefix + "/SPProductEdit";
     }
 
-    @RequiresPermissions("dtp:sp:add")
     @PostMapping("/ProductAdd")
     @ResponseBody
     public AjaxResult ProductAdd() {
@@ -109,6 +108,7 @@ public class SPConfigInfoController extends BaseController {
             }
             PageData pd2=new PageData();
             pd2.put("product_code",productCode);
+            pd2.put("storeId",getSysUser().getDeptId().toString());
             // 直接service 保存
             List<PageData> pageData = sPProductinfoService.findSPProductinfoListBYProductCode(pd2);
             if (pageData.size() > 0){
@@ -127,7 +127,6 @@ public class SPConfigInfoController extends BaseController {
         }
     }
 
-    @RequiresPermissions("dtp:sp:edit")
     @PostMapping("/ProductUpdate")
     @ResponseBody
     public AjaxResult ProductUpdate() {
@@ -145,7 +144,6 @@ public class SPConfigInfoController extends BaseController {
             return AjaxResult.error("修改失败 请联系管理员");
         }
     }
-    @RequiresPermissions("dtp:sp:add")
     @PostMapping("/ProductAdd2")
     @ResponseBody
     public AjaxResult ProductAdd2() {
@@ -164,7 +162,6 @@ public class SPConfigInfoController extends BaseController {
             return AjaxResult.error();
         }
     }
-    @RequiresPermissions("dtp:sp:add")
     @PostMapping("/ProductAdd3")
     @ResponseBody
     public AjaxResult ProductAdd3() {
@@ -183,7 +180,6 @@ public class SPConfigInfoController extends BaseController {
             return AjaxResult.error();
         }
     }
-    @RequiresPermissions("dtp:sp:add")
     @PostMapping("/ProductAdd4")
     @ResponseBody
     public AjaxResult ProductAdd4() {
@@ -202,7 +198,6 @@ public class SPConfigInfoController extends BaseController {
             return AjaxResult.error();
         }
     }
-    @RequiresPermissions("dtp:sp:add")
     @PostMapping("/ProductAdd5")
     @ResponseBody
     public AjaxResult ProductAdd5() {
@@ -221,7 +216,6 @@ public class SPConfigInfoController extends BaseController {
             return AjaxResult.error();
         }
     }
-    @RequiresPermissions("dtp:sp:add")
     @PostMapping("/ProductAdd6")
     @ResponseBody
     public AjaxResult ProductAdd6() {
@@ -241,7 +235,6 @@ public class SPConfigInfoController extends BaseController {
         }
     }
 
-    @RequiresPermissions("dtp:sp:add")
     @PostMapping("/ProductAdd12")
     @ResponseBody
     public AjaxResult ProductAdd12() {

+ 4 - 0
health-admin/src/main/java/com/bzd/web/controller/system/SysPostController.java

@@ -47,6 +47,7 @@ public class SysPostController extends BaseController
     public TableDataInfo list(SysPost post)
     {
         startPage();
+        post.setStoreId(getSysUser().getDeptId().toString());
         List<SysPost> list = postService.selectPostList(post);
         return getDataTable(list);
     }
@@ -57,6 +58,7 @@ public class SysPostController extends BaseController
     @ResponseBody
     public AjaxResult export(SysPost post)
     {
+        post.setStoreId(getSysUser().getDeptId().toString());
         List<SysPost> list = postService.selectPostList(post);
         ExcelUtil<SysPost> util = new ExcelUtil<SysPost>(SysPost.class);
         return util.exportExcel(list, "岗位数据");
@@ -105,6 +107,7 @@ public class SysPostController extends BaseController
             return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在");
         }
         post.setCreateBy(getLoginName());
+        post.setStoreId(getSysUser().getDeptId().toString());
         return toAjax(postService.insertPost(post));
     }
 
@@ -137,6 +140,7 @@ public class SysPostController extends BaseController
             return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在");
         }
         post.setUpdateBy(getLoginName());
+        post.setStoreId(getSysUser().getDeptId().toString());
         return toAjax(postService.updatePost(post));
     }
 

+ 11 - 12
health-admin/src/main/java/com/bzd/web/controller/zlgl/SZlglCfdjSaleprescriptioninfoController.java

@@ -19,7 +19,7 @@ import java.util.List;
 
 /**
 *
-* 处方登记记录 前端控制器Controller
+* 质量管理 前端控制器Controller
 * @author LiXiangFei
 * @since 2024-10-15
 */
@@ -34,18 +34,18 @@ public class SZlglCfdjSaleprescriptioninfoController extends BaseController {
     private SZlglCfdjSaleprescriptioninfoService sZlglCfdjSaleprescriptioninfoService;
 
     /**
-    * 处方登记记录 新增页面跳转
+    * 质量管理 新增页面跳转
     *
     * @return
     */
-    @RequiresPermissions("zlgl:zl:view")
+    @RequiresPermissions("zlgl:zl:add")
     @GetMapping("/sZlglCfdjSaleprescriptioninfoAdd")
     public String add() {
         return prefix + "/SZlglCfdjSaleprescriptioninfoAdd";
     }
 
     /**
-    * 处方登记记录 新增
+    * 质量管理 新增
     *
     * @return
     */
@@ -71,7 +71,7 @@ public class SZlglCfdjSaleprescriptioninfoController extends BaseController {
     }
 
     /**
-    * 处方登记记录 查询页面跳转
+    * 质量管理 查询页面跳转
     *
     * @return
     */
@@ -82,12 +82,12 @@ public class SZlglCfdjSaleprescriptioninfoController extends BaseController {
     }
 
     /**
-    * 处方登记记录 数据查询
+    * 质量管理 数据查询
     *
     * @return
     * @throws Exception
     */
-    @RequiresPermissions("zlgl:zl:list")
+    @RequiresPermissions("zlgl:zl:query")
     @PostMapping("/sZlglCfdjSaleprescriptioninfoList")
     @ResponseBody
     public TableDataInfo szlglcfdjsaleprescriptioninfoList() throws Exception {
@@ -98,13 +98,13 @@ public class SZlglCfdjSaleprescriptioninfoController extends BaseController {
     }
 
     /**
-    * 处方登记记录 数据删除 根据id
+    * 质量管理 数据删除 根据id
     *
     * @return
     * @throws Exception
     */
     @RequiresPermissions("zlgl:zl:remove")
-    @Log(title = "处方登记记录删除", businessType = BusinessType.DELETE)
+    @Log(title = "质量管理删除", businessType = BusinessType.DELETE)
     @PostMapping("/sZlglCfdjSaleprescriptioninfoRemove")
     @ResponseBody
     public AjaxResult szlglcfdjsaleprescriptioninfoRemove() throws Exception {
@@ -133,10 +133,9 @@ public class SZlglCfdjSaleprescriptioninfoController extends BaseController {
     }
 
     /**
-    * 处方登记记录保存修改的数据
+    * 质量管理
     */
-    @RequiresPermissions("zlgl:zl:edit")
-    @Log(title = "处方登记记录修改", businessType = BusinessType.UPDATE)
+    @Log(title = "质量管理", businessType = BusinessType.UPDATE)
     @PostMapping("/sZlglCfdjSaleprescriptioninfoEdit")
     @ResponseBody
     public AjaxResult szlglcfdjsaleprescriptioninfoEditSave() throws Exception {

+ 9 - 1
health-admin/src/main/resources/application.yml

@@ -16,7 +16,7 @@ health:
 # 开发环境配置
 server:
   # 服务器的HTTP端口,默认为80
-  port: 80
+  port: 8088
   servlet:
     # 应用的访问路径
     context-path: /
@@ -145,3 +145,11 @@ xss:
 swagger:
   # 是否开启swagger
   enabled: true
+submail:
+  timestamp-url: "https://api-v4.mysubmail.com/service/timestamp"
+  url: "https://api-v4.mysubmail.com/sms/xsend"
+  appid: "94309"
+  appkey: "8c79de85cc4bc61d93b36ff89862e3e6"
+  project: "3HQxS3"
+  sign-type: "md5"
+  sign-version: "2"

+ 36 - 1
health-admin/src/main/resources/static/health/js/common.js

@@ -659,6 +659,41 @@ function validateIDCard(idCard) {
     return expectedCheckCode === checkCode;
 }
 
+
+function validateIDCard2(id) {
+    // 基本格式检查
+    if (typeof id !== 'string' || id.length !== 18) return false;
+    const str = id.toUpperCase();
+    if (!/^\d{17}[\dX]$/.test(str)) return false;
+
+    // 行政区划代码校验(前两位)
+    const provinceCodes = new Set(["11","12","13","14","15","21","22","23","31","32","33","34","35","36","37","41","42","43","44","45","46","50","51","52","53","54","61","62","63","64","65","71","81","82"]);
+    const provinceCode = str.substring(0, 2);
+    if (!provinceCodes.has(provinceCode)) return false;
+
+    // 出生日期校验
+    const year = parseInt(str.substring(6, 10));
+    const month = parseInt(str.substring(10, 12)) - 1; // JS月份从0开始
+    const day = parseInt(str.substring(12, 14));
+    const birthDate = new Date(year, month, day);
+    if (
+        birthDate.getFullYear() !== year ||
+        birthDate.getMonth() !== month ||
+        birthDate.getDate() !== day
+    ) {
+        return false;
+    }
+
+    // 校验码计算
+    const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
+    const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
+    let sum = 0;
+    for (let i = 0; i < 17; i++) {
+        sum += parseInt(str.charAt(i)) * weights[i];
+    }
+    return str.charAt(17) === checkCodes[sum % 11];
+}
+
 // 验证地址码是否合法(简化处理)
 function isValidAddressCode(addressCode) {
     // 这里可以添加具体的地址码验证逻辑
@@ -720,7 +755,7 @@ function calculateBMI(heightInCm, weightInKg) {
     // 计算BMI
     var bmi = weightInKg / (heightInM * heightInM);
 
-    return bmi;
+    return parseFloat(bmi.toFixed(1));
 }
 
 (function ($) {

+ 39 - 0
health-admin/src/main/resources/static/health/js/ry-ui.js

@@ -879,6 +879,45 @@ var table = {
                     callBack(true);
                 });
             },
+            confirm2: function (options, callBack) {
+                var defaultOptions = {
+                    title: "系统提示",
+                    content: "",
+                    icon: 3,
+                    btn: ['确认', '取消'],
+                    buttons: {
+                        confirm: {
+                            text: '确认',
+                            action: function () {}
+                        },
+                        cancel: {
+                            text: '取消',
+                            action: function () {}
+                        }
+                    }
+                };
+
+                // 合并默认选项和用户提供的选项
+                var settings = $.extend(true, {}, defaultOptions, options);
+
+                top.layer.confirm(settings.content, {
+                    icon: settings.icon,
+                    title: settings.title,
+                    btn: [settings.buttons.confirm.text, settings.buttons.cancel.text]
+                }, function (index) {
+                    $.modal.close(index);
+                    if (callBack) {
+                        callBack(true);
+                    }
+                    settings.buttons.confirm.action();
+                }, function (index) {
+                    $.modal.close(index);
+                    if (callBack) {
+                        callBack(false);
+                    }
+                    settings.buttons.cancel.action();
+                });
+            },
             // 弹出层指定宽度
             open: function (title, url, width, height, callback) {
                 // 如果是移动端,就使用自适应大小弹窗

+ 5 - 5
health-admin/src/main/resources/templates/daas/SDaasChannelManagementList.html

@@ -62,13 +62,13 @@
             </div>
 
             <div class="btn-group-sm" id="toolbar" role="group">
-                <a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="system:user:add">
+                <a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="daas:ds:add">
                     <i class="fa fa-plus"></i> 新增
                 </a>
-                 <a class="btn btn-primary single disabled" onclick="$.operate.edit()" shiro:hasPermission="system:user:edit">
+                 <a class="btn btn-primary single disabled" onclick="$.operate.edit()" shiro:hasPermission="daas:ds:edit">
                     <i class="fa fa-edit"></i> 修改
                 </a>
-                <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="system:user:remove">
+                <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="sdaas:ds:remove">
                     <i class="fa fa-remove"></i> 删除
                 </a>
             </div>
@@ -85,8 +85,8 @@
 <th:block th:include="include :: bootstrap-table-fixed-columns-js" />
 <th:block th:include="include :: ztree-js" />
 <script th:inline="javascript">
-    var editFlag = [[${@permission.hasPermi('dtp:pmService:edit')}]];
-    var removeFlag = [[${@permission.hasPermi('dtp:pmService:remove')}]];
+    var editFlag = [[${@permission.hasPermi('daas:ds:edit')}]];
+    var removeFlag = [[${@permission.hasPermi('daas:ds:remove')}]];
     var prefix = ctx + "daas/sdaaschannelmanagement";
     $(function() {
         var panehHidden = false;

+ 32 - 69
health-admin/src/main/resources/templates/dtp/archives/archivesAdd.html

@@ -1,11 +1,13 @@
 <!DOCTYPE html>
 <html lang="zh" xmlns:th="http://www.thymeleaf.org" >
 <head>
-
     <th:block th:include="include :: header('新建档案')" />
-
+    <th:block th:include="include :: footer" />
     <th:block th:include="include :: select2-css" />
-    <th:block th:include="include :: jasny-bootstrap-css" />
+    <th:block th:include="include :: bootstrap-select-css" />
+    <th:block th:include="include :: select2-js" />
+    <th:block th:include="include :: bootstrap-select-js" />
+
 </head>
 <style>
     .ibox {
@@ -96,18 +98,9 @@
                 <input name="contactName" placeholder="请输入联系人姓名" class="styled-input" type="text" maxlength="30"  required>
             </div>
             <div class="customize-form-group">
-                <label class="col-sm-1 control-label">证件号码:</label>
+                <label class="col-sm-1 control-label is-required">证件号码:</label>
                 <input name="documentNumber" id="documentNumber" placeholder="请输入证件号码" class="styled-input" type="text" maxlength="30"  required>
             </div>
-            <!--<div class="customize-form-group">
-                <label class="col-sm-1 control-label">联系人与患者关系:</label>
-                <select name="contactRelation" class="styled-input" th:with="type=${@dict.getType('sys_select_dtp_ysfw_lxryhzgx')}">
-                    <option>请选择</option>
-                    <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"
-                    ></option>
-                </select>
-            </div>-->
-
             <div class="customize-form-group edit select-time">
                 <label class="col-sm-1 control-label is-required">首次确诊时间:</label>
                 <input name="timeFirstDiagnosis" id="timeFirstDiagnosis" placeholder="首次确诊时间" class="time-input time-input2"  type="text" >
@@ -117,34 +110,6 @@
                 <input name="addr" placeholder="配送地址"  id="addr" class="styled-input" type="text">
             </div>
 
-            <!--<div class="customize-form-group">
-                <label class="col-sm-1 control-label">建档时间:</label>
-                <div class="customize-form-group select-time">
-                    <input name="createTime" placeholder="建档时间"  id="createTime" class="time-input time-input2" type="text">
-                </div>
-            </div>
-
-            <div class="customize-form-group">
-                <label class="col-sm-1 control-label">建档门店:</label>
-                <select name="storeName" class="styled-input" th:with="type=${@dict.getType('sys_dtp_store')}">
-                    <option>请选择</option>
-                    <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"
-                    ></option>
-                </select>
-            </div>
-            <div class="customize-form-group">
-                <label class="col-sm-1 control-label">是否接受随访:</label>
-                <select name="acceptFollowUp" class="styled-input" th:with="type=${@dict.getType('sys_select_yes_no')}">
-                    <option>请选择</option>
-                    <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"
-                    ></option>
-                </select>
-            </div>
-            <div class="customize-form-group">
-                <label class="col-sm-1 control-label">建档人:</label>
-                    <input name="creator" placeholder="建档人"  id="creator" class="styled-input" type="text">
-            </div>-->
-
         </div>
 
         <div class="customize-form-group-container">
@@ -181,10 +146,7 @@
         </div>
     </form>
 </div>
-<!--<th:block th:include="include :: jasny-bootstrap-js" />-->
-<th:block th:include="include :: footer" />
-<th:block th:include="include :: select2-js" />
-<!--<th:block th:include="include :: bootstrap-fileinput-js" />-->
+
 
 </body>
 </html>
@@ -201,7 +163,7 @@
             // 初始化 Select2 插件
             $('#category-select'+i).select2({
                 placeholder: "请选择或直接输入搜索",
-                //allowClear: true //在输入框最后 有一个 删除所有的X 但是会出现 第一个删除出现问题有空格
+                allowClear: true //在输入框最后 有一个 删除所有的X 但是会出现 第一个删除出现问题有空格
             });
         }
 
@@ -217,6 +179,7 @@
             method: 'GET',
             dataType: 'json',
             success: function(data) {
+                debugger;
                 var options1 = $('#category-select1');
                 var options2 = $('#category-select2');
                 var options3 = $('#category-select3');
@@ -256,37 +219,37 @@
                 }).appendTo(options6);
                 // 遍历返回的数据并添加选项
                 $.each(data.value, function(index, item) {
-                    if(item.dict_key==1){
+                    if(item.dict_key==='1'){
                         $('<option>', {
                             value: item.id,
                             text : item.categoryName
                         }).appendTo(options1);
                     }
-                    if(item.dict_key==2){
+                    if(item.dict_key==='2'){
                         $('<option>', {
                             value: item.id,
                             text : item.categoryName
                         }).appendTo(options2);
                     }
-                    if(item.dict_key==3){
+                    if(item.dict_key==='3'){
                         $('<option>', {
                             value: item.id,
                             text : item.categoryName
                         }).appendTo(options3);
                     }
-                    if(item.dict_key==4){
+                    if(item.dict_key==='4'){
                         $('<option>', {
                             value: item.id,
                             text : item.categoryName
                         }).appendTo(options4);
                     }
-                    if(item.dict_key==5){
+                    if(item.dict_key==='5'){
                         $('<option>', {
                             value: item.id,
                             text : item.categoryName
                         }).appendTo(options5);
                     }
-                    if(item.dict_key==6){
+                    if(item.dict_key==='6'){
                         $('<option>', {
                             value: item.id,
                             text : item.categoryName
@@ -312,32 +275,32 @@
 
     // 示例使用
     function submitHandler() {
-            debugger
             var documentType= $("#documentType").val()
-            if(documentType=="居民身份证") {
+
+            if(documentType === "居民身份证") {
                var IDCard= $("#documentNumber").val()
-                if (IDCard == null || IDCard == "" || IDCard == undefined){
-                    $.modal.alert("请输入身份证号码")
+                if (IDCard == null || IDCard === "" || IDCard === undefined){
+                    $.modal.msgWarning("请输入身份证号码")
                     return false
                 }
-                if (validateIDCard(IDCard)==false) {
-                    $.modal.alert("身份证号码格式不正确,请重新输入!")
+                if (validateIDCard2(IDCard)===false) {
+                    $.modal.msgWarning("身份证号码不正确,请重新输入!")
                     return false
                 }
             }
             var gender= $("#gender").val()
-            if (gender==null || gender=="" || gender==undefined) {
-                $.modal.alert("请选择性别")
+            if (gender==null || gender==="" || gender===undefined) {
+                $.modal.msgWarning("请选择性别")
                 return false
             }
             var timeFirstDiagnosis= $("#timeFirstDiagnosis").val()
-            if (timeFirstDiagnosis==null || timeFirstDiagnosis=="" || timeFirstDiagnosis==undefined) {
-                $.modal.alert("请选择首次确诊时间")
+            if (timeFirstDiagnosis==null || timeFirstDiagnosis==="" || timeFirstDiagnosis===undefined) {
+                $.modal.msgWarning("请选择首次确诊时间")
                 return false
             }
         var phoneNumber= $("#phoneNumber").val()
-        if (phoneNumber==null || phoneNumber=="" || phoneNumber==undefined) {
-            $.modal.alert("请输入联系方式")
+        if (phoneNumber==null || phoneNumber==="" || phoneNumber===undefined) {
+            $.modal.msgWarning("请输入联系方式")
             return false
         }
 
@@ -366,7 +329,7 @@
 
             const DL = JSON.stringify(selectedDataDL);
             if (!hasSelection) {
-                $.modal.alert('请至少选择一个疾病选项!');
+                $.modal.msgWarning('请至少选择一个疾病选项!');
                 return;
             }
         if($.common.isNotEmpty(smName)){
@@ -376,7 +339,7 @@
         data.push({name: "age", value: $("#age").val()});
         data.push({name: "dateBirth", value: $("#dateBirth").val()});
         console.log(data);
-        debugger
+
         $.ajax({
             cache : true,
             type : "POST",
@@ -449,11 +412,11 @@
                     const address = result.data.words_result.住址.words;
                     const dateBirth = result.data.words_result.出生.words;
                     //const nation = result.data.words_result.名族.words;
-                    if (gender == "男"){
+                    if (gender === "男"){
                         $("#gender").val("0");
-                    }else if (gender == "女"){
+                    }else if (gender === "女"){
                         $("#gender").val("1");
-                    }else if (gender == "未知"){
+                    }else if (gender === "未知"){
                         $("#gender").val("2");
                     }
                     smName=name;

+ 2 - 2
health-admin/src/main/resources/templates/dtp/archives/archivesAddCallback.html

@@ -96,7 +96,7 @@
                 <input name="contactName" placeholder="请输入联系人姓名" class="styled-input" type="text" maxlength="30"  required>
             </div>
             <div class="customize-form-group">
-                <label class="col-sm-1 control-label">证件号码:</label>
+                <label class="col-sm-1 control-label is-required">证件号码:</label>
                 <input name="documentNumber" id="documentNumber" placeholder="请输入证件号码" class="styled-input" type="text" maxlength="30"  required>
             </div>
             <div class="customize-form-group edit select-time">
@@ -271,7 +271,7 @@
 
     // 示例使用
     function submitHandler() {
-            debugger
+
             var documentType= $("#documentType").val()
             if(documentType=="居民身份证") {
                var IDCard= $("#documentNumber").val()

+ 29 - 10
health-admin/src/main/resources/templates/dtp/archives/archivesEdit.html

@@ -81,7 +81,7 @@
                                              </div>
 
                                              <div class="customize-form-group edit">
-                                                 <label >证件号码:</label>
+                                                 <label class="is-required">证件号码:</label>
                                                  <input name="documentType" placeholder="证件类型" class="styled-input short" type="text"  th:value="${documentType}" disabled="true">
                                                  <input name="documentNumber" placeholder="请输入证件号码" class="styled-input edit_inputs1" type="text" maxlength="30" th:value="${documentNumber}" disabled="true">
                                                  <span class="span_line" readonly></span>
@@ -541,7 +541,6 @@
     var formSubmitted = true;
 
     $(document).ready(function() {
-
         // 初始化 Select2 插件
         for (let i = 1; i <= 6; i++) {
             $(`#category-select${i}`).select2({
@@ -550,7 +549,7 @@
             });
         }
 
-        // 获取所有下拉选项的数据
+// 获取所有下拉选项的数据
         $.ajax({
             url: ctx + 'sp/sp/typeDate',
             method: 'GET',
@@ -578,27 +577,36 @@
                         }).prependTo(select); // 使用 prependTo 确保它成为第一个选项
                     }
                 });
+
                 // 解析 dl 数据并获取 ID 数组
                 var dl = /*[[${dl}]]*/ '';
                 var dlParsed = JSON.parse(dl);
                 var dlIds = Array.isArray(dlParsed) ? dlParsed.map(item => item.id.toString()) : [];
+
+                // 创建一个映射来存储需要选择的选项ID
+                var selectedOptionsMap = {};
+                selects.forEach((_, index) => selectedOptionsMap[index] = []);
+
                 // 遍历返回的数据并添加选项
                 $.each(data.value || [], function(index, item) {
-                    var selectIndex = item.dict_key-1; // 假设 dict_key 是从 1 开始的索引
+                    var selectIndex = item.dict_key - 1; // 假设 dict_key 是从 1 开始的索引
                     if (selectIndex >= 0 && selectIndex < selects.length) {
                         $('<option>', {
                             value: item.id,
                             text : item.categoryName
                         }).appendTo(selects[selectIndex]);
-                        // 设置选中的值(仅当 dlIds 包含该项 id 时)
+                        // 如果该选项的id存在于dlIds中,则记录下来
                         if (dlIds.includes(item.id.toString())) {
-                            selects[selectIndex].val(item.id).trigger('change');
+                            selectedOptionsMap[selectIndex].push(item.id.toString());
                         }
                     }
                 });
-                // 如果使用 Select2 插件,则初始化或刷新它们
-                selects.forEach(function(select) {
-                    select.trigger('change');
+
+                // 设置每个下拉框的选中值
+                selects.forEach(function(select, index) {
+                    if (selectedOptionsMap[index].length > 0) {
+                        select.val(selectedOptionsMap[index]).trigger('change');
+                    }
                 });
             },
             error: function(xhr, status, error) {
@@ -915,7 +923,7 @@
     }
     function initializeTableForTab(tabId) {
         var datas=[];
-        debugger
+
         var tableId = 'bootstrap-table-' + tabId.substring(4);
         var tableElement = $('#' + tableId);
         var data = {
@@ -1324,4 +1332,15 @@
         color: red;
         display: none;
     }
+    .customize-search-form {
+        display: flex;
+        flex-direction: row;
+        flex-wrap: wrap;
+        align-items: center;
+         gap: 0em;
+        max-width: 100%;
+        padding: 1em 1px 1px 1px;
+        box-sizing: border-box;
+        position: relative;
+    }
 </style>

+ 202 - 24
health-admin/src/main/resources/templates/dtp/archives/archivesList.html

@@ -26,11 +26,14 @@
 						</div>
 						<div class="customize-form-group">
 							<label>	药品:</label>
-							<input type="text"  placeholder="请输入商品名/通用名/MDM编码" class="styled-input" name="query"/>
+							<input type="text"  placeholder="商品名/通用名/MDM编码" class="styled-input" name="query"/>
 						</div>
+
 						<div class="customize-form-group">
-							<label>随访跟进人:</label>
-							<input type="text"  placeholder="请输入随访跟进人" class="styled-input" name="followUpPerson"/>
+							<label>任务跟进人:</label>
+							<select name="followUpPerson" id="taskFollowerSelect" class="styled-input" >
+								<option value="">请选择</option>
+							</select>
 						</div>
 						<div class="customize-form-group">
 							<label>是否接受随访:</label>
@@ -46,19 +49,32 @@
 								<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}" ></option>
 							</select>
 						</div>
+<!--						<div class="customize-form-group">-->
+<!--							<label>门店:</label>-->
+<!--							<input type="text"  placeholder="请输入门店" class="styled-input" name="storeName"/>-->
+<!--						</div>-->
 						<div class="customize-form-group">
 							<label>门店:</label>
-							<input type="text"  placeholder="请输入门店" class="styled-input" name="storeName"/>
+							<select name="storeName" id="storeSelect" class="styled-input">
+								<option value="">请选择门店</option>
+							</select>
 						</div>
 
+<!--						<div class="customize-form-group">-->
+<!--							<input id="treeId" name="parentId" type="hidden" th:value="${dept?.deptId}"   />-->
+<!--							<label>上级门店:</label>-->
+<!--									<input class="styled-input" type="text" onclick="selectDeptTree()" id="treeName" readonly="true" th:value="${dept?.deptName}" required>-->
+<!--									<span class="input-group-addon"><i class="fa fa-search"></i></span>-->
+<!--						</div>-->
+
 					</form>
 				</div>
 
 		        <div class="btn-group-sm" id="toolbar" role="group">
-		        <a class="btn btn-success" onclick="$.operate.addSetwh(null,900,750)" shiro:hasPermission="system:user:add">
+		        <a class="btn btn-success" onclick="$.operate.addSetwh(null,900,750)" shiro:hasPermission="dtp:pmService:add">
 		                <i class="fa fa-plus"></i> 患者快速建档
 		            </a>
-		            <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="system:user:remove">
+		            <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="dtp:pmService:remove">
 		                <i class="fa fa-remove"></i> 批量删除
 		            </a>
 		        </div>
@@ -75,9 +91,10 @@
 	<th:block th:include="include :: bootstrap-table-fixed-columns-js" />
 	<th:block th:include="include :: ztree-js" />
 	<script th:inline="javascript">
-		var editFlag = [[${@permission.hasPermi('dtp:RecipeRegister:edit')}]];
-		var removeFlag = [[${@permission.hasPermi('dtp:RecipeRegister:remove')}]];
+		var editFlag = [[${@permission.hasPermi('dtp:pmService:edit')}]];
+		var removeFlag = [[${@permission.hasPermi('dtp:pmService:remove')}]];
 		var prefix = ctx + "dtp/pmService";
+		var prefix_task = ctx + "task/followTask";
 		var w=900;
 		var h=900;
 		$(function() {
@@ -96,7 +113,11 @@
 	    	}
 			queryArchivesList();
 		});
-
+		//初始化加载
+		$(document).ready(function() {
+			findTaskStoreList();
+			findTaskFollowerList()//初始化任务跟进人
+		});
 		function queryArchivesList() {
 
 		    var options = {
@@ -108,7 +129,7 @@
 		        /*exportUrl: prefix + "/export",
 		        importUrl: prefix + "/importData",
 		        importTemplateUrl: prefix + "/importTemplate",*/
-		        sortName: "id",
+		        //sortName: "",
 		        sortOrder: "asc",
 		        modalName: "档案",
 				fitColumns: true,
@@ -127,25 +148,52 @@
 					},
 					{field: 'id', title: '患者ID', align: 'center'},
 					{field: 'name', title: '姓名', align: 'center',sortable:true},
-					{field: 'gender', title: '性别', align: 'center',
+					{field: 'gender', title: '性别', align: 'center'	,
+						formatter: function(value, row, index) {
+							switch (value) {
+								case 0:
+									return "男";
+									break;
+								case 1:
+									return "女";
+									break;
+								default:
+									return "未知";
+							}
+						}},
+				{field: 'age', title: '年龄', align: 'center'},
+				{field: 'phoneNumber', title: '手机号', align: 'center'},
+				{field: 'documentType', title: '证件类型', align: 'center'},
+				{field: 'documentNumber', title: '证件号码', align: 'center',sortable:true},
+				{field: 'realNameStatus', title: '实名状态', align: 'center',
 					formatter: function(value, row, index) {
 						switch (value) {
 							case 0:
-								return "男";
+								return "未实名";
 								break;
 							case 1:
-								return "女";
+								return "已实名";
+								break;
+							case 2:
+								return "待认证";
+								break;
+							default:
+								return "未知";
+						}
+					}},
+				{field: 'flipStatus', title: '上翻状态', align: 'center',
+					formatter: function(value, row, index) {
+						switch (value) {
+							case 1:
+								return "已上翻";
+								break;
+							case 2:
+								return "未上翻";
 								break;
 							default:
 								return "未知";
 						}
 					}},
-				{field: 'age', title: '年龄', align: 'center'},
-				{field: 'phoneNumber', title: '手机号', align: 'center'},
-				{field: 'documentType', title: '证件类型', align: 'center'},
-				{field: 'documentNumber', title: '证件号码', align: 'center',sortable:true},
-				{field: 'realNameStatus', title: '实名状态', align: 'center'},
-				{field: 'flipStatus', title: '上翻状态', align: 'center'},
 				{field: 'disease', title: '疾病', align: 'center'},
 				{field: 'genericName', title: '药品通用名', align: 'center'},
 				{field: 'productName', title: '商品名', align: 'center'},
@@ -156,12 +204,72 @@
 				{field: 'storeName', title: '门店', align: 'center'},
 				{field: 'archiveCreator', title: '档案创建人', align: 'center'},
 				{field: 'archiveCompleter', title: '档案完善人', align: 'center'},
-				{field: 'acceptFollowUp', title: '是否接受随访', align: 'center'},
+				{field: 'acceptFollowUp', title: '是否接受随访', align: 'center',
+					formatter: function(value, row, index) {
+						switch (value) {
+							case 1:
+								return "是";
+								break;
+							case 2:
+								return "否";
+								break;
+							default:
+								return "未知";
+						}
+					}},
 				{field: 'followUpPerson', title: '随访跟进人', align: 'center'},
-				{field: 'archiveCompleteStatus', title: '档案是否完善', align: 'center'},
-				{field: 'charityAssistance', title: '有无慈善援助', align: 'center'},
-				{field: 'joinProject', title: '是否参加共建项目', align: 'center'},
-				{field: 'followUpStatus', title: '随访状态', align: 'center'},
+				{field: 'archiveCompleteStatus', title: '档案是否完善', align: 'center',
+					formatter: function(value, row, index) {
+						switch (value) {
+							case 1:
+								return "已完善";
+								break;
+							case 2:
+								return "未完善";
+								break;
+							default:
+								return "未知";
+						}
+					}},
+				{field: 'charityAssistance', title: '有无慈善援助', align: 'center',
+					formatter: function(value, row, index) {
+						switch (value) {
+							case 1:
+								return "有";
+								break;
+							case 2:
+								return "无";
+								break;
+							default:
+								return "未知";
+						}
+					}},
+				{field: 'joinProject', title: '是否参加共建项目', align: 'center',
+					formatter: function(value, row, index) {
+						switch (value) {
+							case 1:
+								return "是";
+								break;
+							case 2:
+								return "否";
+								break;
+							default:
+								return "未知";
+						}
+					}},
+				{field: 'followUpStatus', title: '随访状态', align: 'center',
+					formatter: function(value, row, index) {
+						switch (value) {
+							case 1:
+								return "已随访过";
+								break;
+							case 2:
+								return "未随访过";
+								break;
+							default:
+								return "未知";
+						}
+					}},
 				{field: 'updateTime2', title: '更新时间', align: 'center',sortable:true},
 				/*{
 		        	formatter: function (value, row, index) {
@@ -199,7 +307,28 @@
 			$.table.search();
 			_refresh();
 		}
+		/*门店管理-新增-选择父门店树*/
+		function selectDeptTree() {
+			var treeId = $("#treeId").val();
+			if ($.common.isEmpty(treeId)) {
+				$.modal.alertWarning("请先添加用户所属的门店!");
+				return;
+			}
+			var options = {
+				title: '门店选择',
+				width: "380",
+				url: prefix + "/selectDeptTree/" + treeId + "/0",
+				callBack: doSubmit
+			};
+			$.modal.openOptions(options);
+		}
 
+		function doSubmit(index, layero){
+			var body = $.modal.getChildFrame(index);
+			$("#treeId").val(body.find('#treeId').val());
+			$("#treeName").val(body.find('#treeName').val());
+			$.modal.close(index);
+		}
 		/* 用户状态显示 */
 		function statusTools(row) {
 		    if (row.status == 1) {
@@ -208,6 +337,55 @@
     			return '<i class=\"fa fa-toggle-on text-info fa-2x\" onclick="disable(\'' + row.userId + '\')"></i> ';
     		}
 		}
+		// 异步加载所有任务跟进人信息并填充下拉框
+		function findTaskFollowerList() {
+			$.ajax({
+				url: prefix_task+ "/findTaskFollowerList", // 这是获取所有任务跟进人的API端点
+				type: 'POST',
+				cache: false, // 设置为 false 防止缓存
+				processData: false, // 告诉 jQuery 不要处理数据(非常重要)
+				contentType: false, // 告诉 jQuery 不要设置 contentType(非常重要)
+				async: true, // 异步请求更为推荐,除非有特殊原因需要同步
+				success: function (data) {
+					var select = $('#taskFollowerSelect');
+					// 清空除了默认选项外的所有选项
+					select.find('option:not(:first)').remove();
+					data.data.forEach(function (pharmacist) {
+						select.append(new Option(pharmacist.pharmacistName, pharmacist.pharmacistName));
+					});
+				},
+				error: function () {
+					$.modal.alertError('加载任务跟进人信息失败');
+				}
+			});
+		}
+		// 异步加载所有门店信息并填充下拉框
+		function findTaskStoreList() {
+			$.ajax({
+				url: prefix_task+ "/findTaskStoreList", // 这是获取所有门店的API端点
+				type: 'POST',
+				cache: false, // 设置为 false 防止缓存
+				processData: false, // 告诉 jQuery 不要处理数据(非常重要)
+				contentType: false, // 告诉 jQuery 不要设置 contentType(非常重要)
+				async: true, // 异步请求更为推荐,除非有特殊原因需要同步
+				success: function (data) {
+					var select = $('#storeSelect');
+					// 清空除了默认选项外的所有选项
+					select.find('option:not(:first)').remove();
+					if (data && data.data) {
+						data.data.forEach(function (store) {
+							select.append(new Option(store.dept_name, store.dept_name));
+						});
+					} else {
+						console.error('Unexpected response format:', data);
+					}
+				},
+				error: function () {
+					$.modal.alertError('加载门店信息失败');
+				}
+			});
+		}
+
 	</script>
 </body>
 

+ 1 - 1
health-admin/src/main/resources/templates/dtp/cold/cold.html

@@ -85,7 +85,7 @@
 		             <a class="btn btn-primary single disabled" onclick="$.operate.editTab()" shiro:hasPermission="dtp:cold:edit">
 			            <i class="fa fa-edit"></i> 修改
 			        </a>
-		            <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="sdtp:cold:remove">
+		            <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="dtp:cold:remove">
 		                <i class="fa fa-remove"></i> 删除
 		            </a>
 <!--		            <a class="btn btn-info" onclick="$.table.importExcel()" shiro:hasPermission="dtp:recipe:import">-->

+ 1 - 1
health-admin/src/main/resources/templates/dtp/followUp/closePlanPage.html

@@ -341,7 +341,7 @@ if(drugData != null || drugData != undefined){
         return tableRows;
     }
     function submitDrugInfo() {
-        debugger
+
        // $('#dValueIdInput').val('');
         var dValueId = $('#dValueIdInput').val().trim();
         var dValueName = $('#dValueNameInput').val().trim();

+ 216 - 0
health-admin/src/main/resources/templates/dtp/followUp/createPlanPage.html

@@ -0,0 +1,216 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+    <meta charset="UTF-8">
+    <th:block th:include="include :: header('创建计划')" />
+    <th:block th:include="include :: layout-latest-css" />
+    <th:block th:include="include :: ztree-css" />
+</head>
+
+<body><div class="main-content">
+
+
+    <div class="row">
+        <div class="col-sm-12 search-collapse">
+            <div class="query-condition-container">
+                <h4 class="query-condition-title">查询条件</h4>
+                <div class="query-buttons">
+                    <a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
+                    <a class="btn btn-warning btn-rounded btn-sm" onclick="resetPre()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
+                </div>
+            </div>
+            <form id="tag-form" class="customize-search-form">
+                <div class="customize-form-group-container">
+                    <div class="customize-form-group">
+                        <label>模版名称/ID:</label>
+                        <input type="text" class="styled-input" style="width: 550px" placeholder="请输入模版名称/ID" name="query"/>
+                    </div>
+                    <div class="customize-form-group">
+                        <label>模版类型:</label>
+                        <select name="modelType" id="modelType" class="styled-input">
+                            <option value="">请选择</option>
+                            <option value="1">单次模版</option>
+                            <option value="2">周期模版</option>
+                        </select>
+                    </div>
+                    <div class="customize-form-group">
+                        <label>是否关联药品:</label>
+                        <select name="linkFlag" id="linkFlag" class="styled-input" th:with="type=${@dict.getType('sys_gxhpz_yes_no')}">
+                            <option value="">请选择</option>
+                            <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+                        </select>
+                    </div>
+                </div>
+            </form>
+        </div>
+
+    </div>
+    <div class="row">
+        <div class="col-sm-12 select-table table-striped">
+            <table id="bootstrap-table"></table>
+        </div>
+    </div>
+</div>
+
+<th:block th:include="include :: footer" />
+<script th:inline="javascript">
+    var viewList = [[${@permission.hasPermi('gxhpz:task:list')}]];
+    var prefix = ctx + "task/followTask";
+
+    $(function() {
+        var panehHidden = false;
+        if ($(this).width() < 1590) {
+            panehHidden = true;
+        }
+        //$('body').layout({ initClosed: panehHidden, west__size: 185, resizeWithWindow: false });
+        // 回到顶部绑定
+        // if ($.fn.toTop !== undefined) {
+        // 	var opt = {
+        // 		win:$('.ui-layout-center'),
+        // 		doc:$('.ui-layout-center')
+        // 	};
+        // 	$('#scroll-up').toTop(opt);
+        // }
+        queryUserList();
+    });
+
+    function queryUserList() {
+        var options = {
+            url: prefix + "/createPlanToGetTemplate",
+            viewUrl: prefix + "/view/{id}",
+            //sortName: "createTime",
+            //sortOrder: "desc",
+            modalName: "计划创建",
+            showFooter:true,  //是否显示表格底部区域。
+            clickToSelect: true, //是否启用点击行时选中整行的功能。
+            singleSelect: true, //是否仅允许选择一行
+            fixedColumns: true,
+            //fixedNumber: 3,
+            //fixedRightNumber: 1,
+            columns: [
+                {
+                    checkbox: true // 如果需要复选框列的话保留此项
+                },
+                { field: "id", title: "主键", visible: false },
+                { field: "templateId", title: "模版ID"},
+                { field: "templateName", title: "模版名称" },
+                { field: "drug", title: "关联药品"},
+                { field: "storeId", title: "所属门店ID"},
+                { field: "businessBelonging", title: "业务归属"},
+                // 新增字段开始
+                { field: "nodeName", title: "节点名称" , visible: false },
+                { field: "nodeId", title: "nodeId" , visible: false },
+                { field: "nodeCode", title: "节点编码" , visible: false },
+                { field: "activateStructures", title: "激活节点(按)" , visible: false },
+                { field: "activateWhichDay", title: "激活节点(第几天激活)" , visible: false },
+                { field: "activateNodeRule", title: "激活节点(节点规则)" , visible: false },
+                { field: "createTaskStructures", title: "生成任务(按)" , visible: false },
+                { field: "createTaskWhen", title: "生成任务(当)" , visible: false },
+                { field: "createTaskEvery", title: "生成任务(每)" , visible: false },
+                { field: "createTaskDi", title: "生成任务(第)" , visible: false },
+                { field: "createTaskTriggerValue", title: "生成任务(为多少时触发动作)" , visible: false },
+                { field: "createTaskExecutionTimes", title: "生成任务(共执行几次)" , visible: false },
+                { field: "filterCondition", title: "过滤条件" , visible: false },
+                { field: "trait", title: "过滤条件(特征)" , visible: false },
+                { field: "traitValue", title: "过滤条件(特征值)" , visible: false },
+                { field: "judgingCondition", title: "过滤条件(判断条件)" , visible: false },
+                { field: "judgmentValue", title: "过滤条件(判断条件值)" , visible: false },
+                { field: "taskActionMode", title: "任务动作(按:执行方式)" , visible: false },
+                { field: "taskActionTaskType", title: "任务动作(任务类型)" , visible: false },
+                { field: "taskActionRole", title: "任务动作(执行角色)" , visible: false },
+                { field: "generationDaysAfter", title: "执行任务(生成后第几天执行任务)" , visible: false },
+                { field: "generationHMS", title: "执行任务(时分秒)" , visible: false },
+                { field: "followUpSubject", title: "随访主题" , visible: false },
+                { field: "taskValidity", title: "任务有效期(天)" , visible: false },
+                { field: "taskMaterial", title: "任务素材" , visible: false },
+                { field: "deliveryChannel", title: "下发渠道" , visible: false },
+                { field: "smsId", title: "短信ID" , visible: false },
+                { field: "useForm", title: "表单", visible: false  },
+                { field: "model", title: "表单类型", visible: false  }
+            ]
+        };
+        $.table.init(options);
+    }
+
+    /* 自定义重置-表单重置/隐藏框/树节点选择色/搜索 */
+    function resetPre() {
+        resetDate();
+        $("#tag-form")[0].reset();
+        $("#deptId").val("");
+        $("#parentId").val("");
+        $(".curSelectedNode").removeClass("curSelectedNode");
+        $.table.search();
+    }
+    /* 添加用户-选择用户-提交(子页面调用父页面形式) */
+    function submitHandler(index, layero) {
+
+        var rows = $.table.selectFirstColumns();
+
+        if (rows.length == 0) {
+            $.modal.alertWarning("请至少选择一条记录");
+            return;
+        }
+        $.modal.close();
+        // 父页面的方法
+        // activeWindow().selectUsers();
+        // 父页面的变量
+        activeWindow().$('#userids').html('我是通过方式一来的:' + rows.join());
+    }
+
+    /* 添加用户-选择用户-提交(回调形式-父页面调用子页面) */
+    function getSelections() {
+        return $.table.selectFirstColumns();
+    }
+
+    function selectTableObject() {
+        // 动态选择所有列并映射到更具描述性的名称
+        var columnsData = {
+            id: $.table.selectColumns('id'),
+            templateId: $.table.selectColumns('templateId'),
+            templateName: $.table.selectColumns('templateName'),
+            businessBelonging: $.table.selectColumns('businessBelonging'),
+            drug: $.table.selectColumns('drug'),
+            nodeId: $.table.selectColumns('nodeId'),
+            model: $.table.selectColumns('model'),
+            storeId: $.table.selectColumns('storeId')
+        };
+
+        // 构建返回的对象数组
+        var selectedRows = [];
+
+        // 假设 $.table.selectColumns 返回的是一个数组,我们检查每个字段是否为空或未定义来判断是否有选择
+        for (var i = 0; i < columnsData.id.length; i++) {
+            if (columnsData.id[i]) { // 如果 id 存在,则认为该行被选中
+                var row = {
+                    id: columnsData.id[i],
+                    templateId: columnsData.templateId[i],
+                    templateName: columnsData.templateName[i],
+                    businessBelonging: columnsData.businessBelonging[i],
+                    drug: columnsData.drug[i],
+                    nodeId: columnsData.nodeId[i],
+                    model: columnsData.model[i],
+                    storeId: columnsData.storeId[i]
+                };
+                selectedRows.push(row);
+            }
+        }
+
+        return selectedRows;
+    }
+    /*回调返回所有的选中行数据*/
+    function selectColumns2() {
+        return $.table.selectColumns2();
+    }
+    $("#bootstrap-table").on("check.bs.table check-all.bs.table uncheck.bs.table uncheck-all.bs.table", function (e, rowsAfter, rowsBefore) {
+        var rows = $.common.equals("uncheck-all", e.type) ? rowsBefore : rowsAfter;
+        var rowIds = $.table.affectedRowIds(rows);
+        $("#rowIds").val(rowIds);
+    });
+</script>
+</body>
+<style>
+    .styled-input {
+        height: 36px;
+    }
+</style>
+</html>

+ 1088 - 0
health-admin/src/main/resources/templates/dtp/followUp/followUp.html

@@ -0,0 +1,1088 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org">
+<head>
+    <th:block th:include="include :: header('随访表单')" />
+    <th:block th:include="include :: bootstrap-select-css" />
+    <th:block th:include="include :: bootstrap-select-js" />
+    <th:block th:include="include :: bootstrap-fileinput-css" />
+    <th:block th:include="include :: bootstrap-fileinput-js" />
+    <th:block th:include="include :: datetimepicker-css" />
+    <th:block th:include="include :: datetimepicker-js" />
+    <style>
+        .patient-info-panel {
+            background-color: #f8f8f8;
+            border: 1px solid #e7eaec;
+            border-radius: 4px;
+            padding: 15px;
+            margin-bottom: 20px;
+        }
+        .patient-info-row {
+            display: flex;
+            flex-wrap: wrap;
+            margin-bottom: 10px;
+        }
+        .patient-info-item {
+            flex: 1;
+            min-width: 200px;
+            margin-right: 20px;
+            margin-bottom: 10px;
+        }
+        .patient-info-label {
+            font-weight: bold;
+            margin-right: 10px;
+        }
+        .patient-info-value {
+            padding: 2px 8px;
+            background-color: #fff;
+            border: 1px solid #e7eaec;
+            border-radius: 3px;
+            display: inline-block;
+        }
+        .status-badge {
+            padding: 2px 5px;
+            border-radius: 3px;
+            font-size: 12px;
+            font-weight: bold;
+        }
+        .status-active {
+            background-color: #1ab394;
+            color: white;
+        }
+        .status-inactive {
+            background-color: #ed5565;
+            color: white;
+        }
+        .form-container {
+            background-color: #fff;
+            border-radius: 4px;
+            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+            margin-bottom: 20px;
+        }
+        .form-section {
+            margin-bottom: 15px;
+            padding: 15px;
+            background-color: #f9f9f9;
+            border-radius: 4px;
+            border: 1px solid #eee;
+        }
+        .form-section-header {
+            border-bottom: 1px solid #eee;
+            padding-bottom: 10px;
+            margin-bottom: 15px;
+            font-weight: bold;
+            font-size: 16px;
+            color: #333;
+            display: flex;
+            justify-content: space-between;
+        }
+        .section-title {
+            display: flex;
+            align-items: center;
+        }
+        .section-actions {
+            display: flex;
+            gap: 10px;
+        }
+        .form-fields {
+            margin-bottom: 15px;
+        }
+        .form-field {
+            margin-bottom: 15px;
+        }
+        .field-label {
+            font-weight: bold;
+            margin-bottom: 5px;
+        }
+        .field-label.required:after {
+            content: ' *';
+            color: red;
+        }
+        .field-help {
+            font-size: 12px;
+            color: #999;
+            margin-top: 5px;
+        }
+        .form-subtable {
+            margin-top: 15px;
+            background-color: #fff;
+            padding: 10px;
+            border-radius: 4px;
+            border: 1px solid #ddd;
+        }
+        .subtable-header {
+            font-weight: bold;
+            margin-bottom: 10px;
+        }
+        .subtable-table {
+            width: 100%;
+            border-collapse: collapse;
+            margin-bottom: 10px;
+        }
+        .subtable-table th, .subtable-table td {
+            padding: 8px;
+            border: 1px solid #ddd;
+            text-align: center;
+        }
+        .subtable-table th {
+            background-color: #f5f5f5;
+            font-weight: bold;
+        }
+        .subtable-add-btn {
+            margin-top: 5px;
+        }
+        .form-actions {
+            margin-top: 20px;
+            padding: 15px;
+            text-align: center;
+            background-color: #f5f5f5;
+            border-top: 1px solid #e7eaec;
+        }
+        .task-info-panel {
+            background-color: #fff;
+            border: 1px solid #e7eaec;
+            border-radius: 4px;
+            padding: 15px;
+            margin-bottom: 20px;
+        }
+        .task-header {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            padding-bottom: 10px;
+            border-bottom: 1px solid #eee;
+            margin-bottom: 10px;
+        }
+        .task-title {
+            font-size: 18px;
+            font-weight: bold;
+        }
+        .task-details {
+            display: flex;
+            flex-wrap: wrap;
+        }
+        .task-info-item {
+            flex: 1;
+            min-width: 200px;
+            margin-right: 20px;
+            margin-bottom: 10px;
+        }
+        .collapsible .form-section-content {
+            display: none;
+        }
+        .collapsible .form-section-header {
+            cursor: pointer;
+        }
+        .collapsible .form-section-header .toggle-icon:after {
+            content: '\f107';
+            font-family: 'FontAwesome';
+            margin-left: 5px;
+        }
+        .collapsible.collapsed .form-section-header .toggle-icon:after {
+            content: '\f105';
+        }
+        .next-follow-panel {
+            background-color: #f0f7fb;
+            border: 1px solid #c3e6fc;
+            border-radius: 4px;
+            padding: 15px;
+            margin-top: 20px;
+        }
+        .tabs-container {
+            margin-bottom: 20px;
+        }
+        .nav-tabs {
+            background-color: #f8f8f8;
+            border-bottom: 1px solid #ddd;
+        }
+        .nav-tabs > li > a {
+            margin-right: 0;
+            border: none;
+            border-radius: 0;
+            color: #676a6c;
+        }
+        .nav-tabs > li.active > a, .nav-tabs > li.active > a:focus, .nav-tabs > li.active > a:hover {
+            border: none;
+            border-bottom: 2px solid #1ab394;
+            color: #1ab394;
+        }
+        .tab-content {
+            padding: 20px 0;
+        }
+        .condition-field-visible {
+            display: block;
+        }
+        .condition-field-hidden {
+            display: none;
+        }
+    </style>
+</head>
+<body class="white-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-12">
+            <div class="ibox">
+                <div class="ibox-title">
+                    <h5>随访表单</h5>
+                    <div class="ibox-tools">
+                        <a class="btn btn-primary btn-xs" onclick="$.modal.close()">返回上一级</a>
+                    </div>
+                </div>
+                <div class="ibox-content">
+                    <!-- 患者基本信息 -->
+                    <div class="patient-info-panel">
+                        <div class="patient-info-row">
+                            <div class="patient-info-item">
+                                <span class="patient-info-label">姓名:</span>
+                                <span class="patient-info-value" th:text="${patientInfo.name}">张三</span>
+                                <span class="status-badge" th:class="${patientInfo.realNameStatus == 1 ? 'status-active' : 'status-inactive'}"
+                                      th:text="${patientInfo.realNameStatus == 1 ? '已实名' : '未实名'}">已实名</span>
+                            </div>
+                            <div class="patient-info-item">
+                                <span class="patient-info-label">性别:</span>
+                                <span class="patient-info-value" th:text="${patientInfo.gender == 1 ? '女' : '男'}">男</span>
+                            </div>
+                            <div class="patient-info-item">
+                                <span class="patient-info-label">出生年月:</span>
+                                <span class="patient-info-value" th:text="${patientInfo.dateBirth}">1990-01-01</span>
+                            </div>
+                            <div class="patient-info-item">
+                                <span class="patient-info-label">年龄:</span>
+                                <span class="patient-info-value" th:text="${patientInfo.age}">30</span> 岁
+                            </div>
+                        </div>
+
+                        <div class="patient-info-row" th:if="${not #lists.isEmpty(dValueList)}">
+                            <div class="patient-info-item" style="flex: 100%;">
+                                <span class="patient-info-label">用药情况:</span>
+                                <div style="margin-top: 10px;">
+                                    <div th:each="item : ${dValueList}" style="margin-bottom: 10px; border: 1px solid #eee; padding: 10px; background-color: #fafafa;">
+                                        <div><strong>药品:</strong> <span th:text="${item.genericName}">药品名称</span></div>
+                                        <div><strong>用药状态:</strong> <span th:text="${item.medication_status}">服药中</span></div>
+                                        <div><strong>剩余用药天数:</strong> <span th:text="${item.adjusted_sum_total}">30</span> 天</div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+
+                        <div class="patient-info-row">
+                            <div class="patient-info-item" style="flex: 100%;">
+                                <span class="patient-info-label">档案完善度:</span>
+                                <div class="progress" style="margin-top: 5px; margin-bottom: 0;">
+                                    <div class="progress-bar" role="progressbar" th:style="'width: ' + ${completenessPercentage} + '%'"
+                                         th:text="${completenessPercentage} + '%'">60%</div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+
+                    <!-- 任务信息 -->
+                    <div class="task-info-panel">
+                        <div class="task-header">
+                            <div class="task-title">
+                                <span>本次任务:</span>
+                                <span class="text-primary" th:text="${task.taskStatus}">进行中</span>
+                            </div>
+                            <div>
+                                <button class="btn btn-primary btn-sm" onclick="editArchives()">完善档案</button>
+                            </div>
+                        </div>
+                        <div class="task-details">
+                            <div class="task-info-item">
+                                <span class="patient-info-label">任务名称:</span>
+                                <span th:text="${task.taskName}">常规随访</span>
+                            </div>
+                            <div class="task-info-item">
+                                <span class="patient-info-label">药品:</span>
+                                <span th:text="${task.productName}">药品名称</span>
+                            </div>
+                            <div class="task-info-item">
+                                <span class="patient-info-label">任务主题:</span>
+                                <select name="taskTheme" class="form-control" style="display: inline-block; width: auto;"
+                                        th:with="type=${@dict.getType('sys_select_dtp_sfrw_rwzt')}">
+                                    <option value="">请选择</option>
+                                    <option th:each="dict : ${type}" th:text="${dict.dictLabel}"
+                                            th:value="${dict.dictLabel}" th:selected="${dict.dictLabel}==${task.taskTheme}"></option>
+                                </select>
+                            </div>
+                            <div class="task-info-item">
+                                <span class="patient-info-label">任务跟进人:</span>
+                                <span th:text="${task.taskFollower}">王医师</span>
+                                <a href="javascript:void(0)" onclick="editTaskFollower()">修改 <i class="fa fa-pencil"></i></a>
+                            </div>
+                        </div>
+                    </div>
+
+                    <!-- 表单选项卡 -->
+                    <div class="tabs-container">
+                        <ul class="nav nav-tabs">
+                            <li th:each="section, sectionStat : ${formData.sections}" th:class="${sectionStat.first ? 'active' : ''}">
+                                <a data-toggle="tab" th:href="'#tab-' + ${section.id}" th:text="${section.sectionName}">选项卡</a>
+                            </li>
+                        </ul>
+
+                        <div class="tab-content">
+                            <div th:each="section, sectionStat : ${formData.sections}" th:id="'tab-' + ${section.id}"
+                                 th:class="${sectionStat.first ? 'tab-pane active' : 'tab-pane'}">
+
+                                <form id="form-followUp" class="form-horizontal">
+                                    <input type="hidden" id="id" name="id" th:value="${task.id}">
+                                    <input type="hidden" id="patientId" name="patientId" th:value="${patientInfo.id}">
+                                    <input type="hidden" id="planId" name="planId" th:value="${task.planId}">
+                                    <input type="hidden" id="templateId" name="templateId" th:value="${formData.template.id}">
+
+                                    <!-- 字段区域 -->
+                                    <div class="form-fields">
+                                        <!-- 遍历分组内的字段 -->
+                                        <div th:each="field : ${section.fields}" class="form-field"
+                                             th:id="'field-container-' + ${field.id}"
+                                             th:attr="data-dependency=${field.dependFieldId},data-dependency-value=${field.dependFieldValue}">
+
+                                            <div class="field-label" th:classappend="${field.isRequired == 1 ? 'required' : ''}"
+                                                 th:text="${field.fieldLabel}">字段标签</div>
+
+                                            <!-- 根据字段类型渲染不同的控件 -->
+                                            <div th:if="${field.fieldType == 'text'}" class="col-sm-8">
+                                                <input type="text" class="form-control" th:name="${field.fieldCode}"
+                                                       th:placeholder="${field.placeholder}"
+                                                       th:attr="data-rule-required=${field.isRequired == 1}"
+                                                       th:style="'width:' + ${field.fieldWidth}"
+                                                       th:value="${#maps.containsKey(savedData, field.fieldCode) ? savedData[field.fieldCode] : field.defaultValue}">
+                                            </div>
+
+                                            <div th:if="${field.fieldType == 'textarea'}" class="col-sm-8">
+                                                    <textarea class="form-control" th:name="${field.fieldCode}"
+                                                              th:placeholder="${field.placeholder}"
+                                                              th:attr="data-rule-required=${field.isRequired == 1}"
+                                                              th:style="'width:' + ${field.fieldWidth}" rows="4"
+                                                              th:text="${#maps.containsKey(savedData, field.fieldCode) ? savedData[field.fieldCode] : field.defaultValue}"></textarea>
+                                            </div>
+
+                                            <div th:if="${field.fieldType == 'number'}" class="col-sm-8">
+                                                <input type="number" class="form-control" th:name="${field.fieldCode}"
+                                                       th:placeholder="${field.placeholder}"
+                                                       th:attr="data-rule-required=${field.isRequired == 1}"
+                                                       th:style="'width:' + ${field.fieldWidth}"
+                                                       th:value="${#maps.containsKey(savedData, field.fieldCode) ? savedData[field.fieldCode] : field.defaultValue}">
+                                            </div>
+
+                                            <div th:if="${field.fieldType == 'select'}" class="col-sm-8">
+                                                <select class="form-control selectpicker" th:name="${field.fieldCode}"
+                                                        th:attr="data-rule-required=${field.isRequired == 1}"
+                                                        th:style="'width:' + ${field.fieldWidth}">
+                                                    <option value="">请选择</option>
+                                                    <option th:each="option : ${field.options}"
+                                                            th:value="${option.optionValue}"
+                                                            th:text="${option.optionLabel}"
+                                                            th:selected="${#maps.containsKey(savedData, field.fieldCode) ? savedData[field.fieldCode] == option.optionValue : option.isDefault == 1}"></option>
+                                                </select>
+                                            </div>
+
+                                            <div th:if="${field.fieldType == 'radio'}" class="col-sm-8">
+                                                <div class="radio-group">
+                                                    <label th:each="option : ${field.options}" class="radio-inline">
+                                                        <input type="radio" th:name="${field.fieldCode}"
+                                                               th:value="${option.optionValue}"
+                                                               th:attr="data-rule-required=${field.isRequired == 1}"
+                                                               th:checked="${#maps.containsKey(savedData, field.fieldCode) ? savedData[field.fieldCode] == option.optionValue : option.isDefault == 1}">
+                                                        <span th:text="${option.optionLabel}"></span>
+                                                    </label>
+                                                </div>
+                                            </div>
+
+                                            <div th:if="${field.fieldType == 'checkbox'}" class="col-sm-8">
+                                                <div class="checkbox-group">
+                                                    <label th:each="option : ${field.options}" class="checkbox-inline">
+                                                        <input type="checkbox" th:name="${field.fieldCode}"
+                                                               th:value="${option.optionValue}"
+                                                               th:attr="data-rule-required=${field.isRequired == 1}"
+                                                               th:checked="${#maps.containsKey(savedData, field.fieldCode) ? #strings.contains(savedData[field.fieldCode], option.optionValue) : option.isDefault == 1}">
+                                                        <span th:text="${option.optionLabel}"></span>
+                                                    </label>
+                                                </div>
+                                            </div>
+
+                                            <div th:if="${field.fieldType == 'date'}" class="col-sm-8">
+                                                <div class="input-group date">
+                                                    <input type="text" class="form-control date-picker" th:name="${field.fieldCode}"
+                                                           th:placeholder="${field.placeholder}"
+                                                           th:attr="data-rule-required=${field.isRequired == 1}"
+                                                           th:style="'width:' + ${field.fieldWidth}"
+                                                           th:value="${#maps.containsKey(savedData, field.fieldCode) ? savedData[field.fieldCode] : field.defaultValue}">
+                                                    <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
+                                                </div>
+                                            </div>
+
+                                            <div th:if="${field.fieldType == 'datetime'}" class="col-sm-8">
+                                                <div class="input-group datetime">
+                                                    <input type="text" class="form-control datetime-picker" th:name="${field.fieldCode}"
+                                                           th:placeholder="${field.placeholder}"
+                                                           th:attr="data-rule-required=${field.isRequired == 1}"
+                                                           th:style="'width:' + ${field.fieldWidth}"
+                                                           th:value="${#maps.containsKey(savedData, field.fieldCode) ? savedData[field.fieldCode] : field.defaultValue}">
+                                                    <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
+                                                </div>
+                                            </div>
+
+                                            <div th:if="${field.fieldType == 'file'}" class="col-sm-8">
+                                                <div class="file-input">
+                                                    <input type="file" class="file-control" th:name="${field.fieldCode}"
+                                                           th:attr="data-rule-required=${field.isRequired == 1}">
+                                                </div>
+                                            </div>
+
+                                            <div th:if="${field.fieldType == 'image'}" class="col-sm-8">
+                                                <div class="fileinput fileinput-new" data-provides="fileinput">
+                                                    <div class="fileinput-preview thumbnail" data-trigger="fileinput" style="width: 200px; height: 150px;">
+                                                        <img th:if="${#maps.containsKey(savedData, field.fieldCode)}"
+                                                             th:src="${savedData[field.fieldCode]}" class="preview-image" />
+                                                        <div th:unless="${#maps.containsKey(savedData, field.fieldCode)}" class="centered-content">
+                                                            <span class="plus-sign">+</span>
+                                                        </div>
+                                                    </div>
+                                                    <div>
+                                                        <input type="file" class="image-upload" th:name="${field.fieldCode}"
+                                                               accept="image/*" style="display: none;"
+                                                               th:attr="data-rule-required=${field.isRequired == 1}">
+                                                        <a class="btn btn-primary fileinput-exists" data-dismiss="fileinput">
+                                                            <i class="fa fa-upload"></i> 上传图片
+                                                        </a>
+                                                        <a href="javascript:;" class="btn btn-white" data-dismiss="fileinput">清除图片</a>
+                                                    </div>
+                                                </div>
+                                            </div>
+
+                                            <div class="field-help" th:if="${field.helpText}" th:text="${field.helpText}">帮助文本</div>
+                                        </div>
+                                    </div>
+
+                                    <!-- 子表区域 -->
+                                    <div th:each="subtable : ${section.subtables}" class="form-subtable"
+                                         th:id="'subtable-container-' + ${subtable.id}"
+                                         th:attr="data-subtable-code=${subtable.subtableCode},data-min-rows=${subtable.minRows},data-max-rows=${subtable.maxRows}">
+
+                                        <div class="subtable-header" th:text="${subtable.subtableName}">子表标题</div>
+                                        <table class="subtable-table" th:id="'subtable-' + ${subtable.id}">
+                                            <thead>
+                                            <tr>
+                                                <th width="5%">序号</th>
+                                                <th th:each="field : ${subtable.fields}" th:text="${field.fieldLabel}"
+                                                    th:attr="data-field-code=${field.fieldCode},data-field-type=${field.fieldType}">列标题</th>
+                                                <th width="10%">操作</th>
+                                            </tr>
+                                            </thead>
+                                            <tbody>
+                                            <!-- 示例行,实际数据会通过JavaScript动态添加 -->
+                                            <tr th:if="${#maps.containsKey(savedData, subtable.subtableCode) && not #lists.isEmpty(savedData[subtable.subtableCode])}"
+                                                th:each="row, rowStat : ${savedData[subtable.subtableCode]}">
+                                                <td th:text="${rowStat.count}">1</td>
+                                                <td th:each="field : ${subtable.fields}"
+                                                    th:text="${#maps.containsKey(row, field.fieldCode) ? row[field.fieldCode] : '-'}">-</td>
+                                                <td>
+                                                    <a class="btn btn-info btn-xs btn-edit-row"><i class="fa fa-edit"></i></a>
+                                                    <a class="btn btn-danger btn-xs btn-delete-row"><i class="fa fa-trash"></i></a>
+                                                </td>
+                                            </tr>
+                                            <tr th:unless="${#maps.containsKey(savedData, subtable.subtableCode) && not #lists.isEmpty(savedData[subtable.subtableCode])}">
+                                                <td colspan="${#lists.size(subtable.fields) + 2}" class="text-center">暂无数据</td>
+                                            </tr>
+                                            </tbody>
+                                        </table>
+                                        <button type="button" class="btn btn-primary btn-sm subtable-add-btn"
+                                                th:attr="data-subtable-id=${subtable.id}">
+                                            <i class="fa fa-plus"></i> <span th:text="${subtable.addButtonText ?: '新增'}">新增</span>
+                                        </button>
+                                    </div>
+                                </form>
+                            </div>
+                        </div>
+                    </div>
+
+                    <!-- 下次随访设置 -->
+                    <div class="next-follow-panel">
+                        <div class="row">
+                            <div class="col-sm-4">
+                                <div class="form-group">
+                                    <label>下次随访时间:</label>
+                                    <div class="input-group date">
+                                        <input type="text" id="next_follow_time" name="next_follow_time"
+                                               class="form-control date-picker" placeholder="下次随访时间"
+                                               th:value="${task.next_follow_time}">
+                                        <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="col-sm-4">
+                                <div class="form-group">
+                                    <label>下次任务主题:</label>
+                                    <select name="next_taskTheme" class="form-control selectpicker"
+                                            th:with="type=${@dict.getType('sys_select_dtp_sfrw_rwzt')}">
+                                        <option value="">请选择</option>
+                                        <option th:each="dict : ${type}" th:text="${dict.dictLabel}"
+                                                th:value="${dict.dictLabel}" th:selected="${dict.dictLabel}==${task.next_taskTheme}"></option>
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="col-sm-4 text-right" style="padding-top: 25px;">
+                                <button type="button" class="btn btn-danger" onclick="$.modal.close()">关闭</button>
+                                <button type="button" class="btn btn-primary" onclick="submitForm()">保存</button>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!-- 子表编辑模态框 -->
+<div class="modal fade" id="subtableModal" tabindex="-1" role="dialog" aria-labelledby="subtableModalLabel">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+                <h4 class="modal-title" id="subtableModalLabel">编辑行数据</h4>
+            </div>
+            <div class="modal-body">
+                <form id="subtableForm">
+                    <input type="hidden" id="subtable_id" name="subtable_id">
+                    <input type="hidden" id="row_index" name="row_index">
+                    <div id="subtableFormFields">
+                        <!-- 字段会动态添加 -->
+                    </div>
+                </form>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-primary" id="saveSubtableRow">保存</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<th:block th:include="include :: footer" />
+
+<script th:inline="javascript">
+    var prefix = ctx + "task/followTask";
+    var formData = [[${formData}]];
+    var savedData = [[${savedData}]];
+
+    $(function() {
+        // 初始化UI组件
+        initComponents();
+
+        // 初始化表单验证
+        initValidation();
+
+        // 初始化字段依赖关系
+        initFieldDependencies();
+
+        // 绑定子表相关事件
+        initSubtables();
+    });
+
+    // 初始化UI组件
+    function initComponents() {
+        // 初始化下拉框
+        $('.selectpicker').selectpicker();
+
+        // 初始化日期选择器
+        $('.date-picker').datetimepicker({
+            format: 'yyyy-mm-dd',
+            minView: 2,
+            autoclose: true
+        });
+
+        // 初始化日期时间选择器
+        $('.datetime-picker').datetimepicker({
+            format: 'yyyy-mm-dd hh:ii:ss',
+            autoclose: true
+        });
+
+        // 初始化文件上传
+        $('.file-control').fileinput({
+            showPreview: false,
+            showUpload: false,
+            showRemove: true,
+            showCancel: false,
+            browseLabel: '选择文件'
+        });
+
+        // 初始化图片上传
+        $('.image-upload').on('change', function(e) {
+            if (e.target.files && e.target.files[0]) {
+                var reader = new FileReader();
+                var preview = $(this).closest('.fileinput').find('.thumbnail');
+
+                reader.onload = function(e) {
+                    preview.find('.centered-content').remove();
+
+                    if (preview.find('.preview-image').length === 0) {
+                        preview.append('<img class="preview-image" src="' + e.target.result + '">');
+                    } else {
+                        preview.find('.preview-image').attr('src', e.target.result);
+                    }
+                }
+
+                reader.readAsDataURL(e.target.files[0]);
+            }
+        });
+
+        // 上传图片按钮点击事件
+        $('.fileinput-exists').on('click', function() {
+            $(this).closest('.fileinput').find('.image-upload').click();
+        });
+
+        // 清除图片
+        $('[data-dismiss="fileinput"]').on('click', function() {
+            var preview = $(this).closest('.fileinput').find('.thumbnail');
+            preview.empty().append('<div class="centered-content"><span class="plus-sign">+</span></div>');
+        });
+    }
+
+    // 初始化表单验证
+    function initValidation() {
+        $("#form-followUp").validate({
+            rules: {
+                // 可以在这里添加自定义验证规则
+            },
+            messages: {
+                // 可以在这里添加自定义验证消息
+            },
+            errorPlacement: function(error, element) {
+                if (element.is(":radio") || element.is(":checkbox")) {
+                    error.appendTo(element.parent().parent());
+                } else {
+                    error.insertAfter(element);
+                }
+            }
+        });
+    }
+
+    // 初始化字段依赖关系
+    function initFieldDependencies() {
+        // 添加所有依赖字段的监听
+        $("[data-dependency]").each(function() {
+            var $field = $(this);
+            var dependencyId = $field.data("dependency");
+            var dependencyValue = $field.data("dependency-value");
+
+            if (dependencyId) {
+                // 查找依赖字段
+                var $dependencyField = $("[data-id='" + dependencyId + "']");
+
+                // 如果找到依赖字段,添加change事件监听
+                if ($dependencyField.length > 0) {
+                    $dependencyField.find("input, select, textarea").on("change", function() {
+                        updateFieldVisibility($field, $(this).val(), dependencyValue);
+                    });
+
+                    // 页面加载时执行一次
+                    updateFieldVisibility($field, $dependencyField.find("input, select, textarea").val(), dependencyValue);
+                }
+            }
+        });
+    }
+
+    // 更新字段可见性
+    function updateFieldVisibility($field, currentValue, requiredValue) {
+        if (currentValue == requiredValue) {
+            $field.removeClass("condition-field-hidden").addClass("condition-field-visible");
+        } else {
+            $field.removeClass("condition-field-visible").addClass("condition-field-hidden");
+
+            // 清除字段值
+            $field.find("input, select, textarea").val("");
+            if ($field.find(".selectpicker").length > 0) {
+                $field.find(".selectpicker").selectpicker("refresh");
+            }
+        }
+    }
+
+    // 初始化子表相关事件
+    function initSubtables() {
+        // 添加行按钮点击事件
+        $(".subtable-add-btn").on("click", function() {
+            var subtableId = $(this).data("subtable-id");
+            var $subtable = $("#subtable-" + subtableId);
+
+            // 查找该子表的最大行数限制
+            var maxRows = $("#subtable-container-" + subtableId).data("max-rows");
+            var currentRows = $subtable.find("tbody tr").not(".no-data").length;
+
+            // 检查是否达到最大行数
+            if (maxRows && currentRows >= maxRows) {
+                $.modal.alertWarning("已达到最大行数限制:" + maxRows);
+                return;
+            }
+
+            // 打开子表行编辑模态框
+            openSubtableRowModal(subtableId);
+        });
+
+        // 编辑行按钮点击事件
+        $(document).on("click", ".btn-edit-row", function() {
+            var subtableId = $(this).closest(".form-subtable").attr("id").replace("subtable-container-", "");
+            var rowIndex = $(this).closest("tr").index();
+
+            // 打开子表行编辑模态框,传入行索引表示编辑现有行
+            openSubtableRowModal(subtableId, rowIndex);
+        });
+
+        // 删除行按钮点击事件
+        $(document).on("click", ".btn-delete-row", function() {
+            var $tr = $(this).closest("tr");
+            var $tbody = $tr.parent();
+
+            $.modal.confirm("确定要删除这一行吗?", function() {
+                $tr.remove();
+
+                // 重新编号序号列
+                $tbody.find("tr").each(function(index) {
+                    $(this).find("td:first").text(index + 1);
+                });
+
+                // 如果没有行了,添加"暂无数据"提示行
+                if ($tbody.find("tr").length === 0) {
+                    var colCount = $tbody.closest("table").find("thead th").length;
+                    $tbody.append('<tr class="no-data"><td colspan="' + colCount + '" class="text-center">暂无数据</td></tr>');
+                }
+            });
+        });
+
+        // 保存子表行按钮点击事件
+        $("#saveSubtableRow").on("click", function() {
+            var subtableId = $("#subtable_id").val();
+            var rowIndex = $("#row_index").val();
+            var $subtable = $("#subtable-" + subtableId);
+            var $tbody = $subtable.find("tbody");
+            var fields = [];
+
+            // 收集子表字段
+            $subtable.find("thead th").each(function() {
+                var fieldCode = $(this).data("field-code");
+                var fieldType = $(this).data("field-type");
+
+                if (fieldCode) {
+                    fields.push({
+                        code: fieldCode,
+                        type: fieldType
+                    });
+                }
+            });
+
+            // 收集表单数据
+            var rowData = {};
+            $("#subtableForm input, #subtableForm select, #subtableForm textarea").each(function() {
+                var name = $(this).attr("name");
+                var value = $(this).val();
+
+                if (name && name !== "subtable_id" && name !== "row_index") {
+                    rowData[name] = value;
+                }
+            });
+
+            // 移除"暂无数据"行
+            $tbody.find(".no-data").remove();
+
+            if (rowIndex !== "") {
+                // 更新现有行
+                var $row = $tbody.find("tr").eq(rowIndex);
+
+                fields.forEach(function(field, i) {
+                    $row.find("td").eq(i + 1).text(rowData[field.code] || "-");
+                });
+            } else {
+                // 添加新行
+                var rowCount = $tbody.find("tr").length;
+                var newRow = "<tr>";
+
+                newRow += "<td>" + (rowCount + 1) + "</td>";
+
+                fields.forEach(function(field) {
+                    newRow += "<td>" + (rowData[field.code] || "-") + "</td>";
+                });
+
+                newRow += '<td>' +
+                    '<a class="btn btn-info btn-xs btn-edit-row"><i class="fa fa-edit"></i></a> ' +
+                    '<a class="btn btn-danger btn-xs btn-delete-row"><i class="fa fa-trash"></i></a>' +
+                    '</td>';
+
+                newRow += "</tr>";
+
+                $tbody.append(newRow);
+            }
+
+            // 关闭模态框
+            $("#subtableModal").modal("hide");
+        });
+    }
+
+    // 打开子表行编辑模态框
+    function openSubtableRowModal(subtableId, rowIndex) {
+        var $subtable = $("#subtable-" + subtableId);
+        var $subtableContainer = $("#subtable-container-" + subtableId);
+        var subtableCode = $subtableContainer.data("subtable-code");
+        var $modal = $("#subtableModal");
+        var $form = $("#subtableForm");
+        var $fields = $("#subtableFormFields");
+
+        // 清空现有字段
+        $fields.empty();
+
+        // 设置子表ID和行索引
+        $("#subtable_id").val(subtableId);
+        $("#row_index").val(rowIndex !== undefined ? rowIndex : "");
+
+        // 模态框标题
+        $("#subtableModalLabel").text(rowIndex !== undefined ? "编辑行数据" : "添加行数据");
+
+        // 获取行数据(如果是编辑现有行)
+        var rowData = {};
+        if (rowIndex !== undefined) {
+            var $row = $subtable.find("tbody tr").eq(rowIndex);
+
+            $subtable.find("thead th").each(function(i) {
+                var fieldCode = $(this).data("field-code");
+
+                if (fieldCode) {
+                    rowData[fieldCode] = $row.find("td").eq(i).text() !== "-" ? $row.find("td").eq(i).text() : "";
+                }
+            });
+        }
+
+        // 添加子表字段到表单
+        $subtable.find("thead th").each(function() {
+            var fieldCode = $(this).data("field-code");
+            var fieldType = $(this).data("field-type");
+            var fieldLabel = $(this).text();
+
+            if (fieldCode) {
+                var fieldHtml = '<div class="form-group">' +
+                    '<label class="control-label">' + fieldLabel + '</label>';
+
+                switch (fieldType) {
+                    case "text":
+                        fieldHtml += '<input type="text" class="form-control" name="' + fieldCode + '" value="' + (rowData[fieldCode] || "") + '">';
+                        break;
+                    case "textarea":
+                        fieldHtml += '<textarea class="form-control" name="' + fieldCode + '" rows="3">' + (rowData[fieldCode] || "") + '</textarea>';
+                        break;
+                    case "number":
+                        fieldHtml += '<input type="number" class="form-control" name="' + fieldCode + '" value="' + (rowData[fieldCode] || "") + '">';
+                        break;
+                    case "select":
+                        fieldHtml += '<select class="form-control" name="' + fieldCode + '">';
+                        fieldHtml += '<option value="">请选择</option>';
+
+                        // TODO: 这里应该添加选项,但示例中暂不实现
+
+                        fieldHtml += '</select>';
+                        break;
+                    case "date":
+                        fieldHtml += '<div class="input-group date">' +
+                            '<input type="text" class="form-control date-picker" name="' + fieldCode + '" value="' + (rowData[fieldCode] || "") + '">' +
+                            '<span class="input-group-addon"><i class="fa fa-calendar"></i></span>' +
+                            '</div>';
+                        break;
+                    default:
+                        fieldHtml += '<input type="text" class="form-control" name="' + fieldCode + '" value="' + (rowData[fieldCode] || "") + '">';
+                }
+
+                fieldHtml += '</div>';
+
+                $fields.append(fieldHtml);
+            }
+        });
+
+        // 初始化表单中的组件
+        $form.find('.date-picker').datetimepicker({
+            format: 'yyyy-mm-dd',
+            minView: 2,
+            autoclose: true
+        });
+
+        // 打开模态框
+        $modal.modal("show");
+    }
+
+    // 完善档案
+    function editArchives() {
+        var patientId = $("#patientId").val();
+        var url = ctx + "dtp/pmService/archivesEdit/" + patientId;
+        $.modal.openTab("完善档案", url);
+    }
+
+    // 修改任务跟进人
+    function editTaskFollower() {
+        var options = {
+            title: '分配跟进人',
+            width: 800,
+            height: 600,
+            url: ctx + "dtp/pmService/followUpAssignAdd",
+            callBack: function(index, layero) {
+                var iframeWin = layero.find("iframe")[0].contentWindow;
+                var rows = iframeWin.selectTableObject();
+
+                if (rows.length === 0) {
+                    $.modal.alertWarning("请至少选择一条记录");
+                    return;
+                }
+
+                var taskId = $("#id").val();
+                var hzparam = {
+                    id: rows[0].id,
+                    pharmacistName: rows[0].pharmacistName,
+                    position: rows[0].position,
+                    storeName: rows[0].storeName,
+                    phone: rows[0].phone,
+                    storeId: rows[0].storeId,
+                    taskId: taskId
+                };
+
+                $.ajax({
+                    cache: true,
+                    type: "POST",
+                    url: ctx + "dtp/pmService/editFollowTaskAssignById",
+                    data: hzparam,
+                    async: false,
+                    success: function(data) {
+                        $.modal.msg("操作成功");
+                        $("#bc_taskFollowerCode").text(hzparam.pharmacistName);
+                        $("#bc_taskFollower").val(hzparam.pharmacistName);
+                    },
+                    error: function(error) {
+                        $.modal.alertError("操作失败");
+                    }
+                });
+
+                $.modal.close(index);
+            }
+        };
+
+        $.modal.openOptions(options);
+    }
+
+    // 提交表单
+    function submitForm() {
+        if (!$("#form-followUp").valid()) {
+            return;
+        }
+
+        // 收集表单数据
+        var formData = new FormData(document.getElementById("form-followUp"));
+
+        // 添加子表数据
+        $(".form-subtable").each(function() {
+            var subtableCode = $(this).data("subtable-code");
+            var subtableData = [];
+
+            $(this).find("tbody tr").not(".no-data").each(function() {
+                var rowData = {};
+
+                $(this).find("td").each(function(i) {
+                    if (i > 0 && i < $(this).parent().find("td").length - 1) {
+                        var fieldCode = $(this).closest("table").find("thead th").eq(i).data("field-code");
+
+                        if (fieldCode) {
+                            rowData[fieldCode] = $(this).text() !== "-" ? $(this).text() : "";
+                        }
+                    }
+                });
+
+                subtableData.push(rowData);
+            });
+
+            // 将子表数据作为JSON字符串添加到表单数据
+            formData.append(subtableCode, JSON.stringify(subtableData));
+        });
+
+        // 添加下次随访设置
+        formData.append("next_follow_time", $("#next_follow_time").val());
+        formData.append("next_taskTheme", $("select[name='next_taskTheme']").val());
+
+        // 检查是否需要关闭计划的敏感值
+        var needClosePlan = false;
+
+        // 处理文件上传
+        var filePromises = [];
+
+        $("input[type='file']").each(function() {
+            if (this.files.length > 0) {
+                var fieldName = $(this).attr("name");
+                var file = this.files[0];
+
+                var promise = new Promise(function(resolve, reject) {
+                    var fileData = new FormData();
+                    fileData.append("file", file);
+
+                    $.ajax({
+                        url: ctx + "dtp/recipe/uploadImg",
+                        data: fileData,
+                        type: "post",
+                        processData: false,
+                        contentType: false,
+                        success: function(result) {
+                            if (result.msg) {
+                                resolve({
+                                    name: fieldName,
+                                    value: result.msg
+                                });
+                            } else {
+                                reject("上传失败");
+                            }
+                        },
+                        error: function() {
+                            reject("上传失败");
+                        }
+                    });
+                });
+
+                filePromises.push(promise);
+            }
+        });
+
+        // 当所有文件上传完成后提交表单
+        Promise.all(filePromises).then(function(results) {
+            // 添加上传的文件URL到表单数据
+            results.forEach(function(result) {
+                formData.append(result.name + "Url", result.value);
+            });
+
+            // 根据需要设置关闭计划标志
+            formData.append("confirmFlag", needClosePlan ? "1" : "888");
+
+            // 提交表单
+            $.ajax({
+                url: prefix + "/followTaskEdit",
+                data: formData,
+                type: "post",
+                processData: false,
+                contentType: false,
+                success: function(result) {
+                    if (result.code == 0) {
+                        $.modal.alertSuccess("保存成功");
+
+                        if (needClosePlan) {
+                            // 如果需要关闭计划,显示关闭计划对话框
+                            // 实际项目中应该在这里打开关闭计划的模态框
+                        } else {
+                            // 否则刷新页面
+                            setTimeout(function() {
+                                window.location.reload();
+                            }, 1500);
+                        }
+                    } else {
+                        $.modal.alertError(result.msg);
+                    }
+                },
+                error: function() {
+                    $.modal.alertError("保存失败");
+                }
+            });
+        }).catch(function(error) {
+            $.modal.alertError("文件上传失败: " + error);
+        });
+    }
+</script>
+</body>
+</html>

File diff suppressed because it is too large
+ 998 - 273
health-admin/src/main/resources/templates/dtp/followUp/followUpEdit.html


File diff suppressed because it is too large
+ 660 - 397
health-admin/src/main/resources/templates/dtp/followUp/followUpEditAll.html


+ 118 - 63
health-admin/src/main/resources/templates/dtp/followUp/followUpList.html

@@ -16,7 +16,7 @@
 							<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
 							<a class="btn btn-warning btn-rounded btn-sm" onclick="resetPre()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
 							<span>&nbsp;&nbsp;&nbsp;</span>
-							<button type="button"  data-toggle="modal" data-target="#myModal" class="btn btn-danger">意见与反馈</button>
+<!--							<button type="button"  data-toggle="modal" data-target="#myModal" class="btn btn-danger">意见与反馈</button>-->
 						</div>
 
 					</div>
@@ -41,45 +41,59 @@
 							</div>
 							<div class="customize-form-group">
 								<label>任务跟进人:</label>
-								<select name="taskFollower" class="styled-input" >
-									<option value="">全部</option>
-<!--									<option th:each="salesclerk : ${clerk}" th:text="${salesclerk.name}" th:value="${salesclerk.id}"></option>-->
+								<select name="taskFollower" id="taskFollowerSelect" class="styled-input" >
+									<option value="">请选择</option>
 								</select>
 							</div>
 
 							<div class="customize-form-group">
 								<label>门店:</label>
-								<select name="storeId" class="styled-input">
+								<select name="storeId" id="storeSelect" class="styled-input">
 									<option value="">全部</option>
-<!--									<option th:each="store : ${stores}" th:text="${store.name}" th:value="${store.id}"></option>-->
 								</select>
 							</div>
 							<div class="customize-form-group">
-								<label>下次外呼时间:</label>
-								<input type="text" class="time-input time-input2" id="beginTime" placeholder="开始时间" name="NextBeginTime">
+								<label>完成日期:</label>
+								<input type="text" class="time-input time-input2" id="completeBeginTime" placeholder="开始时间" name="completeBeginTime">
 								<span>-</span>
-								<input type="text" class="time-input time-input2" id="endTime" placeholder="结束时间" name="NextEndTime">
+								<input type="text" class="time-input time-input2" id="completeEndTime" placeholder="结束时间" name="completeEndTime">
 							</div>
+<!--							<div class="customize-form-group">-->
+<!--								<label>下次外呼时间:</label>-->
+<!--								<input type="text" class="time-input time-input2" id="beginTime" placeholder="开始时间" name="NextBeginTime">-->
+<!--								<span>-</span>-->
+<!--								<input type="text" class="time-input time-input2" id="endTime" placeholder="结束时间" name="NextEndTime">-->
+<!--							</div>-->
 							<div class="customize-form-group">
 								<label>疾病:</label>
 								<input type="text"  class="styled-input" placeholder="请输入疾病" name="disease"/>
 							</div>
-							<div class="customize-form-group">
-								<label>随访小结:</label>
-								<select name="followUpSummary" class="styled-input">
-									<option value="">请选择</option>
-									<option value="待执行" >待执行</option>
-									<option value="已完成" >已完成</option>
-									<option value="已取消" >已取消</option>
-									<option value="已过期" >已过期</option>
-									<option value="" >无</option>
-								</select>
+							<div class="customize-form-group-container">
+								<div class="customize-form-group">
+									<label>业务归属:</label>
+									<select name="businessBelonging" class="styled-input" th:with="type=${@dict.getType('sys_select_dtp_ywgs')}" >
+										<option value="">请选择</option>
+										<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"
+										></option>
+									</select>
+								</div>
 							</div>
+<!--							<div class="customize-form-group">-->
+<!--								<label>随访小结:</label>-->
+<!--								<select name="followUpSummary" class="styled-input">-->
+<!--									<option value="">请选择</option>-->
+<!--									<option value="待执行" >待执行</option>-->
+<!--									<option value="已完成" >已完成</option>-->
+<!--									<option value="已取消" >已取消</option>-->
+<!--									<option value="已过期" >已过期</option>-->
+<!--									<option value="" >无</option>-->
+<!--								</select>-->
+<!--							</div>-->
 							<div class="customize-form-group">
-								<label>完成日期:</label>
-								<input type="text" class="time-input time-input2" id="completeBeginTime" placeholder="开始时间" name="completeBeginTime">
+								<label>剩余用药天数:</label>
+								<input type="number" class="styled-input" id="sum_total_start" placeholder="范围开始" name="sum_total_start">
 								<span>-</span>
-								<input type="text" class="time-input time-input2" id="completeEndTime" placeholder="结束时间" name="completeEndTime">
+								<input type="number" class="styled-input" id="sum_total_end" placeholder="范围结束" name="sum_total_end">
 							</div>
 							<div class="customize-form-group">
 								<label>任务主题:</label>
@@ -100,30 +114,7 @@
 								</select>
 							</div>
 						</div>
-							<div class="customize-form-group-container">
-							<div class="customize-form-group">
-								<label>业务归属:</label>
-								<select name="businessBelonging" class="styled-input" th:with="type=${@dict.getType('sys_select_dtp_ywgs')}" >
-									<option value="">请选择</option>
-									<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"
-									></option>
-								</select>
-							</div>
-							<div class="customize-form-group">
-								<label>最后外呼标记:</label>
-								<select name="lastOutboundFlag" class="styled-input" th:with="type=${@dict.getType('sys_select_dtp_sfrw_zhwhbj')}">
-									<option value="">请选择</option>
-									<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"></option>
-								</select>
-							</div>
-							<div class="customize-form-group">
-								<label>最后外呼状态:</label>
-								<select name="lastOutboundStatus" class="styled-input" th:with="type=${@dict.getType('sys_select_dtp_sfrw_zhwhzt')}">
-									<option value="">请选择</option>
-									<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"></option>
-								</select>
-							</div>
-						</div>
+
 					</form>
 				</div>
 				<div class="col-sm-12 select-table table-striped" style="width: 100%; overflow-x: hidden;">
@@ -170,8 +161,9 @@
 	<th:block th:include="include :: bootstrap-table-fixed-columns-js" />
 	<th:block th:include="include :: ztree-js" />
 	<script th:inline="javascript">
-		var editFlag = [[${@permission.hasPermi('dtp:pmService:edit')}]];
+		var editFlag = [[${@permission.hasPermi('gxhpz:task:edit')}]];
 		var removeFlag = [[${@permission.hasPermi('dtp:pmService:remove')}]];
+		var detailFlag = [[${@permission.hasPermi('task:follow:query')}]];
 		var prefix = ctx + "task/followTask";
 		var prefix_archives = ctx + "dtp/pmService";
 		$(function() {
@@ -190,7 +182,11 @@
 	    	}
 			queryArchivesList();
 		});
-
+		//初始化加载
+		$(document).ready(function() {
+			findTaskFollowerList()//初始化任务跟进人
+			findTaskStoreList();
+		});
 		function queryArchivesList() {
 		    var options = {
 		        url: prefix + "/followTaskList",
@@ -203,7 +199,7 @@
 		        importUrl: prefix + "/importData",
 		        importTemplateUrl: prefix + "/importTemplate",*/
 		        sortName: "id",
-		        sortOrder: "asc",
+		        sortOrder: "desc",
 		        modalName: "随访表单",
 				fitColumns: true,
 				striped: true,
@@ -222,10 +218,14 @@
 				{ field: 'templeName', title: '模版名称', align: 'center'},
 				{ field: 'taskName', title: '任务名称',width: 170, align: 'center' ,
 					formatter: function(value, row, index) {
-						if (row.id) {
-							return '<a href="javascript:void(0)" onclick="$.operate.editTab(\'' + row.id + '\')">' + value + '</a>';
-						} else {
-							return "";
+						if (row.id && !['已取消', '已完成','未下发'].includes(row.taskStatus)) {
+							if (row.id) {
+								return '<a href="javascript:void(0)" onclick="$.operate.editTab(\'' + row.id + '\')">' + value + '</a>';
+							} else {
+								return "";
+							}
+						}else{
+							return value;
 						}
 					}},
 				{ field: 'patientName', title: '患者姓名',width: 200, align: 'center' ,
@@ -243,13 +243,14 @@
 							default: return "未知";
 						}
 					} },
-				{ field: 'age', title: '年龄', align: 'center' },
+
+				{ field: 'adjusted_sum_total', title: '剩余用药天数',width: 120, align: 'center' },
 
 					{ field: 'taskStatus', title: '任务状态', align: 'center' ,
 						formatter: function(value, row, index) {
 							switch (value) {
 								case '待执行':
-									return '<span class=\"btn-warning">待执行</span>';
+									return '<span class=\"btn-info">待执行</span>';
 									break;
 								case '已完成':
 									return '<span class=\"btn-success">已完成</span>';
@@ -266,6 +267,9 @@
 								case '已取消':
 									return '<span class=\"btn-danger">已取消</span>';
 									break;
+								case '已过期':
+									return '<span class=\"btn-danger">已过期</span>';
+									break;
 								default:
 									return '<span class=\"btn-warning">'-'</span>';
 							}
@@ -273,11 +277,12 @@
 					{ field: 'taskTheme', title: '任务主题', align: 'center' },
 					{ field: 'genericName', title: '药品通用名', align: 'center' },
 					{ field: 'productName', title: '商品名', align: 'center' },
-					{ field: 'followUpSummary', title: '随访小结',width: 500, align: 'center' },
+					{ field: 'age', title: '年龄', align: 'center' },
+					{ field: 'followUpSummary', title: '随访小结',width: 200, visible: false,align: 'center' },
 					{ field: 'taskFollower', title: '任务跟进人', align: 'center' },
-					{ field: 'disease', title: '疾病', align: 'center' },
+					{ field: 'dl', title: '疾病', align: 'center' },
 					{ field: 'storeName', title: '门店', align: 'center' },
-					{ field: 'lastOutboundStatus', title: '最后外呼状态', align: 'center',
+					{ field: 'lastOutboundStatus', title: '最后外呼状态', visible: false,align: 'center',
 						formatter: function(value, row, index) {
 							switch (value) {
 								case '未外呼':
@@ -303,20 +308,21 @@
 							}
 						}},
 				{ field: 'actualFollowUpTime', title: '实际随访时间', align: 'center' },
-				{ field: 'callConnectedCount', title: '接通次数', align: 'center' },
-				{ field: 'outboundCallCount', title: '外呼次数', align: 'center' },
-				{ field: 'nextOutboundCallCount', title: '下次外呼次数', align: 'center' },
+				{ field: 'callConnectedCount', title: '接通次数', align: 'center',visible: false },
+				{ field: 'outboundCallCount', title: '外呼次数', align: 'center' ,visible: false},
+				{ field: 'nextOutboundCallCount', title: '下次外呼次数', align: 'center',visible: false },
 				{ field: 'updateTime2', title: '更新时间', align: 'center' },
 		        {
 		            title: '操作',
 		            align: 'center',
-					width: '180px',
+					width: '80px',
 		            formatter: function(value, row, index) {
 						// 根据任务状态决定是否显示操作按钮
 						if (row.id && !['已取消', '已完成','未下发'].includes(row.taskStatus)) {
 							var actions = [];
 							actions.push('<a class="btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.editTab(\'' + row.id + '\')">去随访</a> ');
-							actions.push('<a class="btn-info btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.editTab(\'' + row.id + '\')">查看全部</a> ');
+							// actions.push('<a class="btn btn-info btn-xs ' + detailFlag + '" href="javascript:void(0)" onclick="$.operate.view(\'' + row.id + '\')"><i class="fa fa-eye"></i>详情</a> ');
+							//actions.push('<a class="btn-info btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.editTab(\'' + row.id + '\')">查看全部</a> ');
 							return actions.join('');
 						} else {
 							return "";
@@ -361,6 +367,55 @@
 			// 关闭模态框
 			$('#myModal').modal('hide');
 		}
+
+		// 异步加载所有任务跟进人信息并填充下拉框
+		function findTaskFollowerList() {
+			$.ajax({
+				url: prefix+ "/findTaskFollowerList", // 这是获取所有任务跟进人的API端点
+				type: 'POST',
+				cache: false, // 设置为 false 防止缓存
+				processData: false, // 告诉 jQuery 不要处理数据(非常重要)
+				contentType: false, // 告诉 jQuery 不要设置 contentType(非常重要)
+				async: true, // 异步请求更为推荐,除非有特殊原因需要同步
+				success: function (data) {
+					var select = $('#taskFollowerSelect');
+					// 清空除了默认选项外的所有选项
+					select.find('option:not(:first)').remove();
+					data.data.forEach(function (pharmacist) {
+						select.append(new Option(pharmacist.pharmacistName, pharmacist.pharmacistName));
+					});
+				},
+				error: function () {
+					$.modal.alertError('加载任务跟进人信息失败');
+				}
+			});
+		}
+		// 异步加载所有门店信息并填充下拉框
+		function findTaskStoreList() {
+			$.ajax({
+				url: prefix+ "/findTaskStoreList", // 这是获取所有门店的API端点
+				type: 'POST',
+				cache: false, // 设置为 false 防止缓存
+				processData: false, // 告诉 jQuery 不要处理数据(非常重要)
+				contentType: false, // 告诉 jQuery 不要设置 contentType(非常重要)
+				async: true, // 异步请求更为推荐,除非有特殊原因需要同步
+				success: function (data) {
+					var select = $('#storeSelect');
+					// 清空除了默认选项外的所有选项
+					select.find('option:not(:first)').remove();
+					if (data && data.data) {
+						data.data.forEach(function (store) {
+							select.append(new Option(store.dept_name, store.id));
+						});
+					} else {
+						console.error('Unexpected response format:', data);
+					}
+				},
+				error: function () {
+					$.modal.alertError('加载门店信息失败');
+				}
+			});
+		}
 	</script>
 </body>
 <style>

+ 4951 - 0
health-admin/src/main/resources/templates/dtp/followUp/follwUpEditAllDetail.html

@@ -0,0 +1,4951 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
+<head>
+    <th:block th:include="include :: footer" />
+</head>
+<style type="text/css">
+    #myTabs2 {
+        z-index: 0;
+    }
+    .fileinput-preview.thumbnail {
+        display: flex;
+        justify-content: center; /* 水平居中 */
+        align-items: center; /* 垂直居中 */
+        text-align: center; /* 确保文本内部也居中 */
+        position: relative;
+        background-color: #f5f5f5; /* 设置背景颜色以更好地区分 */
+        border: 2px dashed #ccc; /* 添加虚线边框 */
+        color: #999; /* 文字颜色 */
+        font-size: 14px; /* 文字大小 */
+    }
+
+    .centered-content {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+    }
+
+    .plus-sign {
+        margin-top: 5px;
+        font-size: 24px;
+        font-weight: bold;
+    }
+    /* 隐藏初始的大图 */
+    .preview-large {
+        display: none;
+        position: absolute;
+        border: 1px solid #ccc;
+        background-color: #fff;
+        z-index: 1000;
+    }
+
+    /* 确保预览框在特定容器内 */
+    .customize-form-group-container {
+        position: relative;
+    }
+    .fileinput .fileinput-preview {
+        background-image: none !important;
+        background-color: transparent !important;
+    }
+
+    .expired-label {
+        color: red;
+        font-weight: bold;
+    }
+    /* 统一所有表单元素的基线对齐 */
+    .input-groups * {
+        vertical-align: baseline;  /* 或 middle 根据情况选择 */
+    }
+    .form-group {
+        margin-bottom: -1px;
+    }
+    /* 精确控制自定义单选按钮 */
+    .form-check-radio {
+        width: 18px;
+        height: 18px;
+        position: relative;
+        top: 2px;  /* 根据实际字体大小调整 */
+        margin: 0 6px 0 0;
+        cursor: pointer;
+    }
+
+    /* 标签文字精确调整 */
+    .input-groups label {
+        font-size: 16px;
+        position: relative;
+        top: 1px;  /* 补偿字体渲染的视觉偏差 */
+    }
+
+
+    .form-check-radio:checked::before {
+        background: #1890ff;
+    }
+
+    .input-groups div {
+        white-space: normal;
+        min-width: 150px; /* 设置最小宽度控制换行节奏 */
+    }
+    /* 问号图标的样式 */
+    .question-mark {
+        width: 20px; /* 根据需要调整大小 */
+        height: 20px;
+        cursor: pointer;
+    }
+
+    /* 工具提示内容的默认状态:隐藏 */
+    .tooltip-content {
+        visibility: hidden;
+        opacity: 0;
+        transition: visibility 0s, opacity 0.5s;
+        position: absolute;
+        top: -30px; /* 根据实际情况调整 */
+        left: 300px; /* 根据实际情况调整 */
+        z-index: 1000;
+    }
+
+    /* 当悬停在问号图标上时,显示工具提示内容 */
+    .question-mark:hover + .tooltip-content,
+    .tooltip-content:hover {
+        visibility: visible;
+        opacity: 1;
+    }
+
+    /* 图片缩放效果 */
+    .tooltip-image {
+        width: 400px; /* 初始大小 */
+        transition: transform 0.3s ease;
+    }
+
+    /* 当悬停在图片上时放大图片 */
+    .tooltip-image:hover {
+        transform: scale(1.5); /* 放大倍数 */
+    }
+
+
+    .select2-dropdown {
+        z-index: 9999 !important;
+    }
+    .tabs-container {
+        position: relative;
+    }
+    label {
+        display: inline-block;
+        max-width: 100%;
+        margin-bottom: 0px;
+        font-weight: 700;
+    }
+    input {
+        width: 40px;
+    }
+    .customize-form-group label {
+        width: 250px;
+        text-align: right;
+        margin-right: 0.5em;
+    }
+    .font {
+        font-size: 16px; /* 字体大小 */
+    }
+    .form-groupBottom {
+        margin-bottom: 30px; /* 调整这个值以改变间距 */
+    }
+
+
+    .content-area {
+        margin-top: 50px; /* 调整这个值以确保内容不会被导航栏遮挡 */
+    }
+    .nav-tabs {
+        position: sticky;
+        top: 0; /* 距离顶部的距离 */
+        background-color: #fff; /* 设置背景颜色,以便覆盖下方的内容 */
+        z-index: 1020; /* 确保它位于其他内容之上 */
+    }
+
+    .tabs-container {
+        position: relative;
+    }
+
+    .btn {
+        background-color: #f2f2f2;
+        border: none;
+        color: black;
+        padding: 5px 10px;
+        text-align: center;
+        text-decoration: none;
+        display: inline-block;
+        font-size: 16px;
+        margin: 4px 2px;
+        cursor: pointer;
+    }
+
+    .btn-primary {
+        background-color: #1a7bb9;
+        color: white;
+    }
+
+    .btn-w-m {
+        width: max-content;
+    }
+
+    .btn-default {
+        background-color: #f2f2f2;
+        color: black;
+    }
+    .btn:hover {
+        opacity: 0.8;
+    }
+    .tab-1a {
+        padding-left: 50px;
+        width: 100px;
+        font-size: 15px
+    }
+    .tab-1b {
+        width: 100px;
+        color: blue;
+        font-size:15px;
+    }
+    .task-follow-up {
+        display: flex;
+        align-items: center;
+    }
+
+    .follow-up-person, .plan-buttons {
+        flex: 1;
+    }
+
+    .plan-buttons {
+        text-align: right;
+    }
+    .ibox-title2 {
+        display: flex;
+        align-items: center;
+        justify-content: start;
+        padding: 6px;
+        border-bottom: 1px solid #ddd;
+    }
+    .right-aligned-link {
+        float: right;
+        margin-left: 20px; /* 增加左侧边距 */
+    }
+
+    .right-aligned-link::after {
+        content: ">";
+        color: blue;
+    }
+    .task-info {
+        flex: 1;
+    }
+
+    .task-details {
+        flex: 2;
+        display: flex;
+        align-items: center;
+        gap: 0px;
+    }
+
+    .task-buttons {
+        flex: 1;
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        gap: 10px;
+    }
+
+    .task-buttons span {
+        margin-left: 10px;
+    }
+
+    .task-details span {
+        display: flex;
+        align-items: center;
+        gap: 1px;
+    }
+    .custom-file-upload {
+        position: relative;
+        overflow: hidden;
+        cursor: pointer;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+    }
+
+    .custom-file-upload input[type=file] {
+        opacity: 0;
+        width: 100%;
+        height: 100%;
+        top: 0;
+        left: 0;
+        position: absolute;
+    }
+
+    .custom-file-upload label {
+        background-color: white;
+        border: 2px dashed #007bff;
+        color: #007bff;
+        padding: 20px;
+        text-align: center;
+        display: block;
+        min-height: 150px;
+    }
+    #hbyyjlTable {
+        width: 100%;
+        border-collapse: collapse;
+    }
+
+    th,
+    td {
+        padding: 10px;
+        text-align: center;
+        border-bottom: 1px solid #ddd;
+    }
+
+    thead th {
+        background-color: #f2f2f2;
+        font-weight: bold;
+    }
+
+    tbody tr:hover {
+        background-color: #f5f5f5;
+    }
+
+    .no-data {
+        text-align: center;
+        padding: 0px;
+    }
+
+    .btn-primarys {
+        margin-left: 10px;
+        color: green;
+    }
+
+
+    .radio-item label {
+        /*margin-left: 5px;*/
+    }
+    .hidden {
+        display: none;
+    }
+    .shown {
+        display: block;
+    }
+</style>
+<script>
+
+</script>
+<body>
+<div class="font">
+    <div class="ibox">
+        <form id="form-followUp-edit1">
+            <div class="ibox-title">
+                <button class="btn btn-w-m btn-primary"  onclick="editArchives()">完善档案</button>
+                <button type="button" class="btn btn-sm btn-danger pull-right" onclick="closeItem()">返回上一级</button>
+            </div>
+            <div class="customize-form-group">
+                <label>姓名:</label>
+                <input name="name" id="name"   class="select-input" type="text"  th:value="${name}" disabled="true"/>
+                &nbsp;&nbsp; <i class="fa" th:class="${realNameStatus == 1 ? 'fa fa-check' : 'fa fa-close'}" id="checkName"  ></i>
+                &nbsp;
+                <input name="realNameStatus" id="realNameStatus" class="status" type="text"   th:value="${realNameStatus == 1 ? '已实名' : (realNameStatus == 0 ? '未实名' : '')}" readonly>
+                <label>性别:</label>
+                <input name="gender" id="gender"  type="text" class="select-input"  th:value="${gender == 1 ? '女' : (gender == 0 ? '男' : '')}" readonly>
+                <span class="span_line" readonly></span>
+                <label>出生年月:</label>
+                <input name="dateBirth" id="dateBirth" placeholder="出生年月" class="select-input"  type="text"    th:value="${dateBirth}" disabled="true" />
+                <span class="status" style="width: 66px">
+            <input  name="age" type="text" id="age" th:value="${age}" style="text-align: center;width: 30px;border: none;" readonly />岁</span>
+            </div>
+            <div class="customize-form-group" id="drug-info-container">
+                <div th:each="item : ${dValueList}" style="margin-bottom: 10px; border: 1px solid #ccc; padding: 10px;">
+            <span>
+                <strong>药品: <code th:text="${item.genericName}"></code></strong><br>
+                <strong>用药状态: <code th:text="${item.medication_status}"></code></strong><br>
+                <!--                <strong>剩余用药天数: <code th:text="${item.sum_total}"></code>天</strong><br>-->
+                <strong>剩余用药天数: <code th:text="${item.adjusted_sum_total}"></code>天</strong><br>
+            </span>
+                </div>
+            </div>
+
+            <div class="ibox-content">
+                <div>
+                    <span>档案完善度:已完善---><div></div></span>
+                    <div class="stat-percent" th:text="${completenessPercentage} + ' %'"></div>
+                    <div class="progress progress-mini">
+                        <!-- 使用 Thymeleaf 的 th:style 属性动态设置样式 -->
+                        <div class="progress-bar" th:style="'width: ' + ${completenessPercentage} + '%;'"></div>
+                    </div>
+                </div>
+            </div>
+        </form>
+        <div class="modal inmodal fade" id="myModalClosePlan" tabindex="-1" role="dialog" aria-hidden="true">
+            <div class="modal-dialog modal-lg" style="width: 1200px">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span>
+                        </button>
+                        <h4 class="modal-title">关闭随访计划</h4>
+                        <!--<small class="font-bold">这里可以显示副标题。</small>-->
+                    </div>
+                    <div class="modal-body">
+                        <div id="ClosePlan" class="row">
+                            <form id="ClosePlanForm" class="form-horizontal" style="font-size: 15px;">
+                                <div class="col-sm-12">
+                                    <div >
+                                        <h2><code>关闭原因</code></h2>
+                                    </div>
+                                    <label class="is-required">原因</label>
+                                    <div class="form-groupBottom"
+                                         th:with="type=${@dict.getType('sys_hzgl_sfrw_closeplan_reason')}"
+                                         style="width: 900px; display: flex; flex-wrap: wrap; gap: 10px; align-items: center;">
+                                        <div  th:each="dict : ${type}" style="white-space: nowrap;">
+                                            <input  type="radio"
+                                                    name="reason"
+                                                    class="form-check-radio"
+                                                    th:id="'reason_' + ${dict.dictCode}"
+                                                    th:text="${dict.dictLabel}"
+                                                    th:value="${dict.dictValue}">
+                                        </div>
+                                    </div>
+
+                                    <div class="form-group hidden" id="drugsStatusDiv" >
+                                        <label class="is-required">用药状态调整为</label>
+                                        <div class="input-groups"  th:with="type=${@dict.getType('sys_gxhpz_yyycx')}" style="width: 900px;">
+                                            <input type="radio" name="drugsStatus" class="form-check-radio"  th:each="dict : ${type}"  id="drugsStatus" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}">
+                                        </div>
+                                    </div>
+                                    <div class="form-group hidden form-groupBottom" id="drugsStopDiv ">
+                                        <label class="is-required">永久停药类型</label>
+                                        <div class="input-groups hidden"  th:with="type=${@dict.getType('sys_hzgl_sfrw_closeplan_yjstop')}" style="width: 1200px;">
+                                            <input type="radio" name="drugsStop" class="form-check-radio"  th:each="dict : ${type}"  id="drugsStop" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}">
+                                        </div>
+                                    </div>
+
+                                    <div class="form-group hidden" id="flowNoDiv">
+                                        <label class="is-required">随访不配合原因:</label>
+                                        <div class=""
+                                             th:with="type=${@dict.getType('sys_gxhpz_ysfw_sfbphyy')}"
+                                             style="width: 900px; display: flex; flex-wrap: wrap; gap: 10px; align-items: center;">
+                                            <div  th:each="dict : ${type}" style="white-space: nowrap;">
+                                                <input  type="radio"
+                                                        name="flowNo"
+                                                        class="form-check-radio"
+                                                        th:id="'flowNo_' + ${dict.dictLabel}"
+                                                        th:text="${dict.dictLabel}"
+                                                        th:value="${dict.dictLabel}">
+                                            </div>
+                                        </div>
+                                        <span class="status"></span>
+                                    </div>
+                                    <div class="form-group hidden form-groupBottom" id="otherReasonDiv">
+                                        <label class="is-required">其他原因请补充</label>
+                                        <div>
+                                            <input id="otherReason" name="otherReason" placeholder="这里可以输入...其他原因请补充到这里"
+                                                   class="styled-input"
+                                                   style="width: 800px;height: 40px;">
+                                        </div>
+                                    </div>
+
+                                </div>
+                                <div class="col-sm-12">
+                                    <div class="form-group">
+                                        <div class="form-group">
+                                            <h2><code>关闭范围</code></h2>
+                                        </div>
+                                        <div class="input-groups hidden" th:with="type=${@dict.getType('sys_hzgl_sfrw_follow_up_planning_strategy')}" style="width: 900px;" id="follow_up_planning_strategy_Div">
+                                            <label class="is-required">随访计划策略</label>
+                                            <input type="radio" name="planningStrategy" class="form-check-radio"  th:each="dict : ${type}"  id="planningStrategy" th:text="${dict.dictLabel}" th:value="${dict.dictValue}">
+                                        </div>
+                                        <div class="input-groups" th:with="type=${@dict.getType('sys_hzgl_sfrw_closeplan_scope')}" style="width: 900px;">
+                                            <label class="is-required">关闭计划</label>
+                                            <input type="radio" name="closePlanScope" class="form-check-radio"  th:each="dict : ${type}"  id="closePlanScope" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}">
+                                        </div>
+
+                                    </div>
+                                </div>
+
+                                <div class="col-sm-12 hidden" id="closeplan_isnoDiv">
+                                    <div class="form-group" >
+                                        <div class="input-groups" th:with="type=${@dict.getType('sys_hzgl_sfrw_closeplan_isno')}" style="width: 900px;">
+                                            <label class="is-required">是否禁用此品的计划</label>
+                                            <input type="radio" name="forbidden" class="form-check-radio"  th:each="dict : ${type}"  id="forbidden" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}">
+                                            <label style="color: grey;font-size: 11px;">&nbsp;&nbsp;&nbsp;选择是,购买相同D值品不会自动触发新计划</label>
+                                        </div>
+
+                                    </div>
+
+                                </div>
+
+                                <div class="col-sm-12" id="closeTypeDiv">
+                                    <div class="form-group">
+                                        <div class="input-groups" th:with="type=${@dict.getType('sys_hzgl_sfrw_closepan_type')}" style="width: 900px;">
+                                            <label class="is-required">关闭类型</label>
+                                            <div th:each="dict : ${type}">
+                                                <input type="radio" name="closeType" class="form-check-radio"
+                                                       th:id="'closeType_' + ${dict.dictLabel}"
+                                                       th:value="${dict.dictLabel}"
+                                                       th:text="${dict.dictLabel}"
+                                                       onclick="handleCloseTypeChange(this)">
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <div class="col-sm-12" id="specifiedTypeDiv" style="display: none;">
+                                    <div class="form-group">
+                                        <label class="is-required">指定类型</label>
+                                        <div class="input-groups" th:with="type=${@dict.getType('sys_hzgl_sfrw_closepan_istype')}" style="width: 900px;">
+                                            <div th:each="dict, iterStat : ${type}">
+                                                <input type="checkbox" name="specifiedType"
+                                                       class="form-check-radio"
+                                                       th:id="'specifiedType_' + ${iterStat.index}"
+                                                       th:value="${dict.dictLabel}"
+                                                       th:text="${dict.dictLabel}"
+                                                       onclick="handleSpecifiedTypeChange()">
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </form>
+                        </div>
+                    </div>
+
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-white" data-dismiss="modal">无需关闭 退出操作</button>
+                        <button type="button" class="btn btn-primary"  onclick="submitClosePlan(flagStatus)">提交</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="ui-layout-center">
+        <div class="row">
+            <div class="col-sm-12">
+                <div class="tabs-container">
+                    <ul class="nav nav-tabs" id="myUlTabs">
+                        <li class="active"><a data-toggle="tab" href="#tab-1" aria-expanded="true"> 基本信息</a>
+                        </li>
+                        <li class=""><a data-toggle="tab" href="#tab-2" aria-expanded="false">用药购药</a>
+                        </li>
+                        <li class=""><a data-toggle="tab" href="#tab-3" aria-expanded="false">随访计划</a>
+                        </li>
+                    </ul>
+                    <div class="tab-content">
+                        <div id="tab-1" class="tab-pane active">
+                            <form class="customize-search-form" id="form-followUp-edit2" >
+
+                                <div class="customize-form-group edit">
+                                    <label>手机号码:</label>
+                                    <input name="phoneNumber" id="phoneNumber" class="styled-input" type="text" maxlength="30" th:value="${phoneNumber}" disabled="true">
+                                    <span class="span_line" readonly></span>
+                                </div>
+
+                                <div class="customize-form-group">
+                                    <label>配送地址:</label>
+                                    <input name="addr" disabled="true"  id="addr" class="styled-input " type="text" th:value="${addr}">
+                                    <span class="span_line" readonly></span>
+                                </div>
+                                <div class="customize-form-group">
+                                    <label>联系人手机号:</label>
+                                    <input name="contactPhone" disabled="true"  id="contactPhone" class="styled-input" type="text" th:value="${contactPhone}">
+                                    <span class="span_line" readonly></span>
+                                </div>
+                                <div class="customize-form-group">
+                                    <label>联系人姓名:</label>
+                                    <input name="contactName"disabled="true"  id="contactName" class="styled-input" type="text" th:value="${contactName}">
+                                    <span class="span_line" readonly></span>
+                                </div>
+                                <!--                                <div class="customize-form-group edit">-->
+                                <!--                                    <label>D值品名称:</label>-->
+                                <!--                                    <input name="dvalueName" id="dvalueName" class="styled-input" type="text"  th:value="${dvalueName}" disabled="true">-->
+                                <!--                                    <span class="span_line" readonly></span>-->
+                                <!--                                </div>-->
+                                <!--                                <div class="customize-form-group edit">-->
+                                <!--                                    <label>D值品天数:</label>-->
+                                <!--                                    <input name="dvalueDay" id="dvalueDay" class="styled-input" type="text" th:value="${dvalueDay}" disabled="true">-->
+                                <!--                                    <span class="span_line" readonly></span>-->
+                                <!--                                </div>-->
+                                <div class="customize-form-group edit select-time">
+                                    <label>首次确诊时间:</label>
+                                    <input name="timeFirstDiagnosis" disabled="true" class="styled-input"  type="text"  th:value="${timeFirstDiagnosis}">
+                                    <span class="span_line" readonly></span>
+                                </div>
+                                <div class="customize-form-group edit">
+                                    <label class="is-required">证件号码:</label>
+                                    <input name="documentType" placeholder="证件类型" class="styled-input short" style="font-size: 13px" type="text"  th:value="${documentType}" disabled="true">
+                                    <input name="documentNumber" placeholder="请输入证件号码" class="styled-input" type="text" maxlength="30" th:value="${documentNumber}" disabled="true">
+                                    <span class="span_line" readonly></span>
+                                </div>
+                                <div class="customize-search-form">
+                                    <div class="customize-form-group">
+                                        <label class="col-sm-1 control-label">肿瘤发病部位疾病:</label>
+                                        <select id="category-select1" class="styled-input edit_inputs select2-multiple" multiple onmousedown="return false;" >
+                                        </select>
+                                    </div>
+                                    <div class="customize-form-group">
+                                        <label class="col-sm-1 control-label">肿瘤治疗并发症与合并症:</label>
+                                        <select id="category-select2" class="styled-input edit_inputs select2-multiple" multiple onmousedown="return false;" >
+                                        </select>
+                                    </div>
+                                    <div class="customize-form-group">
+                                        <label class="col-sm-1 control-label">风湿免疫疾病名称:</label>
+                                        <select id="category-select3"   class="styled-input edit_inputs select2-multiple" multiple  onmousedown="return false;" >
+                                        </select>
+                                    </div>
+                                    <div class="customize-form-group">
+                                        <label class="col-sm-1 control-label">罕见病疾病:</label>
+                                        <select id="category-select4" class="styled-input edit_inputs select2-multiple" multiple  onmousedown="return false;">
+                                        </select>
+                                    </div>
+                                    <div class="customize-form-group">
+                                        <label class="col-sm-1 control-label">感染类疾病:</label>
+                                        <select id="category-select5"   class="styled-input edit_inputs select2-multiple" multiple  onmousedown="return false;" >
+                                        </select>
+                                    </div>
+                                    <div class="customize-form-group">
+                                        <label class="col-sm-1 control-label">临时慢病:</label>
+                                        <select id="category-select6"   class="styled-input edit_inputs select2-multiple" multiple onmousedown="return false;">
+                                        </select>
+                                    </div>
+                                </div>
+                            </form>
+                        </div>
+                        <div id="tab-2" class="tab-pane">
+                            <div class="panel-body">
+                                <div class="row">
+                                    <div class="col-sm-12 select-table table-striped">
+                                        <table id="bootstrap-table-2"></table>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                        <div id="tab-3" class="tab-pane">
+                            <div class="task-follow-up">
+                                <div class="follow-up-person">
+                                    <label  style="padding-left: 10px;">随访跟进人:</label>
+                                    <!--                                    <code style="font-size: 1.5rem;width: 200px;padding-left: 15px;">-->
+                                    <!--                                        随访跟进人:-->
+                                    <!--                                    </code>-->
+                                    <code id="followUpPersonNameCode" style="font-size: 1.5rem;width: 200px;padding-left: 15px;" th:text="${followUpPersonName}">
+                                    </code>
+                                    <!--                                    <code type="text" id="follow_up_person"  style="border: none; width: 55px; color: red;"  th:value="${follow_up_person}">-->
+                                    <!--                                    </code>-->
+                                    <input type="hidden" id="followUpPersonId" name="followUpPersonId" th:value="${followUpPersonId}"/>
+                                    <input type="hidden" id="followUpPersonName" name="followUpPersonName" th:value="${followUpPersonName}"/>
+                                    <a href="#" onclick="editSFGenJinRen()">修改<i class="glyphicon glyphicon-pencil"></i></a>
+                                </div>
+                                <div class="plan-buttons">
+                                    <!--                                    <button class="btn btn-w-m btn-primary" onclick="showAllPlan()">查看全部计划</button>-->
+                                    <button class="btn btn-w-m btn-info" onclick="createPlan()">创建计划</button>
+                                </div>
+                            </div>
+                            <div id="cgsfnormal">
+
+                                <!--                                <div class="row">-->
+                                <!--                                    <div class="ibox-content">-->
+                                <!--                                        <div class="ibox-title2">-->
+                                <!--                                            <code style="font-size: 1.5rem;width: 200px;" th:text="${productName}">-->
+                                <!--                                            </code>-->
+                                <!--                                            <code style="font-size: 1.5rem; width: 200px;" th:text="${specification}">-->
+                                <!--                                            </code>-->
+                                <!--                                            <code style="font-size: 1.5rem;color: #00B83F;padding-right: 30px;"><i class="glyphicon glyphicon-ice-lolly" th:text="${businessBelonging}"></i></code>-->
+                                <!--                                            <code style="font-size: 1.5rem;color: #00B83F;padding-right: 30px;"-->
+                                <!--                                                  th:text="${status == '关闭'? '已关闭' : status}">-->
+                                <!--                                                &lt;!&ndash; 默认文本将被 Thymeleaf 动态替换 &ndash;&gt;-->
+                                <!--                                            </code>-->
+                                <!--                                            &lt;!&ndash; 根据 status 的值决定是否显示关闭计划按钮 &ndash;&gt;-->
+                                <!--                                            <a th:if="${status != '关闭'}" onclick="closePlan(1)" href="#" class="right-aligned-link" data-toggle="modal"   data-target="#myModalClosePlan">关闭计划</a>-->
+                                <!--                                            <a th:if="${status == '关闭'}" onclick="RestartPlan(1)">重启计划</a>-->
+                                <!--                                        </div>-->
+                                <!--                                    </div>-->
+                                <!--                                </div>-->
+                                <!--                                <div class="row">-->
+                                <!--                                    <div class="ibox-content">-->
+                                <!--                                        <div class="ibox-title2">-->
+                                <!--                                            <span style="padding-left: 10px;font-size:15px;">创建人:</span>-->
+                                <!--                                            <span style="width: 120px;color: #2E2D3C;font-size:15px;padding-right: 40px;" th:text="${createdBy}"></span>-->
+                                <!--                                            <span style="font-size:15px;">开始时间:</span> <span style="width: 220px;color: #2E2D3C;font-size:15px;padding-right: 70px;" th:text="${createdTime}"></span>-->
+                                <!--                                            <span style="font-size:15px;">更新人:</span> <span style="width: 100px;color: #2E2D3C;font-size:15px;" th:text="${updatedBy}"></span>-->
+                                <!--                                            <span style="font-size:15px;">更新时间:</span> <span style="width: 220px;color: #2E2D3C;font-size:15px;padding-right: 70px;" th:text="${updatedTime}"></span>-->
+                                <!--                                            &lt;!&ndash;                                    <a href="javascript:void(0)" onclick="ViewOperationLog()" class="right-aligned-link">查看操作记录</a>&ndash;&gt;-->
+                                <!--                                        </div>-->
+
+                                <!--                                    </div>-->
+                                <!--                                </div>-->
+                                <!--                                <div class="row">-->
+                                <!--                                    <div class="col-sm-12 select-table table-striped">-->
+                                <!--                                        <table id="bootstrap-table-4"></table>-->
+                                <!--                                    </div>-->
+                                <!--                                </div>-->
+                            </div>
+                            <div id="tlzhback">
+                                <!--                                <div class="row">-->
+                                <!--                                    <div class="ibox-content">-->
+                                <!--                                        <div class="ibox-title2">-->
+                                <!--                                            <code style="font-size: 1.5rem;width: 200px;" th:text="${productName2}">-->
+                                <!--                                            </code>-->
+                                <!--                                            <code style="font-size: 1.5rem; width: 200px;" th:text="${specification2}">-->
+                                <!--                                            </code>-->
+                                <!--                                            <code style="font-size: 1.5rem;color:#ec971f;padding-right: 30px;"><i class="glyphicon glyphicon-ice-lolly-tasted" th:text="${businessBelonging2}"></i></code>-->
+                                <!--                                            <code style="font-size: 1.5rem;color: #00B83F;padding-right: 30px;"-->
+                                <!--                                                  th:text="${status2 == '关闭'? '已关闭' : status2}">-->
+                                <!--                                                &lt;!&ndash; 默认文本将被 Thymeleaf 动态替换 &ndash;&gt;-->
+                                <!--                                            </code>-->
+                                <!--                                            <a th:if="${status2 != '关闭'}" onclick="closePlan(2)" href="#" class="right-aligned-link" data-toggle="modal"   data-target="#myModalClosePlan">关闭计划</a>-->
+                                <!--                                            &lt;!&ndash; 重启计划 &ndash;&gt;-->
+                                <!--                                            <a th:if="${status2 == '关闭'}" onclick="RestartPlan(2)">重启计划</a>-->
+                                <!--                                        </div>-->
+                                <!--                                    </div>-->
+                                <!--                                </div>-->
+                                <!--                                <div class="row">-->
+                                <!--                                    <div class="ibox-content">-->
+                                <!--                                        <div class="ibox-title2">-->
+                                <!--                                            <span style="padding-left: 10px;font-size:15px;">创建人:</span>-->
+                                <!--                                            <span style="width: 120px;color: #2E2D3C;font-size:15px;padding-right: 40px;" th:text="${createdBy2}"></span>-->
+                                <!--                                            <span style="font-size:15px;">开始时间:</span> <span style="width: 220px;color: #2E2D3C;font-size:15px;padding-right: 70px;" th:text="${createdTime2}"></span>-->
+                                <!--                                            <span style="font-size:15px;">更新人:</span> <span style="width: 100px;color: #2E2D3C;font-size:15px;" th:text="${updatedBy2}"></span>-->
+                                <!--                                            <span style="font-size:15px;">更新时间:</span> <span style="width: 220px;color: #2E2D3C;font-size:15px;padding-right: 70px;" th:text="${updatedTime2}"></span>-->
+                                <!--                                            &lt;!&ndash;                                    <a href="javascript:void(0)" onclick="ViewOperationLog()" class="right-aligned-link">查看操作记录</a>&ndash;&gt;-->
+                                <!--                                        </div>-->
+
+                                <!--                                    </div>-->
+                                <!--                                </div>-->
+                                <!--                                <div class="row">-->
+                                <!--                                    <div class="col-sm-12 select-table table-striped">-->
+                                <!--                                        <table id="bootstrap-table-5"></table>-->
+                                <!--                                    </div>-->
+                                <!--                                </div>-->
+                                <!--                            </div>-->
+
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="ibox-content">
+                <div class="ibox-title2">
+                    <div class="task-info">
+                        <h4>本次任务 <code style="font-size: 1.5rem;" th:text="${bc_taskStatus}"></code></h4>
+                    </div>
+                    <span style="font-size:15px;">任务名称:
+                        <span style="font-size:15px;" th:text="${bc_taskName}"></span> &nbsp;&nbsp;&nbsp;&nbsp;
+                    </span>
+                    <span style="font-size:15px;">药品:
+                        <span style="font-size:15px;" th:text="${bc_productName}"></span> &nbsp;&nbsp;&nbsp;&nbsp;
+                    </span>
+                    <span style="font-size:15px;">任务主题:
+                            <span>
+                                <select name="taskTheme" class="styled-input" th:with="type=${@dict.getType('sys_select_dtp_sfrw_rwzt')}">
+                                    <option value="">请选择</option>
+                                    <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}" th:selected="${dict.dictLabel}==${bc_taskTheme}"></option>
+                                </select>
+                           </span> &nbsp;&nbsp;&nbsp;&nbsp;
+                       </span>
+                    <span style="font-size:15px;">任务跟进人:
+                        <input type="hidden" id="bc_taskFollower" name="bc_taskFollower" th:value="bc_taskFollower"/>
+                        <span id="bc_taskFollowerCode" style="color: #2E2D3C;font-size:15px;"><code style="font-size: 1.4rem;font-size:15px;" th:text="${bc_taskFollower}" ></code></span>
+                       <a onclick="editTaskGenJinRen()" style="font-size:15px;"> 修改 <i class="glyphicon glyphicon-pencil"></i></a>
+                    </span>
+
+                    <div class="task-buttons">
+                        <!--                    <button class="btn btn-w-m btn-primary" onclick="showDetailHistory()">查看历史随访</button>-->
+                    </div>
+                </div>
+                <form id="form-defaultValue" class="form-horizontal">
+                    <input type="hidden" id="id" name="id" th:value="${bc_id}">
+                    <input type="hidden" id="planId" name="planId" th:value="${planId}">
+                    <input type="hidden" id="bc_planId" name="bc_planId" th:value="${bc_planId}">
+                    <input type="hidden" id="planId_cg" name="planId_cg" th:value="${planId_cg}">
+                    <input type="hidden" id="planId_tl" name="planId_tl" th:value="${planId_tl}">
+                    <input type="hidden" id="patientId" name="patientId" th:value="${patientId}">
+                    <input type="hidden" id="mdmCode_tl" name="mdmCode_tl" th:value="${mdmCode_tl}">
+                    <input type="hidden" id="mdmCode_cg" name="mdmCode_cg" th:value="${mdmCode_cg}">
+                </form>
+            </div>
+
+            <form id="form-followUp-edit3" class="form-horizontal">
+                <div class="row">
+                    <div class="col-sm-12" >
+                        <ul class="nav nav-tabs" id="myTabs2">
+                            <li class="active"><a data-toggle="tab" href="#tab-4" aria-expanded="true">本次任务</a>
+                            </li>
+                            <li class=""><a data-toggle="tab" href="#tab-6">用药依从性</a>
+                            </li>
+                            <li class=""><a data-toggle="tab" href="#tab-7" >药物安全性</a>
+                            </li>
+                            <li class=""><a data-toggle="tab" href="#tab-8">药物有效性</i></a>
+                            </li>
+                            <li class=""><a data-toggle="tab" href="#tab-9">治疗方案</a>
+                            </li>
+                            <li class=""><a data-toggle="tab" href="#tab-10">适宜性评估</a>
+                            </li>
+                            <li class=""><a data-toggle="tab" href="#tab-11">用药记录</a>
+                            </li>
+                            <li class=""><a data-toggle="tab" href="#tab-12">量表测评</a>
+                            </li>
+                            <li class=""><a data-toggle="tab" href="#tab-13">患者咨询</a>
+                            </li>
+                            <li class=""><a data-toggle="tab" href="#tab-14">其他</a>
+                            </li>
+                        </ul>
+
+                        <div class="panel-body">
+                            <div class="tab-content content-area">
+                                <div id="tab-4" class="tab-pane active">
+                                    <strong> <h4 style="color: #1E9FFF;">| 本次任务</h4> </strong>
+                                    <div class="customize-form-group edit">
+                                        <div class="tooltip-question">
+                                            <label class="is-required">回访方式:</label>
+
+                                            <img th:src="@{/img/wenhao.png}" alt="Question Mark" class="question-mark">
+                                            <!-- 这里可以放置您想显示的图片 -->
+                                            <div class="tooltip-content">
+                                                <img th:src="@{/img/huifanfs.png}" alt="Tooltip Image" class="tooltip-image">
+                                            </div>
+                                        </div>
+                                        <div class="input-groups" th:with="type=${@dict.getType('sys_select_dtp_ysfw_huifangfangshi')}">
+                                            <input type="radio" class="form-check-radio" name="returnMethod" id="returnMethod" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"   th:checked="${dict.dictLabel}==${returnMethod}">
+                                        </div>
+
+                                        <span class="status"></span>
+                                    </div>
+
+                                    <div id="weixin" class="hidden">
+                                        <div class="customize-form-group edit">
+                                            <label class="is-required">回访图片:</label>
+                                            <div class="fileinput fileinput-new" data-provides="fileinput">
+                                                <div class="fileinput-preview thumbnail" data-trigger="fileinput" style="width: 200px; height: 150px;">
+                                                    <img id="returnImgUrl" th:if="${returnImgUrl}" th:src="@{${returnImgUrl}}" class="preview-image" />
+                                                    <div th:unless="${returnImgUrl}" class="centered-content">
+                                                        <span class="plus-sign">+</span>
+                                                    </div>
+                                                </div>
+                                                <div>
+                                                    <input style="display: none;"  id="returnImgUrlUpload" name="returnImgUrlUpload" type="file">
+                                                    <a href="javascript:;" class="btn btn-white" data-dismiss="fileinput">清除图片</a>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <div id="mianfang" class="hidden">
+                                    <div class="customize-form-group edit">
+                                        <label>面访记录:</label>
+                                        <div class="input-groups"  style="width: 50%">
+                                            <input name="InterviewRecord" id="InterviewRecord" placeholder="请输入面访记录" class="styled-input edit_inputs" type="text" th:value="${InterviewRecord}">
+                                        </div>
+                                        <span class="status"></span>
+                                    </div>
+                                </div>
+                                <div class="customize-form-group">
+                                    <label>回访对象:</label>
+                                    <select name="returnObject" class="select-input edit_inputs" id="returnObject" th:with="type=${@dict.getType('sys_select_dtp_ysfw_huifangduixaing')}">
+                                        <option value="">请选择</option>
+                                        <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"
+                                                th:selected="${dict.dictLabel}==${returnObject}"></option>
+                                    </select>
+                                    <span class="status"></span>
+                                </div>
+                                <div class="customize-form-group edit">
+                                    <label class="is-required">是否配合:</label>
+                                    <div class="input-groups" th:with="type=${@dict.getType('sys_select_yes_no')}">
+                                        <input type="radio" class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"  th:checked="${dict.dictValue}==${iscoordinate}"  name="iscoordinate" id="iscoordinate">
+                                    </div>
+                                    <span class="status"></span>
+                                </div>
+                                <div id="iscoordinateDiv" class="hidden">
+                                    <div class="customize-form-group edit">
+                                        <label style="margin-bottom: 73px;">随访不配合原因:</label>
+                                        <div class="form-groupBottom"
+                                             th:with="type=${@dict.getType('sys_gxhpz_ysfw_sfbphyy')}"
+                                             style="width: 900px; display: flex; flex-wrap: wrap; gap: 10px; align-items: center;">
+                                            <div  th:each="dict : ${type}" style="white-space: nowrap;">
+                                                <input  type="radio"
+                                                        name="reasons_uncooperative"
+                                                        class="form-check-radio"
+                                                        th:id="'reasons_uncooperative_' + ${dict.dictLabel}"
+                                                        th:text="${dict.dictLabel}"
+                                                        th:value="${dict.dictLabel}">
+                                            </div>
+                                        </div>
+                                        <span class="status"></span>
+                                    </div>
+                                    <div class="customize-form-group edit">
+                                        <label>不配合:</label>
+                                        <div class="input-groups" th:with="type=${@dict.getType('sys_gxhpz_yong_jiu_ting_yao_form')}" style="color: red">
+                                            <input type="checkbox" id="stopform" name="stopform" class="form-check-radio"  th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"  th:checked="${dict.dictLabel}==${stopForm}">
+                                        </div>
+                                        <span class="status"></span>
+                                    </div>
+
+                                </div>
+
+                            </div>
+                            <div id="tab-6" class="tab-pane active">
+                                <strong> <h4 style="color: #1E9FFF;">| 用药依从性</h4> </strong>
+                                <div class="customize-form-group edit" >
+                                    <label>是否持续用药(用药状态):</label>
+                                    <div class="input-groups" style="width: 90%" th:with="type=${@dict.getType('sys_gxhpz_yyzt')}">
+                                        <input type="radio" class="form-check-radio" name="medicationStatus" id="medicationStatus"  th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"   th:checked="${dict.dictLabel}==${medicationStatus}" >
+                                    </div>
+                                    <span class="status"></span>
+                                </div>
+
+                                <div id="csyzyy" class="hidden">
+                                    <div class="customize-form-group edit" >
+                                        <label>慈善援助用药:</label>
+                                        <div class="input-groups" th:with="type=${@dict.getType('sys_gxhpz_chishan_yzyy')}">
+                                            <input type="radio" name="charitable"  class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"  th:checked="${dict.dictLabel}==${chishanyy}"  >
+                                        </div>
+                                        <span class="status"></span>
+                                    </div>
+                                </div>
+                                <div id="yjty" class="hidden">
+                                    <div class="customize-form-group edit">
+                                        <label>永久停药时间:</label>
+                                        <div class="input-groups">
+                                            <input name="permanentStopTime" id="permanentStopTime" placeholder="永久停药时间" class="time-input time-input2" type="text"  th:value="${permanentStopTime}" required>
+                                        </div>
+                                        <span class="status"></span>
+                                    </div>
+                                    <div class="customize-form-group edit">
+                                        <label class="is-required">永久停药原因:</label>
+                                        <div class="input-groups" style="width: 90%" th:with="type=${@dict.getType('sys_hzgl_sfrw_closeplan_yjstop')}">
+                                            <input type="radio"  name="perpetual_stopdrug_cause" class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"  th:checked="${dict.dictLabel}==${perpetual_stopdrug_cause}"  >
+                                        </div>
+                                        <span class="status"></span>
+                                    </div>
+                                    <div class="customize-form-group edit">
+                                        <label>永久停药:</label>
+                                        <div class="input-groups" th:with="type=${@dict.getType('sys_gxhpz_yong_jiu_ting_yao_form')}" style="color: red">
+                                            <input type="checkbox" id="stoped" name="stoped" class="form-check-radio"  th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"  th:checked="${dict.dictLabel}==${stoped}"   >
+                                        </div>
+                                        <span class="status"></span>
+                                    </div>
+                                    <div class="customize-form-group edit hidden" id="follow_up_planning_strategy_form_Div">
+                                        <label class="is-required">随访计划策略</label>
+                                        <div class="input-groups" th:with="type=${@dict.getType('sys_hzgl_sfrw_follow_up_planning_strategy')}" style="width: 900px;" >
+                                            <input type="radio" name="planningStrategy" class="form-check-radio"  th:each="dict : ${type}"  id="planningStrategy_form" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"  th:checked="${dict.dictValue}==${planningStrategy}">
+                                        </div>
+                                    </div>
+                                    <div id="qtgyqgy" class="hidden">
+                                        <div class="customize-form-group edit">
+                                            <label>其他渠道购药:</label>
+                                            <div class="input-groups" style="width: 90%" th:with="type=${@dict.getType('sys_gxhpz_othetqd_gy')}">
+                                                <input type="radio" id="other_channels"   name="other_channels" class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"  th:checked="${dict.dictLabel}==${other_channels}">
+                                            </div>
+                                            <span class="status"></span>
+                                            <span class="status"></span>
+                                        </div>
+                                    </div>
+                                    <div id="ycgyycjy" class="hidden">
+                                        <div class="customize-form-group edit">
+                                            <label class="is-required">延迟购药(医嘱建议):</label>
+                                            <div class="input-groups" style="width: 90%" th:with="type=${@dict.getType('sys_gxhpz_yanchi_gy_ys_jy')}">
+                                                <input type="radio" id="delay_purchase_doctor"   name="delay_purchase_doctor" class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"  th:checked="${dict.dictLabel}==${delay_purchase_doctor}">
+                                            </div>
+                                            <span class="status"></span>
+                                        </div>
+                                    </div>
+                                    <div id="ycgyhzyybgf" class="hidden">
+                                        <div class="customize-form-group edit">
+                                            <label class="is-required">延迟购药(用药不规范):</label>
+                                            <div class="input-groups" style="width: 90%" th:with="type=${@dict.getType('sys_gxhpz_yanchi_gyhzyybgf')}">
+                                                <input type="radio" id="delayed_purchase_drugs"   name="delayed_purchase_drugs"  class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"  th:checked="${dict.dictLabel}==${delayed_purchase_drugs}">
+                                            </div>
+                                            <span class="status"></span>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div id="tab-no" class="tab-pane active">
+                                    <div class="customize-form-group edit">
+                                        <label class="is-required" style="margin-bottom: 0px">确诊后首次用药日期:</label>
+                                        <div class="input-groups">
+                                            <input name="timeFirstDiagnosis" id="timeFirstDiagnosis" placeholder="确诊后首次用药日期" class="time-input time-input2" type="text"  th:value="${timeFirstDiagnosis}" required>
+                                        </div>
+                                        <span class="status"></span>
+                                    </div>
+                                </div>
+                                <div id="tab-7-14">
+                                    <div id="tab-7" class="tab-pane active">
+                                        <strong> <h4 style="color: #1E9FFF;">| 药物安全性</h4> </strong>
+                                        <div class="customize-form-group edit">
+                                            <label class="is-required">是否出现不良反应:</label>
+                                            <div class="input-groups" th:with="type=${@dict.getType('sys_yes_no')}">
+                                                <input type="radio" class="form-check-radio"  th:each="dict : ${type}"  name="is_adverse_reaction" id="is_adverse_reaction" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"   th:checked="${dict.dictLabel}==${is_adverse_reaction}">
+                                            </div>
+                                            <span class="status"></span>
+                                        </div>
+                                        <div id="bulfydiv" class="hidden">
+
+                                            <div class="customize-form-group edit">
+                                                <label>不良反应时间:</label>
+                                                <div class="input-groups" th:with="type=${@dict.getType('sys_yes_no')}">
+                                                    <input name="adverse_reaction_time" id="adverse_reaction_time" placeholder="不良反应时间" class="time-input time-input2" type="text"  th:value="${adverse_reaction_time}" required>
+                                                </div>
+                                                <span class="status"></span>
+                                            </div>
+                                            <div class="customize-form-group edit">
+                                                <span class="status"></span>
+                                                <div  class="tab-pane fade in active">
+                                                    <div class="panel-body">
+                                                        <div class="customize-search-form">
+                                                            <div class="customize-form-group edit">
+                                                                <div style="/*display: flex;*/">
+                                                                    <label style="width: auto;">不良反应症状:</label>
+                                                                    <button type="button" data-toggle="modal"  class="btn btn-ms btn-primary" data-target="#myModal5">新增</button>
+                                                                    <table id="byfyTable" style="width: 800px;">
+                                                                        <thead>
+                                                                        <tr>
+                                                                            <th>序号</th>
+                                                                            <th>不良反应症状</th>
+                                                                            <th>操作</th>
+                                                                        </tr>
+                                                                        </thead>
+                                                                        <tbody id="blfyTableBody">
+                                                                        <!-- 表格行将在这里动态添加 -->
+                                                                        </tbody>
+                                                                    </table>
+                                                                </div>
+
+                                                                <div class="modal inmodal fade" id="myModal5" tabindex="-1" role="dialog" aria-hidden="true">
+                                                                    <div class="modal-dialog modal-lg" style="width: 1000px">
+                                                                        <div class="modal-content">
+                                                                            <div class="modal-header">
+                                                                                <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span>
+                                                                                </button>
+                                                                                <h4 class="modal-title">不良反应系统</h4>
+                                                                                <!--<small class="font-bold">这里可以显示副标题。</small>-->
+                                                                            </div>
+                                                                            <div class="modal-body">
+                                                                                <div id="element1" class="row">
+                                                                                    <div class="col-sm-2">
+                                                                                        <select class="province form-control m-b" data-first-title="选择不良反应">
+                                                                                            <option value="">请选择</option>
+                                                                                        </select>
+                                                                                    </div>
+                                                                                    <div class="col-sm-3">
+                                                                                        <select class="city form-control m-b" data-first-title="选择不良反应位置">
+                                                                                            <option value="">请选择</option>
+                                                                                        </select>
+                                                                                    </div>
+                                                                                    <div class="col-sm-7">
+                                                                                        <select class="area form-control m-b" data-first-title="选择不良反应数值">
+                                                                                            <option value="">请选择</option>
+                                                                                        </select>
+                                                                                    </div>
+                                                                                </div>
+                                                                            </div>
+
+                                                                            <div class="modal-footer">
+                                                                                <button type="button" class="btn btn-white" data-dismiss="modal">关闭</button>
+                                                                                <button type="button" class="btn btn-primary" data-dismiss="modal" onclick="selectAdverseReactions(0)">保存</button>
+                                                                            </div>
+                                                                        </div>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </div>
+
+                                            <div class="customize-form-group edit">
+                                                <label class="is-required">不良反应处理方式:</label>
+                                                <textarea id="tar" name="tar" class="styled-input edit_inputs textareas"
+                                                          style="width: auto;height: 140px ;border: 1px solid ;"
+                                                          th:text="${tar}" placeholder="请描述不良反应处理方式..." rows="1.9" cols="112" ></textarea>
+
+                                                <span class="status"></span>
+                                            </div>
+
+                                            <div class="customize-form-group edit">
+                                                <label class="is-required">合并用药是否引起不良反应:</label>
+                                                <div class="input-groups" th:with="type=${@dict.getType('sys_select_yes_no')}">
+                                                    <input type="radio" class="form-check-radio"  th:each="dict : ${type}"  name="combinedMedicationAdverseReaction" id="combinedMedicationAdverseReaction" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"   th:checked="${dict.dictValue}==${combinedMedicationAdverseReaction}">
+                                                </div>
+                                                <span class="status"></span>
+                                            </div>
+                                            <div class="hidden" id="hbyysfyiblfy">
+                                                <div class="customize-form-group edit">
+                                                    <label class="is-required">合并用药其他不良反应描述:</label>
+                                                    <textarea id="combinedMedicationOtherAdverseReactions" name="combinedMedicationOtherAdverseReactions" class="styled-input edit_inputs textareas"
+                                                              style="width: auto;height: 140px ;border: 1px solid ;"
+                                                              th:text="${combinedMedicationOtherAdverseReactions}" placeholder="请描述合并用药其他不良反应..." rows="1.9" cols="112" ></textarea>
+
+                                                    <span class="status"></span>
+                                                </div>
+                                                <div class="customize-form-group edit">
+                                                    <label class="is-required" for="returnImgUrl">合并用药其他不良反应图片:</label>
+                                                    <div class="custom-file-upload">
+                                                        <input type="file" id="combinedMedicationOtherAdverseReactionsImage" accept=".jpg,.jpeg,.png,.gif">
+                                                        <label for="combinedMedicationOtherAdverseReactionsImage"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#007bff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg></label>
+                                                    </div>
+                                                </div>
+                                                <div class="customize-form-group edit">
+                                                    <label>合并用药不良反应处理:</label>
+                                                    <div class="input-groups">
+                                                        <input name="combinedMedicationOtherAdverseReactionsHandling" id="combinedMedicationOtherAdverseReactionsHandling" placeholder="请输入合并用药不良反应处理" class="styled-input edit_inputs" type="text"  th:value="${combinedMedicationOtherAdverseReactionsHandling}">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+                                            </div>
+                                        </div>
+
+                                    </div>
+                                    <div id="tab-8" class="tab-pane active">
+                                        <strong> <h4 style="color: #1E9FFF;">| 药物有效性</h4> </strong>
+                                        <div class="customize-form-group edit">
+                                            <label class="is-required">是否复查:</label>
+                                            <div class="input-groups" th:with="type=${@dict.getType('sys_select_yes_no')}">
+                                                <input type="radio" class="form-check-radio"  th:each="dict : ${type}"  name="isReview" id="isReview" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"   th:checked="${dict.dictValue}==${isReview}">
+                                            </div>
+                                            <span class="status"></span>
+                                        </div>
+                                        <div class="hidden" id="isReviewDiv">
+                                            <div class="customize-form-group edit">
+                                                <label class="is-required">影像学检查:</label>
+                                                <div class="input-groups" th:with="type=${@dict.getType('sys_select_dtp_ysfw_yxxjc')}" >
+                                                    <input type="checkbox" class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"   id="imagingInsuranceType" name="imagingInsuranceType">
+                                                </div>
+                                                <span class="status"></span>
+                                            </div>
+                                            <div class="hidden" id="ctradio">
+                                                <div class="customize-form-group edit" >
+                                                    <label>CT:</label>
+                                                    <div class="input-groups" th:with="type=${@dict.getType('sys_select_dtp_ysfw_yxxjc_next')}">
+                                                        <input type="radio" class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"  th:checked="${dict.dictValue}==${ctCheck}"  name="ctCheck" id="ctCheck">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+
+                                                <div class="customize-form-group edit">
+                                                    <label>CT文本:</label>
+                                                    <div class="input-groups">
+                                                        <input name="ctText" id="ctText" placeholder="请输入CT文本" class="styled-input edit_inputs" type="text"  th:value="${ctText}">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+                                            </div>
+                                            <div class="hidden" id="bcradio">
+                                                <div class="customize-form-group edit">
+                                                    <label>B超:</label>
+                                                    <div class="input-groups" th:with="type=${@dict.getType('sys_select_dtp_ysfw_yxxjc_next')}">
+                                                        <input type="radio" class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"  th:checked="${dict.dictValue}==${ultrasoundCheck}"  name="ultrasoundCheck" >
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+
+                                                <div class="customize-form-group edit">
+                                                    <label>B超文本:</label>
+                                                    <div class="input-groups">
+                                                        <input name="ultrasound_text" id="ultrasound_text" placeholder="请输入B超文本" class="styled-input edit_inputs" type="text"  th:value="${ultrasoundText}">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+                                            </div>
+                                            <div class="hidden" id="hcradio">
+                                                <div class="customize-form-group edit">
+                                                    <label>核磁:</label>
+                                                    <div class="input-groups" th:with="type=${@dict.getType('sys_select_dtp_ysfw_yxxjc_next')}">
+                                                        <input type="radio" class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"  th:checked="${dict.dictValue}==${mriCheck}"  name="mriCheck" id="mriCheck">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+
+                                                <div class="customize-form-group edit">
+                                                    <label>核磁文本:</label>
+                                                    <div class="input-groups">
+                                                        <input name="mriText" id="mriText" placeholder="请输入核磁文本" class="styled-input edit_inputs" type="text"  th:value="${mriText}">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+                                            </div>
+                                            <div class="hidden" id="qtradio">
+                                                <div class="customize-form-group edit">
+                                                    <label>影像学检查其它:</label>
+                                                    <div class="input-groups">
+                                                        <input name="otherImagingExaminations" id="otherImagingExaminations" placeholder="请输入影像学检查其它" class="styled-input edit_inputs" type="text"  th:value="${otherImagingExaminations}" >
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+                                            </div>
+                                            <div class="customize-form-group edit">
+                                                <label  for="imagingExaminationsImage">影像学检查图片:</label>
+                                                <div class="custom-file-upload">
+                                                    <input type="file" id="imagingExaminationsImage" name="imagingExaminationsImage" accept=".jpg,.jpeg,.png,.gif">
+                                                    <label for="imagingExaminationsImage"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#007bff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg></label>
+                                                </div>
+                                                <span class="status"></span>
+                                            </div>
+                                            <div class="customize-form-group edit">
+                                                <label  for="tumor_marker_image">肿瘤物标记检查图片:</label>
+                                                <div class="custom-file-upload">
+                                                    <input type="file" id="tumor_marker_image" name="tumor_marker_image" accept=".jpg,.jpeg,.png,.gif">
+                                                    <label for="tumor_marker_image"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#007bff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg></label>
+                                                </div>
+                                                <span class="status"></span>
+                                            </div>
+                                            <div class="customize-form-group edit">
+                                                <label class="is-required">肿瘤标记物检查:</label>
+                                                <div class="input-groups" style="width: 800px" th:with="type=${@dict.getType('sys_select_dtp_zlbjwjc')}" id="zlbjwjc">
+                                                    <input type="checkbox" class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"    name="tumorMarkerCheck" id="tumorMarkerCheck">
+                                                </div>
+
+                                            </div>
+
+                                            <div class="hidden" id="ceaDiv">
+                                                <div class="customize-form-group edit">
+                                                    <label>CEA:</label>
+                                                    <div class="input-groups">
+                                                        <input name="cea" id="cea" placeholder="请输入CEA" class="styled-input edit_inputs" type="text"  th:value="${cea}">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+                                            </div>
+                                            <div class="hidden" id="afpDiv">
+                                                <div class="customize-form-group edit">
+                                                    <label>AFP:</label>
+                                                    <div class="input-groups">
+                                                        <input name="afp" id="afp" placeholder="请输入AFP" class="styled-input edit_inputs" type="text"  th:value="${afp}">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+                                            </div>
+                                            <div class="hidden" id="ca199Div">
+                                                <div class="customize-form-group edit">
+                                                    <label>CA-199:</label>
+                                                    <div class="input-groups">
+                                                        <input name="ca199" id="ca199" placeholder="请输入CA-199" class="styled-input edit_inputs" type="text"  th:value="${ca199}">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+                                            </div>
+                                            <div class="hidden" id="ca125Div">
+                                                <div class="customize-form-group edit">
+                                                    <label>CA125:</label>
+                                                    <div class="input-groups">
+                                                        <input name="ca125" id="ca125" placeholder="请输入CA125" class="styled-input edit_inputs" type="text"  th:value="${ca125}">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+                                            </div>
+                                            <div class="hidden" id="ca153Div">
+                                                <div class="customize-form-group edit">
+                                                    <label>CA153:</label>
+                                                    <div class="input-groups">
+                                                        <input name="ca153" id="ca153" placeholder="请输入CA153" class="styled-input edit_inputs" type="text"  th:value="${ca153}">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+                                            </div>
+                                            <div class="hidden" id="hcgDiv">
+                                                <div class="customize-form-group edit">
+                                                    <label>HCG:</label>
+                                                    <div class="input-groups">
+                                                        <input name="hcg" id="hcg" placeholder="请输入HCG" class="styled-input edit_inputs" type="text"  th:value="${hcg}">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+                                            </div>
+                                            <div class="hidden" id="nseDiv">
+                                                <div class="customize-form-group edit">
+                                                    <label>NSE:</label>
+                                                    <div class="input-groups">
+                                                        <input name="nse" id="nse" placeholder="请输入NSE" class="styled-input edit_inputs" type="text"  th:value="${nse}">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+                                            </div>
+                                            <div class="hidden" id="cyfra211Div">
+                                                <div class="customize-form-group edit">
+                                                    <label>CYFRA21-1:</label>
+                                                    <div class="input-groups">
+                                                        <input name="cyfra21" id="cyfra21" placeholder="请输入CYFRA21-1" class="styled-input edit_inputs" type="text"  th:value="${cyfra21}">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+                                            </div>
+                                            <div class="hidden" id="tgDiv">
+                                                <div class="customize-form-group edit">
+                                                    <label>TG:</label>
+                                                    <div class="input-groups">
+                                                        <input name="tg" id="tg" placeholder="请输入TG" class="styled-input edit_inputs" type="text"  th:value="${tg}">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+                                            </div>
+                                            <div class="hidden" id="psaDiv">
+                                                <div class="customize-form-group edit">
+                                                    <label>PSA:</label>
+                                                    <div class="input-groups">
+                                                        <input name="psa" id="psa" placeholder="请输入PSA" class="styled-input edit_inputs" type="text"  th:value="${psa}">
+                                                    </div>
+                                                    <span class="status"></span>
+                                                </div>
+                                            </div>
+
+
+                                            <div class="customize-form-group edit">
+                                                <label>实验室检查指标:</label>
+                                                <textarea id="labTestIndicators" name="labTestIndicators" class="styled-input edit_inputs textareas"
+                                                          style="width: auto;height: 140px ;border: 1px solid ;"
+                                                          th:text="${labTestIndicators}" placeholder="实验室检查指标..." rows="1.9" cols="112" ></textarea>
+
+                                                <span class="status"></span>
+                                            </div>
+                                            <div class="customize-form-group edit">
+                                                <label>提示</label>
+                                                <input  id="tishi" placeholder="可填写中性粒细胞(NEUT)、血小板(PLT)、白蛋白(AIB)、谷丙转氨酶(ALT)、谷草转氨酶(AST)、肌酐(Crea)、总胆红素(T-BIL)" class="styled-input"
+                                                        style="border: none" type="text"  value="可填写中性粒细胞(NEUT)、血小板(PLT)、白蛋白(AIB)、谷丙转氨酶(ALT)、谷草转氨酶(AST)、肌酐(Crea)、总胆红素(T-BIL)" readonly>
+                                                <span class="status"></span>
+                                            </div>
+                                            <div class="customize-form-group edit">
+                                                <label  for="labTestIndicatorsImage">实验室检查指标图片:</label>
+                                                <div class="custom-file-upload">
+                                                    <input type="file" id="labTestIndicatorsImage" name="labTestIndicatorsImage" accept=".jpg,.jpeg,.png,.gif">
+                                                    <label for="labTestIndicatorsImage"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#007bff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg></label>
+                                                </div>
+                                                <span class="status"></span>
+                                            </div>
+
+                                        </div>
+
+                                    </div>
+                                    <div id="tab-9" class="tab-pane active">
+                                        <strong> <h4 style="color: #1E9FFF;">| 治疗方案</h4> </strong>
+                                        <div class="customize-form-group edit">
+                                            <label class="is-required">治疗类型:</label>
+                                            <div class="input-groups" th:with="type=${@dict.getType('sys_select_dtp_ysfw_treatmenttype')}" >
+                                                <input type="checkbox" class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"  id="treatmentType" name="treatmentType">
+                                            </div>
+                                            <span class="status"></span>
+                                        </div>
+                                        <div class="customize-form-group edit hidden" id="treatmentTypeDiv">
+                                            <div style="display: flex;">
+                                                <label>用药前治疗方案:</label>
+                                                <button type="button"  data-toggle="modal" data-target="#myModal1" class="btn-primarys">+新增</button>
+                                                <table id="preMedicationPlanTable" style="width: 500px;">
+                                                    <thead>
+                                                    <tr>
+                                                        <th>序号</th>
+                                                        <th>治疗方案</th>
+                                                        <th>治疗时间</th>
+                                                        <th>操作</th>
+                                                    </tr>
+                                                    </thead>
+                                                    <tbody id="preMedicationPlanTableBody">
+
+                                                    </tbody>
+                                                </table>
+                                            </div>
+                                            <div class="modal inmodal" id="myModal1" tabindex="-1" role="dialog" aria-hidden="true">
+                                                <div class="modal-dialog">
+                                                    <form class="form-horizontal" id="form-yyqzlfa-add">
+                                                        <div class="modal-content animated bounceInRight">
+                                                            <div class="modal-header">
+                                                                <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">关闭</span>
+                                                                </button>
+                                                                <h4 class="modal-title">用药前治疗方案</h4>
+                                                            </div>
+                                                            <div class="modal-body">
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >治疗时间</label>
+                                                                        <input type="text" name="pre_medication_time" placeholder="治疗时间" class="time-input time-input2"  style="width: 200px;" id="pre_medication_time">
+                                                                    </div>
+                                                                </div>
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label>治疗方案</label>
+                                                                        <select name="pre_medication_plan" class="styled-input edit_inputs"  style="width: 200px;" th:with="type=${@dict.getType('sys_select_dtp_sfrw_zlfa')}" id="pre_medication_plan">
+                                                                            <option value="">请选择治疗方案</option>
+                                                                            <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"
+                                                                            ></option>
+                                                                        </select>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+                                                            <div class="modal-footer">
+                                                                <button type="button" class="btn btn-white" data-dismiss="modal">关闭</button>
+                                                                <button type="button" class="btn btn-primary" onclick="saveRow(1)">保存</button>
+                                                            </div>
+                                                        </div>
+                                                    </form>
+                                                </div>
+                                            </div>
+                                        </div>
+
+                                        <div class="customize-form-group edit hidden" id="currentTreatmentPlanDiv">
+                                            <div style="display: flex;">
+                                                <label>目前治疗方案:</label>
+                                                <button type="button"  data-toggle="modal" data-target="#myModal2" class="btn-primarys">+新增</button>
+                                                <table id="currentTreatmentPlanTable" style="width: 500px;">
+                                                    <thead>
+                                                    <tr>
+                                                        <th>序号</th>
+                                                        <th>治疗方案</th>
+                                                        <th>治疗时间</th>
+                                                        <th>操作</th>
+                                                    </tr>
+                                                    </thead>
+                                                    <tbody id="currentTreatmentPlanTableBody">
+
+                                                    </tbody>
+                                                </table>
+                                            </div>
+                                            <div class="modal inmodal" id="myModal2" tabindex="-1" role="dialog" aria-hidden="true">
+                                                <div class="modal-dialog">
+                                                    <form class="form-horizontal" id="form-mqzlfa-add">
+                                                        <div class="modal-content animated bounceInRight">
+                                                            <div class="modal-header">
+                                                                <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">关闭</span>
+                                                                </button>
+                                                                <h4 class="modal-title">目前治疗方案</h4>
+                                                            </div>
+                                                            <div class="modal-body">
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >治疗时间</label>
+                                                                        <input type="text" name="current_treatment_plan_time" placeholder="治疗时间" class="time-input time-input2"  style="width: 200px;" id="current_treatment_plan_time">
+                                                                    </div>
+                                                                </div>
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label>治疗方案</label>
+                                                                        <select name="current_treatment_plan" class="styled-input edit_inputs"  style="width: 200px;" th:with="type=${@dict.getType('sys_select_dtp_sfrw_zlfa')}" id="current_treatment_plan">
+                                                                            <option value="">请选择治疗方案</option>
+                                                                            <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"
+                                                                            ></option>
+                                                                        </select>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+                                                            <div class="modal-footer">
+                                                                <button type="button" class="btn btn-white" data-dismiss="modal">关闭</button>
+                                                                <button type="button" class="btn btn-primary" onclick="saveRow(2)">保存</button>
+                                                            </div>
+                                                        </div>
+                                                    </form>
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div class="customize-form-group edit hidden" id="postProgressionTreatmentPlanDiv">
+                                            <div style="display: flex;">
+                                                <label>进展后治疗方案:</label>
+                                                <button type="button"  data-toggle="modal" data-target="#myModal3" class="btn-primarys">+新增</button>
+                                                <table id="postProgressionTreatmentPlanTable" style="width: 500px;">
+                                                    <thead>
+                                                    <tr>
+                                                        <th>序号</th>
+                                                        <th>治疗方案</th>
+                                                        <th>治疗时间</th>
+                                                        <th>操作</th>
+                                                    </tr>
+                                                    </thead>
+                                                    <tbody id="postProgressionTreatmentPlanTableBody">
+
+                                                    </tbody>
+                                                </table>
+                                            </div>
+                                            <div class="modal inmodal" id="myModal3" tabindex="-1" role="dialog" aria-hidden="true">
+                                                <div class="modal-dialog">
+                                                    <form class="form-horizontal" id="form-jzhzlfa-add">
+                                                        <div class="modal-content animated bounceInRight">
+                                                            <div class="modal-header">
+                                                                <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">关闭</span>
+                                                                </button>
+                                                                <h4 class="modal-title">进展后治疗方案</h4>
+                                                            </div>
+                                                            <div class="modal-body">
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >治疗时间</label>
+                                                                        <input type="text" name="post_progression_treatment_time" placeholder="治疗时间" class="time-input time-input2"  style="width: 200px;" id="post_progression_treatment_time">
+                                                                    </div>
+                                                                </div>
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label>治疗方案</label>
+                                                                        <select name="post_progression_treatment_plan" class="styled-input edit_inputs"  style="width: 200px;" th:with="type=${@dict.getType('sys_select_dtp_sfrw_zlfa')}" id="post_progression_treatment_plan">
+                                                                            <option value="">请选择治疗方案</option>
+                                                                            <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"
+                                                                            ></option>
+                                                                        </select>
+                                                                    </div>
+                                                                </div>
+                                                            </div>
+                                                            <div class="modal-footer">
+                                                                <button type="button" class="btn btn-white" data-dismiss="modal">关闭</button>
+                                                                <button type="button" class="btn btn-primary" onclick="saveRow(3)">保存</button>
+                                                            </div>
+                                                        </div>
+                                                    </form>
+                                                </div>
+                                            </div>
+                                        </div>
+
+                                    </div>
+                                    <div id="tab-10" class="tab-pane active">
+                                        <strong> <h4 style="color: #1E9FFF;">| 适宜性评估</h4> </strong>
+                                        <div class="customize-form-group edit">
+                                            <label class="is-required">有无药品适应症:</label>
+                                            <div class="input-groups" th:with="type=${@dict.getType('sys_select_yes_no')}">
+                                                <input type="radio" class="form-check-radio"  th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"   th:checked="${dict.dictValue}==${hasMedicationIndications}" name="hasMedicationIndications" id="hasMedicationIndications">
+                                            </div>
+                                            <span class="status"></span>
+                                        </div>
+                                        <span class="status"></span>
+                                        <span class="status"></span>
+                                        <div class="customize-form-group edit">
+                                            <label  >药物对该疾病是否有效:</label>
+                                            <div class="input-groups" th:with="type=${@dict.getType('sys_gxhpz_ysfw_ywdgjbsfyx')}">
+                                                <input type="radio" class="form-check-radio"  th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"   th:checked="${dict.dictValue}==${drugEffectiveness}" name="drugEffectiveness" id="drugEffectiveness">
+                                            </div>
+                                            <span class="status"></span>
+                                        </div>
+
+                                        <div class="customize-form-group edit font form-groupBottom">
+                                            <label class="is-required form-groupBottom">药物间是否存在临床相互作用:</label>
+                                            <div class="input-groups form-groupBottom" th:with="type=${@dict.getType('sys_select_yes_no')}">
+                                                <input type="radio" class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"  th:checked="${dict.dictValue}==${clinicalInteraction}"  name="clinicalInteraction" id="clinicalInteraction">
+                                            </div>
+                                            <span class="status"></span>
+                                        </div>
+                                        <span class="status"></span>
+                                        <span class="status"></span>
+                                        <div class="hidden form-groupBottom" id="clinicalInteractionDiv">
+                                            <div class="customize-form-group edit form-groupBottom">
+                                                <label class="is-required form-groupBottom font">请描述临床相互作用:</label>
+
+                                                <textarea id="clinicalInteractionDescription" name="clinicalInteractionDescription" class="styled-input font edit_inputs textareas"
+                                                          style="width: auto;height: 140px ;border: 1px solid ;"
+                                                          th:text="${clinicalInteractionDescription}" placeholder="请描述临床相互作用..." rows="1.9" cols="112" ></textarea>
+
+                                                <span class="status"></span>
+                                            </div>
+                                        </div>
+                                        <div class="customize-form-group edit">
+                                            <label>是否有不必要重复用药:</label>
+                                            <div class="input-groups" th:with="type=${@dict.getType('sys_select_yes_no')}">
+                                                <input type="radio" class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"  th:checked="${dict.dictValue}==${unnecessaryRepeatedMedication}"  name="unnecessaryRepeatedMedication">
+                                            </div>
+                                            <span class="status"></span>
+                                            <span class="status"></span>
+                                        </div>
+                                        <span class="status"></span>
+                                        <span class="status"></span>
+                                        <div class="form-groupBottom hidden" id="unnecessaryRepeatedMedicationDiv">
+                                            <div class="form-groupBottom customize-form-group edit">
+                                                <label class="form-groupBottom is-required">重复用药使用日期:</label>
+                                                <div class="form-groupBottom input-groups select-time">
+                                                    <input name="repeatedMedicationFirstUseDate" id="repeatedMedicationFirstUseDate" placeholder="重复用药首次使用日期" class="time-input-new styled-input" type="text"  th:value="${repeatedMedicationFirstUseDate}">
+                                                    <span class="status"></span>
+                                                    <span class="status"></span>
+                                                </div>
+
+                                            </div>
+                                            <span class="status"></span>
+                                            <span class="status"></span>
+                                            <div class="form-groupBottom customize-form-group edit">
+                                                <label class="is-required">请描述不必要重复用药:</label>
+                                                <textarea id="unnecessaryRepeatedMedicationDescription" name="unnecessaryRepeatedMedicationDescription" class="styled-input edit_inputs textareas"
+                                                          style="width: auto;height: 140px ;border: 1px solid ;"
+                                                          th:text="${unnecessaryRepeatedMedicationDescription}" placeholder="请描述不必要重复用药..." rows="1.9" cols="112" ></textarea>
+
+                                                <span class="status"></span>
+                                                <span class="status"></span>
+                                            </div>
+                                        </div>
+
+                                        <div class="form-groupBottom customize-form-group edit">
+                                            <label>是否出现用药错误:</label>
+                                            <div class="input-groups" th:with="type=${@dict.getType('sys_select_yes_no')}">
+                                                <input type="radio" class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"  th:checked="${dict.dictValue}==${medicationError}"  name="medicationError">
+                                            </div>
+                                            <span class="status"></span>
+                                            <span class="status"></span>
+                                        </div>
+                                        <div class="form-groupBottom hidden" id="medicationErrorDiv">
+                                            <div class="form-groupBottom customize-form-group edit">
+                                                <label class="form-groupBottom is-required">用药错误出现时间:</label>
+                                                <div class="form-groupBottom input-groups select-time">
+                                                    <input name="medicationErrorTime" placeholder="用药错误出现时间" class="time-input-new styled-input" type="text"  th:value="${medicationErrorTime}" id="medicationErrorTime">
+                                                </div>
+                                                <span class="status"></span>
+                                            </div>
+                                            <div class="form-groupBottom customize-form-group edit">
+                                                <label class="is-required">请描述用药错误:</label>
+                                                <textarea id="medicationErrorDescription" name="medicationErrorDescription" class="styled-input edit_inputs textareas"
+                                                          style="width: auto;height: 140px ;border: 1px solid ;"
+                                                          th:text="${medicationErrorDescription}" placeholder="请描述用药错误..." rows="1.9" cols="112" ></textarea>
+                                                <span class="status"></span>
+                                                <span class="status"></span>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div id="tab-11" class="tab-pane active">
+                                        <strong> <h4 style="color: #1E9FFF;">| 用药记录</h4> </strong>
+                                        <div class="form-groupBottom customize-form-group edit">
+                                            <div style="display: flex;">
+                                                <label>用药记录:</label>
+                                                <button type="button"  data-toggle="modal" data-target="#myModal4" class="btn-primarys">+新增</button>
+                                                <table id="medicationRecordTable" style="width: 800px;">
+                                                    <thead>
+                                                    <tr>
+                                                        <th>序号</th>
+                                                        <th>药品名称</th>
+                                                        <th>剂量</th>
+                                                        <th>用药频率</th>
+                                                        <th>开始用药时间</th>
+                                                        <th>停药时间</th>
+                                                        <th>最后一次用药时间</th>
+                                                        <th>注意事项</th>
+                                                        <th>操作</th>
+                                                    </tr>
+                                                    </thead>
+                                                    <tbody id="medicationRecordTableBody">
+
+                                                    </tbody>
+                                                </table>
+                                            </div>
+                                            <div class="modal inmodal" id="myModal4" tabindex="-1" role="dialog" aria-hidden="true">
+                                                <div class="modal-dialog">
+                                                    <form class="form-horizontal" id="form-yyjl-add">
+                                                        <div class="modal-content animated bounceInRight">
+                                                            <div class="modal-header">
+                                                                <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">关闭</span>
+                                                                </button>
+                                                                <h4 class="modal-title">用药记录</h4>
+                                                            </div>
+                                                            <div class="modal-body">
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >名称</label>
+                                                                        <input type="text" name="durgName" placeholder="请输入药品名称" class="styled-input edit_inputs"  style="width: 200px;" id="durgName">
+                                                                    </div>
+                                                                </div>
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >剂量</label>
+                                                                        <input type="text" name="jiliang" placeholder="请输入剂量" class="styled-input edit_inputs"  style="width: 200px;" id="jiliang">
+                                                                    </div>
+                                                                </div>
+
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >开始用药时间</label>
+                                                                        <input type="text" name="startyyTime" placeholder="请选择开始用药时间" class="time-input time-input2"  style="width: 200px;" id="startyyTime">
+                                                                    </div>
+                                                                </div>
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >停药药时间</label>
+                                                                        <input type="text" name="tyTime" placeholder="请选择停药药时间" class="time-input time-input2"  style="width: 200px;" id="tyTime">
+                                                                    </div>
+                                                                </div>
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >最后一次用药时间</label>
+                                                                        <input type="text" name="lastsyTime" placeholder="请选择最后一次用药时间" class="time-input time-input2"  style="width: 200px;" id="lastsyTime">
+                                                                    </div>
+                                                                </div>
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >使用频率</label>
+                                                                        <input type="text" name="shiypv" placeholder="请输入使用频率" class="styled-input edit_inputs"  style="width: 200px;" id="shiypv">
+                                                                    </div>
+                                                                </div>
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >注意事项</label>
+                                                                        <input type="text" name="zhuyishix" placeholder="请输入注意事项" class="styled-input edit_inputs"  style="width: 200px;" id="zhuyishix">
+                                                                    </div>
+                                                                </div>
+
+                                                            </div>
+                                                            <div class="modal-footer">
+                                                                <button type="button" class="btn btn-white" data-dismiss="modal">关闭</button>
+                                                                <button type="button" class="btn btn-primary" onclick="saveRow(4)">保存</button>
+                                                            </div>
+                                                        </div>
+                                                    </form>
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div class="customize-form-group edit">
+                                            <div style="display: flex;">
+                                                <label>合并用药记录:</label>
+                                                <button type="button"  data-toggle="modal" data-target="#myModal6" class="btn-primarys">+新增</button>
+                                                <table id="combinedMedicationRecordTable" style="width: 800px;">
+                                                    <thead>
+                                                    <tr>
+                                                        <th>序号</th>
+                                                        <th>药品名称</th>
+                                                        <th>剂量</th>
+                                                        <th>用药频率</th>
+                                                        <th>开始用药时间</th>
+                                                        <th>停药时间</th>
+                                                        <th>最后一次用药时间</th>
+                                                        <th>注意事项</th>
+                                                        <th>操作</th>
+                                                    </tr>
+                                                    </thead>
+                                                    <tbody id="combinedMedicationRecordTableBody">
+
+                                                    </tbody>
+                                                </table>
+                                            </div>
+                                            <div class="modal inmodal" id="myModal6" tabindex="-1" role="dialog" aria-hidden="true">
+                                                <div class="modal-dialog">
+                                                    <form class="form-horizontal" id="form-hbyyjl-add">
+                                                        <div class="modal-content animated bounceInRight">
+                                                            <div class="modal-header">
+                                                                <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">关闭</span>
+                                                                </button>
+                                                                <h4 class="modal-title">合并用药记录</h4>
+                                                            </div>
+                                                            <div class="modal-body">
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >名称</label>
+                                                                        <input type="text" name="hbdurgName" placeholder="请输入药品名称" class="styled-input edit_inputs"  style="width: 200px;" id="hbdurgName">
+                                                                    </div>
+                                                                </div>
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >剂量</label>
+                                                                        <input type="text" name="hbjiliang" placeholder="请输入剂量" class="styled-input edit_inputs"  style="width: 200px;" id="hbjiliang">
+                                                                    </div>
+                                                                </div>
+
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >开始用药时间</label>
+                                                                        <input type="text" name="hbstartyyTime" placeholder="请选择开始用药时间" class="time-input time-input2"  style="width: 200px;" id="hbstartyyTime">
+                                                                    </div>
+                                                                </div>
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >停药药时间</label>
+                                                                        <input type="text" name="hbtyTime" placeholder="请选择停药药时间" class="time-input time-input2"  style="width: 200px;" id="hbtyTime">
+                                                                    </div>
+                                                                </div>
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >最后一次用药时间</label>
+                                                                        <input type="text" name="hblastsyTime" placeholder="请选择最后一次用药时间" class="time-input time-input2"  style="width: 200px;" id="hblastsyTime">
+                                                                    </div>
+                                                                </div>
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >使用频率</label>
+                                                                        <input type="text" name="hbshiypv" placeholder="请输入使用频率" class="styled-input edit_inputs"  style="width: 200px;" id="hbshiypv">
+                                                                    </div>
+                                                                </div>
+                                                                <div class="customize-search-form">
+                                                                    <div class="customize-form-group edit">
+                                                                        <label >注意事项</label>
+                                                                        <input type="text" name="hbzhuyishix" placeholder="请输入注意事项" class="styled-input edit_inputs"  style="width: 200px;" id="hbzhuyishix">
+                                                                    </div>
+                                                                </div>
+
+                                                            </div>
+                                                            <div class="modal-footer">
+                                                                <button type="button" class="btn btn-white" data-dismiss="modal">关闭</button>
+                                                                <button type="button" class="btn btn-primary" onclick="saveRow(5)">保存</button>
+                                                            </div>
+                                                        </div>
+                                                    </form>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div id="tab-12" class="tab-pane active">
+                                        <strong> <h4 style="color: #1E9FFF;">| 量表测评</h4> </strong>
+                                        <div class="customize-form-group form-groupBottom edit">
+                                            <label >疼痛标准评分NRS:</label>
+                                            <div class="input-groups " th:with="type=${@dict.getType('sys_select_dtp_ysfw_nrs')}">
+                                                <input type="radio" class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"  th:checked="${dict.dictValue}==${painNrsScore}"  name="painNrsScore" id="painNrsScore">
+                                            </div>
+                                            <span class="status form-groupBottom"></span>
+                                        </div>
+                                        <div class="customize-form-group form-groupBottom edit">
+                                            <label style="margin-bottom: 121px;">体力状况评分:</label>
+                                            <div class=""
+                                                 th:with="type=${@dict.getType('sys_gxhpz_ysfw_tlzkpf')}"
+                                                 style="width: 900px; display: flex; flex-wrap: wrap; gap: 10px; align-items: center;">
+                                                <div  th:each="dict : ${type}" style="white-space: nowrap;">
+                                                    <input  type="radio"
+                                                            name="physicalConditionScore"
+                                                            class="form-check-radio"
+                                                            th:id="'physicalConditionScore_' + ${dict.dictCode}"
+                                                            th:text="${dict.dictLabel}"
+                                                            th:value="${dict.dictValue}" th:checked="${dict.dictValue}==${physicalConditionScore}">
+                                                </div>
+                                            </div>
+
+
+
+                                            <!--                                        <select name="physicalConditionScore"  style="width: 600px" class="select-input" th:with="type=${@dict.getType('sys_gxhpz_ysfw_tlzkpf')}">-->
+                                            <!--                                            <option value="">请选择</option>-->
+                                            <!--                                            <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"-->
+                                            <!--                                                    th:selected="${dict.dictValue}==${physicalConditionScore}"></option>-->
+                                            <!--                                        </select>-->
+                                            <span class="status"></span>
+                                        </div>
+
+                                    </div>
+                                    <div id="tab-13" class="tab-pane active">
+                                        <strong> <h4 style="color: #1E9FFF;">| 患者咨询</h4> </strong>
+                                        <div class="customize-form-group edit">
+                                            <label>本次回访是否咨询:</label>
+                                            <div class="input-groups" th:with="type=${@dict.getType('sys_select_yes_no')}">
+                                                <input type="radio" class="form-check-radio" th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"  th:checked="${dict.dictValue}==${isConsultation}"  name="isConsultation">
+                                            </div>
+                                            <span class="status"></span>
+                                        </div>
+                                        <div class="hidden" id="isConsultationDiv">
+                                            <div class="form-groupBottom customize-form-group edit">
+                                                <label class="is-required">咨询问题类型:</label>
+                                                <select name="consultationType" style="width: 450px" class="select-input" th:with="type=${@dict.getType('sys_gxhpz_ysfw_zxwtlx')}">
+                                                    <option value="">请选择</option>
+                                                    <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"
+                                                            th:selected="${dict.dictLabel}==${consultationType}"></option>
+                                                </select>
+                                                <span class="status"></span>
+                                            </div>
+                                            <div class="form-groupBottom customize-form-group edit">
+                                                <label>具体问题:</label>
+                                                <textarea id="specificQuestion" name="specificQuestion" class="styled-input edit_inputs textareas"
+                                                          style="width: auto;height: 140px ;border: 1px solid ;"
+                                                          th:text="${specificQuestion}" placeholder="具体问题..." rows="1.9" cols="112" ></textarea>
+                                                <span class="status"></span>
+                                            </div>
+                                            <div class="form-groupBottom customize-form-group edit">
+                                                <label>药师解答:</label>
+                                                <textarea id="pharmacistResponse" name="pharmacistResponse" class="styled-input edit_inputs textareas"
+                                                          style="width: auto;height: 140px ;border: 1px solid ;"
+                                                          th:text="${pharmacistResponse}" placeholder="1-未按随访进度联系上 2-未按时取药 3-减量或停药 4-不良反应不能耐受 5-医生说要调整治疗方案 6-挂不到医生床位/转院 7-医保不够,经济压力 8-情绪不好,生活压力 9-其他" rows="1.9" cols="112" ></textarea>
+                                                <span class="status"></span>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div id="tab-14" class="tab-pane active">
+                                        <strong> <h4 style="color: #1E9FFF;">| 其他</h4> </strong>
+                                        <div class="form-groupBottom customize-form-group edit">
+                                            <label>患者病情评估:</label>
+                                            <select name="patientConditionAssessment" style="width: 450px" class="select-input" th:with="type=${@dict.getType('sys_gxhpz_ysfw_hzbqpg')}">
+                                                <option value="">请选择</option>
+                                                <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"
+                                                        th:selected="${dict.dictLabel}==${patientConditionAssessment}"></option>
+                                            </select>
+                                            <span class="status"></span>
+                                        </div>
+                                        <div class="form-groupBottom customize-form-group edit">
+                                            <label class="form-groupBottom">共建项目描述:</label>
+                                            <textarea id="projectDescription" name="projectDescription" class="styled-input edit_inputs textareas"
+                                                      style="width: auto;height: 140px ;border: 1px solid ;"
+                                                      th:text="${projectDescription}" placeholder="请输入共建项目描述" rows="1.9" cols="112" ></textarea>
+                                            <span class="status"></span>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="ibox-content" style="background-color: #f5f3f3;">
+                        <div class="ibox-title2">
+                            <div class="task-info">
+                                <h4>下次随访时间
+                                    <input name="next_follow_time"  id="next_follow_time" placeholder="下次随访时间" class="time-input-new styled-input" type="text"  th:value="${bc_next_follow_time}"/>
+                                </h4>
+                            </div>
+                            <div class="task-details">
+                                <!--                        <h4> <span>间隔本次任务:<span id="interval_this_time" style="color: #1a7bb9;" th:text="${interval_this_time}"></span >天</span></h4>-->
+                                <span class="status">&nbsp;&nbsp;&nbsp;&nbsp;</span>
+                                <h4> <span>下次任务主题:<span style="color: #1a7bb9;">
+                                    <select name="next_taskTheme" class="styled-input" th:with="type=${@dict.getType('sys_select_dtp_sfrw_rwzt')}">
+                                      <option value="">请选择</option>
+                                      <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}" th:selected="${dict.dictLabel}==${bc_next_taskTheme}"></option>
+                                    </select>
+                                </span>
+                                </span></h4>
+                            </div>
+                            <div class="task-buttons">
+                                <div class="ibox-title2">
+                                    <button type="button" class="btn btn-warning" style="background-color: #f56666;" onclick="closeItem()">关闭</button>
+                                    <div class="task-buttons" id="TJ1">
+                                        <button type="button" class="btn btn-primary"   onclick="handleButtonClick()">保 存</button>
+                                    </div>
+
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </form>
+        </div>
+    </div>
+</div>
+<th:block th:include="include :: header('随访任务详情')" />
+<th:block th:include="include :: layout-latest-css" />
+<th:block th:include="include :: jasny-bootstrap-css" />
+
+<th:block th:include="include :: select2-js" />
+<th:block th:include="include :: select2-css" />
+<th:block th:include="include :: bootstrap-select-js" />
+<th:block th:include="include :: bootstrap-select-css" />
+
+<th:block th:include="include :: bootstrap-editable-css" />
+<th:block th:include="include :: bootstrap-fileinput-css" />
+<th:block th:include="include :: jquery-cxselect-js" />
+<th:block th:include="include :: layout-latest-js" />
+<th:block th:include="include :: bootstrap-table-fixed-columns-js" />
+<!--<th:block th:include="include :: ztree-js" />-->
+<!--<th:block th:include="include :: ztree-css" />-->
+<th:block th:include="include :: bootstrap-table-editable-js" />
+<th:block th:include="include :: jasny-bootstrap-js" />
+
+
+
+
+</body>
+
+</html>
+
+<script th:inline="javascript">
+    var formSubmitted = true;
+    var prefix = ctx + "dtp/pmService";
+    var prefix_task = ctx + "task/followTask";
+    var prefix_recipe = ctx + "dtp/recipe";
+    var blfyindex = 0;
+    var closePlanFlag;
+    var flag=false
+    var flagStatus2=0;
+    var flagStatus=false;
+    var TJFlag=false;
+    var formCheck=true;
+    var planId_close;
+    //购药记录
+    var recordsData;
+    // 回显数据随访计划
+    var listPlan1;
+    var listPlan2;
+    // 回显数据随访任务
+    var listTask1;
+    var listTask2;
+    var pcTypes = [];
+    var dosageFrequencyOptions;
+    var dosageFrequencyLookup = {};
+    var planId;
+    var mdmCode
+    var confirmFlag="999";//关闭弹框确认标记
+
+    var preMedicationPlans = [];
+    var currentTreatmentPlans = [];
+    var postProgressionTreatmentPlans = [];
+    var medicationRecords = [];
+    var combinedMedicationRecords = [];
+    var good_response_symptom_ids = [];
+    const sensitiveValues = [
+        '电话号码错误',
+        '本人拒绝',
+        '家属拒绝',
+        '客户拒绝',
+        '接通后配合度差无法获知信息',
+        '领取慈善赠药'
+    ];
+
+
+    document.addEventListener('DOMContentLoaded', function () {
+        // 初始化所有 .fileinput 区域
+        document.querySelectorAll('.fileinput').forEach(function (fileInputWrapper) {
+            initializeImagePreview2(fileInputWrapper);
+        });
+    });
+
+    function initializeImagePreview2(fileInputWrapper) {
+        var fileInput = fileInputWrapper.querySelector('input[type="file"]');
+        var previewThumbnail = fileInputWrapper.querySelector('.fileinput-preview.thumbnail');
+        var largePreview;
+        var clearButton = fileInputWrapper.querySelector('.btn-white[data-dismiss="fileinput"]');
+
+        // 创建大图预览
+        function createLargePreview(imageSrc) {
+            if (largePreview) {
+                largePreview.style.display = 'none';
+                largePreview.remove();
+            }
+            largePreview = document.createElement('div');
+            largePreview.className = 'preview-large';
+            largePreview.style.backgroundImage = 'url(' + imageSrc + ')';
+            largePreview.style.width = '1000px'; // 根据需要调整宽度
+            largePreview.style.height = '900px'; // 根据需要调整高度
+            largePreview.style.backgroundSize = 'contain';
+            largePreview.style.backgroundRepeat = 'no-repeat';
+            largePreview.style.backgroundPosition = 'center';
+            largePreview.style.position = 'absolute';
+            largePreview.style.zIndex = '1000';
+            largePreview.style.display = 'none'; // 初始状态下不显示
+
+            // 添加到DOM
+            fileInputWrapper.appendChild(largePreview);
+
+            // 为大图预览添加事件监听器
+            largePreview.onmouseover = function () {
+                this.style.display = 'block';
+            };
+
+            largePreview.onmousemove = function (e) {
+                updateLargePreviewPosition(e, previewThumbnail);
+            };
+
+            largePreview.onmouseout = function (e) {
+                if (!previewThumbnail.contains(e.relatedTarget)) {
+                    hideLargePreview();
+                }
+            };
+        }
+
+        // 悬停时显示大图
+        function handleMouseOver(e) {
+            /*if (previewThumbnail.style.backgroundImage && !largePreview) {
+                createLargePreview(previewThumbnail.style.backgroundImage.replace(/^url$["']?/, '').replace(/["']?$$/, ''));
+            }*/
+            if (largePreview) {
+                showLargePreview(e);
+            }
+        }
+
+        // 鼠标移动时更新大图位置
+        function handleMouseMove(e) {
+            if (largePreview) {
+                updateLargePreviewPosition(e, previewThumbnail);
+            }
+        }
+
+        // 缩略图移出事件处理
+        function handleMouseOut(e) {
+            if (!largePreview || !largePreview.contains(e.relatedTarget)) {
+                hideLargePreview();
+            }
+        }
+
+        // 显示大图预览
+        function showLargePreview(e) {
+            largePreview.style.display = 'block';
+            updateLargePreviewPosition(e, previewThumbnail);
+        }
+
+        // 更新大图预览位置
+        function updateLargePreviewPosition(e, referenceElement) {
+            const rect = referenceElement.getBoundingClientRect();
+            const winWidth = window.innerWidth;
+            const winHeight = window.innerHeight;
+            const largePreviewWidth = largePreview.offsetWidth;
+            const largePreviewHeight = largePreview.offsetHeight;
+
+            let left = e.pageX ; // 尝试放置在鼠标右侧
+            let top = e.pageY + 150; // 尝试放置在鼠标下方
+
+            // 确保大图预览不超出浏览器窗口边界
+            //if (left + largePreviewWidth > winWidth) left = e.pageX - largePreviewWidth - 150; // 如果右侧空间不足,则放在左侧
+            if (top + largePreviewHeight > winHeight) top = e.pageY - largePreviewHeight; // 如果底部空间不足,则放在上方
+
+            largePreview.style.left = left+100 + 'px';
+            largePreview.style.top = top-200 + 'px';
+        }
+
+        // 隐藏大图预览
+        function hideLargePreview() {
+            if (largePreview) {
+                largePreview.style.display = 'none';
+            }
+        }
+
+        // 移除图片和大图预览
+        function removeImageAndPreview() {
+            previewThumbnail.style.backgroundImage = ''; // 清空缩略图背景图片
+            if (largePreview) {
+                largePreview.style.display = 'none';
+                largePreview.remove(); // 从 DOM 中移除 largePreview
+                largePreview = null;
+            }
+            updateClearButtonVisibility(); // 更新清除按钮的可见性
+        }
+
+        // 更新清除按钮的可见性
+        function updateClearButtonVisibility() {
+            /*if (clearButton) {
+                clearButton.style.display = previewThumbnail.style.backgroundImage ? 'inline-block' : 'none';
+            }*/
+        }
+
+        // 监听文件上传变化事件
+        fileInput.addEventListener('change', function (e) {
+            // 清除现有的大图预览
+            removeImageAndPreview();
+
+            // 读取新文件并更新缩略图背景图片
+            if (this.files && this.files[0]) {
+                var reader = new FileReader();
+                reader.onload = function (event) {
+                    previewThumbnail.style.backgroundImage = 'url(' + event.target.result + ')';
+                    // 立即创建大图预览以反映新图片
+                    createLargePreview(event.target.result);
+                    setupEventListeners(); // 重新设置事件监听器
+                    updateClearButtonVisibility(); // 更新清除按钮的可见性
+                };
+                reader.readAsDataURL(this.files[0]);
+            } else {
+                updateClearButtonVisibility(); // 更新清除按钮的可见性
+            }
+        });
+
+        // 初始化事件监听器
+        function setupEventListeners() {
+            previewThumbnail.onmouseover = handleMouseOver;
+            previewThumbnail.onmousemove = handleMouseMove;
+            previewThumbnail.onmouseout = handleMouseOut;
+
+            // 监听清除按钮点击事件
+            if (clearButton) {
+                clearButton.addEventListener('click', function () {
+                    removeImageAndPreview(); // 清除图片及大图预览
+                });
+            }
+        }
+
+        // 对于回显的图片,确保初始化时创建大图预览并更新清除按钮的可见性
+        if (previewThumbnail.style.backgroundImage) {
+            createLargePreview(previewThumbnail.style.backgroundImage.replace(/^url$["']?/, '').replace(/["']?$$/, ''));
+            updateClearButtonVisibility();
+        } else {
+            updateClearButtonVisibility();
+        }
+
+        // 初始化事件监听器
+        setupEventListeners();
+    }
+    document.addEventListener('DOMContentLoaded', function () {
+        // 初始化所有 .fileinput 区域
+        document.querySelectorAll('.fileinput').forEach(function (fileInputWrapper) {
+            initializeImagePreview(fileInputWrapper);
+        });
+    });
+
+    function initializeImagePreview(fileInputWrapper) {
+        var previewThumbnail = fileInputWrapper.querySelector('.fileinput-preview.thumbnail');
+        var imgElement = previewThumbnail.querySelector('.preview-image');
+        var largePreview;
+        var clearButton = fileInputWrapper.querySelector('.btn-white[data-dismiss="fileinput"]');
+
+        if (!imgElement || !clearButton) return; // 如果没有img元素或清除按钮,则不执行后续操作
+
+        // 创建大图预览
+        function createLargePreview(img) {
+            if (largePreview) {
+                largePreview.style.display = 'none';
+                largePreview.remove();
+            }
+            largePreview = document.createElement('div');
+            largePreview.className = 'preview-large';
+            largePreview.style.backgroundImage = 'url(' + img.src + ')';
+            largePreview.style.width = '1000px'; // 根据需要调整宽度
+            largePreview.style.height = '900px'; // 根据需要调整高度
+            largePreview.style.backgroundSize = 'contain';
+            largePreview.style.backgroundRepeat = 'no-repeat';
+            largePreview.style.backgroundPosition = 'center';
+            largePreview.style.position = 'absolute';
+            largePreview.style.zIndex = '1000';
+            largePreview.style.display = 'none'; // 初始状态下不显示
+
+            // 添加到DOM
+            fileInputWrapper.appendChild(largePreview);
+        }
+
+        // 悬停时显示大图
+        function handleMouseOver(e) {
+            if (imgElement.src && !largePreview) {
+                createLargePreview(imgElement);
+            }
+            if (largePreview) {
+                showLargePreview(e);
+            }
+        }
+
+        // 鼠标移动时更新大图位置
+        function handleMouseMove(e) {
+            if (largePreview) {
+                updateLargePreviewPosition(e, previewThumbnail);
+            }
+        }
+
+        // 缩略图移出事件处理
+        function handleMouseOut(e) {
+            if (!largePreview || !largePreview.contains(e.relatedTarget)) {
+                hideLargePreview();
+            }
+        }
+
+        // 显示大图预览
+        function showLargePreview(e) {
+            largePreview.style.display = 'block';
+            updateLargePreviewPosition(e, previewThumbnail);
+        }
+
+        // 更新大图预览位置
+        function updateLargePreviewPosition(e, referenceElement) {
+            const rect = referenceElement.getBoundingClientRect();
+            const winWidth = window.innerWidth;
+            const winHeight = window.innerHeight;
+            const largePreviewWidth = largePreview.offsetWidth;
+            const largePreviewHeight = largePreview.offsetHeight;
+
+            let left = e.pageX + 150; // 尝试放置在鼠标右侧
+            let top = e.pageY + 150; // 尝试放置在鼠标下方
+
+            // 确保大图预览不超出浏览器窗口边界
+            //if (left + largePreviewWidth > winWidth) left = e.pageX - largePreviewWidth - 150; // 如果右侧空间不足,则放在左侧
+            if (top + largePreviewHeight > winHeight) top = e.pageY - largePreviewHeight; // 如果底部空间不足,则放在上方
+
+            largePreview.style.left = left+100 + 'px';
+            largePreview.style.top = top-200 + 'px'; // 保持原有的偏移量
+        }
+
+        // 隐藏大图预览
+        function hideLargePreview() {
+            if (largePreview) {
+                largePreview.style.display = 'none';
+            }
+        }
+
+        // 初始化事件监听器
+        function setupEventListeners() {
+            previewThumbnail.onmouseover = handleMouseOver;
+            previewThumbnail.onmousemove = handleMouseMove;
+            previewThumbnail.onmouseout = handleMouseOut;
+
+            // 监听清除按钮点击事件
+            clearButton.addEventListener('click', function () {
+                removeImageAndPreview(); // 清除图片及大图预览
+            });
+        }
+
+        // 移除事件监听器
+        function removeEventListeners() {
+            previewThumbnail.onmouseover = null;
+            previewThumbnail.onmousemove = null;
+            previewThumbnail.onmouseout = null;
+        }
+
+        // 监听文件上传变化事件
+        function setupFileInputChange(uploadId) {
+            let upload = fileInputWrapper.querySelector(`#${uploadId}`);
+            if (upload) {
+                upload.addEventListener('change', function (e) {
+                    // 清除现有的大图预览
+                    removeImageAndPreview();
+
+                    // 读取新文件并更新 <img> 元素的 src 属性
+                    if (this.files && this.files[0]) {
+                        var reader = new FileReader();
+                        reader.onload = function (event) {
+                            imgElement.src = event.target.result;
+                            // 立即创建大图预览以反映新图片
+                            createLargePreview(imgElement);
+                            setupEventListeners(); // 重新设置事件监听器
+                            updateClearButtonVisibility(); // 更新清除按钮的可见性
+                        };
+                        reader.readAsDataURL(this.files[0]);
+                    } else {
+                        imgElement.src = ''; // 清空图片源
+                        removeEventListeners(); // 移除事件监听器
+                        updateClearButtonVisibility(); // 更新清除按钮的可见性
+                    }
+                });
+            }
+        }
+
+        // 移除图片和大图预览并更新清除按钮的可见性
+        function removeImageAndPreview() {
+            imgElement.src = ''; // 清空图片源
+            if (largePreview) {
+                largePreview.style.display = 'none';
+                largePreview.remove(); // 从 DOM 中移除 largePreview
+                largePreview = null;
+            }
+            removeEventListeners(); // 移除事件监听器
+            updateClearButtonVisibility(); // 更新清除按钮的可见性
+        }
+
+        // 更新清除按钮的可见性
+        function updateClearButtonVisibility() {
+            if (clearButton && imgElement) {
+                clearButton.style.display = imgElement.src !== '' ? 'inline-block' : 'none';
+            }
+        }
+
+        // 初始化
+        setupEventListeners();
+
+        // 对应的文件上传控件ID
+        let fileId = fileInputWrapper.querySelector('input[type="file"]')?.id;
+        if (fileId) {
+            setupFileInputChange(fileId);
+        }
+
+        // 对于回显的图片,确保初始化时创建大图预览并更新清除按钮的可见性
+        if (imgElement.src) {
+            createLargePreview(imgElement);
+            updateClearButtonVisibility();
+        } else {
+            updateClearButtonVisibility();
+        }
+    }
+
+    function handleButtonClick(){
+        if (submitHandler()) {
+            // 如果submitHandler执行成功,则手动显示模态框
+            $('#myModalClosePlan').modal('show');
+        } else {
+            // 处理失败的情况
+            //alert("操作未成功完成,无法显示模态框。");
+        }
+    }
+    function submitHandler() {
+        var filteredData = []
+        var selectedValue = $('input[name="returnMethod"]:checked').val();
+        if (!selectedValue) {
+            $.modal.alertWarning('请选择回访方式!');
+            formCheck=false;
+            return false;
+        }else{
+            formCheck=true;
+        }
+        let prescriptionImageUpload2 = document.getElementById('returnImgUrlUpload');
+        if (selectedValue === '微信' && prescriptionImageUpload2 != null) {
+            $.modal.alertWarning('微信回访时,回访图片为必填项!');
+            formCheck=false;
+            return false;
+        }else{
+            formCheck=true;
+        }
+        if (selectedValue === '面访' && !$('#InterviewRecord').val()) {
+            $.modal.alertWarning('面访时,面访记录为必填项!');
+            formCheck=false;
+            return false;
+        }else{
+            formCheck=true;
+        }
+        var iscoordinate = $('input[name="iscoordinate"]:checked').val();//是否配合
+        if (!iscoordinate) {
+            $.modal.alertWarning('请选择是否配合!');
+            formCheck=false;
+            return false;
+        }else{
+            formCheck=true;
+        }
+        debugger
+        var returnObject = $("#form-followUp-edit3").serializeArray().find(item => item.name === 'returnObject');
+        if (returnObject.value === '') {
+            $.modal.alertWarning("请选择回访对象");
+            formCheck=false;
+            return false;
+        }else{
+            formCheck=true;
+        }
+        var is_adverse_reaction = $('input[name="is_adverse_reaction"]:checked').val();
+        var stoped = $('input[name="stoped"]:checked').val();
+        var stopForm = $('input[name="stopform"]:checked').val();
+        if (stoped === '不填写完整表单' || stopForm === '不填写完整表单') {
+            $('input[name="is_adverse_reaction"]').not(this).prop('checked', false);
+        }else{
+            if (is_adverse_reaction === '是') {
+                var adverse_reaction_time = $("#form-followUp-edit3").serializeArray().find(item => item.name === 'adverse_reaction_time');
+                if (adverse_reaction_time.value === '') {
+                    $.modal.alertWarning("请选择是否出现不良反应");
+                    formCheck=false;
+                    return false;
+                }else{
+                    formCheck=true;
+                }
+                const blfyTableBody = document.getElementById('blfyTableBody');
+                const rows2 = blfyTableBody.getElementsByTagName('tr');
+                if (rows2.length > 0) {
+                    const rowsbyfys = [];//不良反应table
+                    $('#blfyTableBody tr').each(function () {
+                        const row = {
+                            id: $("#id").val(),
+                            byfyData: $(this).find('td:eq(1)').text(),
+                        };
+                        rowsbyfys.push(row);
+                    });
+                    filteredData.push({name: "good_response_symptom_id", value: JSON.stringify(rowsbyfys)})
+                }else{
+                    $.modal.alertWarning("请选择不良反应症状")
+                    formCheck=false;
+                    return false;
+                }
+            }
+        }
+        // prescriptionImageUpload 处方
+        let prescriptionImageUpload = document.getElementById('returnImgUrlUpload');
+        let prescriptionImageFile = prescriptionImageUpload.files[0];
+        var fd = new FormData();
+        var prescriptionImageUrl =$("#returnImgUrlUpload").val();
+        if (prescriptionImageFile && prescriptionImageFile !== '') {
+            fd.append('file', prescriptionImageFile); // 'file' 是服务器端用来接收文件的字段名
+
+            $.ajax({
+                url: prefix_recipe + "/uploadImg",
+                data: fd,
+                type: "post",
+                processData: false,
+                contentType: false,
+                success: function(result) {
+                    if (result.msg) {
+                        filteredData.push({name: "returnImgUrl", value: result.msg});
+                    }
+                },
+                error: function(xhr, status, error) {
+                    $.modal.alertError("上传图片失败");
+                }
+            });
+        }
+        var reasons_uncooperative = $('input[name="reasons_uncooperative"]:checked').val();
+        var medicationStatus = $('input[name="medicationStatus"]:checked').val();
+        var perpetual_stopdrug_cause = $('input[name="perpetual_stopdrug_cause"]:checked').val();
+        // 需要触发确认的选项列表
+        // 定义自动关闭的条件集合
+        const AUTO_CLOSE_CAUSES = new Set([
+            "离世", "患者因疾病进展/死亡永久停药", "已结束该药治疗疗程",
+            "患者疾病康复好转", "疾病康复好转", "经济原因无购买可能",
+            "不良反应严重", "疾病进展(耐药、效果不明显)更换治疗用药",
+            "回到地级市医院购药"
+        ]);
+        const AUTO_CLOSE_ASK_CAUSES = new Set(["永久停药"]);
+
+        // 判断是否满足自动关闭计划或任务的条件
+        var isAutoCloseCondition = iscoordinate === "2" && medicationStatus === "永久停药" && AUTO_CLOSE_CAUSES.has(perpetual_stopdrug_cause);
+        var isAutoCloseTaskCondition = iscoordinate === "1" && AUTO_CLOSE_ASK_CAUSES.has(medicationStatus);
+
+        if ((sensitiveValues.includes(reasons_uncooperative) || sensitiveValues.includes(medicationStatus)) && flagStatus == false) {
+            // 设置标志位
+            flagStatus2 = 0;
+            $.modal.msg("根据您填写的表单内容,系统判断您需要填写关闭计划原因")
+            showModalAndWaitForClose(function () {
+                console.log("模态框已关闭,继续处理后续逻辑");
+            });
+            updateVisibility4_1();
+            return true;
+            // 显示模态框,并等待用户交互
+        } else{
+            if (isAutoCloseCondition || isAutoCloseTaskCondition) {
+                // 显示确认弹框
+                $.modal.confirm2({
+                    title: '确认关闭',
+                    content: '根据您填写的表单内容,需要您确定是否关闭计划或者任务?',
+                    buttons: {
+                        confirm: {
+                            text: '确认关闭',
+                            btnClass: 'btn-blue',
+                            action: function () {
+                                submitFormWithConfirmFlag(filteredData,"1");
+                            }
+                        },
+                        cancel: {
+                            text: '不关闭',
+                            action: function () {
+                                submitFormWithConfirmFlag(filteredData,"0");
+                            }
+                        }
+                    }
+                });
+            }else{
+                submitFormWithConfirmFlag(filteredData,"888");
+            }
+
+        }
+
+    }
+    function submitFormWithConfirmFlag(filteredData,flagValue) {
+        confirmFlag = flagValue;
+        filteredData.push({name: "confirmFlag", value: confirmFlag});
+        // 获取表单数据并过滤
+        var formData = $("#form-followUp-edit3").serializeArray();
+
+
+
+        $.each(formData, function (index, field) {
+            var element = $('#' + field.name);
+
+            // 检查元素本身是否隐藏或其祖先元素是否隐藏
+            if (element.closest(':hidden').length === 0 && field.value !== '') {
+                filteredData.push(field);
+            }
+        });
+
+        var defaultFormData = $("#form-defaultValue").serializeArray();
+
+        // 合并两个数据集 到这里开始写后端接口了
+        filteredData = filteredData.concat(defaultFormData);
+        $.operate.saveTab(prefix_task + "/followTaskEdit", filteredData);
+        return false;
+    }
+
+    function extractTableData(tableId) {
+        const dataRows = [];
+        if (tableId == 'preMedicationPlanTable') {
+            const tableBody = document.getElementById(tableId + 'Body');
+            if (tableBody) {
+                const rows = tableBody.getElementsByTagName('tr');
+                if (rows.length > 0) {
+                    const rowsbyfys = [];
+                    $('#preMedicationPlanTableBody tr').each(function () {
+                        const row = {
+                            taskId: $("#id").val(),
+                            id: $(this).find('td:eq(0)').text(),
+                            pre_medication_plan: $(this).find('td:eq(1)').text(),
+                            pre_medication_plan_time: $(this).find('td:eq(2)').text(),
+
+                        };
+                        dataRows.push(row);
+                    });
+                }
+            }
+        }
+        if (tableId == 'currentTreatmentPlanTable') {
+
+            const tableBody = document.getElementById(tableId + 'Body');
+            if (tableBody) {
+                const rows = tableBody.getElementsByTagName('tr');
+                if (rows.length > 0) {
+                    $('#currentTreatmentPlanTableBody tr').each(function () {
+                        const row = {
+                            taskId: $("#id").val(),
+                            id: $(this).find('td:eq(0)').text(),
+                            current_treatment_plan: $(this).find('td:eq(1)').text(),
+                            current_treatment_plan_time: $(this).find('td:eq(2)').text(),
+
+                        };
+                        dataRows.push(row);
+                    });
+                }
+            }
+
+        }
+        if (tableId == 'postProgressionTreatmentPlanTable') {
+
+            const tableBody = document.getElementById(tableId + 'Body');
+            if (tableBody) {
+                const rows = tableBody.getElementsByTagName('tr');
+                if (rows.length > 0) {
+                    $('#postProgressionTreatmentPlanTableBody tr').each(function () {
+                        const row = {
+                            taskId: $("#id").val(),
+                            id: $(this).find('td:eq(0)').text(),
+                            post_progression_treatment_plan: $(this).find('td:eq(1)').text(),
+                            post_progression_treatment_time: $(this).find('td:eq(2)').text(),
+
+                        };
+                        dataRows.push(row);
+                    });
+                }
+            }
+        }
+        if (tableId == 'medicationRecordTable') {
+
+            const tableBody = document.getElementById(tableId + 'Body');
+            if (tableBody) {
+                const rows = tableBody.getElementsByTagName('tr');
+                if (rows.length > 0) {
+                    $('#medicationRecordTableBody tr').each(function () {
+                        const row = {
+                            taskId: $("#id").val(),
+                            id: $(this).find('td:eq(0)').text(),
+                            durgName: $(this).find('td:eq(1)').text(),
+                            jiliang: $(this).find('td:eq(2)').text(),
+                            startyyTime: $(this).find('td:eq(3)').text(),
+                            tyTime: $(this).find('td:eq(4)').text(),
+                            lastsyTime: $(this).find('td:eq(5)').text(),
+                            shiypv: $(this).find('td:eq(6)').text(),
+                            zhuyishix: $(this).find('td:eq(7)').text(),
+                        };
+                        dataRows.push(row);
+                    });
+                }
+            }
+
+        }
+        if (tableId == 'combinedMedicationRecordTable') {
+            const tableBody = document.getElementById(tableId + 'Body');
+            if (tableBody) {
+                const rows = tableBody.getElementsByTagName('tr');
+                if (rows.length > 0) {
+                    $('#combinedMedicationRecordTableBody tr').each(function () {
+                        const row = {
+                            taskId: $("#id").val(),
+                            id: $(this).find('td:eq(0)').text(),
+                            hbdurgName: $(this).find('td:eq(1)').text(),
+                            hbjiliang: $(this).find('td:eq(2)').text(),
+                            hbstartyyTime: $(this).find('td:eq(3)').text(),
+                            hbtyTime: $(this).find('td:eq(4)').text(),
+                            hbhblastsyTime: $(this).find('td:eq(5)').text(),
+                            hbshiypv: $(this).find('td:eq(6)').text(),
+                            hbzhuyishix: $(this).find('td:eq(7)').text(),
+                        };
+                        dataRows.push(row);
+                    });
+                }
+            }
+
+        }
+
+        return dataRows;
+    }
+    // 定义一个函数来显示模态框,并在模态框关闭时调用回调函数
+    function showModalAndWaitForClose(callback) {
+        // 监听模态框关闭事件
+        $('#myModalClosePlan').on('close', function () {
+            callback(); // 当模态框关闭时调用回调函数
+        });
+    }
+    function deleteRow(button) {
+        const row = button.parentNode.parentNode;
+        row.remove();
+        updateIndices(); // 删除后更新所有行的序号
+    }
+
+    // 更新所有行的序号
+    function updateIndices(value) {
+        if (value == 0) {
+            $('#blfyTableBody tr').each(function (index) {
+                $(this).find('td:first-child').text(index + 1); // 设置序号为当前索引加1
+            });
+        }
+        if (value == 1) {
+            $('#preMedicationPlanTableBody tr').each(function (index) {
+                $(this).find('td:first-child').text(index + 1); // 设置序号为当前索引加1
+            });
+        }
+        if (value == 2) {
+            $('#currentTreatmentPlanTableBody tr').each(function (index) {
+                $(this).find('td:first-child').text(index + 1); // 设置序号为当前索引加1
+            });
+        }
+        if (value == 3) {
+            $('#postProgressionTreatmentPlanTableBody tr').each(function (index) {
+                $(this).find('td:first-child').text(index + 1); // 设置序号为当前索引加1
+            });
+        }
+        if (value == 4) {
+            $('#medicationRecordTableBody tr').each(function (index) {
+                $(this).find('td:first-child').text(index + 1); // 设置序号为当前索引加1
+            });
+        }
+        if (value == 5) {
+            $('#combinedMedicationRecordTableBody tr').each(function (index) {
+                $(this).find('td:first-child').text(index + 1); // 设置序号为当前索引加1
+            });
+        }
+
+    }
+
+    function selectAdverseReactions(value) {
+
+        var province = $('.province').val();
+        var city = $('.city').val();
+        var area = $('.area').val();
+        $('.province, .city, .area').val('');
+
+        // 打印不良选择的不良反应
+        console.log('Selected: ' + province + ', ' + city + ', ' + area);
+        var row = '<tr>' +
+            '<td></td>' +
+            '<td>' + province + ', ' + city + ', ' + area + '</td>' +
+            '<td><button class="btn-danger" onclick="deleteRow(this)">删除</button></td>' +
+            '</tr>';
+        $('#blfyTableBody').append(row);
+        blfyindex++; // 增加索引
+        updateIndices(0); // 添加后更新所有行的序号
+    };
+
+    function saveRow(button) {
+        if (button == 1) {
+            // 获取表单数据
+            var pre_medication_time = document.getElementById('pre_medication_time').value;
+            var pre_medication_plan = document.getElementById('pre_medication_plan').value;
+
+            // 检查数据是否为空
+            if (!pre_medication_time || !pre_medication_plan) {
+                $.modal.alertWarning('请填写方案信息!');
+                return;
+            }
+            var row = '<tr>' +
+                '<td></td>' +
+                '<td>' + pre_medication_plan + '</td>' +
+                '<td>' + pre_medication_time + '</td>' +
+                '<td><button class="btn-danger" onclick="deleteRow(this)">删除</button></td>' +
+                '</tr>';
+            $('#preMedicationPlanTableBody').append(row);
+            blfyindex++; // 增加索引
+            updateIndices(1); // 添加后更新所有行的序号
+            // 清空表单输入框
+            document.getElementById('pre_medication_time').value = '';
+            document.getElementById('pre_medication_plan').selectedIndex = 0;
+            // 关闭模态框 currentTreatmentPlanTableBody postProgressionTreatmentPlanTableBody
+            $('#myModal1').modal('hide');
+        }
+        if (button == 2) {
+            // 获取表单数据
+            var current_treatment_plan_time = document.getElementById('current_treatment_plan_time').value;
+            var current_treatment_plan = document.getElementById('current_treatment_plan').value;
+
+            // 检查数据是否为空
+            if (!current_treatment_plan || !current_treatment_plan_time) {
+                $.modal.alertWarning('请填写方案信息!');
+                return;
+            }
+            var row = '<tr>' +
+                '<td></td>' +
+                '<td>' + current_treatment_plan + '</td>' +
+                '<td>' + current_treatment_plan_time + '</td>' +
+                '<td><button class="btn-danger" onclick="deleteRow(this)">删除</button></td>' +
+                '</tr>';
+            $('#currentTreatmentPlanTableBody').append(row);
+            blfyindex++; // 增加索引
+            updateIndices(2); // 添加后更新所有行的序号
+            // 清空表单输入框
+            $("#form-mqzlfa-add")[0].reset();
+            //document.getElementById('current_treatment_plan_time').value = '';
+            //document.getElementById('current_treatment_plan').selectedIndex = 0;
+            // 关闭模态框
+            $('#myModal2').modal('hide');
+        }
+        if (button == 3) {
+            // 获取表单数据
+            var post_progression_treatment_time = document.getElementById('post_progression_treatment_time').value;
+            var post_progression_treatment_plan = document.getElementById('post_progression_treatment_plan').value;
+
+            // 检查数据是否为空
+            if (!post_progression_treatment_time || !post_progression_treatment_plan) {
+                $.modal.alertWarning('请填写方案信息!');
+                return;
+            }
+            var row = '<tr>' +
+                '<td></td>' +
+                '<td>' + post_progression_treatment_plan + '</td>' +
+                '<td>' + post_progression_treatment_time + '</td>' +
+                '<td><button class="btn-danger" onclick="deleteRow(this)">删除</button></td>' +
+                '</tr>';
+            $('#postProgressionTreatmentPlanTableBody').append(row);
+            blfyindex++; // 增加索引
+            updateIndices(3); // 添加后更新所有行的序号
+            // 清空表单输入框
+            $("#form-jzhzlfa-add")[0].reset();
+            document.getElementById('post_progression_treatment_time').value = '';
+            document.getElementById('post_progression_treatment_plan').selectedIndex = 0;
+            // 关闭模态框 currentTreatmentPlanTableBody postProgressionTreatmentPlanTableBody
+            $('#myModal3').modal('hide');
+        }
+        if (button == 4) {
+            // 获取表单数据
+            var durgName = document.getElementById('durgName').value;
+            var jiliang = document.getElementById('jiliang').value;
+            var startyyTime = document.getElementById('startyyTime').value;
+            var tyTime = document.getElementById('tyTime').value;
+            var lastsyTime = document.getElementById('lastsyTime').value;
+            var shiypv = document.getElementById('shiypv').value;
+            var zhuyishix = document.getElementById('zhuyishix').value;
+            // 检查数据是否为空
+            if (!durgName) {
+                $.modal.alertWarning('请填写名称!');
+                return;
+            }
+            if (!jiliang) {
+                $.modal.alertWarning('请填写剂量!');
+                return;
+            }
+
+            var row = '<tr>' +
+                '<td></td>' +
+                '<td>' + durgName + '</td>' +
+                '<td>' + jiliang + '</td>' +
+                '<td>' + startyyTime + '</td>' +
+                '<td>' + tyTime + '</td>' +
+                '<td>' + lastsyTime + '</td>' +
+                '<td>' + shiypv + '</td>' +
+                '<td>' + zhuyishix + '</td>' +
+                '<td><button class="btn-danger" onclick="deleteRow(this)">删除</button></td>' +
+                '</tr>';
+            $('#medicationRecordTableBody').append(row);
+            blfyindex++; // 增加索引
+            updateIndices(4); // 添加后更新所有行的序号
+            // 清空表单输入框
+            $("#form-yyjl-add")[0].reset();
+            $('#myModal4').modal('hide');
+        }
+        if (button == 5) {
+
+
+            // 获取表单数据
+            var hbdurgName = document.getElementById('hbdurgName').value;
+            var hbjiliang = document.getElementById('hbjiliang').value;
+            var hbstartyyTime = document.getElementById('hbstartyyTime').value;
+            var hbtyTime = document.getElementById('hbtyTime').value;
+            var hblastsyTime = document.getElementById('hblastsyTime').value;
+            var hbshiypv = document.getElementById('hbshiypv').value;
+            var hbzhuyishix = document.getElementById('hbzhuyishix').value;
+            // 检查数据是否为空
+            if (!hbdurgName) {
+                $.modal.alertWarning('请填写名称!');
+                return;
+            }
+            if (!hbjiliang) {
+                $.modal.alertWarning('请填写剂量!');
+                return;
+            }
+            var row = '<tr>' +
+                '<td></td>' +
+                '<td>' + hbdurgName + '</td>' +
+                '<td>' + hbjiliang + '</td>' +
+                '<td>' + hbstartyyTime + '</td>' +
+                '<td>' + hbtyTime + '</td>' +
+                '<td>' + hblastsyTime + '</td>' +
+                '<td>' + hbshiypv + '</td>' +
+                '<td>' + hbzhuyishix + '</td>' +
+                '<td><button class="btn-danger" onclick="deleteRow(this)">删除</button></td>' +
+                '</tr>';
+            $('#combinedMedicationRecordTableBody').append(row);
+            blfyindex++; // 增加索引
+            updateIndices(5); // 添加后更新所有行的序号
+            // 清空表单输入框
+            $("#form-hbyyjl-add")[0].reset();
+            $('#myModal6').modal('hide');
+        }
+
+    }
+
+    //初始化加载
+    $(document).ready(function () {
+
+
+        // 通过默认url获取
+        var urlChina = prefix + '/selectAdverseReactions';
+        $.cxSelect.defaults.url = urlChina;
+        $('#element1').cxSelect({
+            selects: ['province', 'city', 'area'],
+            nodata: 'none'
+        });
+
+        // 初始化 Select2 插件
+        for (let i = 1; i <= 6; i++) {
+            $(`#category-select${i}`).select2({
+                placeholder: "请选择或直接输入搜索",
+                allowClear: true
+            }).data('readonly', true); // 设置为只读模式
+            // 阻止键盘交互
+            document.getElementById(`category-select${i}`).addEventListener('keydown', (e) => e.preventDefault());
+        }
+
+        $.ajax({
+            url: ctx + 'sp/sp/typeDate',
+            method: 'GET',
+            dataType: 'json',
+            success: function(data) {
+                console.log("Data received from server:", data); // 添加调试信息
+
+                var selects = [
+                    $('#category-select1'),
+                    $('#category-select2'),
+                    $('#category-select3'),
+                    $('#category-select4'),
+                    $('#category-select5'),
+                    $('#category-select6')
+                ];
+
+                selects.forEach(function(select) {
+                    select.find('option').not(':first').remove();
+                });
+
+                selects.forEach(function(select) {
+                    if (!select.find('option:first').length) {
+                        $('<option>', {value: '', text: '请选择疾病'}).prependTo(select);
+                    }
+                });
+
+                var dl = /*[[${dl}]]*/ '';
+                var dlParsed = JSON.parse(dl);
+                var dlIds = Array.isArray(dlParsed) ? dlParsed.map(item => item.id.toString()) : [];
+                console.log("Selected IDs to be preselected:", dlIds); // 添加调试信息
+
+                var selectedOptionsMap = {};
+                selects.forEach((_, index) => selectedOptionsMap[index] = []);
+
+                $.each(data.value || [], function(index, item) {
+                    var selectIndex = item.dict_key - 1;
+                    if (selectIndex >= 0 && selectIndex < selects.length) {
+                        $('<option>', {value: item.id, text: item.categoryName}).appendTo(selects[selectIndex]);
+                        if (dlIds.includes(item.id.toString())) {
+                            selectedOptionsMap[selectIndex].push(item.id.toString());
+                        }
+                    }
+                });
+
+                selects.forEach(function(select, index) {
+                    if (selectedOptionsMap[index].length > 0) {
+                        console.log(`Setting selected values for select ${index}:`, selectedOptionsMap[index]); // 添加调试信息
+                        select.val(selectedOptionsMap[index]).trigger('change');
+                    }
+                });
+// // 禁用下拉框以模拟只读效果
+//                 for (let i = 1; i <= 6; i++) {
+//                     let selectElement = $(`#category-select${i}`);
+//                     selectElement.prop('disabled', true).attr('aria-disabled', 'true');
+//
+//                     // 移除或调整特定的样式
+//                     let select2Container = selectElement.data('select2').$container;
+//                     select2Container.removeClass('select2-container--disabled'); // 移除禁用样式类
+//
+//                     // 或者直接设置背景颜色为透明或其它颜色
+//                     select2Container.find('.select2-selection--single').css('background-color', 'transparent');
+//                 }
+            },
+            error: function(xhr, status, error) {
+                console.error("Failed to load disease categories:", error);
+            }
+        });
+
+
+        $("#myModal5").hide();
+
+
+        // 监听点击事件,激活指定的选项卡
+        $('#myUlTabs li a').click(function (e) {
+            // 阻止默认行为
+            //e.preventDefault();
+            // 获取当前点击的选项卡链接
+            var $this = $(this);
+            if ($this.attr('href') === '#tab-1') {
+                formSubmitted = true;
+            }
+            if ($this.attr('href') === '#tab-2') {
+                formSubmitted = false;
+                // 添加 active 类到当前点击的选项卡
+                $(this).addClass('active');
+                $this.attr('aria-expanded', 'true');
+
+            }
+            // 检查是否为“用药购药”选项卡
+            if ($this.attr('href') === '#tab-2') {
+                // 初始化表格
+                initializeTableForTab('tab-2');
+            }
+            if ($this.attr('href') === '#tab-3') {
+                // 初始化表格
+                initializeTableForTab('tab-3');
+                // 初始化表格
+                initializeTableForTab('tab-4');
+            }
+
+        });
+
+        $('#myTabs2 li a').click(function (e) {
+            // 阻止默认行为
+            e.preventDefault();
+            // 获取当前点击的选项卡链接
+            var $this = $(this);
+            // 移除所有选项卡的 active 类
+            //$('#myTabs li').removeClass('active');
+            //$('.tab-pane').removeClass('active in');
+            $('#myTabs2 li a').addClass('active');
+            // 添加 active 类到当前点击的选项卡
+            $this.parent().addClass('active');
+            // 获取目标选项卡面板的 ID
+            var target = $this.attr('href');
+            // 展示对应的选项卡面板,并更新 aria-expanded
+            $(target).addClass('active in');
+            $this.attr('aria-expanded', 'true');
+            // 执行点击链接对应的动作
+            $this.tab('show');
+            // 移除所有选项卡的激活类
+            //$('.active').removeClass('active');
+            $(this).addClass('active');
+            // 滚动到目标选项卡位置
+            // 将目标内容区滚动到视口中央
+            $('html, body').animate({
+                scrollTop: $(target).offset().top - ($(window).height() / 2)
+            }, 500); // 500毫秒动画时间
+            // scrollToTab($this);
+            // 重新初始化当前选项卡内的表格
+            var tableId = target.replace('#', '');
+            // initializeTableForTab(tableId);
+        });
+
+        //疾病大类
+
+
+
+
+        //购药记录
+        recordsData = /*[[${recordsData}]]*/ [];
+        // 回显数据随访计划
+        listPlan1 = /*[[${Plan1}]]*/ '';
+        listPlan2 = /*[[${Plan2}]]*/ '';
+
+        // 回显数据随访计划
+        listTask1 = /*[[${listTask1}]]*/ [];
+        listTask2 = /*[[${listTask2}]]*/ [];
+        /*<![CDATA[*/
+        pcTypes = /*[[${@dict.getType('sys_select_dtp_ysfw_sfpx')}]]*/ [];
+        /*]]>*/
+        // 提取 dictLabel 字段
+        dosageFrequencyOptions = pcTypes.map(function (item) {
+            return {
+                value: item.dictValue, // 存储的值
+                text: item.dictLabel   // 显示的文本
+            };
+        });
+        // 提取 dictLabel 和 dictValue 字段,并创建一个查找表
+        pcTypes.forEach(function (item) {
+            dosageFrequencyLookup[item.dictValue] = {label: item.dictLabel, value: item.dictValue};
+        });
+        // 构建 options 用于 editable 插件
+        dosageFrequencyOptions = pcTypes.map(function (item) {
+            return {
+                value: item.dictValue, // 存储的值
+                text: item.dictLabel   // 显示的文本
+            };
+        });
+        console.log(pcTypes);
+        var planId_cg = /*[[${planId_cg}]]*/ '';
+        var planId_tl = /*[[${planId_tl}]]*/ '';
+        // if (planId_cg) {
+        //
+        //     $('#cgsfnormal').show();
+        // } else {
+        //     $('#cgsfnormal').hide();
+        // }
+        // if (planId_tl) {
+        //     $('#tlzhback').show();
+        // } else {
+        //     $('#tlzhback').hide();
+        // }
+
+        /*<![CDATA[*/
+        var imagingInsuranceTypeList = /*[[${imagingInsuranceType}]]*/ '[]';
+        /*]]>*/
+
+        if (imagingInsuranceTypeList && imagingInsuranceTypeList !== '[]') {
+            check(imagingInsuranceTypeList);
+        }
+
+
+        /*<![CDATA[*/
+        var tumorMarkerCheckList = /*[[${tumorMarkerCheck}]]*/ '[]';
+        /*]]>*/
+        if(tumorMarkerCheckList && tumorMarkerCheckList !== '[]'){
+            check2(tumorMarkerCheckList);
+        }
+
+
+        /*<![CDATA[*/
+        var treatmentTypeList = /*[[${treatmentType}]]*/ '[]';
+        /*]]>*/
+        if(treatmentTypeList && treatmentTypeList !== '[]'){
+            check3(treatmentTypeList);
+        }
+        // 初始化时回显 影像学检查 已选中的值
+        function check(imagingInsuranceTypeList) {
+            // 将字符串转换为数组
+            var imagingInsuranceType = JSON.parse(imagingInsuranceTypeList.replace(/'/g, '"'));
+
+            $.each(imagingInsuranceType, function(index, value) {
+                var trimmedValue = value.trim();
+                console.log("Checking for value:", trimmedValue);
+
+                // 使用 jQuery 的属性选择器函数形式来避免手动拼接字符串的问题
+                var checkbox = $('input[name="imagingInsuranceType"]').filter(function() {
+                    var checkboxValue = $(this).val().trim(); // 确保值也去除空格
+                    console.log("Checkbox value:", checkboxValue);
+                    return checkboxValue === trimmedValue;
+                });
+
+                if (checkbox.length > 0) {
+                    console.log("Found checkbox with value:", trimmedValue);
+                    checkbox.prop('checked', true);
+                } else {
+                    console.warn("No checkbox found with value:", trimmedValue);
+                }
+            });
+        }
+        // 初始化时回显 影像学检查 已选中的值
+        function check2(tumorMarkerCheckList) {
+            // 将字符串转换为数组
+            var tumorMarkerCheck = JSON.parse(tumorMarkerCheckList.replace(/'/g, '"'));
+
+            $.each(tumorMarkerCheck, function(index, value) {
+                var trimmedValue = value.trim();
+                console.log("Checking for value:", trimmedValue);
+
+                // 使用 jQuery 的属性选择器函数形式来避免手动拼接字符串的问题
+                var checkbox = $('input[name="tumorMarkerCheck"]').filter(function() {
+                    var checkboxValue = $(this).val().trim(); // 确保值也去除空格
+                    console.log("Checkbox value:", checkboxValue);
+                    return checkboxValue === trimmedValue;
+                });
+
+                if (checkbox.length > 0) {
+                    console.log("Found checkbox with value:", trimmedValue);
+                    checkbox.prop('checked', true);
+                } else {
+                    console.warn("No checkbox found with value:", trimmedValue);
+                }
+            });
+        }
+        // 初始化时回显 影像学检查 已选中的值
+        function check3(treatmentTypeList) {
+            // 将字符串转换为数组
+            var treatmentType = JSON.parse(treatmentTypeList.replace(/'/g, '"'));
+
+            $.each(treatmentType, function(index, value) {
+                var trimmedValue = value.trim();
+                console.log("Checking for value:", trimmedValue);
+
+                // 使用 jQuery 的属性选择器函数形式来避免手动拼接字符串的问题
+                var checkbox = $('input[name="treatmentType"]').filter(function() {
+                    var checkboxValue = $(this).val().trim(); // 确保值也去除空格
+                    console.log("Checkbox value:", checkboxValue);
+                    return checkboxValue === trimmedValue;
+                });
+
+                if (checkbox.length > 0) {
+                    console.log("Found checkbox with value:", trimmedValue);
+                    checkbox.prop('checked', true);
+                } else {
+                    console.warn("No checkbox found with value:", trimmedValue);
+                }
+            });
+        }
+
+
+
+
+        //表单中的几个table初始化
+        preMedicationPlans = /*[[${preMedicationPlan}]]*/ [];
+        // 如果后端传递的是JSON字符串,先解析成数组
+        if (typeof preMedicationPlans === 'string') {
+            try {
+                preMedicationPlans = JSON.parse(preMedicationPlans);
+            } catch (e) {
+                console.error("解析JSON字符串失败:", e);
+                preMedicationPlans = []; // 解析失败时初始化为空数组
+            }
+        }
+        currentTreatmentPlans = /*[[${currentTreatmentPlan}]]*/ [];
+        // 如果后端传递的是JSON字符串,先解析成数组
+        if (typeof currentTreatmentPlans === 'string') {
+            try {
+                currentTreatmentPlans = JSON.parse(currentTreatmentPlans);
+            } catch (e) {
+                console.error("解析JSON字符串失败:", e);
+                currentTreatmentPlans = []; // 解析失败时初始化为空数组
+            }
+        }
+        postProgressionTreatmentPlans = /*[[${postProgressionTreatmentPlan}]]*/ [];
+        // 如果后端传递的是JSON字符串,先解析成数组
+        if (typeof postProgressionTreatmentPlans === 'string') {
+            try {
+                postProgressionTreatmentPlans = JSON.parse(postProgressionTreatmentPlans);
+            } catch (e) {
+                console.error("解析JSON字符串失败:", e);
+                postProgressionTreatmentPlans = []; // 解析失败时初始化为空数组
+            }
+        }
+        medicationRecords = /*[[${medicationRecord}]]*/ [];
+        // 如果后端传递的是JSON字符串,先解析成数组
+        if (typeof medicationRecords === 'string') {
+            try {
+                medicationRecords = JSON.parse(medicationRecords);
+            } catch (e) {
+                console.error("解析JSON字符串失败:", e);
+                medicationRecords = []; // 解析失败时初始化为空数组
+            }
+        }
+        combinedMedicationRecords = /*[[${combinedMedicationRecord}]]*/ [];
+        // 如果后端传递的是JSON字符串,先解析成数组
+        if (typeof combinedMedicationRecords === 'string') {
+            try {
+                combinedMedicationRecords = JSON.parse(combinedMedicationRecords);
+            } catch (e) {
+                console.error("解析JSON字符串失败:", e);
+                combinedMedicationRecords = []; // 解析失败时初始化为空数组
+            }
+        }
+        good_response_symptom_ids = /*[[${good_response_symptom_id}]]*/ [];
+        // 如果后端传递的是JSON字符串,先解析成数组
+        if (typeof good_response_symptom_ids === 'string') {
+            try {
+                good_response_symptom_ids = JSON.parse(good_response_symptom_ids);
+            } catch (e) {
+                console.error("解析JSON字符串失败:", e);
+                good_response_symptom_ids = []; // 解析失败时初始化为空数组
+            }
+        }
+        addRowsToTable(preMedicationPlans,'preMedicationPlanTableBody');
+        addRowsToTable(currentTreatmentPlans,'currentTreatmentPlanTableBody');
+        addRowsToTable(postProgressionTreatmentPlans,'postProgressionTreatmentPlanTableBody');
+        addRowsToTable(medicationRecords,'medicationRecordTableBody');
+        addRowsToTable(combinedMedicationRecords,'combinedMedicationRecordTableBody');
+        addRowsToTable(good_response_symptom_ids,'blfyTableBody');
+
+
+
+
+        // 监听回访方式的选择变化
+        $('input[name="returnMethod"]').on('change', updateVisibility1);
+        //是否配合
+        $('input[name="iscoordinate"]').on('change', updateVisibility2);
+
+        // 监听不良反应的选择变化
+        $('input[name="is_adverse_reaction"]').on('change', updateVisibility3);
+        //监听合并用药是否引起不良反应
+        $('input[name="combinedMedicationAdverseReaction"]').on('change', updateVisibility3);
+
+        // 监听用药状态的选择变化
+        $('input[name="medicationStatus"]').on('change', updateVisibility4);
+        $('input[name="reasons_uncooperative"]').on('change', updateVisibility4);
+        $('input[name="perpetual_stopdrug_cause"]').on('change', updateVisibility4_2);
+        $('input[name="stoped"]').on('change', updateVisibility5);
+        $('input[name="stopform"]').on('change', updateVisibility5);
+
+        // 监听弹框 关闭计划原因
+        $('input[name="reason"]').on('change', updateVisibility6);
+        $('input[name="drugsStatus"]').on('change', updateVisibility6);//用药状态调整
+        $('input[name="drugsStop"]').on('change', updateVisibility6_1);//永久停药类型
+        $('input[name="flowNo"]').on('change', updateVisibility6);//随访不配合原因
+        $('input[name="closePlanScope"]').on('change', updateVisibility6_1);//关闭计划范围
+        $('input[name="closeType"]').on('change', updateVisibility6_2);//计划类型
+
+        // 监听治疗类型选择 是否复查 变化
+        $('input[name="isReview"]').on('change', updateVisibility7);//治疗类型
+        $('input[name="imagingInsuranceType"]').on('change', updateVisibility8);//治疗类型
+        $('input[name="clinicalInteraction"]').on('change', updateVisibility9);//计划类型
+        $('input[name="unnecessaryRepeatedMedication"]').on('change', updateVisibility10);//计划类型
+        $('input[name="medicationError"]').on('change', updateVisibility11);//计划类型
+        $('input[name="isConsultation"]').on('change', updateVisibility12);//计划类型
+        $('input[name="treatmentType"]').on('change', updateVisibility13);//治疗类型
+        $('input[name="tumorMarkerCheck"]').on('change', updateVisibility14);//肿瘤标记物检查
+
+        // 初始化时设置默认显示状态
+        updateVisibility1();
+        updateVisibility2();
+        updateVisibility3();
+        updateVisibility4();
+        updateVisibility4_2();
+        updateVisibility5();
+        updateVisibility7();
+        updateVisibility8();
+        updateVisibility9();
+        updateVisibility10();
+        updateVisibility11();
+        updateVisibility12();
+        updateVisibility13();
+        updateVisibility14();
+    });
+    function refreshData(response) {
+        // 解析 JSON 字符串
+        const parsedResponse = JSON.parse(response);
+        const data = parsedResponse.data;
+        // 清空原有的内容
+        $('#drug-info-container').empty();
+        // 遍历 data 数组并为每个对象生成 HTML 内容
+        data.forEach(function(item, index) {
+            // 使用模板字符串生成 HTML 内容
+            const htmlContent = `
+            <div style="margin-bottom: 10px; border: 1px solid #ccc; padding: 10px;">
+                <span>
+                    <strong>药品: <code class="genericName">${item.genericName}</code></strong><br>
+                    <strong>用药状态: <code class="medicationStatus">${item.medication_status}</code></strong><br>
+                    <strong>剩余用药天数: <code class="sumTotal">${item.sum_total}天</code></strong><br>
+                </span>
+            </div>
+        `;
+
+            // 将生成的 HTML 内容添加到容器中
+            $('#drug-info-container').append(htmlContent);
+        });
+    }
+    function addRowsToTable(data,tbodyId) {
+        // 检查 data 是否为 null 或 undefined
+        if (!Array.isArray(data)) {
+            console.warn("提供的数据不是数组", data);
+            return;
+        }
+        if(data.length > 0){
+            var tbody = document.getElementById(tbodyId);
+            if(tbodyId === 'blfyTableBody'){
+                data.forEach(function(item, index) {
+                    var row = document.createElement('tr');
+                    // 序号
+                    var tdIndex = document.createElement('td');
+                    tdIndex.textContent = index + 1;
+                    row.appendChild(tdIndex);
+                    // 不良反应症状
+                    var tdPlan = document.createElement('td');
+                    tdPlan.textContent = item.byfyData;
+                    row.appendChild(tdPlan);
+                    // 操作(这里可以添加按钮或其他交互元素)
+                    var tdAction = document.createElement('td');
+                    // 示例: 添加一个删除按钮
+                    var deleteButton = document.createElement('button');
+                    deleteButton.textContent = '删除';
+                    deleteButton.onclick = function() { deleteRow(this); };
+                    tdAction.appendChild(deleteButton);
+                    row.appendChild(tdAction);
+
+                    tbody.appendChild(row);
+                });
+
+            }
+            if(tbodyId === 'preMedicationPlanTableBody' || tbodyId === 'currentTreatmentPlanTableBody' || tbodyId === 'postProgressionTreatmentPlanTableBody'){
+                if(tbodyId === 'preMedicationPlanTableBody'){
+                    data.forEach(function(item, index) {
+                        var row = document.createElement('tr');
+
+                        // 序号
+                        var tdIndex = document.createElement('td');
+                        tdIndex.textContent = item.id;
+                        row.appendChild(tdIndex);
+
+                        // 治疗方案
+                        var tdPlan = document.createElement('td');
+                        tdPlan.textContent = item.pre_medication_plan;
+                        row.appendChild(tdPlan);
+
+                        // 治疗时间
+                        var tdTime = document.createElement('td');
+                        tdTime.textContent = item.pre_medication_plan_time;
+                        row.appendChild(tdTime);
+
+                        // 操作(这里可以添加按钮或其他交互元素)
+                        var tdAction = document.createElement('td');
+                        // 示例: 添加一个删除按钮
+                        var deleteButton = document.createElement('button');
+                        deleteButton.textContent = '删除';
+                        deleteButton.onclick = function() { deleteRow(this); };
+                        tdAction.appendChild(deleteButton);
+                        row.appendChild(tdAction);
+
+                        tbody.appendChild(row);
+                        updateIndices(1);
+                    });
+                }
+                if(tbodyId === 'currentTreatmentPlanTableBody'){
+                    data.forEach(function(item, index) {
+                        var row = document.createElement('tr');
+
+                        // 序号
+                        var tdIndex = document.createElement('td');
+                        tdIndex.textContent = item.id;
+                        row.appendChild(tdIndex);
+
+                        // 治疗方案
+                        var tdPlan = document.createElement('td');
+                        tdPlan.textContent = item.current_treatment_plan;
+                        row.appendChild(tdPlan);
+
+                        // 治疗时间
+                        var tdTime = document.createElement('td');
+                        tdTime.textContent = item.current_treatment_plan_time;
+                        row.appendChild(tdTime);
+
+                        // 操作(这里可以添加按钮或其他交互元素)
+                        var tdAction = document.createElement('td');
+                        // 示例: 添加一个删除按钮
+                        var deleteButton = document.createElement('button');
+                        deleteButton.textContent = '删除';
+                        deleteButton.onclick = function() { deleteRow(this); };
+                        tdAction.appendChild(deleteButton);
+                        row.appendChild(tdAction);
+
+                        tbody.appendChild(row);
+                        updateIndices(2);
+                    });
+                }
+                if(tbodyId === 'postProgressionTreatmentPlanTableBody'){
+                    data.forEach(function(item, index) {
+                        var row = document.createElement('tr');
+
+                        // 序号
+                        var tdIndex = document.createElement('td');
+                        tdIndex.textContent = item.id;
+                        row.appendChild(tdIndex);
+
+                        // 治疗方案
+                        var tdPlan = document.createElement('td');
+                        tdPlan.textContent = item.post_progression_treatment_plan;
+                        row.appendChild(tdPlan);
+
+                        // 治疗时间
+                        var tdTime = document.createElement('td');
+                        tdTime.textContent = item.post_progression_treatment_plan_time;
+                        row.appendChild(tdTime);
+
+                        // 操作(这里可以添加按钮或其他交互元素)
+                        var tdAction = document.createElement('td');
+                        // 示例: 添加一个删除按钮
+                        var deleteButton = document.createElement('button');
+                        deleteButton.textContent = '删除';
+                        deleteButton.onclick = function() { deleteRow(this); };
+                        tdAction.appendChild(deleteButton);
+                        row.appendChild(tdAction);
+
+                        tbody.appendChild(row);
+                        updateIndices(3);
+                    });
+
+                }
+            }
+
+
+
+        }
+        if(tbodyId === 'medicationRecordTableBody' || tbodyId === 'combinedMedicationRecordTableBody'){
+            if(tbodyId === 'medicationRecordTableBody'){
+                data.forEach(function(item, index) {
+                    var row = document.createElement('tr');
+
+                    // 序号
+                    var tdIndex = document.createElement('td');
+                    tdIndex.textContent = item.id;
+                    row.appendChild(tdIndex);
+
+                    // 1
+                    var durgName = document.createElement('td');
+                    durgName.textContent = item.durgName;
+                    row.appendChild(durgName);
+
+                    // 2
+                    var jiliang = document.createElement('td');
+                    jiliang.textContent = item.jiliang;
+                    row.appendChild(jiliang);
+                    // 1
+                    var startyyTime = document.createElement('td');
+                    startyyTime.textContent = item.startyyTime;
+                    row.appendChild(startyyTime);
+
+                    // 2
+                    var tyTime = document.createElement('td');
+                    tyTime.textContent = item.tyTime;
+                    row.appendChild(tyTime);
+                    // 1
+                    var lastsyTime = document.createElement('td');
+                    lastsyTime.textContent = item.lastsyTime;
+                    row.appendChild(lastsyTime);
+
+                    // 2
+                    var shiypv = document.createElement('td');
+                    shiypv.textContent = item.shiypv;
+                    row.appendChild(shiypv);
+                    // 7
+                    var zhuyishix = document.createElement('td');
+                    zhuyishix.textContent = item.zhuyishix;
+                    row.appendChild(zhuyishix);
+                    // 操作(这里可以添加按钮或其他交互元素)
+                    var tdAction = document.createElement('td');
+                    // 示例: 添加一个删除按钮
+                    var deleteButton = document.createElement('button');
+                    deleteButton.textContent = '删除';
+                    deleteButton.onclick = function() { deleteRow(this); };
+                    tdAction.appendChild(deleteButton);
+                    row.appendChild(tdAction);
+
+                    tbody.appendChild(row);
+                    updateIndices(4);
+                });
+
+
+            }
+            if(tbodyId === 'combinedMedicationRecordTableBody'){
+                data.forEach(function(item, index) {
+                    var row = document.createElement('tr');
+                    // 序号
+                    var tdIndex = document.createElement('td');
+                    tdIndex.textContent = item.id;
+                    row.appendChild(tdIndex);
+                    // 1
+                    var durgName = document.createElement('td');
+                    durgName.textContent = item.hbdurgName;
+                    row.appendChild(durgName);
+
+                    // 2
+                    var jiliang = document.createElement('td');
+                    jiliang.textContent = item.hbjiliang;
+                    row.appendChild(jiliang);
+                    // 1
+                    var startyyTime = document.createElement('td');
+                    startyyTime.textContent = item.hbstartyyTime;
+                    row.appendChild(startyyTime);
+
+                    // 2
+                    var tyTime = document.createElement('td');
+                    tyTime.textContent = item.hbtyTime;
+                    row.appendChild(tyTime);
+                    // 1
+                    var lastsyTime = document.createElement('td');
+                    lastsyTime.textContent = item.hblastsyTime;
+                    row.appendChild(lastsyTime);
+
+                    // 2
+                    var shiypv = document.createElement('td');
+                    shiypv.textContent = item.shiypv;
+                    row.appendChild(shiypv);
+                    // 7
+                    var zhuyishix = document.createElement('td');
+                    zhuyishix.textContent = item.hbzhuyishix;
+                    row.appendChild(zhuyishix);
+
+                    // 操作(这里可以添加按钮或其他交互元素)
+                    var tdAction = document.createElement('td');
+                    // 示例: 添加一个删除按钮
+                    var deleteButton = document.createElement('button');
+                    deleteButton.textContent = '删除';
+                    deleteButton.onclick = function() { deleteRow(this); };
+                    tdAction.appendChild(deleteButton);
+                    row.appendChild(tdAction);
+
+                    tbody.appendChild(row);
+                    updateIndices(5);
+                });
+
+
+            }
+
+        }
+
+    }
+    function updateVisibility1() {
+        var weixinDiv = $('#weixin');
+        var mianfangDiv = $('#mianfang');
+
+        var selectedValue = $('input[name="returnMethod"]:checked').val();
+        if (selectedValue === '微信') {
+            weixinDiv.removeClass('hidden');
+            mianfangDiv.addClass('hidden');
+        } else if (selectedValue === '电话') {
+            weixinDiv.addClass('hidden');
+            mianfangDiv.addClass('hidden');
+        } else if (selectedValue === '面访') {
+            weixinDiv.addClass('hidden');
+            mianfangDiv.removeClass('hidden');
+        }
+    }
+
+    function updateVisibility2() {
+        var iscoordinate = $('input[name="iscoordinate"]:checked').val();
+        var stopForm = $('input[name="stopform"]:checked').val();
+        var iscoordinateDiv = $('#iscoordinateDiv');
+        var formAllArea = $('#tab-7-14')
+        var tab6 = $('#tab-6')
+        if (iscoordinate === '2') {
+            iscoordinateDiv.removeClass('hidden');
+        } else {
+            iscoordinateDiv.addClass('hidden');
+        }
+        if (iscoordinate === '2' && stopForm === '不填写完整表单') {
+            // 取消选中所有具有相同name属性的单选按钮
+            var radios2 = document.querySelectorAll('input[name="medicationStatus"]');
+            radios2.forEach(function (radio) {
+                radio.checked = false;
+            });
+            formAllArea.addClass('hidden');
+            tab6.addClass('hidden');
+        }else{
+            formAllArea.removeClass('hidden');
+            tab6.removeClass('hidden');
+        }
+
+    }
+
+    function updateVisibility3() {
+        //是否出现不良反应
+        var bulfydiv = $('#bulfydiv');
+        var hbyysfyiblfy = $('#hbyysfyiblfy');
+        var is_adverse_reaction = $('input[name="is_adverse_reaction"]:checked').val();
+        var combinedMedicationAdverseReaction = $('input[name="combinedMedicationAdverseReaction"]:checked').val();
+
+        if (is_adverse_reaction === '是') {
+            bulfydiv.removeClass('hidden');
+        }
+        if (is_adverse_reaction === '否') {
+            bulfydiv.addClass('hidden');
+        }
+
+        if (combinedMedicationAdverseReaction === '1') {
+            hbyysfyiblfy.removeClass('hidden');
+        }
+        if (combinedMedicationAdverseReaction === '2') {
+            hbyysfyiblfy.addClass('hidden');
+        }
+
+    }
+
+    function updateVisibility4() {
+        var csyzyy = $('#csyzyy');//慈善援助用药
+        var yjty = $('#yjty');//永久停药
+        var qtgyqgy = $('#qtgyqgy');//其他渠道购药
+        var ycgyycjy = $('#ycgyycjy');//延迟购药(医嘱建议)
+        var ycgyhzyybgf = $('#ycgyhzyybgf');//延迟购药(患者用药不规范
+        var formAllArea = $('#tab-7-14')
+        var medicationStatus = $('input[name="medicationStatus"]:checked').val();
+        var reasons_uncooperative = $('input[name="reasons_uncooperative"]:checked').val();
+        // if ((sensitiveValues.includes(reasons_uncooperative) || sensitiveValues.includes(medicationStatus))  && formCheck===true) {
+        //     // 设置标志位
+        //    TJFlag = true;
+        //    $('#TJ1').removeClass('hidden');
+        //    $('#TJ2').addClass('hidden');
+        // }else{
+        //     TJFlag = false;
+        //     $('#TJ1').addClass('hidden');
+        //     $('#TJ2').removeClass('hidden');
+        // }
+        if (medicationStatus === '持续购药') {
+            csyzyy.addClass('hidden');
+            yjty.addClass('hidden');
+            qtgyqgy.addClass('hidden');
+            ycgyycjy.addClass('hidden');
+            ycgyhzyybgf.addClass('hidden');
+            formAllArea.removeClass('hidden');
+        }
+        if (medicationStatus === '领取慈善赠药') {
+            csyzyy.removeClass('hidden');
+            yjty.addClass('hidden');
+            qtgyqgy.addClass('hidden');
+            ycgyycjy.addClass('hidden');
+            ycgyhzyybgf.addClass('hidden');
+            formAllArea.removeClass('hidden');
+        }
+        if (medicationStatus === '永久停药') {
+            yjty.removeClass('hidden');
+            csyzyy.addClass('hidden');
+            qtgyqgy.addClass('hidden');
+            ycgyycjy.addClass('hidden');
+            ycgyhzyybgf.addClass('hidden');
+        }
+        if (medicationStatus === '其他渠道购药') {
+            qtgyqgy.removeClass('hidden');
+            yjty.addClass('hidden');
+            csyzyy.addClass('hidden');
+            ycgyycjy.addClass('hidden');
+            ycgyhzyybgf.addClass('hidden');
+            formAllArea.removeClass('hidden');
+        }
+        if (medicationStatus === '延迟用药(医嘱建议)') {
+            ycgyycjy.removeClass('hidden');
+            qtgyqgy.addClass('hidden');
+            yjty.addClass('hidden');
+            csyzyy.addClass('hidden');
+            ycgyhzyybgf.addClass('hidden');
+            formAllArea.removeClass('hidden');
+        }
+        if (medicationStatus === '延迟购药 (患者用药不规范)') {
+            ycgyhzyybgf.removeClass('hidden');
+            qtgyqgy.addClass('hidden');
+            yjty.addClass('hidden');
+            csyzyy.addClass('hidden');
+            ycgyycjy.addClass('hidden');
+            formAllArea.removeClass('hidden');
+        }
+
+        if (medicationStatus != '永久停药') {
+            $('input[name="stoped"]').prop('checked', false);
+        }
+    }
+    function updateVisibility4_1() {
+        var strategy_Div = $('#follow_up_planning_strategy_Div');//计划策略
+        var perpetual_stopdrug_cause = $('input[name="perpetual_stopdrug_cause"]:checked').val();
+        if (perpetual_stopdrug_cause === '离世') {
+            strategy_Div.removeClass('hidden');
+        }else{
+            strategy_Div.addClass('hidden');
+        }
+
+    }
+    function updateVisibility4_2() {
+        var strategy_fDiv = $('#follow_up_planning_strategy_form_Div');//计划策略
+        var perpetual_stopdrug_cause = $('input[name="perpetual_stopdrug_cause"]:checked').val();
+        if (perpetual_stopdrug_cause === '离世') {
+            strategy_fDiv.removeClass('hidden');
+        }else{
+            strategy_fDiv.addClass('hidden');
+        }
+
+    }
+    function updateVisibility5() {
+        //不填写完整表单
+        var formAllArea = $('#tab-7-14')
+        var tab6 = $('#tab-6')
+        var yjty = $('#yjty');//永久停药
+        var stoped = $('input[name="stoped"]:checked').val();
+        var stopForm = $('input[name="stopform"]:checked').val();
+
+        if (stoped === '不填写完整表单') {
+            formAllArea.addClass('hidden');
+        } else {
+            formAllArea.removeClass('hidden');
+        }
+        if (stoped === '不填写完整表单' || stopForm === '不填写完整表单') {
+            formAllArea.addClass('hidden');
+        } else {
+            formAllArea.removeClass('hidden');
+        }
+
+        if (stopForm === '不填写完整表单') {
+            // 取消选中所有具有相同name属性的单选按钮
+            var radios = document.querySelectorAll('input[name="medicationStatus"]');
+            radios.forEach(function (radio) {
+                radio.checked = false;
+            });
+            tab6.addClass('hidden');
+            yjty.addClass('hidden');
+            formAllArea.addClass('hidden');
+
+        }else{
+            tab6.removeClass('hidden');
+        }
+    }
+
+    function updateVisibility6() {
+        //原因
+        var drugsStatusDiv = $('#drugsStatusDiv');//用药状态调整
+        var flowNoDiv = $('#flowNoDiv');
+        var otherReasonDiv = $('#otherReasonDiv');
+        var drugsStopDiv = $('#drugsStopDiv');
+        var reason = $('input[name="reason"]:checked').val();//原因
+        var drugsStatus = $('input[name="drugsStatus"]:checked').val();//原因
+        if (reason === '用药状态调整') {
+            drugsStatusDiv.removeClass('hidden');
+        } else {
+            drugsStatusDiv.addClass('hidden');
+            drugsStopDiv.addClass('hidden');
+        }
+        if (reason === '随访不配合') {
+            flowNoDiv.removeClass('hidden');
+        } else {
+            flowNoDiv.addClass('hidden');
+        }
+        if (reason === '其他') {
+            otherReasonDiv.removeClass('hidden');
+        } else {
+            otherReasonDiv.addClass('hidden');
+        }
+
+        if (drugsStatus === '永久停药') {
+            drugsStopDiv.removeClass('hidden');
+        } else {
+            drugsStopDiv.addClass('hidden');
+        }
+        if (drugsStatus === '永久停药' && reason != '用药状态调整') {
+            drugsStopDiv.addClass('hidden');
+        }
+
+
+    }
+    function updateVisibility6_1() {
+        var drugsStop = $('input[name="drugsStop"]:checked').val();//永久关闭是否
+        var closeplan_isnoDiv = $('#closeplan_isnoDiv');
+        var closePlanScope = $('input[name="closePlanScope"]:checked').val();//范围 选中 仅关闭当前计划
+        if (drugsStop === '离世' && closePlanScope === '关闭患者当前的全部计划') {
+            closeplan_isnoDiv.removeClass('hidden');
+        }
+        if (closePlanScope === '关闭当前药品的全部计划') {
+            closeplan_isnoDiv.removeClass('hidden');
+        } else {
+            closeplan_isnoDiv.addClass('hidden');
+        }
+        if (closePlanScope === '关闭当前药品的全部计划') {
+            closeplan_isnoDiv.removeClass('hidden');
+        }else {
+            closeplan_isnoDiv.addClass('hidden');
+        }
+    }
+    function updateVisibility6_2() {
+        var specifiedTypeDiv = $('#specifiedTypeDiv');
+        var closeType = $('input[name="closeType"]:checked').val();//关闭类型 选中点击带过来的
+        if (closeType === '指定类型') {
+            specifiedTypeDiv.removeClass('hidden');
+        }
+        if (closeType === '全部类型') {
+            specifiedTypeDiv.addClass('hidden');
+        }
+    }
+    function updateVisibility7() {
+        var isReview = $('input[name="isReview"]:checked').val();//
+        var isReviewDiv = $('#isReviewDiv');//
+        if (isReview === '1') {
+            isReviewDiv.removeClass('hidden');
+        } else {
+            isReviewDiv.addClass('hidden');
+        }
+    }
+
+    function updateVisibility8() {
+        var imagingInsuranceType = $('input[name="imagingInsuranceType"]:checked').map(function () {
+            return $(this).val();
+        }).get();
+        var ctradio = $('#ctradio');//
+        var bcradio = $('#bcradio');//
+        var hcradio = $('#hcradio');//
+        var qtradio = $('#qtradio');//
+        if (imagingInsuranceType.includes('CT')) {
+            ctradio.removeClass('hidden');
+        } else {
+            ctradio.addClass('hidden');
+        }
+        if (imagingInsuranceType.includes('B超')) {
+            bcradio.removeClass('hidden');
+        } else {
+            bcradio.addClass('hidden');
+        }
+        if (imagingInsuranceType.includes('核磁')) {
+            hcradio.removeClass('hidden');
+        } else {
+            hcradio.addClass('hidden');
+        }
+        if (imagingInsuranceType.includes('其他')) {
+            qtradio.removeClass('hidden');
+        } else {
+            qtradio.addClass('hidden');
+        }
+    }
+
+    function updateVisibility9() {
+        var clinicalInteraction = $('input[name="clinicalInteraction"]:checked').val();//
+        var clinicalInteractionDiv = $('#clinicalInteractionDiv');//
+        if (clinicalInteraction === '1') {
+            clinicalInteractionDiv.removeClass('hidden');
+        } else {
+            clinicalInteractionDiv.addClass('hidden');
+        }
+    }
+
+    function updateVisibility10() {
+        var unnecessaryRepeatedMedication = $('input[name="unnecessaryRepeatedMedication"]:checked').val();//
+        var unnecessaryRepeatedMedicationDiv = $('#unnecessaryRepeatedMedicationDiv');//
+        if (unnecessaryRepeatedMedication === '1') {
+            unnecessaryRepeatedMedicationDiv.removeClass('hidden');
+        } else {
+            unnecessaryRepeatedMedicationDiv.addClass('hidden');
+        }
+    }
+
+    function updateVisibility11() {
+        var medicationError = $('input[name="medicationError"]:checked').val();//
+        var medicationErrorDiv = $('#medicationErrorDiv');//
+        if (medicationError === '1') {
+            medicationErrorDiv.removeClass('hidden');
+        } else {
+            medicationErrorDiv.addClass('hidden');
+        }
+    }
+
+    function updateVisibility12() {
+        var isConsultation = $('input[name="isConsultation"]:checked').val();//
+        var isConsultationDiv = $('#isConsultationDiv');//
+        if (isConsultation === '1') {
+            isConsultationDiv.removeClass('hidden');
+        } else {
+            isConsultationDiv.addClass('hidden');
+        }
+    }
+
+    function updateVisibility13() {
+        var treatmentTypeDiv = $('#treatmentTypeDiv');//
+        var currentTreatmentPlanDiv = $('#currentTreatmentPlanDiv');//
+        var postProgressionTreatmentPlanDiv = $('#postProgressionTreatmentPlanDiv');//
+        // 获取所有选中的复选框的值 repeatedMedicationFirstUseDateDiv
+        var selectedValues = $('input[name="treatmentType"]:checked').map(function () {
+            return $(this).val();
+        }).get();
+        // 遍历所有可能的值并根据是否选中来显示或隐藏对应的 div
+        if (selectedValues.includes('用药前治疗方案')) {
+            treatmentTypeDiv.removeClass('hidden');
+        } else {
+            treatmentTypeDiv.addClass('hidden');
+        }
+        if (selectedValues.includes('目前治疗方案')) {
+            currentTreatmentPlanDiv.removeClass('hidden');
+        } else {
+            currentTreatmentPlanDiv.addClass('hidden');
+        }
+        if (selectedValues.includes('进展后治疗方案')) {
+            postProgressionTreatmentPlanDiv.removeClass('hidden');
+        } else {
+            postProgressionTreatmentPlanDiv.addClass('hidden');
+        }
+    }
+
+    function updateVisibility14() {
+        //全量表单控制
+        var tumorMarkerChecks = $('input[name="tumorMarkerCheck"]:checked').map(function () {
+            return $(this).val();
+        }).get();
+        var ceaDiv = $('#ceaDiv');
+        var afpDiv = $('#afpDiv');
+        var ca199Div = $('#ca199Div');
+        var ca125Div = $('#ca125Div');
+        var ca153Div = $('#ca153Div');
+        var hcgDiv = $('#hcgDiv');
+        var nseDiv = $('#nseDiv');
+        var cyfra211Div = $('#cyfra211Div');
+        var tgDiv = $('#tgDiv');
+        var psaDiv = $('#psaDiv');
+
+        // 遍历所有可能的值并根据是否选中来显示或隐藏对应的 div
+        if (tumorMarkerChecks.includes('CEA')) {
+            ceaDiv.removeClass('hidden');
+        } else {
+            ceaDiv.addClass('hidden');
+        }
+        if (tumorMarkerChecks.includes('AFP')) {
+            afpDiv.removeClass('hidden');
+        } else {
+            afpDiv.addClass('hidden');
+        }
+        if (tumorMarkerChecks.includes('CA-199')) {
+            ca199Div.removeClass('hidden');
+        } else {
+            ca199Div.addClass('hidden');
+        }
+        if (tumorMarkerChecks.includes('CA125')) {
+            ca125Div.removeClass('hidden');
+        } else {
+            ca125Div.addClass('hidden');
+        }
+        if (tumorMarkerChecks.includes('CA153')) {
+            ca153Div.removeClass('hidden');
+        } else {
+            ca153Div.addClass('hidden');
+        }
+        if (tumorMarkerChecks.includes('HCG')) {
+            hcgDiv.removeClass('hidden');
+        } else {
+            hcgDiv.addClass('hidden');
+        }
+        if (tumorMarkerChecks.includes('NSE')) {
+            nseDiv.removeClass('hidden');
+        } else {
+            nseDiv.addClass('hidden');
+        }
+        if (tumorMarkerChecks.includes('CYFRA21-1')) {
+            cyfra211Div.removeClass('hidden');
+        } else {
+            cyfra211Div.addClass('hidden');
+        }
+        if (tumorMarkerChecks.includes('TG')) {
+            tgDiv.removeClass('hidden');
+        } else {
+            tgDiv.addClass('hidden');
+        }
+        if (tumorMarkerChecks.includes('PSA')) {
+            psaDiv.removeClass('hidden');
+        } else {
+            psaDiv.addClass('hidden');
+        }
+
+    }
+    function handleCloseTypeChange(selectedRadio) {
+        const selectedValue = selectedRadio.value;
+
+        // 如果选择了“指定类型”,则展开指定类型部分
+        if (selectedValue === '指定类型') {
+            document.getElementById('specifiedTypeDiv').style.display = 'block';
+        } else {
+            document.getElementById('specifiedTypeDiv').style.display = 'none';
+        }
+
+        // 如果选择了“全部类型”,则勾选所有指定类型的复选框
+        if (selectedValue === '全部类型') {
+            specifiedTypeCheckboxes.forEach(checkbox => {
+                checkbox.checked = true;
+            });
+        } else {
+            specifiedTypeCheckboxes.forEach(checkbox => {
+                checkbox.checked = false;
+            });
+        }
+    }
+
+    function handleSpecifiedTypeChange() {
+        let anyChecked = false;
+        specifiedTypeCheckboxes.forEach(checkbox => {
+            if (checkbox.checked) {
+                anyChecked = true;
+            }
+        });
+
+        // 如果有任何一个指定类型的复选框被选中,则自动选中“指定类型”的单选按钮
+        if (anyChecked) {
+            closeTypeRadios.forEach(radio => {
+                if (radio.value === '指定类型') {
+                    radio.checked = true;
+                    document.getElementById('specifiedTypeDiv').style.display = 'block'; // 展开指定类型部分
+                }
+            });
+        }
+    }
+    function makeEditableIfLatest(cellValue, row) {
+        return row.is_latest_registration === 1 ? {
+            type: 'text',
+            title: '单次剂量',
+            validate: function (value) {
+                if ($.trim(value) === '') return '单次剂量不能为空';
+            }
+        } : {disabled: true}; // 如果不是最新的记录,则禁用编辑
+    }
+    function initializeTableForTab(tabId) {
+
+        var tableId = 'bootstrap-table-' + tabId.substring(4);
+        var tableElement = $('#' + tableId);
+
+        var data = {
+            "patientPhone": $('#phoneNumber').val(),
+            "patientName": $('#name').val(),
+            "id": $('#id').val(),
+            "flag": tabId.substring(4),
+        };
+        console.log("tabId=" + tabId.substring(4));
+        if (tabId === 'tab-2') {
+            //购药记录
+            var data2 = [];
+            data2 = recordsData;
+            var options = {
+                data: data2,
+                columns: [
+                    {field: 'id', title: 'ID', visible: false,align: 'center'},
+                    {field: 'mdmCode', title: '药品编码', visible: false,align: 'center'},
+                    {field: 'patientId', title: '患者ID', visible: false,align: 'center'},
+                    {field: 'storeId', title: '门店Id', visible: false,align: 'center'},
+                    {field: 'is_latest_registration', title: '标识',align: 'center'},
+                    {field: 'dvalueDays',title: 'D值用药天数', visible: false,align: 'center'},
+                    {field: 'salesOrderNumber', title: '销售单号',align: 'center'},
+                    {field: 'prescriptionNumber', title: '处方编号',align: 'center'},
+                    {field: 'prescriptionDate', title: '处方日期',align: 'center'},
+                    {field: 'hospital', title: '医院',align: 'center'},
+                    {field: 'prescribingDoctor', title: '处方医生',align: 'center'},
+                    {field: 'department', title: '科室', visible: false,align: 'center'},
+                    {field: 'attendingDoctor', title: '主管医生',align: 'center'},  // 注意这里使用的是 "attendingDoctor" 而不是 "attendingPhysician"
+                    {field: 'prescriptionDiagnosis', title: '处方诊断',align: 'center'},
+                    {field: 'genericName', title: '药品通用名',align: 'center'},
+                    // {field: 'productName', title: '商品名',align: 'center'},
+                    {field: 'specification', title: '规格',align: 'center'},
+                    {field: 'packageQuantity', title: '取药数量',align: 'center'},
+                    {
+                        field: 'singleDoseValue',align: 'center',
+                        title: '单次剂量',
+                        editable: function(cellValue, row) {
+                            return makeEditableIfLatest(cellValue, row);
+                        }
+                    },
+                    {
+                        field: 'singleDoseUnit',
+                        align: 'center',
+                        title: '单次剂量单位',
+                        editable: function(cellValue, row) {
+                            return row.is_latest_registration === 1 ? {
+                                type: 'select',
+                                source: [
+                                    "mg", "g", "μg", "L", "ml", "μL", "U", "IU", "T", "S", "片", "丸", "粒", "支", "袋", "瓶", "盒", "板", "包", "小盒"
+                                ],
+                                title: '单次剂量单位',
+                                validate: function (value) {
+                                    if ($.trim(value) === '') return '单次剂量单位不能为空';
+                                }
+                            } : {disabled: true};
+                        }
+                    },
+                    {
+                        field: 'dosageFrequency',
+                        title: '用药频次',
+                        align: 'center',
+                        editable: function(cellValue, row) {
+                            return row.is_latest_registration === 1 ? {
+                                type: 'select',
+                                source: dosageFrequencyOptions,
+                                valueField: 'value',
+                                textField: 'text',
+                                validate: function (value) {
+                                    if ($.trim(value) === '') return '用药频次不能为空';
+                                }
+                            } : {disabled: true};
+                        }
+                    },
+                    {field: 'medicationRoute', title: '用药途径', visible: false,align: 'center'},
+                    {field: 'registrant', title: '登记人', visible: false,align: 'center'},
+                    {field: 'registrationDate', title: '登记日期', visible: false,align: 'center'},
+                    {field: 'saleDate', title: '销售日期', visible: false,align: 'center'},
+                    {mdmCode: 'mdmCode', title: '药品编码', visible: false,align: 'center'},
+                    {field: 'pharmacyName', title: '购药门店名称',align: 'center'},
+                    {
+                        field: 'actions',
+                        title: '操作',
+                        formatter: actionFormatter,
+                        events: window.actionEvents
+                    }
+                ]
+
+            };
+            if (Array.isArray(data2) && data2.length > 0) {
+                tableElement.bootstrapTable(options);
+            } else if (!Array.isArray(data2)) {
+                console.error('listTask1 is not a valid array:', data2);
+            } else {
+                console.log('No data available');
+            }
+
+        }
+        if (tabId === 'tab-3') {
+            // var tableElement4 = $('#bootstrap-table-4');
+            // // 回显数据随访计划
+            // var data4 = [];
+            // data4 = listTask1;
+            // var options4 = {
+            //     // 配置表格的相关属性
+            //     // 例如数据源、列定义等
+            //     // 示例配置
+            //     data: data4,
+            //     fitColumns: true,
+            //     striped: true,
+            //     autoRowHeight: true,
+            //     rowNumbers: true,
+            //     showFooter: true,  //是否显示表格底部区域。
+            //     clickToSelect: true, //是否启用点击行时选中整行的功能。
+            //     singleSelect: false, //是否仅允许选择一行
+            //     fixedColumns: true,
+            //     //fixedNumber: 3,
+            //     fixedRightNumber: 1,
+            //     columns: [
+            //         {field: 'id', title: '序号'},
+            //         {field: 'taskName', title: '任务名称'},
+            //         {field: 'taskTheme', title: '任务类型'},
+            //         {field: 'appointmentDate', title: '预约时间'},
+            //         {field: 'completionTime', title: '完成时间'},
+            //         {field: 'taskFollower', title: '任务跟进人'},
+            //         {field: 'taskStatus', title: '任务状态'},
+            //         {
+            //             title: '操作',
+            //             align: 'center',
+            //             width: '180px',
+            //             formatter: function (value, row, index) {
+            //                 if (row.id && !['已取消', '已完成','已过期'].includes(row.taskStatus)) {
+            //                     var actions = [];
+            //                     actions.push('<a class="btn-xs" href="javascript:void(0)" onclick="edit(\'' + row.id + '\')">查看</a> ');
+            //                     actions.push('<a class="btn-xs" href="javascript:void(0)" onclick="closeTask(\'' + row.id + '\')">关闭任务</a> ');
+            //                     return actions.join('');
+            //                 }
+            //                 if (row.id && ['已取消', '已完成','已过期'].includes(row.taskStatus)) {
+            //                     var actions = [];
+            //                     actions.push('<a class="btn-xs" href="javascript:void(0)" onclick="edit(\'' + row.id + '\')">查看</a> ');
+            //                     return actions.join('');
+            //                 } else {
+            //                     return "";
+            //                 }
+            //             }
+            //         }
+            //     ]
+            // };
+            // if (Array.isArray(data4) && data4.length > 0) {
+            //     tableElement4.bootstrapTable(options4);
+            // } else if (!Array.isArray(data4)) {
+            //     console.error('listTask1 is not a valid array:', data4);
+            // } else {
+            //     console.log('No data available');
+            // }
+
+
+            // var data5 = [];
+            // data5 = listTask2;
+            // var tableElement5 = $('#bootstrap-table-5');
+            // var options5 = {
+            //     // 配置表格的相关属性
+            //     // 例如数据源、列定义等
+            //     // 示例配置
+            //     data: data5,
+            //     fitColumns: true,
+            //     striped: true,
+            //     autoRowHeight: true,
+            //     rowNumbers: true,
+            //     showFooter: true,  //是否显示表格底部区域。
+            //     clickToSelect: true, //是否启用点击行时选中整行的功能。
+            //     singleSelect: false, //是否仅允许选择一行
+            //     fixedColumns: true,
+            //     //fixedNumber: 3,
+            //     fixedRightNumber: 1,
+            //     columns: [
+            //         {field: 'id', title: '序号'},
+            //         {field: 'taskName', title: '任务名称'},
+            //         {field: 'taskTheme', title: '任务类型'},
+            //         {field: 'appointmentDate', title: '预约时间'},
+            //         {field: 'completionTime', title: '完成时间'},
+            //         {field: 'taskFollower', title: '任务跟进人'},
+            //         {field: 'taskStatus', title: '任务状态'},
+            //         {
+            //             title: '操作',
+            //             align: 'center',
+            //             width: '180px',
+            //             formatter: function (value, row, index) {
+            //                 if (row.id && !['已取消', '已完成','已过期'].includes(row.taskStatus)) {
+            //                     var actions = [];
+            //                     actions.push('<a class="btn-xs" href="javascript:void(0)" onclick="edit(\'' + row.id + '\')">查看</a> ');
+            //                     actions.push('<a class="btn-xs" href="javascript:void(0)" onclick="closeTask(\'' + row.id + '\')">关闭任务</a> ');
+            //                     return actions.join('');
+            //                 }
+            //                 if (row.id && ['已取消', '已完成','已过期'].includes(row.taskStatus)) {
+            //                     var actions = [];
+            //                     actions.push('<a class="btn-xs" href="javascript:void(0)" onclick="edit(\'' + row.id + '\')">查看</a> ');
+            //                     return actions.join('');
+            //                 } else {
+            //                     return "";
+            //                 }
+            //             }
+            //         }
+            //     ]
+            // };
+            // if (Array.isArray(data5) && data5.length > 0) {
+            //     tableElement5.bootstrapTable(options5);
+            // } else if (!Array.isArray(data5)) {
+            //     console.error('listTask1 is not a valid array:', data4);
+            // } else {
+            //     console.log('No data available');
+            // }
+
+            //99999
+            var listPlanCG = /*[[${planListCG}]]*/ [];
+            debugger;
+            // 确保 listPlanCG 是数组并且包含数据
+            if (Array.isArray(listPlanCG) && listPlanCG.length > 0) {
+                // 遍历每个计划
+                listPlanCG.forEach(function(plan, index) {
+                    // 创建一个新的div来放置每个计划的信息和任务表格
+                    var planDiv = $('<div class="plan-container"></div>');
+
+                    // 添加计划信息到planDiv中
+                    var planInfo = `
+                <div class="row">
+                    <div class="ibox-content">
+                        <div class="ibox-title2">
+                            <code style="font-size: 1.5rem;width: 200px;">${plan.productName === undefined || plan.productName === null? '' : plan.productName}</code>
+                            <code style="font-size: 1.5rem; width: 200px;">${plan.specification=== undefined || plan.specification === null? '' : plan.specification}</code>
+                            <code style="font-size: 1.5rem;color: #00B83F;padding-right: 30px;"><i class="glyphicon glyphicon-ice-lolly">${plan.businessBelonging}</i></code>
+                            <code style="font-size: 1.5rem;color: #00B83F;padding-right: 30px;">
+                                 ${plan.status === 0 ? '已关闭' : plan.status === 1 ? '进行中' : plan.status === 2 ? '已创建' : plan.status}
+                            </code>
+                            <!-- 根据状态决定是否显示关闭计划按钮 -->
+                            <a href="#" class="right-aligned-link" data-toggle="modal" data-target="#myModalClosePlan"
+                               onclick="${plan.status !== '0' ? 'closePlan(' + (2,plan.id) + ')' : ''}">
+                                ${plan.status !== '0' ? '关闭计划' : ''}
+                            </a>
+                            <a href="#" onclick="${plan.status === '0' ? 'RestartPlan(' + (plan.id) + ')' : ''}">
+                                ${plan.status === '1' ? '重启计划' : ''}
+                            </a>
+                        </div>
+                    </div>
+                </div>
+                <div class="row">
+                    <div class="ibox-content">
+                        <div class="ibox-title2">
+                            <span style="padding-left: 10px;font-size:15px;">创建人:</span>
+                            <span style="width: 120px;color: #2E2D3C;font-size:15px;padding-right: 40px;">${plan.createdBy}</span>
+                            <span style="font-size:15px;">开始时间:</span>
+                            <span style="width: 220px;color: #2E2D3C;font-size:15px;padding-right: 70px;">${plan.createdTime}</span>
+                            <span style="font-size:15px;">更新人:</span>
+                            <span style="width: 100px;color: #2E2D3C;font-size:15px;">${plan.updatedBy}</span>
+                            <span style="font-size:15px;">更新时间:</span>
+                            <span style="width: 220px;color: #2E2D3C;font-size:15px;padding-right: 70px;">${plan.updatedTime}</span>
+                        </div>
+                    </div>
+                </div>
+                <div class="row">
+                    <div class="col-sm-12 select-table table-striped">
+                        <table id="bootstrap-table-${index}" class="bootstrap-table-class"></table>
+                    </div>
+                </div>
+            `;
+                    planDiv.html(planInfo);
+
+                    // 将新创建的planDiv添加到cgsfnormal div中
+                    $('#cgsfnormal').append(planDiv);
+
+                    // 初始化任务表格
+                    var tableElement = $(`#bootstrap-table-${index}`);
+                    var data = plan.tasks; // 获取当前计划的任务列表
+
+                    var options = {
+                        data: data,
+                        fitColumns: true,
+                        striped: true,
+                        autoRowHeight: true,
+                        rowNumbers: true,
+                        showFooter: true,
+                        clickToSelect: true,
+                        singleSelect: false,
+                        fixedColumns: true,
+                        fixedRightNumber: 1,
+                        columns: [
+                            {field: 'id', title: '序号',align: 'center'},
+                            {field: 'taskName', title: '任务名称',align: 'center'},
+                            {field: 'taskTheme', title: '任务类型',align: 'center'},
+                            {field: 'appointmentDate', title: '预约时间',align: 'center'},
+                            {field: 'completionTime', title: '完成时间',align: 'center'},
+                            {field: 'taskFollower', title: '任务跟进人',align: 'center'},
+                            {field: 'taskStatus', title: '任务状态',align: 'center'},
+                            {
+                                title: '操作',
+                                align: 'center',
+                                width: '180px',
+                                formatter: function(value, row, index) {
+                                    if (row.id && !['已取消', '已完成','已过期'].includes(row.taskStatus)) {
+                                        return `<a class="btn-xs" href="javascript:void(0)" onclick="edit('${row.id}')">查看</a> ` +
+                                            `<a class="btn-xs" href="javascript:void(0)" onclick="closeTask('${row.id}')">关闭任务</a>`;
+                                    } else if (row.id && ['已取消', '已完成','已过期'].includes(row.taskStatus)) {
+                                        return `<a class="btn-xs" href="javascript:void(0)" onclick="edit('${row.id}')">查看</a>`;
+                                    } else {
+                                        return "";
+                                    }
+                                }
+                            }
+                        ]
+                    };
+
+                    tableElement.bootstrapTable(options);
+                });
+
+                $('#cgsfnormal').show();
+            }else {
+                $('#cgsfnormal').hide();
+            }
+            //88888
+
+            //66666
+            var listPlanTL = /*[[${planListTL}]]*/ [];
+
+            // 确保 listPlanCG 是数组并且包含数据
+            if (Array.isArray(listPlanTL) && listPlanTL.length > 0) {
+                // 遍历每个计划
+                listPlanTL.forEach(function(plan, index) {
+                    // 创建一个新的div来放置每个计划的信息和任务表格
+                    var planDiv = $('<div class="plan-container"></div>');
+
+                    // 添加计划信息到planDiv中
+                    var planInfo = `
+                <div class="row">
+                    <div class="ibox-content">
+                        <div class="ibox-title2">
+                             <code style="font-size: 1.5rem;width: 200px;">${plan.productName === undefined || plan.productName === null? '' : plan.productName}</code>
+                            <code style="font-size: 1.5rem; width: 200px;">${plan.specification=== undefined || plan.specification === null? '' : plan.specification}</code>
+                            <code style="font-size: 1.5rem;color: #00B83F;padding-right: 30px;"><i class="glyphicon glyphicon-ice-lolly-tasted">${plan.businessBelonging}</i></code>
+                            <code style="font-size: 1.5rem;color: #00B83F;padding-right: 30px;">
+                                ${plan.status === '0' ? '已关闭' : plan.status === '1' ? '进行中' : plan.status === '2' ? '已创建' : plan.status}
+                            </code>
+                            <!-- 根据状态决定是否显示关闭计划按钮 -->
+                            <a href="#" class="right-aligned-link" data-toggle="modal" data-target="#myModalClosePlan"
+                               onclick="${plan.status !== '0' ? 'closePlan(' + (2,plan.id) + ')' : ''}">
+                                ${plan.status !== '0' ? '关闭计划' : ''}
+                            </a>
+                            <a href="#" onclick="${plan.status === '0' ? 'RestartPlan(' + (plan.id) + ')' : ''}">
+                                ${plan.status === '1' ? '重启计划' : ''}
+                            </a>
+                        </div>
+                    </div>
+                </div>
+                <div class="row">
+                    <div class="ibox-content">
+                        <div class="ibox-title2">
+                            <span style="padding-left: 10px;font-size:15px;">创建人:</span>
+                            <span style="width: 120px;color: #2E2D3C;font-size:15px;padding-right: 40px;">${plan.createdBy}</span>
+                            <span style="font-size:15px;">开始时间:</span>
+                            <span style="width: 220px;color: #2E2D3C;font-size:15px;padding-right: 70px;">${plan.createdTime}</span>
+                            <span style="font-size:15px;">更新人:</span>
+                            <span style="width: 100px;color: #2E2D3C;font-size:15px;">${plan.updatedBy}</span>
+                            <span style="font-size:15px;">更新时间:</span>
+                            <span style="width: 220px;color: #2E2D3C;font-size:15px;padding-right: 70px;">${plan.updatedTime}</span>
+                        </div>
+                    </div>
+                </div>
+                <div class="row">
+                    <div class="col-sm-12 select-table table-striped">
+                        <table id="bootstrap-table-${index}" class="bootstrap-table-class"></table>
+                    </div>
+                </div>
+            `;
+                    planDiv.html(planInfo);
+
+                    // 将新创建的planDiv添加到cgsfnormal div中
+                    $('#tlzhback').append(planDiv);
+
+                    // 初始化任务表格
+                    var tableElement = $(`#bootstrap-table-${index}`);
+                    var data = plan.tasks; // 获取当前计划的任务列表
+
+                    var options = {
+                        data: data,
+                        fitColumns: true,
+                        striped: true,
+                        autoRowHeight: true,
+                        rowNumbers: true,
+                        showFooter: true,
+                        clickToSelect: true,
+                        singleSelect: false,
+                        fixedColumns: true,
+                        fixedRightNumber: 1,
+                        columns: [
+                            {field: 'id', title: '序号'},
+                            {field: 'taskName', title: '任务名称'},
+                            {field: 'taskTheme', title: '任务类型'},
+                            {field: 'appointmentDate', title: '预约时间'},
+                            {field: 'completionTime', title: '完成时间'},
+                            {field: 'taskFollower', title: '任务跟进人'},
+                            {field: 'taskStatus', title: '任务状态'},
+                            {
+                                title: '操作',
+                                align: 'center',
+                                width: '180px',
+                                formatter: function(value, row, index) {
+                                    if (row.id && !['已取消', '已完成','已过期'].includes(row.taskStatus)) {
+                                        return `<a class="btn-xs" href="javascript:void(0)" onclick="edit('${row.id}')">查看</a> ` +
+                                            `<a class="btn-xs" href="javascript:void(0)" onclick="closeTask('${row.id}')">关闭任务</a>`;
+                                    } else if (row.id && ['已取消', '已完成','已过期'].includes(row.taskStatus)) {
+                                        return `<a class="btn-xs" href="javascript:void(0)" onclick="edit('${row.id}')">查看</a>`;
+                                    } else {
+                                        return "";
+                                    }
+                                }
+                            }
+                        ]
+                    };
+
+                    tableElement.bootstrapTable(options);
+                });
+
+                $('#tlzhback').show();
+            }else {
+                $('#tlzhback').hide();
+            }
+
+            //77777
+
+        }
+    }
+
+    // 定义操作列的事件处理
+    window.actionEvents = {
+        'click .save': function (e, value, row, index) {
+            saveDrug(row, index);
+        }
+    };
+
+    // 定义操作列的格式化函数
+    function actionFormatter(value, row, index) {
+        // 检查 dvalueDays 是否小于 0
+        if (row.dvalueDays !== undefined && parseFloat(row.dvalueDays) < 0) {
+            return '<span class="expired-label">已过期</span>';
+        }
+        if (row.is_latest_registration === 1) {
+            return '<button class="save ml10 btn btn-success btn-xs" type="button">保存</button>';
+        } else {
+            return '';
+        }
+        // // 如果 dvalueDays >= 0,则返回保存按钮
+        // return [
+        //     '<button class="save ml10 btn btn-success btn-xs" type="button">保存</button>'
+        // ].join('');
+    }
+
+    // 定义保存药品记录的方法
+    function saveDrug(row, index) {
+        // 查找对应的 dictLabel 和 dictValue 并添加到 row 对象中
+        var option = dosageFrequencyLookup[row.dosageFrequency];
+        if (option) {
+            row.dosageFrequencyLabel = option.label; // 添加 dictLabel 到 row 对象中
+            row.dosageFrequencyValue = option.value; // 添加 dictValue 到 row 对象中
+        } else {
+            row.dosageFrequencyLabel = ''; // 或者设置为默认值
+            row.dosageFrequencyValue = ''; // 或者设置为默认值
+        }
+
+        // 发送 AJAX 请求更新数据
+        $.ajax({
+            cache: true,
+            type: "POST",
+            url: ctx + "dtp/pmService/updateDrugPurchaseRecord",
+            data: row,
+            async: false,
+            success: function (data) {
+                $.modal.msg("保存成功");
+                // 刷新页面数据
+                refreshData(data.data);
+            },
+            error: function (error) {
+                $.modal.msg("更新失败");
+            }
+        });
+    };
+
+    function editArchives() {
+        var id = $("#patientId").val();
+        // table.set();
+        var prefix = ctx + "dtp/pmService/archivesEdit/{id}";
+        $.modal.openTab("修改档案", prefix.replace("{id}", id));
+    }
+
+    // 关闭计划  提交关闭原因
+    function submitClosePlan() {
+        var reason = $('input[name="reason"]:checked').val(); // 原因
+        if (reason==='' || reason===null) {
+            $.modal.msgError("请选择关闭原因");
+            return;
+        }
+        var closePlanScope = $('input[name="closePlanScope"]:checked').val(); // 原因
+        if (closePlanScope==='' || closePlanScope===null) {
+            $.modal.msgError("请选择关闭关闭范围");
+            return;
+        }
+        var closeType = $('input[name="closeType"]:checked').val(); // 原因
+        if (closeType==='' || closeType===null) {
+            $.modal.msgError("请选择关闭类型");
+            return;
+        }
+
+        var drugsStatus = $('input[name="drugsStatus"]:checked').val(); // 用药状态调整为
+        // 检查是否需要显示确认弹框
+        var needsConfirmation = (reason === '电话号码错误' || drugsStatus === '领取慈善赠药');
+        if (needsConfirmation) {
+            $.modal.confirm("确认要关闭计划吗? 若关闭计划,同时关闭此任务", function () {
+                executeClosePlan();
+            });
+        } else {
+            executeClosePlan();
+        }
+    }
+
+    function executeClosePlan() {
+        // 获取指定类型的选中值 常规随访 脱落召回
+        var specifiedType = [];
+        $('input[name="specifiedType"]:checked').each(function () {
+            specifiedType.push($(this).val());
+        });
+        var taskId, planId_cg, planId_tl, patientId, mdmCode_cg, mdmCode_tl;
+        // 根据选中的指定类型设置相应的planId
+        specifiedType.forEach(function (type) {
+            if (type === '常规随访') {
+                planId_cg = planId_close;
+                mdmCode_cg = $("#mdmCode_cg").val();
+            } else if (type === '脱落召回') {
+                planId_tl = planId_close;
+                mdmCode_tl = $("#mdmCode_tl").val();
+            }
+        });
+        patientId = $("#patientId").val();
+        taskId = $("#id").val();
+
+        // 收集所有非隐藏的表单项
+        var formData = new FormData();
+        $('#ClosePlanForm').find(':not(.hidden)').find('input, select, radio,checkbox,textarea').each(function() {
+            var $this = $(this);
+            if ($this.is(':radio')) {
+                if ($this.is(':checked')) {
+                    formData.append($this.attr('name'), $this.val());
+                }
+            } else if ($this.is(':checkbox')) {
+                if ($this.is(':checked')) {
+                    formData.append($this.attr('name'), $this.val());
+                }
+            } else if ($this.attr('type') !== 'submit' && $this.attr('type') !== 'button') {
+                formData.append($this.attr('name'), $this.val());
+            }
+        });
+
+        // 添加额外的参数
+        formData.append('taskId', taskId);
+        formData.append('planId_cg', planId_close || '');
+        formData.append('planId_cg', planId_close || '');
+        formData.append('planId', planId_close || '');
+        formData.append('mdmCode_tl', mdmCode_tl || '');
+        formData.append('mdmCode_cg', mdmCode_cg || '');
+        formData.append('mdmCode', mdmCode || '');
+        formData.append('patientId', patientId);
+
+        // 如果有指定类型被选中,添加到formData
+        if (specifiedType.length > 0) {
+            formData.append('specifiedType', specifiedType.join(','));
+        }
+
+        console.log(formData);
+
+        $.ajax({
+            url: prefix_task + "/closePlan",
+            data: formData,
+            method: 'POST',
+            processData: false, // 防止 jQuery 自动转换数据
+            contentType: false, // 不设置内容类型
+            dataType: 'json',
+            error: function (data) {
+                $.modal.alertError("关闭计划失败");
+                flag = true;
+            },
+            success: function(data) {
+                if(data.code==0){
+                    flagStatus = true;
+                    if(flagStatus && flagStatus2 != 1){
+                        console.log("flagStatus="+flagStatus);
+                        submitHandler()
+                    }
+                    $.modal.alertSuccess("关闭计划成功");
+                    updateTabState('#tab-3');
+                    window.location.reload();
+                    flag=false;
+                }else{
+                    $.modal.alertError("关闭计划失败");
+                    flag=true;
+                }
+
+            }
+        });
+
+        if (flag == false) {
+            $('#myModalClosePlan').hide();
+        } else {
+            $('#myModalClosePlan').show();
+        }
+    }
+
+    /* 去关联 */
+    function correlation(id) {
+        //弹框做操作
+        // $.modal.confirm("确认要启用用户吗?", function() {
+        //     $.operate.post(prefix + "/toCorrelation", { "id": id, "status": 1 });
+        // })
+        onclick = "$.operate.edit(\'' + row.id + '\')"
+        var options = {
+            title: '添加复购品',
+            url: prefix + "/toCorrelation" + id,
+            callBack: doSubmit2
+        };
+        $.modal.openOptions(options);
+    }
+
+    // 关闭计划
+    function closePlan(planFlag, planId) {
+        closePlanFlag = planFlag;
+        flagStatus2 = 1;
+        var taskId;
+
+        if (planFlag === 1) {
+            taskId = $("#id").val();
+            planId_close = planId;
+            mdmCode = $("#mdmCode_cg").val();
+            // 设置默认选中的选项
+            $('input[name="closeType"][value="指定类型"]').prop('checked', true);
+            $('input[name="specifiedType"][value="常规随访"]').prop('checked', true);
+        } else if (planFlag === 2) {
+            $('input[name="closeType"][value="指定类型"]').prop('checked', true);
+            $('input[name="specifiedType"][value="脱落召回"]').prop('checked', true);
+            taskId = $("#id").val();
+            planId_close = planId;
+            mdmCode = $("#mdmCode_tl").val();
+        }
+        // updateVisibility4_1();
+        $('#myModalClosePlan').show();
+        $('input[name="closePlanScope"][value="仅关闭当前计划"]').prop('checked', true);
+        // 更新界面元素的可见性
+        updateVisibility6_1();
+        updateVisibility6_2();
+        updateVisibility6();
+
+    }
+    //重启计划
+    function RestartPlan(planFlagStart){
+
+        debugger;
+
+        if (planFlag === 1) {
+            taskId = $("#id").val();
+            planId = $("#planId_cg").val();
+            mdmCode = $("#mdmCode_cg").val();
+            // 设置默认选中的选项
+            $('input[name="closeType"][value="指定类型"]').prop('checked', true);
+            $('input[name="specifiedType"][value="常规随访"]').prop('checked', true);
+        } else if (planFlag === 2) {
+            $('input[name="closeType"][value="指定类型"]').prop('checked', true);
+            $('input[name="specifiedType"][value="脱落召回"]').prop('checked', true);
+            taskId = $("#id").val();
+            planId = $("#planId_tl").val();
+            mdmCode = $("#mdmCode_tl").val();
+        }
+        patientId = $("#patientId").val();
+        var formData = new FormData();
+        formData.append('taskId', taskId);
+        formData.append('planId', planId);
+        formData.append('mdmCode', mdmCode);
+        formData.append('patientId', patientId);
+        formData.append('planFlag', planFlag);
+        $.modal.confirm("确认要关闭任务吗? 关闭任务后将停止该任务的随访操作", function () {
+            $.ajax({
+                url: prefix_task + "/RestartPlan",
+                data: formData,
+                method: 'POST',
+                processData: false, // 防止 jQuery 自动转换数据
+                contentType: false, // 不设置内容类型
+                success: function (data) {
+                    $.modal.alertSuccess("重启计划成功");
+                    window.location.reload();
+                },
+                error: function (xhr, status, error) {
+                    $.modal.alertError("重启计划失败");
+                }
+            });
+        });
+    }
+    // 关闭任务
+    function closeTask(id) {
+        $.modal.confirm("确认要关闭任务吗? 关闭任务后将停止该任务的随访操作", function () {
+            var formData = new FormData();
+            formData.append('id', id);
+
+            $.ajax({
+                url: prefix_task + "/closeTask",
+                data: formData,
+                method: 'POST',
+                processData: false, // 防止 jQuery 自动转换数据
+                contentType: false, // 不设置内容类型
+                success: function (data) {
+                    $.modal.alertSuccess("关闭任务成功");
+                    // $.modal.reload();
+                    window.location.reload();
+                },
+                error: function (xhr, status, error) {
+                    $.modal.alertError("关闭任务失败");
+                }
+            });
+        });
+    }
+
+    // 辅助函数:用于更新选项卡状态
+    function updateTabState(tabId) {
+        // 移除所有 li 的 active 类
+        $('#myUlTabs .nav-item').removeClass('active');
+        // 设置指定 tab 的 li 为 active
+        $(tabId).closest('.nav-tabs').find('a[href="' + tabId + '"]').parent().addClass('active');
+
+        // 更新 a 标签的 aria-expanded 属性
+        $('#myUlTabs a').attr('aria-expanded', 'false');
+        $(tabId).closest('.nav-tabs').find('a[href="' + tabId + '"]').attr('aria-expanded', 'true');
+
+        // 移除所有 tab-pane 的 active 和 in 类
+        $('.tab-pane').removeClass('active in show'); // 注意这里使用了 'show' 类,取决于你使用的 Bootstrap 版本
+        // 为指定 tab-pane 添加 active 和 in 类
+        $(tabId).addClass('active in show'); // 同上,确保与你的 Bootstrap 版本匹配
+    }
+
+    function editSFGenJinRen() {
+        //随访计划跟进人
+        var followUpPersonId = /*[[${followUpPersonId}]]*/ '';
+        var followUpPersonName = /*[[${followUpPersonName}]]*/ '';
+        // $.operate.addSetwht('分配跟进人','dtp/pmService/followUpAssignAdd',800,800)
+        //var followUpPersonId=$('#followUpPersonId').val()
+        //alert("编辑随访跟进人"+followUpPersonId);
+        var options = {
+            title: '分配跟进人',
+            width: 800,
+            height: 600,
+            url: prefix + '/followUpAssignAdd',
+            callBack: doSubmit2
+        };
+        $.modal.openOptions(options);
+    }
+    function createPlan() {
+        //随访计划跟进人
+        var followUpPersonId = /*[[${followUpPersonId}]]*/ '';
+        var followUpPersonName = /*[[${followUpPersonName}]]*/ '';
+        // $.operate.addSetwht('分配跟进人','dtp/pmService/followUpAssignAdd',800,800)
+        //var followUpPersonId=$('#followUpPersonId').val()
+        //alert("编辑随访跟进人"+followUpPersonId);
+        var options = {
+            title: '创建计划',
+            width: 1000,
+            height: 600,
+            url: prefix_task + '/createPlanPage',//createPlan
+            callBack: doSubmitPlan
+        };
+        $.modal.openOptions(options);
+    }
+    function doSubmitPlan(index, layero) {
+        var hzparam;
+        var rows = layero.find("iframe")[0].contentWindow.selectTableObject();
+        if (rows.length == 0) {
+            $.modal.alertWarning("请至少选择一条记录");
+            return;
+        }
+        rows.forEach(function (item) {
+            // 动态选择所有列并映射到更具描述性的名称
+            hzparam = {
+                id: item.id,
+                templateId: item.templateId,
+                templateName: item.templateName,
+                businessBelonging: item.businessBelonging,
+                drug: item.drug,
+                nodeId: item.nodeId,
+                model: item.model,
+                storeId: item.storeId,
+                patientId:  $("#patientId").val(),
+            };
+
+        });
+        $.ajax({
+            cache: true,
+            type: "POST",
+            url: prefix_task + '/createPlanAdd', // 创建计划
+            data: hzparam,
+            async: false,
+            success: function (data) {
+                $.modal.alertSuccess(data.msg);
+            },
+            error: function (error) {
+                $.modal.alertError("失败");
+            }
+        });
+        $.modal.close(index);
+    }
+    // callBack获取父窗口方法(方式二)
+    function doSubmit2(index, layero) {
+        var hzparam;
+        var rows = layero.find("iframe")[0].contentWindow.selectTableObject();
+        if (rows.length == 0) {
+            $.modal.alertWarning("请至少选择一条记录");
+            return;
+        }
+        var followUpPersonId = $('#followUpPersonId').val();//s_dtp_ysfw_follow_up_assign
+        rows.forEach(function (item) {
+            // 动态选择所有列并映射到更具描述性的名称
+            hzparam = {
+                id: item.id,
+                pharmacistName: item.pharmacistName,
+                position: item.position,
+                storeName: item.storeName,
+                phone: item.phone,
+                storeId: item.storeId,
+                followUpPersonId: followUpPersonId
+            };
+
+        });
+        $.ajax({
+            cache: true,
+            type: "POST",
+            url: prefix + '/editFollowUpAssign', // 替换为实际的搜索 URL
+            data: hzparam,
+            async: false,
+            success: function (data) {
+                $.modal.msg("操作成功");
+                // 更新 <code> 标签的内容
+                var followUpPersonName = hzparam.pharmacistName; // 假设需要第一个对象的 pharmacistName
+                $('#followUpPersonNameCode').text(followUpPersonName);
+                $('#followUpPersonName').val(followUpPersonName); // 更新输入框的值
+
+            },
+            error: function (error) {
+                $.modal.alertError("失败");
+            }
+        });
+        $.modal.close(index);
+    }
+
+    function editTaskGenJinRen() {
+        //任务跟进人
+
+        var options = {
+            title: '分配跟进人',
+            width: 800,
+            height: 600,
+            url: prefix + '/followUpAssignAdd',
+            callBack: doSubmit1
+        };
+        $.modal.openOptions(options);
+    }
+
+    function doSubmit1(index, layero) {
+        var bc_id = $('#bc_id').val()
+        var hzparam;
+        var rows = layero.find("iframe")[0].contentWindow.selectTableObject();
+        if (rows.length == 0) {
+            $.modal.alertWarning("请至少选择一条记录");
+            return;
+        }
+        rows.forEach(function (item) {
+            // 动态选择所有列并映射到更具描述性的名称
+            hzparam = {
+                id: item.id,
+                pharmacistName: item.pharmacistName,
+                position: item.position,
+                storeName: item.storeName,
+                phone: item.phone,
+                storeId: item.storeId,
+                taskId: bc_id
+            };
+
+        });
+        $.ajax({
+            cache: true,
+            type: "POST",
+            url: prefix + '/editFollowTaskAssignById', // 替换为实际的搜索 URL
+            data: hzparam,
+            async: false,
+            success: function (data) {
+                $.modal.msg("操作成功");
+                // 更新 <code> 标签的内容
+                var followUpPersonName = hzparam.pharmacistName; // 假设需要第一个对象的 pharmacistName
+                $('#bc_taskFollowerCode').text(followUpPersonName);
+                $('#bc_taskFollower').val(followUpPersonName); // 更新输入框的值
+
+            },
+            error: function (error) {
+                $.modal.alertError("失败");
+            }
+        });
+        $.modal.close(index);
+    }
+
+
+    function updateInterval(nextFollowTimeStr) {
+        // 获取当前时间
+        const now = new Date();
+        if (nextFollowTimeStr) {
+            // 将字符串转换为日期对象
+            const nextFollowTime = new Date(nextFollowTimeStr);
+
+            // 计算间隔天数
+            const intervalDays = Math.floor((nextFollowTime - now) / (1000 * 60 * 60 * 24));
+
+            // 显示间隔天数
+            const intervalThisTimeSpan = $('#interval_this_time');
+            if (intervalDays < 0) {
+                intervalThisTimeSpan.css('color', 'red').text(intervalDays);
+            } else {
+                intervalThisTimeSpan.css('color', '#1a7bb9').text(intervalDays);
+            }
+        } else {
+            // 如果没有选择下次随访时间,清除间隔天数显示
+            const intervalThisTimeSpan = $('#interval_this_time');
+            intervalThisTimeSpan.text('');
+        }
+    }
+
+    function onselectsf() {
+        // 初始化时检查是否有预设的下次随访时间
+        const initialNextFollowTime = $('#next_follow_time').val();
+        if (initialNextFollowTime) {
+            $('#next_follow_time').val(initialNextFollowTime);
+            updateInterval(initialNextFollowTime);
+        }
+    }
+</script>

+ 10 - 0
health-admin/src/main/resources/templates/dtp/followUp/follwUpEditDetail.html

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Title</title>
+</head>
+<body>
+
+</body>
+</html>

+ 1 - 1
health-admin/src/main/resources/templates/dtp/followUpAssign/followUpAssignAdd.html

@@ -118,7 +118,7 @@
     }
     /* 添加用户-选择用户-提交(子页面调用父页面形式) */
     function submitHandler(index, layero) {
-        debugger
+
         var rows = $.table.selectFirstColumns();
 
         if (rows.length == 0) {

+ 35 - 130
health-admin/src/main/resources/templates/dtp/followUpAssign/followUpAssignList.html

@@ -16,7 +16,7 @@
 					<div class="query-buttons">
 						<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
 						<a class="btn btn-warning btn-rounded btn-sm" onclick="resetPre()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
-						<a class="btn btn-w-m btn-dark" onclick="defultConfig"><i class="glyphicon glyphicon-cog" style="top: 2px;"></i><span style="font-size: 15px;">默认分配--->请设置</span></a>
+<!--						<a class="btn btn-w-m btn-dark" onclick="defultConfig"><i class="glyphicon glyphicon-cog" style="top: 2px;"></i><span style="font-size: 15px;">默认分配-&ndash;&gt;请设置</span></a>-->
 					</div>
 				</div>
 					<form id="followUpAssign-form"  class="customize-search-form">
@@ -27,9 +27,8 @@
 							</div>
 							<div class="customize-form-group">
 								<label>门店:</label>
-								<select name="storeId" class="styled-input">
+								<select name="storeId" id="storeSelect" class="styled-input">
 									<option value="">全部</option>
-									<!--									<option th:each="store : ${stores}" th:text="${store.name}" th:value="${store.id}"></option>-->
 								</select>
 							</div>
 							<div class="customize-form-group">
@@ -107,6 +106,7 @@
 		var editFlag = [[${@permission.hasPermi('dtp:pmService:edit')}]];
 		var removeFlag = [[${@permission.hasPermi('dtp:pmService:remove')}]];
 		var prefix = ctx + "dtp/pmService";
+		var prefix_task = ctx + "task/followTask";
 		var prefix_recipe = ctx + "dtp/recipe";
 		var patientId='';
 		var lzid;
@@ -230,6 +230,7 @@
 		var classWhiteName = 'btn btn-white';
 		//初始化加载
 		$(document).ready(function() {
+			findTaskStoreList();
 			var HZFPButton = document.getElementById('myButtonhzfp');
 			var myButtonyglzjj = document.getElementById('myButtonyglzjj');
 			HZFPButton.className=className;
@@ -281,7 +282,7 @@
 
 		// 分别给每个按钮添加class
 		HZFPButton.onclick = function() {
-			debugger
+
 			HZFPButton.className=className;
 			myButtonyglzjj.className=classWhiteName;
 			var x = document.getElementById("tabsDiv");
@@ -307,6 +308,35 @@
     			return '<i class=\"fa fa-toggle-on text-info fa-2x\" onclick="disable(\'' + row.userId + '\')"></i> ';
     		}
 		}
+
+		// 异步加载所有门店信息并填充下拉框
+		function findTaskStoreList() {
+			$.ajax({
+				url: prefix_task+ "/findTaskStoreList", // 这是获取所有门店的API端点
+				type: 'POST',
+				cache: false, // 设置为 false 防止缓存
+				processData: false, // 告诉 jQuery 不要处理数据(非常重要)
+				contentType: false, // 告诉 jQuery 不要设置 contentType(非常重要)
+				async: true, // 异步请求更为推荐,除非有特殊原因需要同步
+				success: function (data) {
+					var select = $('#storeSelect');
+					// 清空除了默认选项外的所有选项
+					select.find('option:not(:first)').remove();
+					if (data && data.data) {
+						data.data.forEach(function (store) {
+							select.append(new Option(store.dept_name, store.id));
+						});
+					} else {
+						console.error('Unexpected response format:', data);
+					}
+				},
+				error: function () {
+					$.modal.alertError('加载门店信息失败');
+				}
+			});
+		}
+
+
 		function dlFormatter(value, row, index) {
 			if (!value || typeof value !== 'string') {
 				return ''; // 如果值为空或不是字符串,返回空字符串
@@ -487,132 +517,7 @@
 			};
 
 			$.table.init(options);
-			// debugger;
-			// var datas=[];
-			// debugger
-			// var tableId = 'bootstrap-table-' + tabId.substring(4);
-			// var tableElement = $('#' + tableId);
-			// var data = $("#followUpAssign-form").serializeArray();
-			// data.push({name: 'flag', value: tabId.substring(4)});
-			// console.log("tableId="+tabId.substring(4));
-			// $.ajax({
-			// 	cache : true,
-			// 	type : "POST",
-			// 	url: prefix + "/followUpAssignList",
-			// 	data : data,
-			// 	async : false,
-			// 	error : function(request) {
-			// 		$.modal.alertError("系统错误");
-			// 	},
-			// 	success : function(data) {
-			// 		datas=data.rows;
-			// 		$.operate.successCallback(data);
-			// 	}
-			// });
-			// var options = {
-			// 	// 配置表格的相关属性
-			// 	// 例如数据源、列定义等
-			// 	// 示例配置
-			// 	modalName: "随访跟进人分配",
-			// 			fitColumns: true,
-			// 		striped: true,
-			// 		autoRowHeight: true,
-			// 		rowNumbers: true,
-			// 		showFooter:true,  //是否显示表格底部区域。
-			// 		clickToSelect: true, //是否启用点击行时选中整行的功能。
-			// 		singleSelect: false, //是否仅允许选择一行
-			// 		fixedColumns: true,
-			// 		//fixedNumber: 3,
-			// 		fixedRightNumber: 1,
-			// 		pagination: true, // 启用分页
-			// 		pageSize: 10, // 每页显示的条目数
-			// 		pageNumber: 1, // 当前页码
-			// 		pageList: [10, 25, 50, 100], // 可供选择的每页条目数
-			// 		sidePagination: 'server', // 使用服务器端分页
-			// 	     queryParams: function(params) {
-			// 			return {
-			// 				limit: params.limit, // 每页显示的条目数
-			// 				offset: params.offset, // 当前页的偏移量
-			// 				pageNumber: params.pageNumber, // 当前页码
-			// 				pageSize: params.pageSize, // 每页显示的条目数
-			// 				flag: tabId.substring(4)
-			// 			};
-			// 		},
-			// 		data: datas,
-			// 		columns: [{
-			// 	checkbox: true
-			// },
-			// 	{ field: 'name', title: '患者姓名', align: 'center' },
-			// 	{field: 'gender', title: '性别', align: 'center',width: 60,
-			// 		formatter: function(value, row, index) {
-			// 			switch (value) {
-			// 				case 0:
-			// 					return "男";
-			// 					break;
-			// 				case 1:
-			// 					return "女";
-			// 					break;
-			// 				default:
-			// 					return "-";
-			// 			}
-			// 		}},
-			// 	{ field: 'age', title: '年龄', align: 'center',width: 60, },
-			// 	{ field: 'dl', title: '疾病', align: 'center',formatter: dlFormatter },
-			// 	{ field: 'disease', title: '临床诊断', align: 'center' },
-			// 	{ field: 'productName', title: '药品名称', align: 'center' },
-			// 	{ field: 'status', title: '跟进人状态', align: 'center',width: 80,
-			// 		formatter: function(value, row, index) {
-			// 			switch (value) {
-			// 					//0离职,1在职,2请假
-			// 				case 0:
-			// 					return "离职";
-			// 					break;
-			// 				case 1:
-			// 					return "在职";
-			// 					break;
-			// 				case 2:
-			// 					return "请假";
-			// 					break;
-			// 				default:
-			// 					return "-";
-			// 			}
-			// 		} },
-			// 	{ field: 'follow_up_assign', title: '随访跟进人', align: 'center',width: 80,
-			// 		formatter: function(value, row, index) {
-			// 			switch (value) {
-			// 				case "":
-			// 					return "未分配";
-			// 					break;
-			// 				case undefined:
-			// 					return "未分配";
-			// 					break;
-			// 				case " ":
-			// 					return "未分配";
-			// 					break;
-			// 				default:
-			// 					return value;
-			// 			}
-			// 		}  },
-			// 	{ field: 'updateTime', title: '最后一次购药', align: 'center' },
-			// 	{ field: 'createTime', title: '建档日期', align: 'center' },
-			// 	{
-			// 		title: '操作',
-			// 		align: 'center',
-			// 		width: 150,
-			// 		formatter: function(value, row, index) {
-			// 			if (row.id) {
-			// 				var actions = [];
-			// 				actions.push('<a class="btn-xs' + editFlag + '" href="javascript:void(0)" onclick="selectUsersToParentCallBack2(\'' + row.id + '\')">修改跟进人</a> ');
-			// 				actions.push('<a class="btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.id + '\')"><i class="fa fa-remove"></i>删除</a> ');
-			// 				return actions.join('');
-			// 			} else {
-			// 				return "";
-			// 			}
-			// 		}
-			// 	}]
-			//
-			// }
-			// tableElement.bootstrapTable(options);
+
 		}
 		function selectUsersToParentCallBack2(rowid){
 			console.log("rowid=="+rowid)

+ 1 - 1
health-admin/src/main/resources/templates/dtp/followUpAssign/followUpInformationPage.html

@@ -103,7 +103,7 @@
 	}
 	/* 添加用户-选择用户-提交(子页面调用父页面形式) */
 	function submitHandler(index, layero) {
-		debugger
+
 		var rows = $.table.selectFirstColumns();
 
 		if (rows.length == 0) {

+ 2 - 2
health-admin/src/main/resources/templates/dtp/patientCounseling/patientCounselingEdit.html

@@ -99,9 +99,9 @@
         }
     }
     $(document).ready(function() {
-        debugger
+
     function checkKnowledgeLink(KnowledgeLinkList) {
-        debugger
+
         var KnowledgeLink = KnowledgeLinkList.split(',');
         $.each(KnowledgeLink, function(index, value) {
             var trimmedValue = value.trim();

+ 1 - 1
health-admin/src/main/resources/templates/dtp/print/detail.html

@@ -48,7 +48,7 @@
     <th:block th:include="include :: jsonview-js" />
     <script th:inline="javascript">
 		$(function() {
-			debugger
+
 			var operParam = [[${oorderData}]];
 			if ($.common.isNotEmpty(operParam) && operParam.length < 2000) {
 				$("#operParam").JSONView(operParam);

+ 4 - 4
health-admin/src/main/resources/templates/dtp/print/print.html

@@ -50,7 +50,7 @@
 
 		        <div class="btn-group-sm" id="toolbar" role="group">
 
-		            <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="sdtp:print:remove">
+		            <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="dtp:print:remove">
 		                <i class="fa fa-remove"></i> 删除
 		            </a>
 
@@ -72,9 +72,9 @@
 	<th:block th:include="include :: bootstrap-table-fixed-columns-js" />
 	<th:block th:include="include :: ztree-js" />
 	<script th:inline="javascript">
-		var editFlag = [[${@permission.hasPermi('system:user:edit')}]];
-		var removeFlag = [[${@permission.hasPermi('system:user:remove')}]];
-		var detailFlag = [[${@permission.hasPermi('dtp:print:view')}]];
+		var editFlag = [[${@permission.hasPermi('dtp:print:edit')}]];
+		var removeFlag = [[${@permission.hasPermi('dtp:print:remove')}]];
+		var detailFlag = [[${@permission.hasPermi('dtp:print:detail')}]];
 		var prefix = ctx + "dtp/print";
 
 		$(function() {

+ 36 - 2
health-admin/src/main/resources/templates/dtp/recipe/drugInfo.html

@@ -26,6 +26,40 @@
 									<input type="text" class="styled-input" placeholder="请输入MDM编码或药品商品名" name="query" style="width: 350px;"/>
 								</div>
 							</div>
+							<!-- 是否随访管理品 -->
+							<div class="customize-form-group">
+								<label>是否随访管理品:</label>
+								<select name="isFollowUpManaged" class="styled-input" th:with="type=${@dict.getType('sys_gxhpz_yes_no')}">
+									<option value="">请选择</option>
+									<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+								</select>
+							</div>
+							<!-- 是否冷链管理品 -->
+							<div class="customize-form-group">
+								<label>是否冷链管理品:</label>
+								<select name="isColdChainManaged" class="styled-input" th:with="type=${@dict.getType('sys_gxhpz_yes_no')}">
+									<option value="">请选择</option>
+									<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+								</select>
+							</div>
+
+							<!-- 是否登记管理品 -->
+							<div class="customize-form-group">
+								<label>是否登记管理品:</label>
+								<select name="isRegisteredManaged" class="styled-input" th:with="type=${@dict.getType('sys_gxhpz_yes_no')}">
+									<option value="">请选择</option>
+									<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+								</select>
+							</div>
+
+							<!-- 是否慈善援助管理品 -->
+							<div class="customize-form-group">
+								<label>是否慈善援助管理品:</label>
+								<select name="isCharityAidManaged" class="styled-input" th:with="type=${@dict.getType('sys_gxhpz_yes_no')}">
+									<option value="">请选择</option>
+									<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+								</select>
+							</div>
 						</form>
 					</div>
 
@@ -39,7 +73,7 @@
 
 <th:block th:include="include :: footer" />
 <script th:inline="javascript">
-	var viewFlag = [[${@permission.hasPermi('dtp:configInfo:list')}]];
+	var viewFlag = [[${@permission.hasPermi('dtp:configInfo:query')}]];
 	var prefix = ctx + "dtp/configInfo";
 
 	$(function() {
@@ -122,7 +156,7 @@
 	}
 	/* 添加用户-选择用户-提交(子页面调用父页面形式) */
 	function submitHandler(index, layero) {
-		debugger
+
 		var rows = $.table.selectFirstColumns();
 
 		if (rows.length == 0) {

+ 28 - 10
health-admin/src/main/resources/templates/dtp/recipe/edit.html

@@ -292,7 +292,7 @@
                             <input name="contactName" placeholder="请输入联系人姓名" class="styled-input" type="text" maxlength="30"  required>
                         </div>
                         <div class="customize-form-group">
-                            <label class="col-sm-1 control-label">证件号码:</label>
+                            <label class="col-sm-1 control-label is-required">证件号码:</label>
                             <input name="documentNumber" id="documentNumber" placeholder="请输入证件号码" class="styled-input" type="text" maxlength="30"  required>
                         </div>
                         <div class="customize-form-group edit select-time">
@@ -304,8 +304,6 @@
                             <input name="addr" placeholder="配送地址"  id="addr" class="styled-input" type="text">
                         </div>
 
-
-
                         <div class="customize-form-group">
                             <label class="col-sm-1 control-label">肿瘤发病部位疾病:</label>
                             <select id="category-select1"   class="styled-input select2-multiple edit_inputs" multiple placeholder="请选择或输入搜索">
@@ -379,6 +377,7 @@
     var reviewingName;
     var reviewStatus;
     var reviewFlag=false;
+    var salesOrderNumberfLag=false;//判断是否是 先销售后登记 的处方
     var data;
     var patientFlag = true;//患者是否建档
     var shangcigyList = [];
@@ -840,7 +839,7 @@
                 // 遍历返回的数据并添加选项
                 $.each(data.value, function (index, item) {
                     $('<option>', {
-                        value: item.id,
+                        value: item.standardName,
                         text: item.standardName
                     }).appendTo(options);
 
@@ -1248,7 +1247,7 @@
             // 计算 D 值
             var num = rowData.packageQuantity;  // 买药数量
             var dnum = (num * rowData.dosageFrequencyDays) / (dcnum * pcnum);   //  买药数量 * 包装单位数量 / 频次天数 = D 值天数
-            if ((dcnum * pcnum) > rowData.dosageMax) {
+            if (dcnum > rowData.dosageMax) {
                 ts_msg = '单次剂量数值频次大于 最大值 已使用默认值';
                 if (rowData.dosageNormal == '' || rowData.dosageNormal == null || rowData.dosageNormal == undefined) {
                     dnum = (num * rowData.dosageFrequencyDays) / rowData.dosageMax;
@@ -1256,7 +1255,7 @@
                     dnum = (num * rowData.dosageFrequencyDays) / rowData.dosageNormal;
                 }
             }
-            if ((dcnum * pcnum) < rowData.dosageMin) {
+            if (dcnum < rowData.dosageMin) {
                 ts_msg = '单次剂量数值频次小于 最小值 已使用默认值';
                 if (rowData.dosageNormal == '' || rowData.dosageNormal == null || rowData.dosageNormal == undefined) {
                     dnum = (num * rowData.dosageFrequencyDays) / rowData.dosageMin;
@@ -1397,6 +1396,7 @@
         }
     }
     function updateCFDJ(formData) {
+        $.modal.loading("保存中...");
         $.ajax({
             cache: false, // 设置为 false 防止缓存
             type: "POST",
@@ -1409,7 +1409,7 @@
                 $.modal.alertError("操作失败");
             },
             success: function(data) {
-                $.modal.alertSuccess(data.msg);
+                $.modal.closeLoading();
                 $.modal.closeTab();
                 window.location.reload();
             }
@@ -1840,14 +1840,20 @@
                 $.modal.alertError("系统错误");
             },
             success: function (data) {
+                debugger;
                 var shangciObj = {};
                 shangcigyList = data.data.recipeList;
                 if (data.data.recipeList != null || data.data.recipeList != undefined) {
                     initTabShow(datas = data.data.recipeList)
                 }
                 if (data.data.recipe != null) {
+                    debugger;
                     shangciObj = data.data.recipe;
-                    // 动态填充表单字段
+                 var   salesOrderNumber= shangciObj.salesOrderNumber;
+                 if(salesOrderNumber){
+                     salesOrderNumberfLag=true;
+                 }
+                    // 动态填充表单字段 判断是不是已经有过的销售单子 有销售单子 就不能选药
                     $.each(shangciObj, function (key, value) {
                         $('#' + key).val(value);
                         if (key == "gender") {
@@ -1859,6 +1865,14 @@
                             $('#' + key).val(value);
                         }
                     });
+
+                    // 根据salesOrderNumberfLag的状态隐藏或显示“添加药品”按钮
+                    if(salesOrderNumberfLag){
+                        // 使用更具体的选择器来定位到具体的按钮,这里使用onclick属性进行精确匹配
+                        $("a[onclick='selectUsersToParentCallBack2()']").hide(); // 隐藏指定的按钮
+                    } else {
+                        $("a[onclick='selectUsersToParentCallBack2()']").show(); // 确保按钮可见,以防之前被隐藏过
+                    }
                 }
             }
         });
@@ -1884,6 +1898,7 @@
                 registeredItem: item.registeredItem,
                 followUpItem: item.followUpItem,
                 coldChainItem: item.coldChainItem,
+                salesOrderNumber: item.salesOrderNumber,
                 flowItem: item.flowItem,
                 packageQuantity: item.packageQuantity,
                 singleDoseValue: item.singleDoseValue,
@@ -1906,11 +1921,14 @@
 
             // 检查是否已经存在该药品
             if (!isProductExist(columnsData.productId)) {
-
+// 根据salesOrderNumber决定是否展示删除按钮
+                var deleteButtonHtml = item.salesOrderNumber ? item.salesOrderNumber :
+                    `<button onclick="deleteRow('${columnsData.productId}')" class="btn btn-danger">删除重选择药品</button>`;
                 // 向表格中添加一行
+                //<button onclick="deleteRow('${columnsData.productId}')" class="btn btn-danger">删除重选择药品</button>
                 var row = `
                 <tr data-product-id="${columnsData.productId}">
-                    <td><button onclick="deleteRow('${columnsData.productId}')" class="btn btn-danger">删除重选择药品</button></td>
+                    <td>${deleteButtonHtml}</td>
                     <td>${columnsData.productCode}</td>
                     <td>${columnsData.productName}</td>
                     <td class="hidden-column">${columnsData.genericName}</td>

+ 1 - 1
health-admin/src/main/resources/templates/dtp/recipe/huanzheBanding.html

@@ -98,7 +98,7 @@
 	}
 	/* 添加用户-选择用户-提交(子页面调用父页面形式) */
 	function submitHandler(index, layero) {
-		debugger
+
 		var rows = $.table.selectFirstColumns();
 
 		if (rows.length == 0) {

+ 179 - 15
health-admin/src/main/resources/templates/dtp/recipe/newRecipe.html

@@ -20,7 +20,6 @@
                     <a class="btn btn-primary btn-rounded btn-sm" onclick="search_hz()">&nbsp;搜索</a>
                     <a class="btn btn-warning btn-rounded btn-sm" onclick="resetPre()">重置</a>
                     <a class="btn btn-info btn-rounded btn-sm" onclick="bookbuilding()">&nbsp快速建档</a>
-<!--        bookbuilding  $.operate.addSetwht('快速建档','dtp/pmService/archivesAdd',800,750)-->
                 </div>
             </div>
      </form>
@@ -88,10 +87,7 @@
                             <label class="is-required">处方开具日期:</label>
                             <input name="prescriptionIssueDate" id="prescriptionIssueDate" placeholder="处方开具日期"   class="time-input time-input2" type="text">
                         </div>
-                        <div class="customize-form-group">
-                            <label>发票编码:</label>
-                            <input name="invoiceCode" id="invoiceCode" placeholder="发票编码"  class="styled-input" type="text">
-                        </div>
+
                         <!--<div class="customize-form-group">
                             <label>发票图片:</label>
                             <input name="invoiceImageUrl" id="invoiceImageUrl" placeholder="发票图片"  class="styled-input" type="text">
@@ -124,7 +120,10 @@
 
                             </select>
                         </div>
-
+                        <div class="customize-form-group">
+                            <label>发票编码:</label>
+                            <input name="invoiceCode" id="invoiceCode" placeholder="发票编码"  class="styled-input" style="width: 360px;" type="text">
+                        </div>
                     </div>
         <div class="customize-form-group-container">
             <div class="customize-form-group">
@@ -151,7 +150,6 @@
                     <div class="fileinput fileinput-new" data-provides="fileinput">
                         <div class="fileinput-preview thumbnail" data-trigger="fileinput" style="width: 200px; height: 150px;">
                             <img id="invoiceImageUrl" th:if="${invoiceImageUrl}" th:src="@{${invoiceImageUrl}}" class="preview-image" />
-
                             <div th:unless="${invoiceImageUrl}" class="centered-content">
                                 <span class="plus-sign">+</span>
                             </div>
@@ -163,6 +161,7 @@
                     </div>
                 </div>
             </div>
+
         </div>
      </form>
         <!-- 假设这是你的 HTML 结构class="table table-bordered" -->
@@ -270,9 +269,11 @@
 <th:block th:include="include :: bootstrap-table-fixed-columns-js" />
 <th:block th:include="include :: layout-latest-js" />
 <script th:inline="javascript">
-    var editFlag = [[${@permission.hasPermi('dtp:RecipeRegister:edit')}]];
-    var removeFlag = [[${@permission.hasPermi('dtp:RecipeRegister:remove')}]];
+    var editFlag = [[${@permission.hasPermi('dtp:recipe:edit')}]];
+    var removeFlag = [[${@permission.hasPermi('dtp:recipe:remove')}]];
+    var queryFlag = [[${@permission.hasPermi('dtp:recipe:query')}]];
     var prefix_recipe = ctx + "dtp/recipe";
+    var prefix_ocr_recipe = ctx + "ocr/recipe";
     var prefix_yppz= ctx + "yppz/drugConfig";
     var prefix_configInfo= ctx + "dtp/configInfo";
 
@@ -709,7 +710,7 @@
                  // 遍历返回的数据并添加选项
                  $.each(data.value, function(index, item) {
                      $('<option>', {
-                         value: item.id,
+                         value: item.standardName,
                          text : item.standardName
                      }).appendTo(options);
                  });
@@ -763,8 +764,152 @@
              $('#category-select').val(data.categoryName);//.trigger('change')
          }
 
+
+       //识别处方
+         $('#prescriptionImageUpload').on('change', function (event) {
+             var formData = new FormData();
+             var fileInput = event.target;
+             if (fileInput.files.length > 0) {
+                 var file = fileInput.files[0];
+                 formData.append('image', file);
+                 formData.append('flag', 'CF');
+                 $.ajax({
+                     url: prefix_ocr_recipe+'/uploadInvoice',
+                     type: 'POST',
+                     data: formData,
+                     processData: false,
+                     contentType: false,
+                     success: function (response) {
+                         try {
+                             debugger;
+                             var result = JSON.parse(response);
+                             if (result.error_code) {
+                                 $.modal.alertWarning('Error:' + result.error_msg);
+                                 return;
+                             }
+                             // 解析返回的JSON数据
+                             console.log(result);
+                             // 根据实际返回的数据结构更新表单字段
+                             // 2. 提取字段
+                             const results = parseOCRResult(result);
+                             const {
+                                 '临床诊断': LCZD = '',
+                                 '科别': KS = '',
+                                 '医师': YS = '',
+                                 '审核': SHYS = '',
+                                 '开具日期': KJRQ = '',
+                                 '姓名': name = '',
+                                 '用法用量': YFYL = ''
+                             } = results;
+
+                            // 3. 填充表单(确保ID正确)
+                             $('#prescriptionDiagnosis').val(LCZD);
+                             $('#attendingDoctor').val(YS);
+                             $('#auditPharmacist').val(SHYS); // 需要HTML中有对应ID
+                             $('#prescriptionIssueDate').val(KJRQ);
+                             $('#query').val(name);
+                             $('#department').val(KS);
+                             // 显示上传后的图片
+                             var reader = new FileReader();
+                             reader.onload = function (e) {
+                                 $('#prescriptionImageUrl').attr('src', e.target.result);
+                             };
+                             reader.readAsDataURL(file);
+                         } catch (e) {
+                             console.error('Failed to parse response:', e);
+                         }
+                     },
+                     error: function (xhr, status, error) {
+                         console.error('Error uploading image:', error);
+                         try {
+                             var errorResponse = JSON.parse(xhr.responseText);
+                             alert('Error: ' + errorResponse.error_msg);
+                         } catch (e) {
+                             alert('An unexpected error occurred.');
+                         }
+                     }
+                 });
+             }
+         });
+     //发票识别
+         $('#invoiceImageUpload').on('change', function (event) {
+             var formData = new FormData();
+             var fileInput = event.target;
+             if (fileInput.files.length > 0) {
+                 var file = fileInput.files[0];
+                 formData.append('image', file);
+                 formData.append('flag', 'FP');
+
+                 $.ajax({
+                     url: prefix_ocr_recipe+'/uploadInvoice',
+                     type: 'POST',
+                     data: formData,
+                     processData: false,
+                     contentType: false,
+                     success: function (response) {
+                         try {
+                             var result = JSON.parse(response);
+                             if (result.error_code) {
+                                 $.modal.alertWarning('Error:' + result.error_msg);
+                                 return;
+                             }
+                             // 解析返回的JSON数据
+                             console.log(result);
+                            // 提取关键字段
+                             // 提取票据代码和票据号码(兼容字段不存在的情况)
+                             const RegionSupplement = result.words_result.RegionSupplement;
+                             const PJCODE = RegionSupplement.find(it => it.name === "票据代码")?.word || "";
+                             const PJNUMBER = RegionSupplement.find(it => it.name === "票据号码")?.word || "";
+
+                            // 组装成目标JSON对象格式
+                             // 转换为JSON字符串
+                             const PJCODE_PJNUMBER_STR = JSON.stringify({
+                                 "票据代码": PJCODE,
+                                 "票据号码": PJNUMBER
+                             });
+
+                            // 填充到表单(假设前端支持对象直接赋值)
+                             $('#invoiceCode').val(PJCODE_PJNUMBER_STR);
+                             // 显示上传后的图片
+                             var reader = new FileReader();
+                             reader.onload = function (e) {
+                                 $('#prescriptionImageUrl').attr('src', e.target.result);
+                             };
+                             reader.readAsDataURL(file);
+                         } catch (e) {
+                             console.error('Failed to parse response:', e);
+                         }
+                     },
+                     error: function (xhr, status, error) {
+                         console.error('Error uploading image:', error);
+                         try {
+                             var errorResponse = JSON.parse(xhr.responseText);
+                             alert('Error: ' + errorResponse.error_msg);
+                         } catch (e) {
+                             alert('An unexpected error occurred.');
+                         }
+                     }
+                 });
+             }
+         });
+
     });
 
+    // 封装提取函数(复用其他字段提取逻辑)
+    const extractFromRegionSupplement = (supplementArray, targetName) => {
+        const item = supplementArray.find(it => it.name === targetName);
+        return item ? item.word : '';
+    };
+
+    // 1. 转换数据为键值对
+    const parseOCRResult = (data) => {
+        return data.words_result.reduce((acc, item) => {
+            const [key, ...values] = item.words.split(':');
+            const value = values.join(':').trim();
+            if (key && value) acc[key] = value; // 过滤空值(如"住址/电话:")
+            return acc;
+        }, {});
+    };
     // 监听药师审核选择事件
     function onclickshow() {
         reviewFlag=true;
@@ -1094,7 +1239,16 @@ function initTab(datas){
             var num = rowData.packageQuantity;  // 买药数量
             var dnum = (num*rowData.dosageFrequencyDays)/(dcnum*pcnum);   //  买药数量 * 包装单位数量 / 频次天数 = D 值天数
 
-            if((dcnum*pcnum)>rowData.dosageMax){
+            // if((dcnum*pcnum)>rowData.dosageMax){
+            //     $.modal.confirm("单次剂量数值频次大于最大值 是否使用默认值?", function() {
+            //         if (rowData.dosageNormal == ''|| rowData.dosageNormal == null|| rowData.dosageNormal == undefined){
+            //             dnum = (num*rowData.dosageFrequencyDays)/rowData.dosageMax;
+            //         }else {
+            //             dnum = (num*rowData.dosageFrequencyDays)/rowData.dosageNormal;
+            //         }
+            //     });
+            // }
+            if(dcnum>rowData.dosageMax){
                 $.modal.confirm("单次剂量数值频次大于最大值 是否使用默认值?", function() {
                     if (rowData.dosageNormal == ''|| rowData.dosageNormal == null|| rowData.dosageNormal == undefined){
                         dnum = (num*rowData.dosageFrequencyDays)/rowData.dosageMax;
@@ -1103,7 +1257,16 @@ function initTab(datas){
                     }
                 });
             }
-            if((dcnum*pcnum)<rowData.dosageMin){
+            // if((dcnum*pcnum)<rowData.dosageMin){
+            //     $.modal.confirm("单次剂量数值频次小于最小值 是否使用默认值?", function() {
+            //         if (rowData.dosageNormal == ''|| rowData.dosageNormal == null|| rowData.dosageNormal == undefined){
+            //             dnum = (num*rowData.dosageFrequencyDays)/rowData.dosageMin;
+            //         }else {
+            //             dnum = (num*rowData.dosageFrequencyDays)/rowData.dosageNormal;
+            //         }
+            //     });
+            // }
+            if(dcnum<rowData.dosageMin){
                 $.modal.confirm("单次剂量数值频次小于最小值 是否使用默认值?", function() {
                     if (rowData.dosageNormal == ''|| rowData.dosageNormal == null|| rowData.dosageNormal == undefined){
                         dnum = (num*rowData.dosageFrequencyDays)/rowData.dosageMin;
@@ -1215,6 +1378,8 @@ function initTab(datas){
         }
     }
     function updateCFDJ(formData) {
+        $.modal.loading("保存中...");
+
         $.ajax({
             cache: false, // 设置为 false 防止缓存
             type: "POST",
@@ -1230,10 +1395,9 @@ function initTab(datas){
                 if(data.data.code==200){
                     prescriptionNumber = data.data.prescriptionNumber;
                     drugsLinkId = data.data.drugsLinkId;
-                    $.modal.msg(data.msg);
-
-                    $.modal.close();
+                    $.modal.closeLoading();
                     $.modal.msgSuccessReload();
+                    $.modal.close();
                 }else{
                     $.modal.alertError(data.data.msg);
                 }

+ 24 - 17
health-admin/src/main/resources/templates/dtp/recipe/recipe.html

@@ -89,8 +89,9 @@
 <th:block th:include="include :: bootstrap-table-fixed-columns-js" />
 <th:block th:include="include :: ztree-js" />
 <script th:inline="javascript">
-	var editFlag = [[${@permission.hasPermi('system:user:edit')}]];
-	var removeFlag = [[${@permission.hasPermi('system:user:remove')}]];
+	var editFlag = [[${@permission.hasPermi('dtp:recipe:edit')}]];
+	var removeFlag = [[${@permission.hasPermi('dtp:recipe:remove')}]];
+	var queryFlag = [[${@permission.hasPermi('dtp:recipe:query')}]];
 	var prefix = ctx + "dtp/recipe";
 
 	$(function() {
@@ -145,6 +146,25 @@
 				{ field: "dvalueDays", title: "剂量天数" },
 				{ field: "prescriptionNumber", title: "处方单号" },
 				{ field: "salesOrderNumber", title: "销售单号" },
+				{ field: "status", title: "状态", formatter: function(value, row, index) {
+						switch (value) {
+							case 1: return "订单已完成";
+							case 2: return "待上传处方";
+							case 3: return "待确认信息";
+							case 4: return "待处方登记";
+							case 5: return "待订单销售";
+							case 6: return "待绑定患者";
+							case 7: return "处方已完成";
+							case 8: return "订单已退款";
+							case 9: return "退款中";
+							case 10: return "已取消";
+							case 11: return "待支付";
+							case 12: return "已支付";
+							case 15: return "处方已完成-订单已完成";
+							default: return "待确认信息";
+						}
+					}
+				},
 				{ field: "saleDate", title: "销售日期" },
 				{ field: "standardName", title: "医院" },
 				{ field: "department", title: "科室" },
@@ -172,20 +192,7 @@
 						}
 					}
 				},
-				{ field: "status", title: "状态", formatter: function(value, row, index) {
-						switch (value) {
-							case 1: return "订单已完成";
-							case 2: return "待上传处方";
-							case 3: return "待确认信息";
-							case 4: return "待处方登记";
-							case 5: return "待订单销售";
-							case 6: return "待绑定患者";
-							case 7: return "处方已完成";
-							case 8: return "订单已退款";
-							default: return "待确认信息";
-						}
-					}
-				},
+
 				{ field: "createdTime", title: "创建时间" },
 				{ field: "updatedTime", title: "最后更新时间", visible: false },
 				{ field: "prescriptionImageUrl", title: "处方图片URL", visible: false },
@@ -218,7 +225,7 @@
 					if (row.id) {
 						var actions = [];
 						actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.editTab(\'' + row.id + '\')"><i class="fa fa-edit"></i>编辑</a> ');
-						actions.push('<a class="btn btn-warning btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.view(\'' + row.id + '\')"><i class="fa fa-eye"></i>详情</a> ');
+						actions.push('<a class="btn btn-warning btn-xs ' + queryFlag + '" href="javascript:void(0)" onclick="$.operate.view(\'' + row.id + '\')"><i class="fa fa-eye"></i>详情</a> ');
 						actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.id + '\')"><i class="fa fa-remove"></i>删除</a> ');
 						return actions.join('');
 					} else {

+ 101 - 17
health-admin/src/main/resources/templates/dtp/recipe/salesRegistration.html

@@ -66,7 +66,40 @@
 							</select>
 						</div>
 					</div>
+
+				</form>
+				<form id="user-form2" class="customize-search-form">
+					<h4>模拟生成销售单</h4>
+						<div class="customize-form-group">
+							<label>随机生成几个销售订单:</label>
+							<input type="number" class="styled-input" placeholder="请输入数字" name="limitNumber" />
+							<a class="btn btn-primary btn-rounded btn-sm" onclick="createOrder()" shiro:hasPermission="ddgl:dd:add"><i class="fa fa-search"></i>生成订单</a>
+							<a class="btn btn-warning btn-rounded btn-sm" onclick="resetPre2()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
+						</div>
 				</form>
+<!--				<form id="user-form3" class="customize-search-form">-->
+<!--					<h4>按条件查询生成指定的订单</h4>-->
+<!--					<div class="customize-form-group-container">-->
+<!--						<div class="customize-form-group">-->
+<!--							<label>患者姓名:</label>-->
+<!--							<input type="text" class="styled-input" placeholder="请输入患者姓名" name="patientName"/>-->
+<!--&lt;!&ndash;							<label>药品编码:</label>&ndash;&gt;-->
+<!--&lt;!&ndash;							<input type="text" class="styled-input" placeholder="请输入药品编码" name="mdmCode"/>&ndash;&gt;-->
+<!--							<label>门店ID:</label>-->
+<!--							<input type="text" class="styled-input" placeholder="请输入门店ID" name="storeId"/>-->
+<!--						</div>-->
+<!--						<div class="customize-form-group select-time">-->
+<!--							<label>销售日期:</label>-->
+<!--							<input type="text" class="time-input" id="startTime2" placeholder="开始时间" name="startTime"/>-->
+<!--							<span>-</span>-->
+<!--							<input type="text" class="time-input" id="endTime2" placeholder="结束时间" name="endTime"/>-->
+<!--							<a class="btn btn-primary btn-rounded btn-sm" onclick="createOrder2()" shiro:hasPermission="ddgl:dd:add"><i class="fa fa-search"></i>生成订单</a>-->
+<!--							<a class="btn btn-warning btn-rounded btn-sm" onclick="resetPre3()"><i class="fa fa-refresh"></i>&nbsp;重置</a>-->
+<!--						</div>-->
+
+<!--					</div>-->
+
+<!--				</form>-->
 			</div>
 			<div class="btn-group-sm" id="toolbar" role="group">
 				<!--		        	<a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="dtp:recipe:add">-->
@@ -89,8 +122,9 @@
 <th:block th:include="include :: bootstrap-table-fixed-columns-js" />
 <th:block th:include="include :: ztree-js" />
 <script th:inline="javascript">
-	var editFlag = [[${@permission.hasPermi('system:user:edit')}]];
-	var removeFlag = [[${@permission.hasPermi('system:user:remove')}]];
+	var editFlag = [[${@permission.hasPermi('dtp:recipe:edit')}]];
+	var removeFlag = [[${@permission.hasPermi('dtp:recipe:add')}]];
+	var queryFlag = [[${@permission.hasPermi('dtp:recipe:query')}]];
 	var prefix = ctx + "dtp/recipe";
 	var prefix_pmService = ctx + "dtp/pmService";
 	$(function() {
@@ -145,6 +179,24 @@
 				{ field: "mdmCode", title: "药品编码" },
 				{ field: "prescriptionNumber", title: "处方单号" },
 				{ field: "salesOrderNumber", title: "销售单号" },
+				{ field: "status", title: "状态", formatter: function(value, row, index) {
+						switch (value) {
+							case 1: return "订单已完成";
+							case 2: return "待上传处方";
+							case 3: return "待确认信息";
+							case 4: return "待处方登记";
+							case 5: return "待订单销售";
+							case 6: return "待绑定患者";
+							case 7: return "处方已完成";
+							case 8: return "订单已退款";
+							case 9: return "退款中";
+							case 10: return "已取消";
+							case 11: return "待支付";
+							case 12: return "已支付";
+							default: return "待确认信息";
+						}
+					}
+				},
 				{ field: "saleDate", title: "销售日期" },
 				{ field: "prescriptionIssueDate", title: "处方开具日期" },
 				{ field: "registrationDate", title: "处方登记日期" },
@@ -167,20 +219,6 @@
 				{ field: "age", title: "年龄" },
 				{ field: "patientPhone", title: "患者手机号" },
 				{ field: "storeName", title: "建档门店", visible: true },
-				{ field: "status", title: "状态", formatter: function(value, row, index) {
-						switch (value) {
-							case 1: return "订单已完成";
-							case 2: return "待上传处方";
-							case 3: return "待确认信息";
-							case 4: return "待处方登记";
-							case 5: return "待订单销售";
-							case 6: return "待绑定患者";
-							case 7: return "处方已完成";
-							case 8: return "订单已退款";
-							default: return "待确认信息";
-						}
-					}
-				},
 				{ field: "createdTime", title: "创建时间" },
 				{ field: "updatedTime", title: "最后更新时间", visible: false },
 				{ field: "drugQuantity", title: "处方取药数量" },
@@ -203,7 +241,7 @@
 					if (row.id) {
 						var actions = [];
 						actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.editTab(\'' + row.id + '\')"><i class="fa fa-edit"></i>去登记</a> ');
-						actions.push('<a class="btn btn-warning btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.view(\'' + row.id + '\')"><i class="fa fa-eye"></i>详情</a> ');
+						actions.push('<a class="btn btn-warning btn-xs ' + queryFlag + '" href="javascript:void(0)" onclick="$.operate.view(\'' + row.id + '\')"><i class="fa fa-eye"></i>详情</a> ');
 						actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.id + '\')"><i class="fa fa-remove"></i>删除</a> ');
 						return actions.join('');
 					} else {
@@ -214,7 +252,42 @@
 		};
 		$.table.init(options);
 	}
+	function createOrder() {
+		var data = $("#user-form2").serializeArray();
+		$.ajax({//写到这里
+			cache : true,
+			type : "POST",
+			url : ctx + "ddgl/ddglorderstore/getRandomOrdersWithMedications",
+			data : data,
+			async : false,
+			error : function(request) {
+				$.modal.alertError("失败");
+			},
+			success : function(data) {
+				$.table.refresh();
+			}
+		});
+
+
+	}
+	function createOrder2() {
+
+		var data = $("#user-form3").serializeArray();
+		$.ajax({
+			cache : true,
+			type : "POST",
+			url : ctx + "ddgl/ddglorderstore/getFilteredOrdersWithMedications",
+			data : data,
+			async : false,
+			error : function(request) {
+				$.modal.alertError("失败");
+			},
+			success : function(data) {
+				$.table.refresh()
+			}
+		});
 
+	}
 	/* 自定义重置-表单重置/隐藏框/树节点选择色/搜索 */
 	function resetPre() {
 		resetDate();
@@ -224,7 +297,18 @@
 		$(".curSelectedNode").removeClass("curSelectedNode");
 		$.table.search();
 	}
+	function resetPre3() {
+
+		$("#user-form3")[0].reset();
 
+	}
+	/* 自定义重置-表单重置/隐藏框/树节点选择色/搜索 */
+	function resetPre2() {
+		$("#user-form2")[0].reset();
+		// $("#deptId").val("");
+		// $("#parentId").val("");
+		// $(".curSelectedNode").removeClass("curSelectedNode");
+	}
 </script>
 </body>
 <style>

+ 3 - 3
health-admin/src/main/resources/templates/dtp/recipe/view.html

@@ -273,7 +273,7 @@
 
         /*门店列表-详细*/
         function detail(id) {
-            debugger
+
             $.ajax({
                 type : "GET",
                 async : true,            //异步请求(同步请求将会锁住浏览器,用户其他操作必须等待请求完成才可以执行)
@@ -281,7 +281,7 @@
                 data : {},
                 dataType : "json",        //返回数据形式为json
                 success : function(result) {
-                    debugger
+
 
                     //请求成功时执行该函数内容,result即为服务器返回的json对象
                     names=[];
@@ -337,7 +337,7 @@
             data : {},
             dataType : "json",        //返回数据形式为json
             success : function(result) {
-                debugger
+
                 //请求成功时执行该函数内容,result即为服务器返回的json对象
 
                 if (result.data.length>0) {

+ 486 - 152
health-admin/src/main/resources/templates/dtp/sfrw/SDtpYypzFollowUpSopAdd.html

@@ -3,8 +3,8 @@
 <head>
     <th:block th:include="include :: header('随访SOP新增')" />
     <th:block th:include="include :: datetimepicker-css" />
-    <th:block th:include="include :: select2-css" />
     <th:block th:include="include :: layout-latest-css" />
+
 </head>
 <body>
 <div class="ui-layout-center">
@@ -14,26 +14,34 @@
                 <h5>随访SOP</h5>
             </div>
             <div class="ibox-content">
-            <form id="form-SDtpYypzFollowUpSop-edit">
+                <form id="form-SDtpYypzFollowUpSop-edit">
                     <div class="customize-form-group-row">
                         <label class="control-label is-required">SOP名称:</label>
                         <input name="templateName" id="templateName" placeholder="请输入SOP名称" class="styled-input input-field" type="text"  required>
                     </div>
-                        <div class="customize-form-group-row">
-                            <label class="control-label is-required">&nbsp;业务归属:&nbsp;&nbsp;</label>
-                            <select name="businessBelonging" id="businessBelonging" class="styled-input input-field"   th:with="type=${@dict.getType('sys_select_dtp_ywgs')}">
-                                <option disabled selected>请选择业务归属</option>
-                                <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"></option>
-                            </select>
-                        </div>
-            </form>
-            <form id="form-syyp-durg">
+                    <div class="customize-form-group-row">
+                        <label class="control-label is-required">&nbsp;业务归属:&nbsp;&nbsp;</label>
+                        <select name="businessBelonging" id="businessBelonging" class="styled-input input-field"   th:with="type=${@dict.getType('sys_select_dtp_ywgs')}">
+                            <option disabled selected>请选择业务归属</option>
+                            <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"></option>
+                        </select>
+                    </div>
+
+                    <div class="customize-form-group-row">
+                        <button type="button" class="btn btn-outline btn-primary" onclick="GLYAOPIN()">关联药品</button>
+                        <button type="button" class="btn btn-outline btn-danger" onclick="NOGLYAOPIN()">不关联药品</button>
+                    </div>
+                </form>
+                <div id="ypDIv" class="display">
+                    <form id="form-syyp-durg">
                         <div class="ibox" id="data-ibox" style="overflow: auto;">
                             <div class="ibox-title" style="width: 100%;">适用药品:已选择 <span id="drug-count">0</span> 种药品</div>
                             <table class="fixed-layout-table table1" id="drugInfoTable">
                                 <thead>
-                                <tr>
-                                    <th>操作+<a type="button" class="btn btn-primary btn-sm" onclick="selectUsersToParentCallBack2()">添加药品</a></th>
+                                <tr style="background-color: #fff;">
+                                    <th style="width: 230px;">操作+<a type="button" class="btn btn-primary btn-sm" onclick="selectUsersToParentCallBack2()">添加药品</a>&nbsp;
+                                    </th>
+                                    <th style="width: 230px; text-align: center">添加绑定门店</th>
                                     <th>药品编码</th>
                                     <th>药品名</th>
                                     <th>规格</th>
@@ -44,7 +52,8 @@
                                 </tbody>
                             </table>
                         </div>
-             </form>
+                    </form>
+                </div>
                 <form id="form-jdpz-nodes" >
                     <div class="customize-form-group-row">
                         <label class="control-label is-required">节点配置:</label>
@@ -74,13 +83,18 @@
 </div>
 <th:block th:include="include :: footer" />
 <th:block th:include="include :: datetimepicker-js" />
+<th:block th:include="include :: select2-css" />
+<th:block th:include="include :: bootstrap-select-css" />
 <th:block th:include="include :: select2-js" />
+<th:block th:include="include :: bootstrap-select-js" />
 <th:block th:include="include :: bootstrap-table-fixed-columns-js" />
 <th:block th:include="include :: layout-latest-js" />
+
 </body>
 </html>
 <script>
     var prefix = ctx + "dtp/recipe";
+    var prefix_Task = ctx + "task/followTask";
     var prefix_yppz= ctx + "yppz/drugConfig";
     var count = 0;
     let carNum=1;//选项卡id
@@ -89,9 +103,56 @@
     var selectedOption = []; // 这里存储当前选中的选项
     var selectCount=0;
     var applicableDrug=[];
+    var storeList=[];
+    var GLYP=true;
+    const compareValues = [
+        '大于等于',
+        '小于等于',
+        '大于',
+        '小于',
+        '等于',
+        '不等于'
+    ];
+    const compareValues2 = [
+        '--等于--',
+        '--不等于--'
+    ];
+    const compareValues3 = [
+        '最近订单距今天数',
+        '最近待执行随访距今天数',
+        '最近已完成随访距今天数'
+
+    ];
+
+    const compareValues4 = [
+        '永久停止随访(脱落召回)',
+        '永久停止随访(常规随访)',
+        '永久停止随访(全部)'
+    ];
+    const compareValues5 = [
+        '用药状态'
+    ];
+    const compareValues6 = [
+        '随访不配合原因'
+    ];
+    const compareValues7Number = [
+        '最近订单距今天数',
+        '最近待执行随访距今天数',
+        '最近已完成随访距今天数',
+
+    ];
+    const compareValues8Select = [
+        '永久停止随访(脱落召回)',
+        '永久停止随访(常规随访)',
+        '永久停止随访(全部)',
+        '随访不配合原因',
+        '用药状态'
+    ];
     // 页面加载时初始化药品数量
     $(document).ready(function() {
         updateDrugCount(); // 初始化药品数量
+        //初始化加载
+        findTaskStoreList();
         // 绑定删除按钮点击事件
         $(document).on('click', '.delete-drug-btn', function(event) {
             event.preventDefault(); // 阻止默认行为
@@ -99,29 +160,135 @@
             deleteRow(productId);
         });
     });
+    function validateForm() {
+        var isValid = true;
+        debugger;
+        // 获取所有的动态节点
+        $('.tab-content > .tab-pane').each(function () {
+            var $this = $(this);
+            var nodeIdstr = $this.attr('id');
+            var  nodeId =nodeIdstr.substring(4);
+            // 验证激活节点
+            if (!$(`#jhjd${nodeId}`).val()) {
+                $.modal.alertWarning("请选择激活结构");
+                isValid = false;
+                return false; // 跳出循环
+            }
+            if (!$(`#activateWhichDay${nodeId}`).val()) {
+                $.modal.alertWarning("请输入激活哪一天");
+                isValid = false;
+                return false;
+            }
+            if (!$(`#ts${nodeId}`).val()) {
+                $.modal.alertWarning("请选择激活节点规则");
+                isValid = false;
+                return false;
+            }
+
+            // 继续验证其他表单项...
+            // 生成任务项
+            if (!$(`#scrw${nodeId}`).val()) {
+                $.modal.alertWarning("请选择创建生成任务结构");
+                isValid = false;
+                return false;
+            }
+            // if (!$(`#createTaskDi${nodeId}`).val()) {
+            //     $.modal.alertWarning("请输入第几天生成任务");
+            //     isValid = false;
+            //     return false;
+            // }
+
+            // 任务动作
+            if (!$(`#cj${nodeId}`).val()) {
+                $.modal.alertWarning("请选择任务动作");
+                isValid = false;
+                return false;
+            }
+            if (!$(`#rwdz${nodeId}`).val()) {
+                $.modal.alertWarning("请选择任务动作类型");
+                isValid = false;
+                return false;
+            }
+
+            // 执行任务:时间
+            if (!$(`#generationDaysAfter${nodeId}`).val()) {
+                $.modal.alertWarning("请输入生成后第几天执行");
+                isValid = false;
+                return false;
+            }
+
+            // 随访主题
+            if (!$(`#sfzt${nodeId}`).val()) {
+                $.modal.alertWarning("请选择随访主题");
+                isValid = false;
+                return false;
+            }
+
+            // 任务有效期
+            if (!$(`#taskValidity${nodeId}`).val()) {
+                $.modal.alertWarning("请输入任务有效期");
+                isValid = false;
+                return false;
+            }
 
+            // 任务素材
+            if (!$(`#taskMaterial${nodeId}`).val()) {
+                $.modal.alertWarning("请选择任务素材");
+                isValid = false;
+                return false;
+            }
+
+            var rwbd=  $(`#rwdz${nodeId}`).val();
+            if(rwbd==="触达任务"){
+            }else{
+                // 使用表单
+                if (!$(`#useForm${nodeId}`).val()) {
+                    $.modal.alertWarning("请选择使用的表单");
+                    isValid = false;
+                    return false;
+                }
+            }
+            // 继续验证其他表单项...
+        });
+        return isValid;
+    }
     function submitHandler() {
+        var prefix = ctx + "dtp/sdtpyypzfollowupsop";
+        var data = $("#form-SDtpYypzFollowUpSop-edit").serializeArray();
+
+        var nodesDataCheck = [];
         var businessBelonging=$("#businessBelonging").val();
         var templateName=$("#templateName").val();
-        if(templateName=="" || templateName==undefined){
+        if(templateName==="" || templateName===undefined || templateName===null){
             $.modal.alertWarning("请输入SOP名称");
             return;
         }
-        if(businessBelonging=="" || businessBelonging==undefined){
+        if(!businessBelonging){
             $.modal.alertWarning("请选择业务归属");
             return;
         }
-        applicableDrug=  getTableData();
-        if(applicableDrug.length==0){
-            $.modal.alertWarning("请添加药品");
-            return;
+        if (!validateForm()) {
+            return;// 阻止表单提交
         }
-        var prefix = ctx + "dtp/sdtpyypzfollowupsop";
-        var data = $("#form-SDtpYypzFollowUpSop-edit").serializeArray();
-        debugger;
-        var nodesDataCheck = [];
         nodesDataCheck=collectNodesData();
-        if(nodesDataCheck.length==0){
+        if(GLYP){
+            applicableDrug=  getTableData();
+            if(applicableDrug.length===0){
+                $.modal.alertWarning("请添加药品");
+                return;
+            }
+            data.push({name:"applicableDrug",value:JSON.stringify(applicableDrug)});
+
+        }else{
+            if(nodesDataCheck.length>=2){
+                $.modal.alertWarning("自由模版最多添加1个节点");
+                return;
+            }
+        }
+        data.push({name:"GLYP",value:GLYP});
+
+
+        if(nodesDataCheck.length===0){
             $.modal.alertWarning("请添加节点");
             return;
         }if(nodesDataCheck.length>=11){
@@ -129,7 +296,7 @@
             return;
         }
         data.push({name:"nodesData",value:JSON.stringify(collectNodesData())});
-        data.push({name:"applicableDrug",value:JSON.stringify(applicableDrug)});
+
         console.log("data="+data)
         $.operate.saveTab(prefix + "/sDtpYypzFollowUpSopAdd", data);
     }
@@ -174,57 +341,60 @@
         return nodesData;
     }
     function collectFiltersData(nodeId) {
-           var filtersContainer = document.getElementById('filtersContainer'+nodeId);
-             if(filtersContainer){
-                 // 获取所有过滤条件行
-                 var filterRows = filtersContainer.getElementsByClassName('filter-row');
-                 // 创建一个数组来存储所有行的数据
-                 var allFilterData = [];
-                 // 遍历每一行
-                 Array.from(filterRows).forEach(function(filterRow, rowIndex) {
-                     // 获取每一行内各个选择器的值
-                     var traits = filterRow.querySelector('[name^="trait"]');
-                     var traitValue = filterRow.querySelector('[name^="traitValue"]');
-                     var judgingCondition = filterRow.querySelector('[name^="judgingCondition"]');
-                     var judgmentSelectValue = filterRow.querySelector('[name^="judgmentSelectValue"]');
-                     var judgmentInputValue =filterRow.querySelector('[name^="judgmentInputValue"]');
-                     // 创建一个对象来存储这一行的数据
-                     var rowFilterData = {};
-                     if (traits) {
-                         rowFilterData['traits'] = traits.value;
-                     }
-                     if (traitValue) {
-                         rowFilterData['traitValue'] = traitValue.value;
-                     }
-                     if (judgingCondition) {
-                         rowFilterData['judgingCondition'] = judgingCondition.value;
-                     }
-                     if (judgmentSelectValue) {
-                         rowFilterData['judgmentValue'] = judgmentSelectValue.value;
-                     }
-
-                     if (judgmentInputValue) {
-                         rowFilterData['judgmentValue'] = judgmentInputValue.value;
-                     }
-                     // 添加一个属性来标识这一行的位置
-                     rowFilterData['rowIndex'] = rowIndex + 1; // 行号从 1 开始
-                     // 创建一个对象来存储这一行的数据
-                     var filterData = {
-                         traits: traits,
-                         traitValue: traitValue,
-                         judgingCondition: judgingCondition,
-                         judgmentValue: judgmentSelectValue,
-                         judgmentValueInput: judgmentInputValue
-                     };
-
-                     // 将这一行的数据添加到总数据数组中
-                     allFilterData.push(rowFilterData);
-                 });
-                 // 将总数据数组转换为 JSON 字符串
-                 return allFilterData;
-             } else {
-                 return null;
-             }
+        var filtersContainer = document.getElementById('filtersContainer'+nodeId);
+        if(filtersContainer){
+            // 获取所有过滤条件行
+            var filterRows = filtersContainer.getElementsByClassName('filter-row');
+            // 创建一个数组来存储所有行的数据
+            var allFilterData = [];
+            // 遍历每一行
+            Array.from(filterRows).forEach(function(filterRow, rowIndex) {
+                // 获取每一行内各个选择器的值
+                debugger
+                var traits = filterRow.querySelector('[name^="trait"]');
+                var traitValue = filterRow.querySelector('[name^="traitValue"]');
+                var judgingCondition = filterRow.querySelector('[name^="judgingCondition"]');
+                var judgmentSelectValue = filterRow.querySelector('[name^="judgmentSelectValue"]');
+                var judgmentInputValue =filterRow.querySelector('[name^="judgmentInputValue"]');
+                // 创建一个对象来存储这一行的数据
+                var rowFilterData = {};
+                if (traits) {
+                    rowFilterData['traits'] = traits.value;
+                }
+                if (traitValue) {
+                    rowFilterData['traitValue'] = traitValue.value;
+                }
+                if (judgingCondition) {
+                    rowFilterData['judgingCondition'] = judgingCondition.value;
+                }
+                if(compareValues7Number.includes(traitValue.value)){
+                    if (judgmentInputValue) {
+                        rowFilterData['judgmentValue'] = judgmentInputValue.value;
+                    }
+                }if(compareValues8Select.includes(traitValue.value)){
+                    if (judgmentSelectValue) {
+                        rowFilterData['judgmentValue'] = judgmentSelectValue.value;
+                    }
+                }
+                // 添加一个属性来标识这一行的位置
+                rowFilterData['rowIndex'] = rowIndex + 1; // 行号从 1 开始
+                // 创建一个对象来存储这一行的数据
+                var filterData = {
+                    traits: traits,
+                    traitValue: traitValue,
+                    judgingCondition: judgingCondition,
+                    judgmentValue: judgmentSelectValue,
+                    judgmentValueInput: judgmentInputValue
+                };
+                debugger
+                // 将这一行的数据添加到总数据数组中
+                allFilterData.push(rowFilterData);
+            });
+            // 将总数据数组转换为 JSON 字符串
+            return allFilterData;
+        } else {
+            return null;
+        }
     }
     function selectUsersToParentCallBack2(){
         var options = {
@@ -236,6 +406,14 @@
         };
         $.modal.openOptions(options);
     }
+    function GLYAOPIN(){
+        GLYP=true;
+        $("#ypDIv").show();//#ypDIv
+    }
+    function NOGLYAOPIN(){
+        GLYP=false;
+        $("#ypDIv").hide();//#ypDIv
+    }
     function  toTask(){
         selectCount++;
         // 使用 includes 方法检查 carNum 是否已在 carNumFlag2 数组中
@@ -338,61 +516,74 @@
             hideLevelThreeAndFourContainers();
         });
 
-        // 初始化二级下拉菜单的事件监听器
+        // 初始化二级下拉菜单的事件监听器 compareValues4
         $(document).on('change', '#level-two'+id, function(e) {
             var selectedValue = this.value;
             // 清空三级下拉菜单
             while (levelThreeSelect.firstChild) {
                 levelThreeSelect.removeChild(levelThreeSelect.firstChild);
             }
-
-            // 根据二级选择加载三级选项
-            if (selectedValue === '最近待执行随访距今天数' ||
-                selectedValue === '最近已完成随访距今天数' ||
-                selectedValue === '最近订单距今天数') {
-                // 加载数值比较类型的三级选项
-                loadLevelThreeOptionsForNumericComparison();
-            } else {
-                // 加载字符串比较类型的三级选项
-                loadLevelThreeOptionsForStringComparison();
-            }
-            // 隐藏四级容器
-            hideLevelFourContainer();
-        });
-
-        // 初始化三级下拉菜单的事件监听器
-        $(document).on('change', '#level-three'+id, function(e) {
-            var selectedValue = this.value;
-
-            // 清空四级下拉菜单
             while (levelFourSelect.firstChild) {
                 levelFourSelect.removeChild(levelFourSelect.firstChild);
             }
-            // 根据三级选择加载四级选项
-            if (selectedValue === '大于等于' ||
-                selectedValue === '小于等于' ||
-                selectedValue === '大于' ||
-                selectedValue === '小于' ||
-                selectedValue === '等于' ||
-                selectedValue === '不等于') {
+            debugger
+            // 根据二级选择加载三级选项
+            if (compareValues3.includes(selectedValue.trim())) {
+                // 加载数值比较类型的三级选项
+                loadLevelThreeOptionsForNumericComparison();
                 // 显示输数字
                 showLevelFourInput();
-            } else if (selectedValue === '--等于--' || selectedValue === '--不等于--') {
+            }else if (compareValues4.includes(selectedValue.trim())) {
+                // 加载字符串比较类型的三级选项
+                loadLevelThreeOptionsForStringComparison()
                 // 显示选择器
                 loadLevelThreeOptionsForSelection();
                 showLevelFourSelect();
-            }else {
+            }else if (compareValues5.includes(selectedValue.trim())) {
+                // 加载字符串比较类型的三级选项
+                loadLevelThreeOptionsForStringComparison()
+                // 显示选择器
+                loadLevelfiveOptionsForSelection();
+            }else if (compareValues6.includes(selectedValue.trim())) {
+                // 加载字符串比较类型的三级选项
+                loadLevelThreeOptionsForStringComparison();
+                // 显示选择器
+                loadPhoneNumberOptionsForSelection();
+            }else{
+                // 加载数值比较类型的三级选项
+                loadLevelThreeOptionsForNumericComparison();
                 // 显示输数字
                 showLevelFourInput();
             }
+            // 隐藏四级容器
+            //hideLevelFourContainer();
         });
+
+        // // 初始化三级下拉菜单的事件监听器
+        // $(document).on('change', '#level-three'+id, function(e) {
+        //     var selectedValue = this.value;
+        //     debugger
+        //     // 清空四级下拉菜单
+        //     while (levelFourSelect.firstChild) {
+        //         levelFourSelect.removeChild(levelFourSelect.firstChild);
+        //     }
+        //     // 根据三级选择加载四级选项
+        //     if (compareValues.includes(selectedValue)) {
+        //         // 显示输数字
+        //         showLevelFourInput();
+        //     }if (compareValues2.includes(selectedValue)) {
+        //         // 显示选择器
+        //         loadLevelThreeOptionsForSelection();
+        //         showLevelFourSelect();
+        //     }
+        // });
         // 初始化三级下拉菜单的事件监听器
         $(document).on('change', '#level-four'+id, function(e) {
             var selectedValue = this.value;
             // 根据三级选择加载四级选项
             if (selectedValue === '是' ||
                 selectedValue === '否'
-               ) {
+            ) {
                 // 显示输择器
                 showLevelFourSelect();
             } else {
@@ -405,7 +596,7 @@
 
             var options = [
                 '请选择二级分类',
-                '默认触达手机号',
+                '随访不配合原因',
                 '永久停止随访(脱落召回)',
                 '永久停止随访(常规随访)',
                 '永久停止随访(全部)',
@@ -414,10 +605,12 @@
             ];
             options.forEach((option, index) => {
                 var newOption = document.createElement('option');
-                if (newOption === '请选择二级分类') {
+                if (option === '请选择二级分类') {
                     newOption.setAttribute('value', '');
                     newOption.disabled = true; // 设置为禁用状态
                     newOption.selected = true; // 设置为预选中状态
+                    newOption.value = option;
+                    newOption.textContent = option;
                 } else {
                     newOption.value = option;
                     newOption.textContent = option;
@@ -440,10 +633,12 @@
             ];
             options.forEach((option, index) => {
                 var newOption = document.createElement('option');
-                if (newOption === '请选择二级分类') {
+                if (option === '请选择二级分类') {
                     newOption.setAttribute('value', '');
                     newOption.disabled = true; // 设置为禁用状态
                     newOption.selected = true; // 设置为预选中状态
+                    newOption.value = option;
+                    newOption.textContent = option;
                 } else {
                     newOption.value = option;
                     newOption.textContent = option;
@@ -465,18 +660,24 @@
                 '等于',
                 '不等于'
             ];
+
             options.forEach((option, index) => {
                 var newOption = document.createElement('option');
-                if (newOption === '请选择二级分类') {
+                // 检查是否是提示选项,并进行相应设置
+                if (option === '请选择三级分类') {
                     newOption.setAttribute('value', '');
-                    newOption.disabled = true; // 设置为禁用状态
+                    newOption.disabled = true; // 设置为禁用状态,防止用户选择
                     newOption.selected = true; // 设置为预选中状态
+                    newOption.value = option;
+                    newOption.textContent = option;
                 } else {
                     newOption.value = option;
                     newOption.textContent = option;
                 }
+
                 levelThreeSelect.appendChild(newOption);
             });
+
             // 显示三级容器
             showLevelThreeContainer();
         }
@@ -490,10 +691,12 @@
             ];
             options.forEach((option, index) => {
                 var newOption = document.createElement('option');
-                if (newOption === '请选择二级分类') {
+                if (option === '请选择三级分类') {
                     newOption.setAttribute('value', '');
                     newOption.disabled = true; // 设置为禁用状态
                     newOption.selected = true; // 设置为预选中状态
+                    newOption.value=option;
+                    newOption.textContent =option;
                 }else{
                     if(option==='等于'){
                         newOption.setAttribute('value','--等于--');
@@ -523,10 +726,12 @@
 
             options.forEach(option => {
                 var newOption = document.createElement('option');
-                if (newOption === '请选择') {
+                if (option === '请选择') {
                     newOption.setAttribute('value', '');
                     newOption.disabled = true; // 设置为禁用状态
                     newOption.selected = true; // 设置为预选中状态
+                    newOption.value=option;
+                    newOption.textContent = option;
                 }
                 newOption.value=option;
                 newOption.textContent = option;
@@ -534,6 +739,57 @@
             });
 
         }
+        // 加载字符串比较类型的四级选项
+        function loadLevelfiveOptionsForSelection() {
+            var options = [
+                '请选择',
+                '领取慈善赠药',
+                '永久停药',
+                '其他渠道购药',
+                '延迟用药(医嘱建议)',
+                '延迟购药(患者用药不规范)'
+            ];
+
+            options.forEach(option => {
+                var newOption = document.createElement('option');
+                if (option === '请选择') {
+                    newOption.setAttribute('value', '');
+                    newOption.disabled = true; // 设置为禁用状态
+                    newOption.selected = true; // 设置为预选中状态
+                    newOption.value=option;
+                    newOption.textContent = option;
+                }
+                newOption.value=option;
+                newOption.textContent = option;
+                levelFourSelect.appendChild(newOption);
+            });
+            showLevelFourSelect();
+        }
+        // 加载字符串比较类型的四级选项
+        function loadPhoneNumberOptionsForSelection() {
+            var options = [
+                '请选择',
+                '电话号码错误',
+                '本人拒绝',
+                '家属拒绝',
+                '客户拒绝'
+            ];
+
+            options.forEach(option => {
+                var newOption = document.createElement('option');
+                if (option === '请选择') {
+                    newOption.setAttribute('value', '');
+                    newOption.disabled = true; // 设置为禁用状态
+                    newOption.selected = true; // 设置为预选中状态
+                    newOption.value=option;
+                    newOption.textContent = option;
+                }
+                newOption.value=option;
+                newOption.textContent = option;
+                levelFourSelect.appendChild(newOption);
+            });
+            showLevelFourSelect();
+        }
         // 显示输入框
         function showLevelFourInput() {
             levelFourInput.style.display = 'block';
@@ -542,8 +798,9 @@
 
         // 显示选择器
         function showLevelFourSelect() {
-            levelFourInput.style.display = 'none';
             levelFourSelect.style.display = 'block';
+            levelFourInput.style.display = 'none';
+
         }
 
         // 隐藏三级和四级容器
@@ -594,9 +851,12 @@
         });
         // 绑定 change 事件
         $(document).on('change', '#ts'+carNum, function(e) {
+            debugger;
             var selectedValue = $(this).val();
+
+             TJ = $("#scrw"+carNum).val();
             if (selectedValue === "周期节点" && TJ=='事件条件') {
-                flag = false;
+
                 $('#span'+carNum).remove();
                 var addNodeOne ='<span id="span'+carNum+'"> 当 '+
                     '<select name="createTaskWhen" id="scrw-t'+carNum+'" class="styled-input">' +
@@ -605,12 +865,12 @@
                     '<option value="其他值">其他值</option>' +
                     '</select>' +
                     "  为" +
-                    '<input type="number" name="createTaskTriggerValue" placeholder="请输入" class="styled-input" value="">触发动作' +
+                    '<input type="number" name="createTaskTriggerValue" id="createTaskTriggerValue'+carNum+'" placeholder="请输入" class="styled-input" value="">触发动作' +
                     "  天生成任务,共执行  " +
                     '<input type="number" name="createTaskExecutionTimes" placeholder="请输入" class="styled-input" value="">次</span>'
                 $('#scrw'+carNum).after(addNodeOne);
             }if (selectedValue === "单次节点" && TJ=='时间条件') {
-                flag = true;
+
                 $('#span'+carNum).remove();
                 var addNodeOne ="  <span id=\"span"+carNum+"\">第  " +
                     '<input type="number" id="rwday'+carNum+'" name="createTaskDi" placeholder="请输入" class="styled-input" value="">' +
@@ -618,7 +878,7 @@
                 $('#scrw'+carNum).after(addNodeOne);
             }
             if (selectedValue === "单次节点" && TJ=='事件条件') {
-                flag = true;
+
                 $('#span'+carNum).remove();
                 var addNodeOne ='<span id="span'+carNum+'"> 当 '+
                     '<select name="createTaskWhen" id="scrw-t'+carNum+'" class="styled-input">' +
@@ -627,14 +887,14 @@
                     '<option value="其他值">其他值</option>' +
                     '</select>' +
                     "  为" +
-                    '<input type="number" name="createTaskTriggerValue" placeholder="请输入" class="styled-input" value="">触发动作'
+                    '<input type="number" name="createTaskTriggerValue" id="createTaskTriggerValue'+carNum+'" placeholder="请输入" class="styled-input" value="">触发动作'
                 $('#scrw'+carNum).after(addNodeOne);
             }
             if (selectedValue === "周期节点" && TJ=='时间条件') {
-                flag = false;
+
                 $('#span'+carNum).remove();
                 var addNodeOne ='<span id="span'+carNum+'"> 每 '+
-                    '<input type="number" name="createTaskEvery" placeholder="请输入" class="styled-input" value="">' +
+                    '<input type="number" name="createTaskEvery" id="createTaskEvery'+carNum+'" placeholder="请输入" class="styled-input" value="">' +
                     "  天生成任务,共执行  " +
                     '<input type="number" name="createTaskExecutionTimes" placeholder="请输入" class="styled-input" value="">次</span>'
                 $('#scrw'+carNum).after(addNodeOne);
@@ -643,8 +903,11 @@
         });
         // 绑定 change 事件
         $(document).on('change', '#scrw'+carNum, function(e) {
+            debugger;
+            var ts = '';
             var selectedValue = $(this).val();
-            if (selectedValue === "事件条件" && flag===false) {
+            ts = $("#ts"+carNum).val();
+            if (selectedValue === "事件条件" && ts==='周期节点') {
                 TJ='事件条件';
                 $('#span'+carNum).remove();
                 var addNodeOne ='<span id="span'+carNum+'"> 当 '+
@@ -654,11 +917,11 @@
                     '<option value="其他值">其他值</option>' +
                     '</select>' +
                     "  为" +
-                    '<input type="number" name="createTaskTriggerValue" placeholder="请输入" class="styled-input" value="">触发动作'+
+                    '<input type="number" name="createTaskTriggerValue" id="createTaskTriggerValue'+carNum+'" placeholder="请输入" class="styled-input" value="">触发动作'+
                     " 共执行  " +
                     '<input type="number" name="createTaskExecutionTimes" placeholder="请输入" class="styled-input" value="">次</span>'
                 $('#scrw'+carNum).after(addNodeOne);
-            } if (selectedValue === "事件条件" && flag===true) {
+            } if (selectedValue === "事件条件" && ts==='单次节点') {
                 TJ='事件条件';
                 $('#span'+carNum).remove();
                 var addNodeOne ='<span id="span'+carNum+'"> 当 '+
@@ -668,17 +931,17 @@
                     '<option value="其他值">其他值</option>' +
                     '</select>' +
                     "  为" +
-                    '<input type="number" name="createTaskTriggerValue" placeholder="请输入" class="styled-input" value="">触发动作'
+                    '<input type="number" name="createTaskTriggerValue" id="createTaskTriggerValue'+carNum+'" placeholder="请输入" class="styled-input" value="">触发动作'
                 $('#scrw'+carNum).after(addNodeOne);
-            }if (selectedValue === "时间条件" && flag===false) {
+            }if (selectedValue === "时间条件" && ts==='周期节点') {
                 TJ='时间条件';
                 $('#span'+carNum).remove();
                 var addNodeOne ='<span id="span'+carNum+'"> 每 '+
-                    '<input type="number" name="createTaskEvery" placeholder="请输入" class="styled-input" value="">' +
+                    '<input type="number" name="createTaskEvery" id="createTaskEvery'+carNum+'" placeholder="请输入" class="styled-input" value="">' +
                     "  天生成任务,共执行  " +
                     '<input type="number" name="createTaskExecutionTimes" placeholder="请输入" class="styled-input" value="">次</span>'
                 $('#scrw'+carNum).after(addNodeOne);
-            }if (selectedValue === "时间条件" && flag===true) {
+            }if (selectedValue === "时间条件" && ts==='单次节点') {
                 TJ='时间条件';
                 $('#span'+carNum).remove();
                 var addNodeOne ="  <span id=\"span"+carNum+"\">第  " +
@@ -741,10 +1004,10 @@
 
 
     $(function(){
-    $("#datetimepicker-demo-3").datetimepicker({
-        format: 'HH:mm:ss',
-        autoclose: true
-    });
+        $("#datetimepicker-demo-3").datetimepicker({
+            format: 'HH:mm:ss',
+            autoclose: true
+        });
     });
     document.addEventListener("DOMContentLoaded", function() {
 
@@ -792,6 +1055,12 @@
 
     /* 添加节点配置 */
     function addNode(){
+        var    nodesDataCheck=collectNodesData();
+        if(GLYP ===false && nodesDataCheck.length>=1){
+            $.modal.alertWarning("自由模版最多添加1个节点");
+            return;
+        }
+
         count++;
 
         // 生成 ibox 容器
@@ -811,7 +1080,7 @@
             '<option value="创建计划">创建计划</option>' +
             '</select>' +
             "  第  " +
-            '<input type="number" name="activateWhichDay" placeholder="规格" class="styled-input" value="">' +
+            '<input type="number" name="activateWhichDay" id="activateWhichDay'+count+'" placeholder="第" class="styled-input" value="">' +
             "  天激活  "+
             '<select name="activateNodeRule" id="ts' + count + '" class="styled-input">' +
             '<option value="单次节点">单次节点</option>' +
@@ -827,7 +1096,7 @@
             '<option value="事件条件">事件条件</option>' +
             '</select>' +
             "  <span id=\"span" + count + "\">第  " +
-            '<input type="number" id="createTaskDi' + count + '" name="createTaskDi" placeholder="请输入" class="styled-input" value="">' +
+            '<input type="number" id="createTaskDi' + count + '" name="createTaskDi" placeholder="请输入" class="styled-input">' +
             "  天生成任务 </span>"+' &nbsp;<a href="#" onclick="toTask()" id=toTask"">添加过滤条件</a>' +
             "<div id=\"filtersContainer" + count + "\" name=\"filtersContainer\"></div>" +
             '<p>'+
@@ -852,9 +1121,9 @@
             '<p>'+
             '</p>'+
             '<strong>执行任务: </strong>生成后第&nbsp;'+
-            '<input type="number" name="generationDaysAfter" placeholder="请输入" class="styled-input">' +
+            '<input type="number" name="generationDaysAfter" id="generationDaysAfter' + count + '" placeholder="请输入" class="styled-input">' +
             '  天  ' +
-            '<input type="text" name="generationHMS" placeholder="请输入时间(时分秒)" id="datetimepicker-demo-3"  class="styled-input" value="">' +
+            '<input type="text" name="generationHMS" placeholder="请输入时间(时分秒)" id="datetimepicker-demo-3"  class="styled-input">' +
             '  执行任务  ' +
             '<p>'+
             '</p>'+
@@ -870,18 +1139,18 @@
             '<p>'+
             '</p>'+
             '<strong>&emsp;&emsp;&emsp;&nbsp;&nbsp;</strong>任务有效期&nbsp;'+
-            '<input type="number" name="taskValidity" placeholder="请输入" class="styled-input">&emsp;天' +
+            '<input type="number" name="taskValidity"  id="taskValidity' + count + '" placeholder="请输入" class="styled-input">&emsp;天' +
             '<p>'+
             '</p>'+
             '<strong>&emsp;&emsp;&emsp;&emsp;&nbsp;&nbsp;</strong>任务素材&nbsp;'+
-            '<select name="taskMaterial" id="1-' + count + '" class="styled-input">' +
+            '<select name="taskMaterial" id="taskMaterial' + count + '" class="styled-input">' +
             '<option value="素材1">素材1</option>' +
             '<option value="素材2">素材2</option>' +
             '</select>' +
             '<p>'+
             '</p><div id="rwdzform' + count + '" class="myDivrwdz">'+
             '<strong>&emsp;&emsp;&emsp;&emsp;&nbsp;&nbsp;</strong>使用表单&nbsp;'+
-            '<select name="useForm" id="2-' + count + '" class="styled-input">' +
+            '<select name="useForm" id="useForm' + count + '" class="styled-input">' +
             '<option value="">请选择</option>' +
             '<option value="精简表单">精简表单</option>' +
             '<option value="全量表单">全量表单</option>' +
@@ -952,13 +1221,22 @@
                 var row = `
                <tr data-product-id="${columnsData.productId}">
                     <td><button class="btn btn-danger delete-drug-btn" data-product-id="${columnsData.productId}">删除重选择药品</button></td>
+                    <td style="width: 250px; text-align: center">
+                      <select name="storeId" id="store${columnsData.productId}" class="styled-input select2-multiple" style="width: 200px" multiple placeholder="请选择或输入搜索">
+                        </select></td>
                     <td>${columnsData.productCode}</td>
                     <td>${columnsData.productName}</td>
                     <td>${columnsData.specification}</td>
                 </tr>
             `;
                 tableBody.append(row);
+                dataInfo(columnsData.productId);
                 updateDrugCount(); // 更新药品数量
+                // 初始化新添加的选择框为Select2
+                $(`#store${columnsData.productId}`).select2({
+                    placeholder: "请选择或输入搜索",
+                    allowClear: true
+                });
             } else {
                 $.modal.alertWarning(`药品 [ ${columnsData.productName} ] 已经添加了`);
             }
@@ -966,6 +1244,9 @@
 
         $.modal.close(index);
     }
+
+
+
     function deleteRow(productId) {
         $.modal.confirm("确认删除该药品吗?", function() {
             // 使用 attr 而不是直接拼接字符串,以防止潜在的XSS攻击
@@ -990,6 +1271,47 @@
         var count = $('#drugInfoTable tbody tr').length;
         $('#drug-count').text(count);
     }
+
+    function dataInfo(productId){
+        // 获取<select>元素
+        var selectElement = document.getElementById('store' + productId);
+        storeList.forEach(function(item, index){
+            var option = document.createElement("option");
+            option.value = item.id;
+            option.text = item.dept_name;
+            selectElement.add(option);
+        })
+    }
+    // 异步加载所有门店信息并填充下拉框
+    function findTaskStoreList() {
+        $.ajax({
+            url: prefix_Task+ "/findTaskStoreList", // 这是获取所有门店的API端点
+            type: 'POST',
+            cache: false, // 设置为 false 防止缓存
+            processData: false, // 告诉 jQuery 不要处理数据(非常重要)
+            contentType: false, // 告诉 jQuery 不要设置 contentType(非常重要)
+            async: true, // 异步请求更为推荐,除非有特殊原因需要同步
+            success: function (data) {
+                // 清空除了默认选项外的所有选项
+                if (data && data.data) {
+                    storeList=data.data;
+                    // 如果需要立即填充下拉框,请调用dataInfo函数
+                    $('[id^="store"]').each(function(){
+                        var productId = this.id.replace('store', '');
+                        dataInfo(productId);
+                    });
+                } else {
+                    console.error('Unexpected response format:', data);
+                }
+            },
+            error: function () {
+                $.modal.alertError('加载门店信息失败');
+            }
+        });
+    }
+
+
+
     function deleteNode(id) {
         if(count===0){
             return false
@@ -1032,19 +1354,31 @@
     }
     //获取药品表格数据
     function getTableData() {
-        var tableRows = [];
-
-        $('#drugInfoTable tbody tr').each(function(index, row) {
-            var rowData = {
-                productId: $(row).data('product-id'),
-                productCode: $(row).find('td:eq(1)').text().trim(), // 药品编码
-                productName: $(row).find('td:eq(2)').text().trim(), // 药品名
-                specification: $(row).find('td:eq(3)').text().trim(), // 规格
+        var formData = [];
+        // 遍历表格中的每一行
+        $('#drugInfoTable tbody tr').each(function() {
+            var productId = $(this).data('product-id');
+            var storeSelect = $(this).find('select[name="storeId"]');
+
+            // 获取当前行的所有数据
+            var columnsData = {
+                productId: productId,
+                productCode: $(this).find('td:eq(2)').text(), // 假设第三列是产品编码
+                productName: $(this).find('td:eq(3)').text(), // 假设第四列是产品名称
+                specification: $(this).find('td:eq(4)').text(), // 假设第五列是规格
+                // 添加其他需要的字段...
             };
-            tableRows.push(rowData);
-        });
 
-        return tableRows;
+            // 获取选中的门店ID,并将其转换为逗号分隔的字符串
+            var selectedStoreIds = storeSelect.val();
+            if (selectedStoreIds && selectedStoreIds.length > 0) {
+                columnsData.storeIds = selectedStoreIds.join(',');
+            } else {
+                columnsData.storeIds = ''; // 或者你可以设置为null或其他默认值
+            }
+            formData.push(columnsData);
+        });
+        return formData;
     }
 </script>
 <style>

File diff suppressed because it is too large
+ 494 - 121
health-admin/src/main/resources/templates/dtp/sfrw/SDtpYypzFollowUpSopPageEdit.html


+ 4 - 21
health-admin/src/main/resources/templates/gxhpz/addRepurchasedGoods.html

@@ -11,7 +11,7 @@
     <div class="form-horizontal">
         <input type="hidden" id="id" name="id" th:value="${id}">
         <div class="customize-form-group">
-            <label style="color:olive;font-size: 15px;">复购配置->关联D值品:</label>
+            <label style="color:olive;font-size: 14px;">复购配置->关联D值品:</label>
             <input name="dValueKey" placeholder="输入D值品名称或D值id查询" class="styled-input" type="text" id="dValueKeyInput">
             <input name="dValueId" placeholder="D值品id" class="styled-input" style="width: 60px;" type="text" readonly id="dValueIdInput">
             <input name="dValueNameInput" placeholder="" class="styled-input" type="text" disabled id="dValueNameInput">
@@ -207,30 +207,13 @@ if(drugData != null || drugData != undefined){
                             $('#dValueNameInput').val(data.data.dValueName);
 
                             updateButtonVisibility(); // 更新按钮可见性
-            //                 var row = `
-            //     <tr data-product-id="${data.data.id}">
-            //         <td>${data.data.mdmCode}</td>
-            //         <td>${data.data.productName}</td>
-            //         <td>${data.data.genericName}</td>
-            //         <td>${data.data.specification}</td>
-            //          <td>${data.data.packaging}</td>
-            //         <td>${data.data.manufacturerShortName}</td>
-            //          <td class="hidden-column">${data.isFollowUpManaged}</td>
-            //         <td class="hidden-column">${data.isColdChainManaged}</td>
-            //         <td class="hidden-column">${data.isRegisteredManaged}</td>
-            //         <td class="hidden-column">${data.isCharityAidManaged}</td>
-            //         <td class="hidden-column">${data.administrationMethod}</td>
-            //         <td class="hidden-column">${data.indicationDescription}</td>
-            //         <td class="hidden-column">${data.dosageAndAdministration}</td>
-            //         <td><button onclick="deleteRow('${data.data.id}')" class="btn btn-danger">删除</button></td>
-            //     </tr>
-            // `;
-            //                 tableBody.append(row);
+
                         }else{
                             // 清空表格中的所有数据行
                            // $('#drugInfoTable tbody').empty();
                             // 如果没有找到匹配的数据,清空 dValueId 输入框
                             $('#dValueIdInput').val('');
+                            $('#dValueNameInput').val();
                             updateButtonVisibility(); // 更新按钮可见性
                         }
 
@@ -341,7 +324,7 @@ if(drugData != null || drugData != undefined){
         return tableRows;
     }
     function submitDrugInfo() {
-        debugger
+
        // $('#dValueIdInput').val('');
         var dValueId = $('#dValueIdInput').val().trim();
         var dValueName = $('#dValueNameInput').val().trim();

+ 1 - 1
health-admin/src/main/resources/templates/gxhpz/allDrugsInfo.html

@@ -348,7 +348,7 @@
 	}
 	/* 添加用户-选择用户-提交(子页面调用父页面形式) */
 	function submitHandler(index, layero) {
-		debugger
+
 		var rows = $.table.selectFirstColumns();
 
 		if (rows.length == 0) {

+ 37 - 2
health-admin/src/main/resources/templates/gxhpz/allProduct.html

@@ -25,11 +25,46 @@
 									<label>药品:</label>
 									<input type="text" class="styled-input" placeholder="请输入MDM编码或药品商品名/通用名" name="query" style="width: 350px;"/>
 								</div>
-
+								<!-- 是否冷链管理品 -->
+								<div class="customize-form-group">
+									<label>是否冷链管理品:</label>
+									<select name="isColdChainManaged" class="styled-input" th:with="type=${@dict.getType('sys_gxhpz_yes_no')}">
+										<option value="">请选择</option>
+										<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+									</select>
+								</div>
 								<div class="customize-form-group">
 									<label>厂家简称:</label>
 									<input type="text" class="styled-input" placeholder="请输入厂家简称" name="manufacturerShortName" style="width: 350px;"/>
 								</div>
+								<!-- 是否随访管理品 -->
+								<div class="customize-form-group">
+									<label>是否随访管理品:</label>
+									<select name="isFollowUpManaged" class="styled-input" th:with="type=${@dict.getType('sys_gxhpz_yes_no')}">
+										<option value="">请选择</option>
+										<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+									</select>
+								</div>
+							</div>
+							<div class="customize-form-group-container">
+								<!-- 是否登记管理品 -->
+								<div class="customize-form-group">
+									<label>是否登记管理品:</label>
+									<select name="isRegisteredManaged" class="styled-input" th:with="type=${@dict.getType('sys_gxhpz_yes_no')}">
+										<option value="">请选择</option>
+										<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+									</select>
+								</div>
+							</div>
+							<div class="customize-form-group-container">
+								<!-- 是否慈善援助管理品 -->
+								<div class="customize-form-group">
+									<label>是否慈善援助管理品:</label>
+									<select name="isCharityAidManaged" class="styled-input" th:with="type=${@dict.getType('sys_gxhpz_yes_no')}">
+										<option value="">请选择</option>
+										<option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+									</select>
+								</div>
 							</div>
 						</form>
 					</div>
@@ -552,7 +587,7 @@
 	}
 	/* 添加用户-选择用户-提交(子页面调用父页面形式) */
 	function submitHandler(index, layero) {
-		debugger
+
 		var rows = $.table.selectFirstColumns();
 
 		if (rows.length == 0) {

+ 773 - 0
health-admin/src/main/resources/templates/gxhpz/design.html

@@ -0,0 +1,773 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org">
+<head>
+    <th:block th:include="include :: header('表单设计器')" />
+    <th:block th:include="include :: bootstrap-select-css" />
+    <th:block th:include="include :: bootstrap-select-js" />
+    <th:block th:include="include :: bootstrap-fileinput-css" />
+    <th:block th:include="include :: bootstrap-fileinput-js" />
+    <!--<th:block th:include="include :: jquery-ui-css" />
+    <th:block th:include="include :: jquery-ui-js" />-->
+    <style>
+        .form-designer-container {
+            display: flex;
+            min-height: 600px;
+        }
+        .components-panel {
+            width: 250px;
+            border-right: 1px solid #ddd;
+            padding: 10px;
+            background-color: #f8f8f8;
+        }
+        .form-canvas {
+            flex: 1;
+            padding: 20px;
+            background-color: #ffffff;
+            overflow: auto;
+        }
+        .property-panel {
+            width: 300px;
+            border-left: 1px solid #ddd;
+            padding: 10px;
+            background-color: #f8f8f8;
+        }
+        .component-item {
+            border: 1px solid #ddd;
+            margin-bottom: 10px;
+            padding: 8px;
+            background-color: #fff;
+            cursor: move;
+            border-radius: 4px;
+        }
+        .component-item:hover {
+            background-color: #f0f0f0;
+        }
+        .section-item {
+            border: 1px dashed #ccc;
+            margin-bottom: 15px;
+            padding: 15px;
+            background-color: #fafafa;
+            position: relative;
+        }
+        .section-header {
+            font-weight: bold;
+            margin-bottom: 10px;
+            padding-bottom: 5px;
+            border-bottom: 1px solid #eee;
+            display: flex;
+            justify-content: space-between;
+        }
+        .field-item {
+            border: 1px solid #eee;
+            margin-bottom: 10px;
+            padding: 10px;
+            background-color: #fff;
+            position: relative;
+        }
+        .field-actions {
+            position: absolute;
+            right: 5px;
+            top: 5px;
+        }
+        .section-actions {
+            display: flex;
+            gap: 5px;
+        }
+        .subtable-item {
+            border: 1px solid #ddd;
+            margin-top: 15px;
+            padding: 10px;
+            background-color: #f5f5f5;
+        }
+        .subtable-header {
+            font-weight: bold;
+            margin-bottom: 10px;
+            padding-bottom: 5px;
+            border-bottom: 1px solid #eee;
+            display: flex;
+            justify-content: space-between;
+        }
+        .subtable-columns {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 10px;
+            padding: 10px;
+            background-color: #ffffff;
+        }
+        .property-group {
+            margin-bottom: 15px;
+            border: 1px solid #eee;
+            border-radius: 4px;
+            overflow: hidden;
+        }
+        .property-group-title {
+            background-color: #f0f0f0;
+            padding: 8px;
+            font-weight: bold;
+        }
+        .property-group-content {
+            padding: 10px;
+        }
+        .property-row {
+            margin-bottom: 10px;
+        }
+        .handle {
+            cursor: move;
+            color: #999;
+            margin-right: 5px;
+        }
+        .form-canvas {
+            min-height: 400px;
+        }
+        .placeholder {
+            border: 2px dashed #ccc;
+            height: 50px;
+            margin: 10px 0;
+            background-color: #f9f9f9;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            color: #999;
+        }
+        .ui-sortable-helper {
+            z-index: 9999;
+        }
+    </style>
+</head>
+<body class="white-bg">
+<div class="wrapper wrapper-content animated fadeInRight ibox-content">
+    <form class="form-horizontal m" id="form-template-design">
+       <input type="hidden" id="templateId" name="id" th:value="${id}">
+        <div class="form-group">
+            <label class="col-sm-2 control-label">模板名称:</label>
+            <div class="col-sm-4">
+                <input class="form-control" type="text" readonly th:value="${template_name}">
+            </div>
+            <label class="col-sm-2 control-label">模板版本:</label>
+            <div class="col-sm-4">
+                <input class="form-control" type="text" name="version" th:value="${version}" readonly>
+            </div>
+        </div>
+
+        <div class="form-designer-container">
+            <!-- 组件面板 -->
+            <div class="components-panel">
+                <h4>组件</h4>
+                <div class="panel-group" id="componentAccordion">
+                    <div class="panel panel-default">
+                        <div class="panel-heading">
+                            <h4 class="panel-title">
+                                <a data-toggle="collapse" data-parent="#componentAccordion" href="#layoutComponents">
+                                    布局组件
+                                </a>
+                            </h4>
+                        </div>
+                        <div id="layoutComponents" class="panel-collapse collapse in">
+                            <div class="panel-body">
+                                <div class="component-item" data-type="section">
+                                    <i class="fa fa-columns"></i> 分组
+                                </div>
+                                <div class="component-item" data-type="subtable">
+                                    <i class="fa fa-table"></i> 子表
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="panel panel-default">
+                        <div class="panel-heading">
+                            <h4 class="panel-title">
+                                <a data-toggle="collapse" data-parent="#componentAccordion" href="#basicComponents">
+                                    基础组件
+                                </a>
+                            </h4>
+                        </div>
+                        <div id="basicComponents" class="panel-collapse collapse in">
+                            <div class="panel-body">
+                                <div class="component-item" data-type="text">
+                                    <i class="fa fa-font"></i> 单行文本
+                                </div>
+                                <div class="component-item" data-type="textarea">
+                                    <i class="fa fa-align-left"></i> 多行文本
+                                </div>
+                                <div class="component-item" data-type="number">
+                                    <i class="fa fa-calculator"></i> 数字
+                                </div>
+                                <div class="component-item" data-type="select">
+                                    <i class="fa fa-list"></i> 下拉选择
+                                </div>
+                                <div class="component-item" data-type="radio">
+                                    <i class="fa fa-dot-circle-o"></i> 单选按钮
+                                </div>
+                                <div class="component-item" data-type="checkbox">
+                                    <i class="fa fa-check-square-o"></i> 复选框
+                                </div>
+                                <div class="component-item" data-type="date">
+                                    <i class="fa fa-calendar"></i> 日期
+                                </div>
+                                <div class="component-item" data-type="datetime">
+                                    <i class="fa fa-clock-o"></i> 日期时间
+                                </div>
+                                <div class="component-item" data-type="file">
+                                    <i class="fa fa-file"></i> 文件上传
+                                </div>
+                                <div class="component-item" data-type="image">
+                                    <i class="fa fa-picture-o"></i> 图片上传
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 表单设计画布 -->
+            <div class="form-canvas" id="formCanvas">
+                <div class="alert alert-info">
+                    拖拽左侧组件到这里来设计表单
+                </div>
+                <div id="formSections">
+                    <!-- 表单分组和字段将在这里动态创建 -->
+                </div>
+            </div>
+
+            <!-- 属性面板 -->
+            <div class="property-panel" id="propertyPanel">
+                <h4>属性</h4>
+                <div class="alert alert-warning">
+                    选择表单元素来编辑其属性
+                </div>
+                <!-- 属性编辑表单将在这里动态创建 -->
+            </div>
+        </div>
+
+        <div class="row">
+            <div class="col-sm-offset-5 col-sm-10 mt20">
+                <button type="button" class="btn btn-sm btn-primary" onclick="submitForm()"><i class="fa fa-check"></i>保 存</button>&nbsp;
+                <button type="button" class="btn btn-sm btn-danger" onclick="$.modal.closeTab()"><i class="fa fa-close"></i>关 闭</button>
+            </div>
+        </div>
+    </form>
+</div>
+<th:block th:include="include :: footer" />
+
+<script th:inline="javascript">
+    var prefix = ctx + "form/template";
+    var formDesigner = {
+        currentSelectedElement: null,
+        sectionCounter: 0,
+        fieldCounter: 0,
+        subtableCounter: 0,
+
+        init: function() {
+            // 初始化拖拽功能
+            this.initDragAndDrop();
+
+            // 加载已有设计
+            this.loadExistingDesign();
+
+            // 绑定事件
+            this.bindEvents();
+        },
+
+        initDragAndDrop: function() {
+            // 使组件可拖拽
+            $(".component-item").draggable({
+                helper: "clone",
+                connectToSortable: "#formSections"
+            });
+
+            // 使画布可接收拖拽
+            $("#formSections").sortable({
+                placeholder: "placeholder",
+                handle: ".section-drag-handle",
+                update: function(event, ui) {
+                    if (ui.item.hasClass('component-item')) {
+                        var type = ui.item.data('type');
+                        if (type === 'section') {
+                            var sectionHtml = formDesigner.createSection();
+                            ui.item.replaceWith(sectionHtml);
+                        } else {
+                            ui.item.remove();
+                        }
+                    }
+                }
+            });
+        },
+
+        loadExistingDesign: function() {
+            // 这里会从后端加载已有的表单设计数据
+            // 在实际项目中,应该通过AJAX请求获取
+            // 示例中暂时不实现
+        },
+
+        bindEvents: function() {
+            // 绑定组件点击事件
+            $(document).on('click', '.section-item, .field-item, .subtable-item', function(e) {
+                e.stopPropagation();
+
+                // 先移除所有已选中的元素
+                $('.section-item, .field-item, .subtable-item').removeClass('selected');
+                $(this).addClass('selected');
+
+                // 更新当前选中的元素
+                formDesigner.currentSelectedElement = $(this);
+
+                // 显示属性面板
+                formDesigner.showProperties($(this));
+            });
+
+            // 绑定添加字段按钮事件
+            $(document).on('click', '.btn-add-field', function() {
+                var sectionId = $(this).closest('.section-item').data('id');
+                var fieldHtml = formDesigner.createField(sectionId);
+                $(this).closest('.section-item').find('.section-fields').append(fieldHtml);
+            });
+
+            // 绑定添加子表按钮事件
+            $(document).on('click', '.btn-add-subtable', function() {
+                var sectionId = $(this).closest('.section-item').data('id');
+                var subtableHtml = formDesigner.createSubtable(sectionId);
+                $(this).closest('.section-item').find('.section-subtables').append(subtableHtml);
+            });
+
+            // 绑定删除分组按钮事件
+            $(document).on('click', '.btn-delete-section', function() {
+                $(this).closest('.section-item').remove();
+            });
+
+            // 绑定删除字段按钮事件
+            $(document).on('click', '.btn-delete-field', function() {
+                $(this).closest('.field-item').remove();
+            });
+
+            // 绑定删除子表按钮事件
+            $(document).on('click', '.btn-delete-subtable', function() {
+                $(this).closest('.subtable-item').remove();
+            });
+
+            // 字段排序
+            $(".section-fields").sortable({
+                handle: ".field-drag-handle",
+                placeholder: "placeholder",
+                connectWith: ".section-fields"
+            });
+        },
+
+        createSection: function() {
+            this.sectionCounter++;
+            var sectionId = "section_" + this.sectionCounter;
+
+            var html = '<div class="section-item" data-id="' + sectionId + '" data-type="section">' +
+                '<div class="section-header">' +
+                '<span><i class="fa fa-bars section-drag-handle handle"></i> 分组 ' + this.sectionCounter + '</span>' +
+                '<div class="section-actions">' +
+                '<button type="button" class="btn btn-primary btn-xs btn-add-field"><i class="fa fa-plus"></i> 添加字段</button> ' +
+                '<button type="button" class="btn btn-info btn-xs btn-add-subtable"><i class="fa fa-plus"></i> 添加子表</button> ' +
+                '<button type="button" class="btn btn-danger btn-xs btn-delete-section"><i class="fa fa-trash"></i></button>' +
+                '</div>' +
+                '</div>' +
+                '<div class="section-fields" data-section-id="' + sectionId + '"></div>' +
+                '<div class="section-subtables" data-section-id="' + sectionId + '"></div>' +
+                '</div>';
+
+            return html;
+        },
+
+        createField: function(sectionId) {
+            this.fieldCounter++;
+            var fieldId = "field_" + this.fieldCounter;
+
+            var html = '<div class="field-item" data-id="' + fieldId + '" data-section-id="' + sectionId + '" data-type="field">' +
+                '<i class="fa fa-bars field-drag-handle handle"></i> <span class="field-label">字段 ' + this.fieldCounter + '</span>' +
+                '<div class="field-actions">' +
+                '<button type="button" class="btn btn-danger btn-xs btn-delete-field"><i class="fa fa-trash"></i></button>' +
+                '</div>' +
+                '</div>';
+
+            return html;
+        },
+
+        createSubtable: function(sectionId) {
+            this.subtableCounter++;
+            var subtableId = "subtable_" + this.subtableCounter;
+
+            var html = '<div class="subtable-item" data-id="' + subtableId + '" data-section-id="' + sectionId + '" data-type="subtable">' +
+                '<div class="subtable-header">' +
+                '<span>子表 ' + this.subtableCounter + '</span>' +
+                '<div class="subtable-actions">' +
+                '<button type="button" class="btn btn-primary btn-xs btn-add-subtable-field"><i class="fa fa-plus"></i> 添加列</button> ' +
+                '<button type="button" class="btn btn-danger btn-xs btn-delete-subtable"><i class="fa fa-trash"></i></button>' +
+                '</div>' +
+                '</div>' +
+                '<div class="subtable-columns">' +
+                '<div class="subtable-column">序号</div>' +
+                '<div class="subtable-column">操作</div>' +
+                '</div>' +
+                '</div>';
+
+            return html;
+        },
+
+        showProperties: function(element) {
+            var type = element.data('type');
+            var propertyHtml = '';
+
+            if (type === 'section') {
+                propertyHtml = this.getSectionProperties(element);
+            } else if (type === 'field') {
+                propertyHtml = this.getFieldProperties(element);
+            } else if (type === 'subtable') {
+                propertyHtml = this.getSubtableProperties(element);
+            }
+
+            $('#propertyPanel').html('<h4>属性</h4>' + propertyHtml);
+
+            // 初始化下拉框等UI组件
+            $('.selectpicker').selectpicker();
+        },
+
+        getSectionProperties: function(section) {
+            var sectionName = section.find('.section-header span').text().trim().replace('分组 ', '');
+
+            var html = '<div class="property-group">' +
+                '<div class="property-group-title">基本属性</div>' +
+                '<div class="property-group-content">' +
+                '<div class="property-row">' +
+                '<label>分组名称</label>' +
+                '<input type="text" class="form-control" id="section_name" value="' + sectionName + '" ' +
+                'onchange="formDesigner.updateSectionProperty(\'name\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>是否可折叠</label>' +
+                '<select class="form-control selectpicker" id="section_collapsible" ' +
+                'onchange="formDesigner.updateSectionProperty(\'collapsible\', this.value)">' +
+                '<option value="0">否</option>' +
+                '<option value="1">是</option>' +
+                '</select>' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>是否必填分组</label>' +
+                '<select class="form-control selectpicker" id="section_required" ' +
+                'onchange="formDesigner.updateSectionProperty(\'required\', this.value)">' +
+                '<option value="0">否</option>' +
+                '<option value="1">是</option>' +
+                '</select>' +
+                '</div>' +
+                '</div>' +
+                '</div>';
+
+            return html;
+        },
+
+        getFieldProperties: function(field) {
+            var fieldLabel = field.find('.field-label').text().trim();
+            var fieldType = field.data('field-type') || 'text';
+
+            var html = '<div class="property-group">' +
+                '<div class="property-group-title">基本属性</div>' +
+                '<div class="property-group-content">' +
+                '<div class="property-row">' +
+                '<label>字段标签</label>' +
+                '<input type="text" class="form-control" id="field_label" value="' + fieldLabel + '" ' +
+                'onchange="formDesigner.updateFieldProperty(\'label\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>字段类型</label>' +
+                '<select class="form-control selectpicker" id="field_type" ' +
+                'onchange="formDesigner.updateFieldProperty(\'type\', this.value)">' +
+                '<option value="text" ' + (fieldType === 'text' ? 'selected' : '') + '>单行文本</option>' +
+                '<option value="textarea" ' + (fieldType === 'textarea' ? 'selected' : '') + '>多行文本</option>' +
+                '<option value="number" ' + (fieldType === 'number' ? 'selected' : '') + '>数字</option>' +
+                '<option value="select" ' + (fieldType === 'select' ? 'selected' : '') + '>下拉选择</option>' +
+                '<option value="radio" ' + (fieldType === 'radio' ? 'selected' : '') + '>单选按钮</option>' +
+                '<option value="checkbox" ' + (fieldType === 'checkbox' ? 'selected' : '') + '>复选框</option>' +
+                '<option value="date" ' + (fieldType === 'date' ? 'selected' : '') + '>日期</option>' +
+                '<option value="datetime" ' + (fieldType === 'datetime' ? 'selected' : '') + '>日期时间</option>' +
+                '<option value="file" ' + (fieldType === 'file' ? 'selected' : '') + '>文件上传</option>' +
+                '<option value="image" ' + (fieldType === 'image' ? 'selected' : '') + '>图片上传</option>' +
+                '</select>' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>是否必填</label>' +
+                '<select class="form-control selectpicker" id="field_required" ' +
+                'onchange="formDesigner.updateFieldProperty(\'required\', this.value)">' +
+                '<option value="0">否</option>' +
+                '<option value="1">是</option>' +
+                '</select>' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>默认值</label>' +
+                '<input type="text" class="form-control" id="field_default" value="" ' +
+                'onchange="formDesigner.updateFieldProperty(\'default\', this.value)">' +
+                '</div>' +
+                '</div>' +
+                '</div>';
+
+            // 对于选择类型字段,添加选项编辑区域
+            if (fieldType === 'select' || fieldType === 'radio' || fieldType === 'checkbox') {
+                html += '<div class="property-group">' +
+                    '<div class="property-group-title">选项设置</div>' +
+                    '<div class="property-group-content">' +
+                    '<div class="property-row">' +
+                    '<label>选项列表</label>' +
+                    '<textarea class="form-control" id="field_options" rows="5" ' +
+                    'placeholder="每行一个选项,格式:值=标签" ' +
+                    'onchange="formDesigner.updateFieldProperty(\'options\', this.value)"></textarea>' +
+                    '</div>' +
+                    '</div>' +
+                    '</div>';
+            }
+
+            html += '<div class="property-group">' +
+                '<div class="property-group-title">高级设置</div>' +
+                '<div class="property-group-content">' +
+                '<div class="property-row">' +
+                '<label>字段宽度</label>' +
+                '<select class="form-control selectpicker" id="field_width" ' +
+                'onchange="formDesigner.updateFieldProperty(\'width\', this.value)">' +
+                '<option value="100%">100%</option>' +
+                '<option value="75%">75%</option>' +
+                '<option value="50%">50%</option>' +
+                '<option value="25%">25%</option>' +
+                '</select>' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>占位文本</label>' +
+                '<input type="text" class="form-control" id="field_placeholder" value="" ' +
+                'onchange="formDesigner.updateFieldProperty(\'placeholder\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>帮助文本</label>' +
+                '<input type="text" class="form-control" id="field_help" value="" ' +
+                'onchange="formDesigner.updateFieldProperty(\'help\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>数据字典</label>' +
+                '<input type="text" class="form-control" id="field_dict" value="" ' +
+                'onchange="formDesigner.updateFieldProperty(\'dict\', this.value)">' +
+                '</div>' +
+                '</div>' +
+                '</div>';
+
+            return html;
+        },
+
+        getSubtableProperties: function(subtable) {
+            var subtableName = subtable.find('.subtable-header span').text().trim().replace('子表 ', '');
+
+            var html = '<div class="property-group">' +
+                '<div class="property-group-title">基本属性</div>' +
+                '<div class="property-group-content">' +
+                '<div class="property-row">' +
+                '<label>子表名称</label>' +
+                '<input type="text" class="form-control" id="subtable_name" value="' + subtableName + '" ' +
+                'onchange="formDesigner.updateSubtableProperty(\'name\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>添加按钮文本</label>' +
+                '<input type="text" class="form-control" id="subtable_add_text" value="新增" ' +
+                'onchange="formDesigner.updateSubtableProperty(\'addButtonText\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>是否必填</label>' +
+                '<select class="form-control selectpicker" id="subtable_required" ' +
+                'onchange="formDesigner.updateSubtableProperty(\'required\', this.value)">' +
+                '<option value="0">否</option>' +
+                '<option value="1">是</option>' +
+                '</select>' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>最少行数</label>' +
+                '<input type="number" class="form-control" id="subtable_min_rows" value="0" min="0" ' +
+                'onchange="formDesigner.updateSubtableProperty(\'minRows\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>最多行数</label>' +
+                '<input type="number" class="form-control" id="subtable_max_rows" value="" min="0" ' +
+                'onchange="formDesigner.updateSubtableProperty(\'maxRows\', this.value)">' +
+                '</div>' +
+                '</div>' +
+                '</div>';
+
+            return html;
+        },
+
+        updateSectionProperty: function(property, value) {
+            if (!this.currentSelectedElement) return;
+
+            var section = this.currentSelectedElement;
+
+            if (property === 'name') {
+                section.find('.section-header span').text(value);
+            } else if (property === 'collapsible') {
+                section.data('collapsible', value);
+            } else if (property === 'required') {
+                section.data('required', value);
+            }
+        },
+
+        updateFieldProperty: function(property, value) {
+            if (!this.currentSelectedElement) return;
+
+            var field = this.currentSelectedElement;
+
+            if (property === 'label') {
+                field.find('.field-label').text(value);
+            } else if (property === 'type') {
+                field.data('field-type', value);
+                // 刷新属性面板以显示类型相关的设置
+                this.showProperties(field);
+            } else if (property === 'required') {
+                field.data('required', value);
+            } else if (property === 'default') {
+                field.data('default', value);
+            } else if (property === 'options') {
+                field.data('options', value);
+            } else if (property === 'width') {
+                field.data('width', value);
+            } else if (property === 'placeholder') {
+                field.data('placeholder', value);
+            } else if (property === 'help') {
+                field.data('help', value);
+            } else if (property === 'dict') {
+                field.data('dict', value);
+            }
+        },
+
+        updateSubtableProperty: function(property, value) {
+            if (!this.currentSelectedElement) return;
+
+            var subtable = this.currentSelectedElement;
+
+            if (property === 'name') {
+                subtable.find('.subtable-header span').text(value);
+            } else if (property === 'addButtonText') {
+                subtable.data('add-button-text', value);
+            } else if (property === 'required') {
+                subtable.data('required', value);
+            } else if (property === 'minRows') {
+                subtable.data('min-rows', value);
+            } else if (property === 'maxRows') {
+                subtable.data('max-rows', value);
+            }
+        },
+
+        getDesignData: function() {
+            var designData = {
+                sections: []
+            };
+
+            // 遍历所有分组
+            $('.section-item').each(function(sectionIndex) {
+                var section = $(this);
+                var sectionId = section.data('id');
+
+                var sectionData = {
+                    sectionCode: 'section_' + (sectionIndex + 1),
+                    sectionName: section.find('.section-header span').text().trim().replace('分组 ', ''),
+                    sectionType: 'normal',
+                    isCollapsible: section.data('collapsible') === '1',
+                    isRequired: section.data('required') === '1',
+                    fields: [],
+                    subtables: []
+                };
+
+                // 遍历分组内的字段
+                section.find('.field-item').each(function(fieldIndex) {
+                    var field = $(this);
+
+                    var fieldData = {
+                        fieldCode: 'field_' + (sectionIndex + 1) + '_' + (fieldIndex + 1),
+                        fieldName: field.find('.field-label').text().trim(),
+                        fieldType: field.data('field-type') || 'text',
+                        fieldLabel: field.find('.field-label').text().trim(),
+                        placeholder: field.data('placeholder') || '',
+                        defaultValue: field.data('default') || '',
+                        isRequired: field.data('required') === '1',
+                        validationRule: field.data('validation') || '',
+                        validationMessage: field.data('validation-message') || '',
+                        dictType: field.data('dict') || '',
+                        fieldWidth: field.data('width') || '100%',
+                        helpText: field.data('help') || ''
+                    };
+
+                    // 如果是选择类字段,处理选项
+                    if (fieldData.fieldType === 'select' || fieldData.fieldType === 'radio' || fieldData.fieldType === 'checkbox') {
+                        var optionsText = field.data('options') || '';
+                        var options = [];
+
+                        if (optionsText) {
+                            var optionsLines = optionsText.split('\n');
+                            optionsLines.forEach(function(line, i) {
+                                if (line.trim()) {
+                                    var parts = line.split('=');
+                                    var optionValue = parts[0].trim();
+                                    var optionLabel = parts.length > 1 ? parts[1].trim() : optionValue;
+
+                                    options.push({
+                                        optionCode: 'option_' + (fieldIndex + 1) + '_' + (i + 1),
+                                        optionLabel: optionLabel,
+                                        optionValue: optionValue,
+                                        isDefault: false
+                                    });
+                                }
+                            });
+                        }
+
+                        fieldData.options = options;
+                    }
+
+                    sectionData.fields.push(fieldData);
+                });
+
+                // 遍历分组内的子表
+                section.find('.subtable-item').each(function(subtableIndex) {
+                    var subtable = $(this);
+
+                    var subtableData = {
+                        subtableCode: 'subtable_' + (sectionIndex + 1) + '_' + (subtableIndex + 1),
+                        subtableName: subtable.find('.subtable-header span').text().trim().replace('子表 ', ''),
+                        addButtonText: subtable.data('add-button-text') || '新增',
+                        isRequired: subtable.data('required') === '1',
+                        minRows: parseInt(subtable.data('min-rows') || '0'),
+                        maxRows: parseInt(subtable.data('max-rows') || '0') || null,
+                        fields: []
+                    };
+
+                    // 此处应该处理子表字段,但示例中暂不实现
+                    // 实际项目中,应该遍历子表中的列定义
+
+                    sectionData.subtables.push(subtableData);
+                });
+
+                designData.sections.push(sectionData);
+            });
+
+            return designData;
+        }
+    };
+
+    // 初始化表单设计器
+    $(function() {
+        formDesigner.init();
+    });
+
+    function submitForm() {
+        // 获取表单设计数据
+        var designData = formDesigner.getDesignData();
+
+        // 将数据转为JSON字符串
+        var designDataStr = JSON.stringify(designData);
+
+        // 构建表单数据
+        var data = {
+            "id": $("#templateId").val(),
+            "version": $("input[name='version']").val(),
+            "designData": designDataStr
+        };
+
+        // 提交表单
+        $.operate.saveTab(prefix + "/saveDesign", data);
+    }
+</script>
+</body>
+</html>

+ 2 - 0
health-admin/src/main/resources/templates/gxhpz/drugconfigAdd.html

@@ -330,6 +330,7 @@
                     data: hzparam,
                     async: false,
                     success: function(data) {
+                        debugger;
                         console.log("details====" + JSON.stringify(data));
                         if (data.code == 0) {
                             populateFormFields(data.data);
@@ -371,6 +372,7 @@
 
             // 填充表单字段
             function populateFormFields(data) {
+                debugger
                 $('#mdmCode').val(data.product_code);//.trigger('change')
                 $('#genericName').val(data.generic_name);
                 $('#productName').val(data.product_name);

+ 1 - 1
health-admin/src/main/resources/templates/gxhpz/drugconfigDetail.html

@@ -125,7 +125,7 @@
     <th:block th:include="include :: jsonview-js" />
     <script th:inline="javascript">
 		$(function() {
-			debugger
+
 			// var operParam = [[${oorderData}]];
 			// if ($.common.isNotEmpty(operParam) && operParam.length < 2000) {
 			// 	$("#operParam").JSONView(operParam);

+ 5 - 4
health-admin/src/main/resources/templates/gxhpz/drugconfigList.html

@@ -64,7 +64,8 @@
                     <input type="text" class="styled-input" placeholder="请输入厂家简称" name="manufacturerShortName"/>
                 </div>
                     <div class="customize-form-group">
-                        <label>是否关联D值:</label>
+                        <!-- 是否关联D值 -->
+                        <label>是否是复购品:</label>
                         <select name="isdvalue" class="styled-input" th:with="type=${@dict.getType('sys_gxhpz_yes_no')}">
                             <option value="">请选择</option>
                             <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
@@ -134,10 +135,10 @@
              </form>
             </div>
             <div class="btn-group-sm" id="toolbar" role="group">
-                <a class="btn btn-success" id="button-open-2"  shiro:hasPermission="system:user:add">
+                <a class="btn btn-success" id="button-open-2"  shiro:hasPermission="gxhpz:yppz:add">
                     <i class="fa fa-plus"></i> 新增患者管理品
                 </a>
-                <a class="btn btn-success" onclick="$.operate.add2()" shiro:hasPermission="system:user:add">
+                <a class="btn btn-success" onclick="$.operate.add2()" shiro:hasPermission="gxhpz:yppz:xzfgpz">
                     <i class="fa fa-plus"></i> 新增复购配置
                 </a>
 <!--                <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="system:user:remove">-->
@@ -162,7 +163,7 @@
 <script th:inline="javascript">
     var editFlag = [[${@permission.hasPermi('gxhpz:yppz:edit')}]];
     var removeFlag = [[${@permission.hasPermi('gxhpz:yppz:remove')}]];
-    var detailFlag = [[${@permission.hasPermi('gxhpz:yppz:view')}]];
+    var detailFlag = [[${@permission.hasPermi('gxhpz:yppz:detail')}]];
     var prefix = ctx + "yppz/drugConfig";
     $(function() {
         var panehHidden = false;

+ 1 - 1
health-admin/src/main/resources/templates/gxhpz/dvalueconfigDetail.html

@@ -50,7 +50,7 @@
     <th:block th:include="include :: jsonview-js" />
     <script th:inline="javascript">
 		$(function() {
-			debugger
+
 			// var operParam = [[${oorderData}]];
 			// if ($.common.isNotEmpty(operParam) && operParam.length < 2000) {
 			// 	$("#operParam").JSONView(operParam);

+ 15 - 14
health-admin/src/main/resources/templates/gxhpz/dvalueconfigList.html

@@ -25,14 +25,14 @@
 
                 <!-- D值品编码 -->
                 <div class="customize-form-group">
-                    <label>D值品编码:</label>
-                    <input type="text" class="styled-input" placeholder="请输入D值品编码" name="d_value_code"/>
-                </div>
-                <!-- D值品名称 -->
-                <div class="customize-form-group">
-                    <label>D值品名称:</label>
-                    <input type="text" class="styled-input" placeholder="请输入D值品名称" name="dValueName"/>
+                    <label>D值品编码/D值品名称:</label>
+                    <input type="text" class="styled-input" style="width: 400px" placeholder="请输入D值品编码或D值品名称" name="query"/>
                 </div>
+<!--                &lt;!&ndash; D值品名称 &ndash;&gt;-->
+<!--                <div class="customize-form-group">-->
+<!--                    <label>D值品名称:</label>-->
+<!--                    <input type="text" class="styled-input" placeholder="请输入D值品名称" name="dValueName"/>-->
+<!--                </div>-->
                 <!-- 状态 -->
                 <div class="customize-form-group">
                     <label>状态:</label>
@@ -46,7 +46,7 @@
             </div>
 
             <div class="btn-group-sm" id="toolbar" role="group">
-                <a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="system:user:add">
+                <a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="gxhpz:dzpz:add">
                     <i class="fa fa-plus"></i> 新增
                 </a>
 <!--                <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="system:user:remove">-->
@@ -67,8 +67,9 @@
 <th:block th:include="include :: bootstrap-table-fixed-columns-js" />
 <th:block th:include="include :: ztree-js" />
 <script th:inline="javascript">
-    var editFlag = [[${@permission.hasPermi('dtp:pmService:edit')}]];
-    var removeFlag = [[${@permission.hasPermi('dtp:pmService:remove')}]];
+    var editFlag = [[${@permission.hasPermi('gxhpz:dzpz:edit')}]];
+    var detailFlag = [[${@permission.hasPermi('gxhpz:dzpz:detail')}]];
+    var removeFlag = [[${@permission.hasPermi('gxhpz:dzpz:remove')}]];
     var prefix = ctx + "dzpz/dvalueConfig";
     $(function() {
         var panehHidden = false;
@@ -116,7 +117,7 @@
                 { field: 'dValueName', title: 'D值品名称', align: 'center' },
                 {
                     visible: editFlag == 'hidden' ? false : true,
-                    title: '配置状态',
+                    title: '状态(绿色可用,红色禁用)',
                     align: 'center',
                     formatter: function (value, row, index) {
                         return statusTools(row);
@@ -142,9 +143,9 @@
                     formatter: function(value, row, index) {
                         if (row.id) {
                             var actions = [];
-                            // actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.edit(\'' + row.id + '\')"><i class="fa fa-edit"></i>修改</a> ');
-                            actions.push('<a class="btn btn-info btn-xs" href="javascript:void(0)" onclick="$.operate.view(\'' + row.id + '\')"><i class="fa fa-eye"></i>详情</a> ');
-                            // actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.id + '\')"><i class="fa fa-remove"></i>删除</a> ');
+                             actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.edit(\'' + row.id + '\')"><i class="fa fa-edit"></i>修改</a> ');
+                            actions.push('<a class="btn btn-info btn-xs ' + detailFlag + '" href="javascript:void(0)" onclick="$.operate.view(\'' + row.id + '\')"><i class="fa fa-eye"></i>详情</a> ');
+                             actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.id + '\')"><i class="fa fa-remove"></i>删除</a> ');
                             return actions.join('');
                         } else {
                             return "";

+ 37 - 5
health-admin/src/main/resources/templates/gxhpz/followUpTaskAdd.html

@@ -32,17 +32,23 @@
                         <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
                     </select>
                 </div>
-            <div class="customize-form-group">
-                <label>所属门店:</label>
-                <input name="storeName" placeholder="请输入所属门店" id="storeName" class="styled-input" type="text">
-            </div>
+                <div class="customize-form-group">
+                    <label>所属门店:</label>
+                    <select name="storeName" id="storeSelect" class="styled-input">
+                        <option value="">全部</option>
+                    </select>
+                </div>
         </div>
     </form>
 </div>
 <th:block th:include="include :: footer" />
 <th:block th:include="include :: ztree-js" />
 <script type="text/javascript">
-
+    var prefix_task = ctx + "task/followTask";
+    //初始化加载
+    $(document).ready(function() {
+        findTaskStoreList();
+    });
     function submitHandler() {
         var selectedValue = $('#default_follow_up').val();
 
@@ -71,6 +77,32 @@
             }
         });
     }
+    // 异步加载所有门店信息并填充下拉框
+    function findTaskStoreList() {
+        $.ajax({
+            url: prefix_task+ "/findTaskStoreList", // 这是获取所有门店的API端点
+            type: 'POST',
+            cache: false, // 设置为 false 防止缓存
+            processData: false, // 告诉 jQuery 不要处理数据(非常重要)
+            contentType: false, // 告诉 jQuery 不要设置 contentType(非常重要)
+            async: true, // 异步请求更为推荐,除非有特殊原因需要同步
+            success: function (data) {
+                var select = $('#storeSelect');
+                // 清空除了默认选项外的所有选项
+                select.find('option:not(:first)').remove();
+                if (data && data.data) {
+                    data.data.forEach(function (store) {
+                        select.append(new Option(store.dept_name, store.dept_name));
+                    });
+                } else {
+                    console.error('Unexpected response format:', data);
+                }
+            },
+            error: function () {
+                $.modal.alertError('加载门店信息失败');
+            }
+        });
+    }
 </script>
 </body>
 </html>

+ 1248 - 0
health-admin/src/main/resources/templates/gxhpz/formEdit.html

@@ -0,0 +1,1248 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org">
+<head>
+    <th:block th:include="include :: header('修改表单模板')" />
+    <th:block th:include="include :: bootstrap-select-css" />
+    <th:block th:include="include :: bootstrap-select-js" />
+    <th:block th:include="include :: bootstrap-fileinput-css" />
+    <th:block th:include="include :: bootstrap-fileinput-js" />
+    <th:block th:include="include :: datetimepicker-css" />
+    <th:block th:include="include :: datetimepicker-js" />
+    <th:block th:include="include :: summernote-css" />
+    <th:block th:include="include :: summernote-js" />
+    <style>
+        .form-container {
+            padding: 20px;
+            background-color: #fff;
+            border-radius: 4px;
+            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+        }
+        .form-section {
+            margin-bottom: 20px;
+            padding: 15px;
+            background-color: #f9f9f9;
+            border-radius: 4px;
+            border: 1px solid #eee;
+        }
+        .form-section-header {
+            border-bottom: 1px solid #eee;
+            padding-bottom: 10px;
+            margin-bottom: 15px;
+            font-weight: bold;
+            font-size: 16px;
+            color: #333;
+        }
+        .form-fields {
+            margin-bottom: 15px;
+        }
+        .form-field {
+            margin-bottom: 15px;
+            padding: 10px;
+            background-color: #fff;
+            border: 1px solid #eee;
+            border-radius: 4px;
+        }
+        .field-label {
+            font-weight: bold;
+            margin-bottom: 5px;
+        }
+        .field-help {
+            font-size: 12px;
+            color: #999;
+            margin-top: 5px;
+        }
+        .form-subtable {
+            margin-top: 15px;
+            background-color: #fff;
+            padding: 10px;
+            border-radius: 4px;
+            border: 1px solid #ddd;
+        }
+        .subtable-header {
+            font-weight: bold;
+            margin-bottom: 10px;
+        }
+        .subtable-table {
+            width: 100%;
+            border-collapse: collapse;
+            margin-bottom: 10px;
+        }
+        .subtable-table th, .subtable-table td {
+            padding: 8px;
+            border: 1px solid #ddd;
+            text-align: center;
+        }
+        .subtable-table th {
+            background-color: #f5f5f5;
+            font-weight: bold;
+        }
+        .subtable-add-btn {
+            margin-top: 5px;
+        }
+        .form-actions {
+            margin-top: 20px;
+            text-align: center;
+        }
+        .action-btns {
+            margin-top: 10px;
+            text-align: right;
+        }
+        .field-options-container {
+            padding: 10px;
+            background-color: #f9f9f9;
+            border-radius: 4px;
+            margin-top: 10px;
+        }
+        .field-option {
+            display: flex;
+            align-items: center;
+            margin-bottom: 5px;
+        }
+        .field-option input {
+            margin-right: 5px;
+        }
+        .add-option-btn {
+            margin-top: 5px;
+        }
+        .add-section-btn, .add-field-btn, .add-subtable-btn {
+            margin-bottom: 10px;
+        }
+        .drag-handle {
+            cursor: move;
+            color: #999;
+            margin-right: 5px;
+        }
+    </style>
+</head>
+<body class="white-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-12">
+            <div class="ibox">
+                <div class="ibox-title">
+                    <h5>修改表单模板</h5>
+                    <div class="ibox-tools">
+                        <a class="btn btn-primary btn-xs" onclick="$.modal.closeTab()">关闭</a>
+                    </div>
+                </div>
+                <div class="ibox-content">
+                    <form id="form-template-edit" class="form-horizontal">
+                        <input type="hidden" name="id" th:value="${id}"/>
+
+                        <!-- 基本信息 -->
+                        <div class="form-section">
+                            <div class="form-section-header">基本信息</div>
+                            <div class="form-section-content">
+                                <div class="form-group">
+                                    <label class="col-sm-2 control-label">模板名称:</label>
+                                    <div class="col-sm-4">
+                                        <input name="template_name" class="form-control" type="text" th:value="${template_name}" required>
+                                    </div>
+                                    <label class="col-sm-2 control-label">模板编码:</label>
+                                    <div class="col-sm-4">
+                                        <input name="template_code" class="form-control" type="text" th:value="${template_code}" required>
+                                    </div>
+                                </div>
+
+                                <div class="form-group">
+                                    <label class="col-sm-2 control-label">模板类型:</label>
+                                    <div class="col-sm-4">
+                                        <select name="template_type" class="form-control selectpicker" required>
+                                            <option value="">请选择</option>
+                                            <option value="常规随访" th:selected="${template_type == '常规随访'}">常规随访</option>
+                                            <option value="脱落召回" th:selected="${template_type == '脱落召回'}">脱落召回</option>
+                                            <option value="其他" th:selected="${template_type == '其他'}">其他</option>
+                                        </select>
+                                    </div>
+                                    <label class="col-sm-2 control-label">模板版本:</label>
+                                    <div class="col-sm-4">
+                                        <input name="version" class="form-control" type="text" th:value="${version}" required>
+                                    </div>
+                                </div>
+
+                                <div class="form-group">
+                                    <label class="col-sm-2 control-label">业务归属:</label>
+                                    <div class="col-sm-4">
+                                        <input name="business_belonging" class="form-control" type="text" th:value="${business_belonging}">
+                                    </div>
+                                    <label class="col-sm-2 control-label">状态:</label>
+                                    <div class="col-sm-4">
+                                        <div class="radio-inline">
+                                            <input type="radio" name="status" value="1" th:checked="${status == 1}"> 启用
+                                        </div>
+                                        <div class="radio-inline">
+                                            <input type="radio" name="status" value="0" th:checked="${status == 0}"> 停用
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <div class="form-group">
+                                    <label class="col-sm-2 control-label">是否默认:</label>
+                                    <div class="col-sm-4">
+                                        <div class="radio-inline">
+                                            <input type="radio" name="is_default" value="1" th:checked="${is_default == 1}"> 是
+                                        </div>
+                                        <div class="radio-inline">
+                                            <input type="radio" name="is_default" value="0" th:checked="${is_default == 0}"> 否
+                                        </div>
+                                    </div>
+                                    <label class="col-sm-2 control-label">门店:</label>
+                                    <div class="col-sm-4">
+                                        <input name="storeName" class="form-control" type="text" th:value="${storeName}" readonly>
+                                        <input name="storeId" type="hidden" th:value="${storeId}">
+                                    </div>
+                                </div>
+
+                                <div class="form-group">
+                                    <label class="col-sm-2 control-label">模板描述:</label>
+                                    <div class="col-sm-10">
+                                        <textarea name="description" class="form-control" rows="3" th:text="${description}"></textarea>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+
+                        <!-- 表单分组 -->
+                        <div id="sections-container">
+                            <!-- 遍历所有分组 -->
+                            <div th:each="section, sectionStat : ${sections}" class="form-section" th:id="'section-' + ${sectionStat.index}">
+                                <input type="hidden" th:name="'sections[' + ${sectionStat.index} + '].id'" th:value="${section.id}">
+                                <div class="form-section-header">
+                                    <i class="fa fa-bars drag-handle"></i>
+                                    <span th:text="'分组 ' + (${sectionStat.index} + 1) + ': ' + ${section.section_name}">分组标题</span>
+                                    <div class="pull-right">
+                                        <button type="button" class="btn btn-danger btn-xs remove-section-btn">
+                                            <i class="fa fa-trash"></i> 删除分组
+                                        </button>
+                                    </div>
+                                </div>
+                                <div class="form-section-content">
+                                    <div class="form-group">
+                                        <label class="col-sm-2 control-label">分组名称:</label>
+                                        <div class="col-sm-4">
+                                            <input th:name="'sections[' + ${sectionStat.index} + '].section_name'" class="form-control" type="text" th:value="${section.section_name}" required>
+                                        </div>
+                                        <label class="col-sm-2 control-label">分组编码:</label>
+                                        <div class="col-sm-4">
+                                            <input th:name="'sections[' + ${sectionStat.index} + '].section_code'" class="form-control" type="text" th:value="${section.section_code}" required>
+                                        </div>
+                                    </div>
+
+                                    <div class="form-group">
+                                        <label class="col-sm-2 control-label">分组类型:</label>
+                                        <div class="col-sm-4">
+                                            <select th:name="'sections[' + ${sectionStat.index} + '].section_type'" class="form-control selectpicker">
+                                                <option value="normal" th:selected="${section.section_type == 'normal'}">普通</option>
+                                                <option value="tab" th:selected="${section.section_type == 'tab'}">选项卡</option>
+                                                <option value="accordion" th:selected="${section.section_type == 'accordion'}">手风琴</option>
+                                            </select>
+                                        </div>
+                                        <label class="col-sm-2 control-label">显示顺序:</label>
+                                        <div class="col-sm-4">
+                                            <input th:name="'sections[' + ${sectionStat.index} + '].display_order'" class="form-control" type="number" th:value="${section.display_order}" required>
+                                        </div>
+                                    </div>
+
+                                    <div class="form-group">
+                                        <label class="col-sm-2 control-label">是否可折叠:</label>
+                                        <div class="col-sm-4">
+                                            <div class="radio-inline">
+                                                <input type="radio" th:name="'sections[' + ${sectionStat.index} + '].is_collapsible'" value="1" th:checked="${section.is_collapsible == 1}"> 是
+                                            </div>
+                                            <div class="radio-inline">
+                                                <input type="radio" th:name="'sections[' + ${sectionStat.index} + '].is_collapsible'" value="0" th:checked="${section.is_collapsible == 0}"> 否
+                                            </div>
+                                        </div>
+                                        <label class="col-sm-2 control-label">是否必填:</label>
+                                        <div class="col-sm-4">
+                                            <div class="radio-inline">
+                                                <input type="radio" th:name="'sections[' + ${sectionStat.index} + '].is_required'" value="1" th:checked="${section.is_required == 1}"> 是
+                                            </div>
+                                            <div class="radio-inline">
+                                                <input type="radio" th:name="'sections[' + ${sectionStat.index} + '].is_required'" value="0" th:checked="${section.is_required == 0}"> 否
+                                            </div>
+                                        </div>
+                                    </div>
+
+                                    <div class="form-group">
+                                        <label class="col-sm-2 control-label">显示条件:</label>
+                                        <div class="col-sm-10">
+                                            <textarea th:name="'sections[' + ${sectionStat.index} + '].visibility_condition'" class="form-control" rows="2" th:text="${section.visibility_condition}"></textarea>
+                                        </div>
+                                    </div>
+
+                                    <!-- 字段容器 -->
+                                    <div class="fields-container" th:id="'fields-container-' + ${sectionStat.index}">
+                                        <div class="form-section-header">字段列表</div>
+                                        <button type="button" class="btn btn-primary btn-sm add-field-btn" th:onclick="'addField(' + ${sectionStat.index} + ')'">
+                                            <i class="fa fa-plus"></i> 添加字段
+                                        </button>
+
+                                        <!-- 遍历分组内的字段 -->
+                                        <div th:each="field, fieldStat : ${section.fields}" class="form-field" th:id="'section-' + ${sectionStat.index} + '-field-' + ${fieldStat.index}">
+                                            <input type="hidden" th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].id'" th:value="${field.id}">
+                                            <div class="row">
+                                                <div class="col-sm-11">
+                                                    <div class="field-header">
+                                                        <i class="fa fa-bars drag-handle"></i>
+                                                        <span th:text="'字段 ' + (${fieldStat.index} + 1) + ': ' + ${field.field_label}">字段标签</span>
+                                                    </div>
+                                                </div>
+                                                <div class="col-sm-1 text-right">
+                                                    <button type="button" class="btn btn-danger btn-xs remove-field-btn" th:onclick="'removeField(' + ${sectionStat.index} + ', ' + ${fieldStat.index} + ')'">
+                                                        <i class="fa fa-trash"></i>
+                                                    </button>
+                                                </div>
+                                            </div>
+
+                                            <div class="form-group">
+                                                <label class="col-sm-2 control-label">字段标签:</label>
+                                                <div class="col-sm-4">
+                                                    <input th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].field_label'"
+                                                           class="form-control" type="text" th:value="${field.field_label}" required>
+                                                </div>
+                                                <label class="col-sm-2 control-label">字段名称:</label>
+                                                <div class="col-sm-4">
+                                                    <input th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].field_name'"
+                                                           class="form-control" type="text" th:value="${field.field_name}" required>
+                                                </div>
+                                            </div>
+
+                                            <div class="form-group">
+                                                <label class="col-sm-2 control-label">字段编码:</label>
+                                                <div class="col-sm-4">
+                                                    <input th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].field_code'"
+                                                           class="form-control" type="text" th:value="${field.field_code}" required>
+                                                </div>
+                                                <label class="col-sm-2 control-label">字段类型:</label>
+                                                <div class="col-sm-4">
+                                                    <select th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].field_type'"
+                                                            class="form-control field-type-selector"
+                                                            th:onchange="'fieldTypeChanged(' + ${sectionStat.index} + ', ' + ${fieldStat.index} + ')'" required>
+                                                        <option value="text" th:selected="${field.field_type == 'text'}">文本</option>
+                                                        <option value="textarea" th:selected="${field.field_type == 'textarea'}">多行文本</option>
+                                                        <option value="number" th:selected="${field.field_type == 'number'}">数字</option>
+                                                        <option value="select" th:selected="${field.field_type == 'select'}">下拉框</option>
+                                                        <option value="radio" th:selected="${field.field_type == 'radio'}">单选</option>
+                                                        <option value="checkbox" th:selected="${field.field_type == 'checkbox'}">多选</option>
+                                                        <option value="date" th:selected="${field.field_type == 'date'}">日期</option>
+                                                        <option value="datetime" th:selected="${field.field_type == 'datetime'}">日期时间</option>
+                                                        <option value="file" th:selected="${field.field_type == 'file'}">文件</option>
+                                                        <option value="image" th:selected="${field.field_type == 'image'}">图片</option>
+                                                    </select>
+                                                </div>
+                                            </div>
+
+                                            <div class="form-group">
+                                                <label class="col-sm-2 control-label">占位文本:</label>
+                                                <div class="col-sm-4">
+                                                    <input th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].placeholder'"
+                                                           class="form-control" type="text" th:value="${field.placeholder}">
+                                                </div>
+                                                <label class="col-sm-2 control-label">默认值:</label>
+                                                <div class="col-sm-4">
+                                                    <input th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].default_value'"
+                                                           class="form-control" type="text" th:value="${field.default_value}">
+                                                </div>
+                                            </div>
+
+                                            <div class="form-group">
+                                                <label class="col-sm-2 control-label">字段宽度:</label>
+                                                <div class="col-sm-4">
+                                                    <input th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].field_width'"
+                                                           class="form-control" type="text" th:value="${field.field_width}" placeholder="如: 100% 或 300px">
+                                                </div>
+                                                <label class="col-sm-2 control-label">显示顺序:</label>
+                                                <div class="col-sm-4">
+                                                    <input th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].display_order'"
+                                                           class="form-control" type="number" th:value="${field.display_order}" required>
+                                                </div>
+                                            </div>
+
+                                            <div class="form-group">
+                                                <label class="col-sm-2 control-label">是否必填:</label>
+                                                <div class="col-sm-4">
+                                                    <div class="radio-inline">
+                                                        <input type="radio" th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].is_required'"
+                                                               value="1" th:checked="${field.is_required == 1}"> 是
+                                                    </div>
+                                                    <div class="radio-inline">
+                                                        <input type="radio" th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].is_required'"
+                                                               value="0" th:checked="${field.is_required == 0}"> 否
+                                                    </div>
+                                                </div>
+                                                <label class="col-sm-2 control-label">是否可编辑:</label>
+                                                <div class="col-sm-4">
+                                                    <div class="radio-inline">
+                                                        <input type="radio" th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].is_editable'"
+                                                               value="1" th:checked="${field.is_editable == 1}"> 是
+                                                    </div>
+                                                    <div class="radio-inline">
+                                                        <input type="radio" th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].is_editable'"
+                                                               value="0" th:checked="${field.is_editable == 0}"> 否
+                                                    </div>
+                                                </div>
+                                            </div>
+
+                                            <div class="form-group">
+                                                <label class="col-sm-2 control-label">验证规则:</label>
+                                                <div class="col-sm-10">
+                                                    <input th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].validation_rule'"
+                                                           class="form-control" type="text" th:value="${field.validation_rule}" placeholder="正则表达式">
+                                                </div>
+                                            </div>
+
+                                            <div class="form-group">
+                                                <label class="col-sm-2 control-label">验证提示:</label>
+                                                <div class="col-sm-10">
+                                                    <input th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].validation_message'"
+                                                           class="form-control" type="text" th:value="${field.validation_message}">
+                                                </div>
+                                            </div>
+
+                                            <div class="form-group">
+                                                <label class="col-sm-2 control-label">帮助文本:</label>
+                                                <div class="col-sm-10">
+                                                    <textarea th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].help_text'"
+                                                              class="form-control" rows="2" th:text="${field.help_text}"></textarea>
+                                                </div>
+                                            </div>
+
+                                            <!-- 字段选项(对于select, radio, checkbox) -->
+                                            <div th:if="${field.field_type == 'select' || field.field_type == 'radio' || field.field_type == 'checkbox'}"
+                                                 class="field-options-container" th:id="'options-container-' + ${sectionStat.index} + '-' + ${fieldStat.index}">
+                                                <h5>选项列表</h5>
+                                                <button type="button" class="btn btn-info btn-xs add-option-btn"
+                                                        th:onclick="'addOption(' + ${sectionStat.index} + ', ' + ${fieldStat.index} + ')'">
+                                                    <i class="fa fa-plus"></i> 添加选项
+                                                </button>
+
+                                                <div class="options-list" th:id="'options-list-' + ${sectionStat.index} + '-' + ${fieldStat.index}">
+                                                    <!-- 遍历选项 -->
+                                                    <div th:each="option, optionStat : ${field.options}" class="field-option"
+                                                         th:id="'section-' + ${sectionStat.index} + '-field-' + ${fieldStat.index} + '-option-' + ${optionStat.index}">
+                                                        <input type="hidden" th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].options[' + ${optionStat.index} + '].id'"
+                                                               th:value="${option.id}">
+
+                                                        <div class="row">
+                                                            <div class="col-sm-3">
+                                                                <div class="input-group">
+                                                                    <span class="input-group-addon">标签</span>
+                                                                    <input th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].options[' + ${optionStat.index} + '].option_label'"
+                                                                           class="form-control" type="text" th:value="${option.option_label}" required>
+                                                                </div>
+                                                            </div>
+                                                            <div class="col-sm-3">
+                                                                <div class="input-group">
+                                                                    <span class="input-group-addon">值</span>
+                                                                    <input th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].options[' + ${optionStat.index} + '].option_value'"
+                                                                           class="form-control" type="text" th:value="${option.option_value}" required>
+                                                                </div>
+                                                            </div>
+                                                            <div class="col-sm-2">
+                                                                <div class="input-group">
+                                                                    <span class="input-group-addon">编码</span>
+                                                                    <input th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].options[' + ${optionStat.index} + '].option_code'"
+                                                                           class="form-control" type="text" th:value="${option.option_code}" required>
+                                                                </div>
+                                                            </div>
+                                                            <div class="col-sm-2">
+                                                                <div class="input-group">
+                                                                    <span class="input-group-addon">顺序</span>
+                                                                    <input th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].options[' + ${optionStat.index} + '].display_order'"
+                                                                           class="form-control" type="number" th:value="${option.display_order}" required>
+                                                                </div>
+                                                            </div>
+                                                            <div class="col-sm-1">
+                                                                <div class="checkbox">
+                                                                    <label>
+                                                                        <input type="checkbox" th:name="'sections[' + ${sectionStat.index} + '].fields[' + ${fieldStat.index} + '].options[' + ${optionStat.index} + '].is_default'"
+                                                                               value="1" th:checked="${option.is_default == 1}"> 默认
+                                                                    </label>
+                                                                </div>
+                                                            </div>
+                                                            <div class="col-sm-1">
+                                                                <button type="button" class="btn btn-danger btn-xs remove-option-btn"
+                                                                        th:onclick="'removeOption(' + ${sectionStat.index} + ', ' + ${fieldStat.index} + ', ' + ${optionStat.index} + ')'">
+                                                                    <i class="fa fa-trash"></i>
+                                                                </button>
+                                                            </div>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+
+                                    <!-- 子表容器 -->
+                                    <div class="subtables-container" th:id="'subtables-container-' + ${sectionStat.index}">
+                                        <div class="form-section-header">子表列表</div>
+                                        <button type="button" class="btn btn-primary btn-sm add-subtable-btn" th:onclick="'addSubtable(' + ${sectionStat.index} + ')'">
+                                            <i class="fa fa-plus"></i> 添加子表
+                                        </button>
+
+                                        <!-- 遍历分组内的子表 -->
+                                        <div th:each="subtable, subtableStat : ${section.subtables}" class="form-subtable"
+                                             th:id="'section-' + ${sectionStat.index} + '-subtable-' + ${subtableStat.index}">
+                                            <input type="hidden" th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].id'" th:value="${subtable.id}">
+                                            <div class="row">
+                                                <div class="col-sm-11">
+                                                    <div class="subtable-header">
+                                                        <i class="fa fa-bars drag-handle"></i>
+                                                        <span th:text="'子表 ' + (${subtableStat.index} + 1) + ': ' + ${subtable.subtable_name}">子表标题</span>
+                                                    </div>
+                                                </div>
+                                                <div class="col-sm-1 text-right">
+                                                    <button type="button" class="btn btn-danger btn-xs remove-subtable-btn"
+                                                            th:onclick="'removeSubtable(' + ${sectionStat.index} + ', ' + ${subtableStat.index} + ')'">
+                                                        <i class="fa fa-trash"></i>
+                                                    </button>
+                                                </div>
+                                            </div>
+
+                                            <div class="form-group">
+                                                <label class="col-sm-2 control-label">子表名称:</label>
+                                                <div class="col-sm-4">
+                                                    <input th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].subtable_name'"
+                                                           class="form-control" type="text" th:value="${subtable.subtable_name}" required>
+                                                </div>
+                                                <label class="col-sm-2 control-label">子表编码:</label>
+                                                <div class="col-sm-4">
+                                                    <input th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].subtable_code'"
+                                                           class="form-control" type="text" th:value="${subtable.subtable_code}" required>
+                                                </div>
+                                            </div>
+
+                                            <div class="form-group">
+                                                <label class="col-sm-2 control-label">显示顺序:</label>
+                                                <div class="col-sm-4">
+                                                    <input th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].display_order'"
+                                                           class="form-control" type="number" th:value="${subtable.display_order}" required>
+                                                </div>
+                                                <label class="col-sm-2 control-label">添加按钮文本:</label>
+                                                <div class="col-sm-4">
+                                                    <input th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].add_button_text'"
+                                                           class="form-control" type="text" th:value="${subtable.add_button_text}" placeholder="默认为'新增'">
+                                                </div>
+                                            </div>
+
+                                            <div class="form-group">
+                                                <label class="col-sm-2 control-label">是否必填:</label>
+                                                <div class="col-sm-4">
+                                                    <div class="radio-inline">
+                                                        <input type="radio" th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].is_required'"
+                                                               value="1" th:checked="${subtable.is_required == 1}"> 是
+                                                    </div>
+                                                    <div class="radio-inline">
+                                                        <input type="radio" th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].is_required'"
+                                                               value="0" th:checked="${subtable.is_required == 0}"> 否
+                                                    </div>
+                                                </div>
+                                                <label class="col-sm-2 control-label">状态:</label>
+                                                <div class="col-sm-4">
+                                                    <div class="radio-inline">
+                                                        <input type="radio" th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].status'"
+                                                               value="1" th:checked="${subtable.status == 1}"> 启用
+                                                    </div>
+                                                    <div class="radio-inline">
+                                                        <input type="radio" th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].status'"
+                                                               value="0" th:checked="${subtable.status == 0}"> 停用
+                                                    </div>
+                                                </div>
+                                            </div>
+
+                                            <div class="form-group">
+                                                <label class="col-sm-2 control-label">最少行数:</label>
+                                                <div class="col-sm-4">
+                                                    <input th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].min_rows'"
+                                                           class="form-control" type="number" th:value="${subtable.min_rows}" min="0">
+                                                </div>
+                                                <label class="col-sm-2 control-label">最多行数:</label>
+                                                <div class="col-sm-4">
+                                                    <input th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].max_rows'"
+                                                           class="form-control" type="number" th:value="${subtable.max_rows}" min="0">
+                                                </div>
+                                            </div>
+
+                                            <!-- 子表字段 -->
+                                            <div class="subtable-fields-container" th:id="'subtable-fields-container-' + ${sectionStat.index} + '-' + ${subtableStat.index}">
+                                                <h5>子表字段</h5>
+                                                <button type="button" class="btn btn-info btn-xs add-subtable-field-btn"
+                                                        th:onclick="'addSubtableField(' + ${sectionStat.index} + ', ' + ${subtableStat.index} + ')'">
+                                                    <i class="fa fa-plus"></i> 添加字段
+                                                </button>
+
+                                                <div class="table-responsive">
+                                                    <table class="table table-bordered">
+                                                        <thead>
+                                                        <tr>
+                                                            <th width="5%">序号</th>
+                                                            <th width="15%">字段标签</th>
+                                                            <th width="15%">字段名称</th>
+                                                            <th width="10%">字段编码</th>
+                                                            <th width="10%">字段类型</th>
+                                                            <th width="10%">必填</th>
+                                                            <th width="10%">顺序</th>
+                                                            <th width="15%">操作</th>
+                                                        </tr>
+                                                        </thead>
+                                                        <tbody th:id="'subtable-fields-list-' + ${sectionStat.index} + '-' + ${subtableStat.index}">
+                                                        <!-- 遍历子表字段 -->
+                                                        <tr th:each="field, fieldStat : ${subtable.fields}"
+                                                            th:id="'section-' + ${sectionStat.index} + '-subtable-' + ${subtableStat.index} + '-field-' + ${fieldStat.index}">
+                                                            <input type="hidden" th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].fields[' + ${fieldStat.index} + '].id'"
+                                                                   th:value="${field.id}">
+                                                            <td th:text="${fieldStat.count}">1</td>
+                                                            <td>
+                                                                <input th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].fields[' + ${fieldStat.index} + '].field_label'"
+                                                                       class="form-control" type="text" th:value="${field.field_label}" required>
+                                                            </td>
+                                                            <td>
+                                                                <input th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].fields[' + ${fieldStat.index} + '].field_name'"
+                                                                       class="form-control" type="text" th:value="${field.field_name}" required>
+                                                            </td>
+                                                            <td>
+                                                                <input th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].fields[' + ${fieldStat.index} + '].field_code'"
+                                                                       class="form-control" type="text" th:value="${field.field_code}" required>
+                                                            </td>
+                                                            <td>
+                                                                <select th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].fields[' + ${fieldStat.index} + '].field_type'"
+                                                                        class="form-control" required>
+                                                                    <option value="text" th:selected="${field.field_type == 'text'}">文本</option>
+                                                                    <option value="textarea" th:selected="${field.field_type == 'textarea'}">多行文本</option>
+                                                                    <option value="number" th:selected="${field.field_type == 'number'}">数字</option>
+                                                                    <option value="select" th:selected="${field.field_type == 'select'}">下拉框</option>
+                                                                    <option value="date" th:selected="${field.field_type == 'date'}">日期</option>
+                                                                </select>
+                                                            </td>
+                                                            <td>
+                                                                <select th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].fields[' + ${fieldStat.index} + '].is_required'"
+                                                                        class="form-control">
+                                                                    <option value="1" th:selected="${field.is_required == 1}">是</option>
+                                                                    <option value="0" th:selected="${field.is_required == 0}">否</option>
+                                                                </select>
+                                                            </td>
+                                                            <td>
+                                                                <input th:name="'sections[' + ${sectionStat.index} + '].subtables[' + ${subtableStat.index} + '].fields[' + ${fieldStat.index} + '].display_order'"
+                                                                       class="form-control" type="number" th:value="${field.display_order}" required>
+                                                            </td>
+                                                            <td>
+                                                                <button type="button" class="btn btn-info btn-xs edit-subtable-field-btn"
+                                                                        th:onclick="'editSubtableField(' + ${sectionStat.index} + ', ' + ${subtableStat.index} + ', ' + ${fieldStat.index} + ')'">
+                                                                    <i class="fa fa-edit"></i> 编辑
+                                                                </button>
+                                                                <button type="button" class="btn btn-danger btn-xs remove-subtable-field-btn"
+                                                                        th:onclick="'removeSubtableField(' + ${sectionStat.index} + ', ' + ${subtableStat.index} + ', ' + ${fieldStat.index} + ')'">
+                                                                    <i class="fa fa-trash"></i> 删除
+                                                                </button>
+                                                            </td>
+                                                        </tr>
+                                                        </tbody>
+                                                    </table>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+
+                        <button type="button" class="btn btn-success btn-sm add-section-btn" onclick="addSection()">
+                            <i class="fa fa-plus"></i> 添加分组
+                        </button>
+
+                        <div class="form-actions">
+                            <button type="button" class="btn btn-primary" onclick="submitForm()">保存</button>
+                            <button type="button" class="btn btn-default" onclick="$.modal.closeTab()">关闭</button>
+                        </div>
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<th:block th:include="include :: footer" />
+
+<script th:inline="javascript">
+    // 表单提交
+    function submitForm() {
+        if (!$("#form-template-edit").valid()) {
+            return;
+        }
+
+        var formData = new FormData($("#form-template-edit")[0]);
+        var data = {};
+
+        for (const [key, value] of formData.entries()) {
+            // 处理多层级表单字段名
+            var keys = key.split(/\[|]\.|\]|\[/).filter(function(k) { return k !== ""; });
+            var lastKey = keys.pop();
+
+            var obj = data;
+            for (var i = 0; i < keys.length; i++) {
+                var k = keys[i];
+                if (!obj[k]) {
+                    if (isNaN(keys[i + 1]) && keys[i + 1] !== undefined) {
+                        obj[k] = {};
+                    } else {
+                        obj[k] = [];
+                    }
+                }
+                obj = obj[k];
+            }
+
+            obj[lastKey] = value;
+        }
+
+        $.ajax({
+            url: ctx + "form/template/edit",
+            type: "post",
+            dataType: "json",
+            contentType: "application/json",
+            data: JSON.stringify(data),
+            success: function(result) {
+                if (result.code === 0) {
+                    $.modal.alertSuccess("保存成功");
+                    $.modal.close();
+                    $.table.search();
+                } else {
+                    $.modal.alertError(result.msg);
+                }
+            },
+            error: function() {
+                $.modal.alertError("系统错误");
+            }
+        });
+    }
+
+    // 添加分组
+    function addSection() {
+        var sectionIndex = $("#sections-container").children(".form-section").length;
+        var section = `
+            <div class="form-section" id="section-${sectionIndex}">
+                <input type="hidden" name="sections[${sectionIndex}].id" value="">
+                <div class="form-section-header">
+                    <i class="fa fa-bars drag-handle"></i>
+                    <span>新分组</span>
+                    <div class="pull-right">
+                        <button type="button" class="btn btn-danger btn-xs remove-section-btn">
+                            <i class="fa fa-trash"></i> 删除分组
+                        </button>
+                    </div>
+                </div>
+                <div class="form-section-content">
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label">分组名称:</label>
+                        <div class="col-sm-4">
+                            <input name="sections[${sectionIndex}].section_name" class="form-control" type="text" required>
+                        </div>
+                        <label class="col-sm-2 control-label">分组编码:</label>
+                        <div class="col-sm-4">
+                            <input name="sections[${sectionIndex}].section_code" class="form-control" type="text" required>
+                        </div>
+                    </div>
+
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label">分组类型:</label>
+                        <div class="col-sm-4">
+                            <select name="sections[${sectionIndex}].section_type" class="form-control selectpicker">
+                                <option value="normal" selected>普通</option>
+                                <option value="tab">选项卡</option>
+                                <option value="accordion">手风琴</option>
+                            </select>
+                        </div>
+                        <label class="col-sm-2 control-label">显示顺序:</label>
+                        <div class="col-sm-4">
+                            <input name="sections[${sectionIndex}].display_order" class="form-control" type="number" value="${sectionIndex}" required>
+                        </div>
+                    </div>
+
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label">是否可折叠:</label>
+                        <div class="col-sm-4">
+                            <div class="radio-inline">
+                                <input type="radio" name="sections[${sectionIndex}].is_collapsible" value="1"> 是
+                            </div>
+                            <div class="radio-inline">
+                                <input type="radio" name="sections[${sectionIndex}].is_collapsible" value="0" checked> 否
+                            </div>
+                        </div>
+                        <label class="col-sm-2 control-label">是否必填:</label>
+                        <div class="col-sm-4">
+                            <div class="radio-inline">
+                                <input type="radio" name="sections[${sectionIndex}].is_required" value="1"> 是
+                            </div>
+                            <div class="radio-inline">
+                                <input type="radio" name="sections[${sectionIndex}].is_required" value="0" checked> 否
+                            </div>
+                        </div>
+                    </div>
+
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label">显示条件:</label>
+                        <div class="col-sm-10">
+                            <textarea name="sections[${sectionIndex}].visibility_condition" class="form-control" rows="2"></textarea>
+                        </div>
+                    </div>
+
+                    <!-- 字段容器 -->
+                    <div class="fields-container" id="fields-container-${sectionIndex}">
+                        <div class="form-section-header">字段列表</div>
+                        <button type="button" class="btn btn-primary btn-sm add-field-btn" onclick="addField(${sectionIndex})">
+                            <i class="fa fa-plus"></i> 添加字段
+                        </button>
+                    </div>
+
+                    <!-- 子表容器 -->
+                    <div class="subtables-container" id="subtables-container-${sectionIndex}">
+                        <div class="form-section-header">子表列表</div>
+                        <button type="button" class="btn btn-primary btn-sm add-subtable-btn" onclick="addSubtable(${sectionIndex})">
+                            <i class="fa fa-plus"></i> 添加子表
+                        </button>
+                    </div>
+                </div>
+            </div>
+        `;
+
+        $("#sections-container").append(section);
+        $('.selectpicker').selectpicker('refresh');
+        initRemoveButtons();
+    }
+
+    // 添加字段
+    function addField(sectionIndex) {
+        var fieldIndex = $(`#fields-container-${sectionIndex}`).children(".form-field").length;
+        var field = `
+            <div class="form-field" id="section-${sectionIndex}-field-${fieldIndex}">
+                <input type="hidden" name="sections[${sectionIndex}].fields[${fieldIndex}].id" value="">
+                <div class="row">
+                    <div class="col-sm-11">
+                        <div class="field-header">
+                            <i class="fa fa-bars drag-handle"></i>
+                            <span>新字段</span>
+                        </div>
+                    </div>
+                    <div class="col-sm-1 text-right">
+                        <button type="button" class="btn btn-danger btn-xs remove-field-btn" onclick="removeField(${sectionIndex}, ${fieldIndex})">
+                            <i class="fa fa-trash"></i>
+                        </button>
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">字段标签:</label>
+                    <div class="col-sm-4">
+                        <input name="sections[${sectionIndex}].fields[${fieldIndex}].field_label" class="form-control" type="text" required>
+                    </div>
+                    <label class="col-sm-2 control-label">字段名称:</label>
+                    <div class="col-sm-4">
+                        <input name="sections[${sectionIndex}].fields[${fieldIndex}].field_name" class="form-control" type="text" required>
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">字段编码:</label>
+                    <div class="col-sm-4">
+                        <input name="sections[${sectionIndex}].fields[${fieldIndex}].field_code" class="form-control" type="text" required>
+                    </div>
+                    <label class="col-sm-2 control-label">字段类型:</label>
+                    <div class="col-sm-4">
+                        <select name="sections[${sectionIndex}].fields[${fieldIndex}].field_type" class="form-control field-type-selector" onchange="fieldTypeChanged(${sectionIndex}, ${fieldIndex})" required>
+                            <option value="text" selected>文本</option>
+                            <option value="textarea">多行文本</option>
+                            <option value="number">数字</option>
+                            <option value="select">下拉框</option>
+                            <option value="radio">单选</option>
+                            <option value="checkbox">多选</option>
+                            <option value="date">日期</option>
+                            <option value="datetime">日期时间</option>
+                            <option value="file">文件</option>
+                            <option value="image">图片</option>
+                        </select>
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">占位文本:</label>
+                    <div class="col-sm-4">
+                        <input name="sections[${sectionIndex}].fields[${fieldIndex}].placeholder" class="form-control" type="text">
+                    </div>
+                    <label class="col-sm-2 control-label">默认值:</label>
+                    <div class="col-sm-4">
+                        <input name="sections[${sectionIndex}].fields[${fieldIndex}].default_value" class="form-control" type="text">
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">字段宽度:</label>
+                    <div class="col-sm-4">
+                        <input name="sections[${sectionIndex}].fields[${fieldIndex}].field_width" class="form-control" type="text" placeholder="如: 100% 或 300px">
+                    </div>
+                    <label class="col-sm-2 control-label">显示顺序:</label>
+                    <div class="col-sm-4">
+                        <input name="sections[${sectionIndex}].fields[${fieldIndex}].display_order" class="form-control" type="number" value="${fieldIndex}" required>
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">是否必填:</label>
+                    <div class="col-sm-4">
+                        <div class="radio-inline">
+                            <input type="radio" name="sections[${sectionIndex}].fields[${fieldIndex}].is_required" value="1"> 是
+                        </div>
+                        <div class="radio-inline">
+                            <input type="radio" name="sections[${sectionIndex}].fields[${fieldIndex}].is_required" value="0" checked> 否
+                        </div>
+                    </div>
+                    <label class="col-sm-2 control-label">是否可编辑:</label>
+                    <div class="col-sm-4">
+                        <div class="radio-inline">
+                            <input type="radio" name="sections[${sectionIndex}].fields[${fieldIndex}].is_editable" value="1" checked> 是
+                        </div>
+                        <div class="radio-inline">
+                            <input type="radio" name="sections[${sectionIndex}].fields[${fieldIndex}].is_editable" value="0"> 否
+                        </div>
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">验证规则:</label>
+                    <div class="col-sm-10">
+                        <input name="sections[${sectionIndex}].fields[${fieldIndex}].validation_rule" class="form-control" type="text" placeholder="正则表达式">
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">验证提示:</label>
+                    <div class="col-sm-10">
+                        <input name="sections[${sectionIndex}].fields[${fieldIndex}].validation_message" class="form-control" type="text">
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">帮助文本:</label>
+                    <div class="col-sm-10">
+                        <textarea name="sections[${sectionIndex}].fields[${fieldIndex}].help_text" class="form-control" rows="2"></textarea>
+                    </div>
+                </div>
+            </div>
+        `;
+
+        $(`#fields-container-${sectionIndex}`).append(field);
+    }
+
+    // 字段类型变更
+    function fieldTypeChanged(sectionIndex, fieldIndex) {
+        var fieldType = $(`[name="sections[${sectionIndex}].fields[${fieldIndex}].field_type"]`).val();
+
+        // 移除旧的选项容器
+        $(`#options-container-${sectionIndex}-${fieldIndex}`).remove();
+
+        // 如果是select、radio或checkbox类型,添加选项容器
+        if (fieldType === 'select' || fieldType === 'radio' || fieldType === 'checkbox') {
+            var optionsContainer = `
+                <div class="field-options-container" id="options-container-${sectionIndex}-${fieldIndex}">
+                    <h5>选项列表</h5>
+                    <button type="button" class="btn btn-info btn-xs add-option-btn" onclick="addOption(${sectionIndex}, ${fieldIndex})">
+                        <i class="fa fa-plus"></i> 添加选项
+                    </button>
+
+                    <div class="options-list" id="options-list-${sectionIndex}-${fieldIndex}">
+                    </div>
+                </div>
+            `;
+
+            $(`#section-${sectionIndex}-field-${fieldIndex}`).append(optionsContainer);
+        }
+    }
+
+    // 添加选项
+    function addOption(sectionIndex, fieldIndex) {
+        var optionIndex = $(`#options-list-${sectionIndex}-${fieldIndex}`).children(".field-option").length;
+        var option = `
+            <div class="field-option" id="section-${sectionIndex}-field-${fieldIndex}-option-${optionIndex}">
+                <input type="hidden" name="sections[${sectionIndex}].fields[${fieldIndex}].options[${optionIndex}].id" value="">
+
+                <div class="row">
+                    <div class="col-sm-3">
+                        <div class="input-group">
+                            <span class="input-group-addon">标签</span>
+                            <input name="sections[${sectionIndex}].fields[${fieldIndex}].options[${optionIndex}].option_label" class="form-control" type="text" required>
+                        </div>
+                    </div>
+                    <div class="col-sm-3">
+                        <div class="input-group">
+                            <span class="input-group-addon">值</span>
+                            <input name="sections[${sectionIndex}].fields[${fieldIndex}].options[${optionIndex}].option_value" class="form-control" type="text" required>
+                        </div>
+                    </div>
+                    <div class="col-sm-2">
+                        <div class="input-group">
+                            <span class="input-group-addon">编码</span>
+                            <input name="sections[${sectionIndex}].fields[${fieldIndex}].options[${optionIndex}].option_code" class="form-control" type="text" required>
+                        </div>
+                    </div>
+                    <div class="col-sm-2">
+                        <div class="input-group">
+                            <span class="input-group-addon">顺序</span>
+                            <input name="sections[${sectionIndex}].fields[${fieldIndex}].options[${optionIndex}].display_order" class="form-control" type="number" value="${optionIndex}" required>
+                        </div>
+                    </div>
+                    <div class="col-sm-1">
+                        <div class="checkbox">
+                            <label>
+                                <input type="checkbox" name="sections[${sectionIndex}].fields[${fieldIndex}].options[${optionIndex}].is_default" value="1"> 默认
+                            </label>
+                        </div>
+                    </div>
+                    <div class="col-sm-1">
+                        <button type="button" class="btn btn-danger btn-xs remove-option-btn" onclick="removeOption(${sectionIndex}, ${fieldIndex}, ${optionIndex})">
+                            <i class="fa fa-trash"></i>
+                        </button>
+                    </div>
+                </div>
+            </div>
+        `;
+
+        $(`#options-list-${sectionIndex}-${fieldIndex}`).append(option);
+    }
+
+    // 添加子表
+    function addSubtable(sectionIndex) {
+        var subtableIndex = $(`#subtables-container-${sectionIndex}`).children(".form-subtable").length;
+        var subtable = `
+            <div class="form-subtable" id="section-${sectionIndex}-subtable-${subtableIndex}">
+                <input type="hidden" name="sections[${sectionIndex}].subtables[${subtableIndex}].id" value="">
+                <div class="row">
+                    <div class="col-sm-11">
+                        <div class="subtable-header">
+                            <i class="fa fa-bars drag-handle"></i>
+                            <span>新子表</span>
+                        </div>
+                    </div>
+                    <div class="col-sm-1 text-right">
+                        <button type="button" class="btn btn-danger btn-xs remove-subtable-btn" onclick="removeSubtable(${sectionIndex}, ${subtableIndex})">
+                            <i class="fa fa-trash"></i>
+                        </button>
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">子表名称:</label>
+                    <div class="col-sm-4">
+                        <input name="sections[${sectionIndex}].subtables[${subtableIndex}].subtable_name" class="form-control" type="text" required>
+                    </div>
+                    <label class="col-sm-2 control-label">子表编码:</label>
+                    <div class="col-sm-4">
+                        <input name="sections[${sectionIndex}].subtables[${subtableIndex}].subtable_code" class="form-control" type="text" required>
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">显示顺序:</label>
+                    <div class="col-sm-4">
+                        <input name="sections[${sectionIndex}].subtables[${subtableIndex}].display_order" class="form-control" type="number" value="${subtableIndex}" required>
+                    </div>
+                    <label class="col-sm-2 control-label">添加按钮文本:</label>
+                    <div class="col-sm-4">
+                        <input name="sections[${sectionIndex}].subtables[${subtableIndex}].add_button_text" class="form-control" type="text" placeholder="默认为'新增'">
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">是否必填:</label>
+                    <div class="col-sm-4">
+                        <div class="radio-inline">
+                            <input type="radio" name="sections[${sectionIndex}].subtables[${subtableIndex}].is_required" value="1"> 是
+                        </div>
+                        <div class="radio-inline">
+                            <input type="radio" name="sections[${sectionIndex}].subtables[${subtableIndex}].is_required" value="0" checked> 否
+                        </div>
+                    </div>
+                    <label class="col-sm-2 control-label">状态:</label>
+                    <div class="col-sm-4">
+                        <div class="radio-inline">
+                            <input type="radio" name="sections[${sectionIndex}].subtables[${subtableIndex}].status" value="1" checked> 启用
+                        </div>
+                        <div class="radio-inline">
+                            <input type="radio" name="sections[${sectionIndex}].subtables[${subtableIndex}].status" value="0"> 停用
+                        </div>
+                    </div>
+                </div>
+
+                <div class="form-group">
+                    <label class="col-sm-2 control-label">最少行数:</label>
+                    <div class="col-sm-4">
+                        <input name="sections[${sectionIndex}].subtables[${subtableIndex}].min_rows" class="form-control" type="number" value="0" min="0">
+                    </div>
+                    <label class="col-sm-2 control-label">最多行数:</label>
+                    <div class="col-sm-4">
+                        <input name="sections[${sectionIndex}].subtables[${subtableIndex}].max_rows" class="form-control" type="number" min="0">
+                    </div>
+                </div>
+
+                <!-- 子表字段 -->
+                <div class="subtable-fields-container" id="subtable-fields-container-${sectionIndex}-${subtableIndex}">
+                    <h5>子表字段</h5>
+                    <button type="button" class="btn btn-info btn-xs add-subtable-field-btn" onclick="addSubtableField(${sectionIndex}, ${subtableIndex})">
+                        <i class="fa fa-plus"></i> 添加字段
+                    </button>
+
+                    <div class="table-responsive">
+                        <table class="table table-bordered">
+                            <thead>
+                                <tr>
+                                    <th width="5%">序号</th>
+                                    <th width="15%">字段标签</th>
+                                    <th width="15%">字段名称</th>
+                                    <th width="10%">字段编码</th>
+                                    <th width="10%">字段类型</th>
+                                    <th width="10%">必填</th>
+                                    <th width="10%">顺序</th>
+                                    <th width="15%">操作</th>
+                                </tr>
+                            </thead>
+                            <tbody id="subtable-fields-list-${sectionIndex}-${subtableIndex}">
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        `;
+
+        $(`#subtables-container-${sectionIndex}`).append(subtable);
+    }
+
+    // 添加子表字段
+    function addSubtableField(sectionIndex, subtableIndex) {
+        var fieldIndex = $(`#subtable-fields-list-${sectionIndex}-${subtableIndex}`).children("tr").length;
+        var field = `
+            <tr id="section-${sectionIndex}-subtable-${subtableIndex}-field-${fieldIndex}">
+                <input type="hidden" name="sections[${sectionIndex}].subtables[${subtableIndex}].fields[${fieldIndex}].id" value="">
+                <td>${fieldIndex + 1}</td>
+                <td>
+                    <input name="sections[${sectionIndex}].subtables[${subtableIndex}].fields[${fieldIndex}].field_label" class="form-control" type="text" required>
+                </td>
+                <td>
+                    <input name="sections[${sectionIndex}].subtables[${subtableIndex}].fields[${fieldIndex}].field_name" class="form-control" type="text" required>
+                </td>
+                <td>
+                    <input name="sections[${sectionIndex}].subtables[${subtableIndex}].fields[${fieldIndex}].field_code" class="form-control" type="text" required>
+                </td>
+                <td>
+                    <select name="sections[${sectionIndex}].subtables[${subtableIndex}].fields[${fieldIndex}].field_type" class="form-control" required>
+                        <option value="text" selected>文本</option>
+                        <option value="textarea">多行文本</option>
+                        <option value="number">数字</option>
+                        <option value="select">下拉框</option>
+                        <option value="date">日期</option>
+                    </select>
+                </td>
+                <td>
+                    <select name="sections[${sectionIndex}].subtables[${subtableIndex}].fields[${fieldIndex}].is_required" class="form-control">
+                        <option value="1">是</option>
+                        <option value="0" selected>否</option>
+                    </select>
+                </td>
+                <td>
+                    <input name="sections[${sectionIndex}].subtables[${subtableIndex}].fields[${fieldIndex}].display_order" class="form-control" type="number" value="${fieldIndex}" required>
+                </td>
+                <td>
+                    <button type="button" class="btn btn-info btn-xs edit-subtable-field-btn" onclick="editSubtableField(${sectionIndex}, ${subtableIndex}, ${fieldIndex})">
+                        <i class="fa fa-edit"></i> 编辑
+                    </button>
+                    <button type="button" class="btn btn-danger btn-xs remove-subtable-field-btn" onclick="removeSubtableField(${sectionIndex}, ${subtableIndex}, ${fieldIndex})">
+                        <i class="fa fa-trash"></i> 删除
+                    </button>
+                </td>
+            </tr>
+        `;
+
+        $(`#subtable-fields-list-${sectionIndex}-${subtableIndex}`).append(field);
+    }
+
+    // 编辑子表字段(打开详细编辑页面)
+    function editSubtableField(sectionIndex, subtableIndex, fieldIndex) {
+        var fieldData = {
+            sectionIndex: sectionIndex,
+            subtableIndex: subtableIndex,
+            fieldIndex: fieldIndex
+        };
+
+        $.modal.open("编辑子表字段", ctx + "form/template/editSubtableField", '800', '600', fieldData);
+    }
+
+    // 移除分组
+    function removeSection(sectionIndex) {
+        $(`#section-${sectionIndex}`).remove();
+    }
+
+    // 移除字段
+    function removeField(sectionIndex, fieldIndex) {
+        $(`#section-${sectionIndex}-field-${fieldIndex}`).remove();
+    }
+
+    // 移除选项
+    function removeOption(sectionIndex, fieldIndex, optionIndex) {
+        $(`#section-${sectionIndex}-field-${fieldIndex}-option-${optionIndex}`).remove();
+    }
+
+    // 移除子表
+    function removeSubtable(sectionIndex, subtableIndex) {
+        $(`#section-${sectionIndex}-subtable-${subtableIndex}`).remove();
+    }
+
+    // 移除子表字段
+    function removeSubtableField(sectionIndex, subtableIndex, fieldIndex) {
+        $(`#section-${sectionIndex}-subtable-${subtableIndex}-field-${fieldIndex}`).remove();
+    }
+
+    // 初始化删除按钮
+    function initRemoveButtons() {
+        $(".remove-section-btn").off("click").on("click", function() {
+            $(this).closest(".form-section").remove();
+        });
+    }
+
+    $(function() {
+        // 初始化下拉框
+        $('.selectpicker').selectpicker();
+
+        // 初始化删除按钮
+        initRemoveButtons();
+
+        // 初始化表单验证
+        $("#form-template-edit").validate({
+            rules: {
+                template_name: {
+                    required: true
+                },
+                template_code: {
+                    required: true
+                },
+                template_type: {
+                    required: true
+                },
+                version: {
+                    required: true
+                }
+            },
+            messages: {
+                template_name: {
+                    required: "请输入模板名称"
+                },
+                template_code: {
+                    required: "请输入模板编码"
+                },
+                template_type: {
+                    required: "请选择模板类型"
+                },
+                version: {
+                    required: "请输入模板版本"
+                }
+            }
+        });
+    });
+</script>
+</body>
+</html>

+ 1183 - 0
health-admin/src/main/resources/templates/gxhpz/formTemplateEdit.html

@@ -0,0 +1,1183 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org">
+<head>
+    <th:block th:include="include :: header('编辑表单模板')" />
+    <th:block th:include="include :: bootstrap-select-css" />
+    <th:block th:include="include :: bootstrap-select-js" />
+    <th:block th:include="include :: bootstrap-fileinput-css" />
+    <th:block th:include="include :: bootstrap-fileinput-js" />
+    <th:block th:include="include :: datetimepicker-css" />
+    <th:block th:include="include :: datetimepicker-js" />
+    <th:block th:include="include :: summernote-css" />
+    <th:block th:include="include :: summernote-js"/>
+    <style>
+        .form-designer-container {
+            display: flex;
+            min-height: 600px;
+        }
+        .components-panel {
+            width: 250px;
+            border-right: 1px solid #ddd;
+            padding: 10px;
+            background-color: #f8f8f8;
+        }
+        .form-canvas {
+            flex: 1;
+            padding: 20px;
+            background-color: #ffffff;
+            overflow: auto;
+        }
+        .property-panel {
+            width: 300px;
+            border-left: 1px solid #ddd;
+            padding: 10px;
+            background-color: #f8f8f8;
+        }
+        .component-item {
+            border: 1px solid #ddd;
+            margin-bottom: 10px;
+            padding: 8px;
+            background-color: #fff;
+            cursor: move;
+            border-radius: 4px;
+        }
+        .component-item:hover {
+            background-color: #f0f0f0;
+        }
+        .section-item {
+            border: 1px dashed #ccc;
+            margin-bottom: 15px;
+            padding: 15px;
+            background-color: #fafafa;
+            position: relative;
+        }
+        .section-header {
+            font-weight: bold;
+            margin-bottom: 10px;
+            padding-bottom: 5px;
+            border-bottom: 1px solid #eee;
+            display: flex;
+            justify-content: space-between;
+        }
+        .field-item {
+            border: 1px solid #eee;
+            margin-bottom: 10px;
+            padding: 10px;
+            background-color: #fff;
+            position: relative;
+        }
+        .field-actions {
+            position: absolute;
+            right: 5px;
+            top: 5px;
+        }
+        .section-actions {
+            display: flex;
+            gap: 5px;
+        }
+        .subtable-item {
+            border: 1px solid #ddd;
+            margin-top: 15px;
+            padding: 10px;
+            background-color: #f5f5f5;
+        }
+        .subtable-header {
+            font-weight: bold;
+            margin-bottom: 10px;
+            padding-bottom: 5px;
+            border-bottom: 1px solid #eee;
+            display: flex;
+            justify-content: space-between;
+        }
+        .subtable-columns {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 10px;
+            padding: 10px;
+            background-color: #ffffff;
+        }
+        .property-group {
+            margin-bottom: 15px;
+            border: 1px solid #eee;
+            border-radius: 4px;
+            overflow: hidden;
+        }
+        .property-group-title {
+            background-color: #f0f0f0;
+            padding: 8px;
+            font-weight: bold;
+        }
+        .property-group-content {
+            padding: 10px;
+        }
+        .property-row {
+            margin-bottom: 10px;
+        }
+        .handle {
+            cursor: move;
+            color: #999;
+            margin-right: 5px;
+        }
+        .form-canvas {
+            min-height: 400px;
+        }
+        .placeholder {
+            border: 2px dashed #ccc;
+            height: 50px;
+            margin: 10px 0;
+            background-color: #f9f9f9;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            color: #999;
+        }
+        .ui-sortable-helper {
+            z-index: 9999;
+        }
+        .section-item.selected, .field-item.selected, .subtable-item.selected {
+            border: 2px solid #1890ff;
+            box-shadow: 0 0 5px rgba(24, 144, 255, 0.3);
+        }
+        .field-options-container {
+            margin-top: 10px;
+            padding: 8px;
+            background-color: #f9f9f9;
+            border: 1px solid #eee;
+        }
+    </style>
+</head>
+<body class="white-bg">
+<div class="wrapper wrapper-content animated fadeInRight ibox-content">
+    <form class="form-horizontal m" id="form-template-edit">
+        <input type="hidden" id="templateId" name="id" th:value="${id}">
+        <div class="form-group">
+            <label class="col-sm-2 control-label">模板名称:</label>
+            <div class="col-sm-4">
+                <input class="form-control" type="text" name="template_name" th:value="${template_name}">
+            </div>
+            <label class="col-sm-2 control-label">模板版本:</label>
+            <div class="col-sm-4">
+                <input class="form-control" type="text" name="version" th:value="${version}">
+            </div>
+        </div>
+
+        <div class="form-group">
+            <label class="col-sm-2 control-label">模板编码:</label>
+            <div class="col-sm-4">
+                <input class="form-control" type="text" name="template_code" th:value="${template_code}">
+            </div>
+            <label class="col-sm-2 control-label">模板类型:</label>
+            <div class="col-sm-4">
+                <select name="template_type" class="form-control selectpicker">
+                    <option value="常规随访" th:selected="${template_type == '常规随访'}">常规随访</option>
+                    <option value="脱落召回" th:selected="${template_type == '脱落召回'}">脱落召回</option>
+                    <option value="其他" th:selected="${template_type == '其他'}">其他</option>
+                </select>
+            </div>
+        </div>
+
+        <div class="form-group">
+            <label class="col-sm-2 control-label">模板描述:</label>
+            <div class="col-sm-10">
+                <textarea name="description" class="form-control" rows="2" th:text="${description}"></textarea>
+            </div>
+        </div>
+
+        <div class="form-designer-container">
+            <!-- 组件面板 -->
+            <div class="components-panel">
+                <h4>组件</h4>
+                <div class="panel-group" id="componentAccordion">
+                    <div class="panel panel-default">
+                        <div class="panel-heading">
+                            <h4 class="panel-title">
+                                <a data-toggle="collapse" data-parent="#componentAccordion" href="#layoutComponents">
+                                    布局组件
+                                </a>
+                            </h4>
+                        </div>
+                        <div id="layoutComponents" class="panel-collapse collapse in">
+                            <div class="panel-body">
+                                <div class="component-item" data-type="section">
+                                    <i class="fa fa-columns"></i> 分组
+                                </div>
+                                <div class="component-item" data-type="subtable">
+                                    <i class="fa fa-table"></i> 子表
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="panel panel-default">
+                        <div class="panel-heading">
+                            <h4 class="panel-title">
+                                <a data-toggle="collapse" data-parent="#componentAccordion" href="#basicComponents">
+                                    基础组件
+                                </a>
+                            </h4>
+                        </div>
+                        <div id="basicComponents" class="panel-collapse collapse in">
+                            <div class="panel-body">
+                                <div class="component-item" data-type="text">
+                                    <i class="fa fa-font"></i> 单行文本
+                                </div>
+                                <div class="component-item" data-type="textarea">
+                                    <i class="fa fa-align-left"></i> 多行文本
+                                </div>
+                                <div class="component-item" data-type="number">
+                                    <i class="fa fa-calculator"></i> 数字
+                                </div>
+                                <div class="component-item" data-type="select">
+                                    <i class="fa fa-list"></i> 下拉选择
+                                </div>
+                                <div class="component-item" data-type="radio">
+                                    <i class="fa fa-dot-circle-o"></i> 单选按钮
+                                </div>
+                                <div class="component-item" data-type="checkbox">
+                                    <i class="fa fa-check-square-o"></i> 复选框
+                                </div>
+                                <div class="component-item" data-type="date">
+                                    <i class="fa fa-calendar"></i> 日期
+                                </div>
+                                <div class="component-item" data-type="datetime">
+                                    <i class="fa fa-clock-o"></i> 日期时间
+                                </div>
+                                <div class="component-item" data-type="file">
+                                    <i class="fa fa-file"></i> 文件上传
+                                </div>
+                                <div class="component-item" data-type="image">
+                                    <i class="fa fa-picture-o"></i> 图片上传
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 表单设计画布 -->
+            <div class="form-canvas" id="formCanvas">
+                <div id="formSections">
+                    <!-- 表单分组和字段将在这里动态加载 -->
+                </div>
+            </div>
+
+            <!-- 属性面板 -->
+            <div class="property-panel" id="propertyPanel">
+                <h4>属性</h4>
+                <div class="alert alert-warning">
+                    选择表单元素来编辑其属性
+                </div>
+                <!-- 属性编辑表单将在这里动态创建 -->
+            </div>
+        </div>
+
+        <div class="row">
+            <div class="col-sm-offset-5 col-sm-10 mt20">
+                <button type="button" class="btn btn-sm btn-primary" onclick="submitForm()"><i class="fa fa-check"></i>保 存</button>&nbsp;
+                <button type="button" class="btn btn-sm btn-danger" onclick="$.modal.closeTab()"><i class="fa fa-close"></i>关 闭</button>
+            </div>
+        </div>
+    </form>
+</div>
+<th:block th:include="include :: footer" />
+
+<script th:inline="javascript">
+    var prefix = ctx + "form/template";
+    /*<![CDATA[*/
+    var formData = /*[[${formData}]]*/ '';
+    /*]]>*/
+    debugger;
+    console.log(formData);
+    var formEditor = {
+        currentSelectedElement: null,
+        sectionCounter: 0,
+        fieldCounter: 0,
+        subtableCounter: 0,
+
+        init: function() {
+            // 初始化拖拽功能
+            this.initDragAndDrop();
+
+            // 加载已有表单数据
+            this.loadExistingForm();
+
+            // 绑定事件
+            this.bindEvents();
+        },
+
+        initDragAndDrop: function() {
+            // 使组件可拖拽
+            $(".component-item").draggable({
+                helper: "clone",
+                connectToSortable: "#formSections"
+            });
+
+            // 使画布可接收拖拽
+            $("#formSections").sortable({
+                placeholder: "placeholder",
+                handle: ".section-drag-handle",
+                update: function(event, ui) {
+                    if (ui.item.hasClass('component-item')) {
+                        var type = ui.item.data('type');
+                        if (type === 'section') {
+                            var sectionHtml = formEditor.createSection();
+                            ui.item.replaceWith(sectionHtml);
+                        } else {
+                            ui.item.remove();
+                        }
+                    }
+                }
+            });
+        },
+
+        loadExistingForm: function() {
+            // 清空画布
+            $("#formSections").empty();
+
+            // 如果有表单数据,加载显示
+            if (formData && formData.sections) {
+                this.sectionCounter = 0;
+                this.fieldCounter = 0;
+                this.subtableCounter = 0;
+
+                // 加载分组
+                $.each(formData.sections, function(i, section) {
+                    formEditor.sectionCounter++;
+                    var sectionId = "section_" + formEditor.sectionCounter;
+
+                    var sectionHtml = '<div class="section-item" data-id="' + sectionId + '" data-type="section" ' +
+                        'data-section-code="' + section.section_code + '" ' +
+                        'data-collapsible="' + (section.is_collapsible === 1 ? "1" : "0") + '" ' +
+                        'data-required="' + (section.is_required === 1 ? "1" : "0") + '">' +
+                        '<div class="section-header">' +
+                        '<span><i class="fa fa-bars section-drag-handle handle"></i> ' + section.section_name + '</span>' +
+                        '<div class="section-actions">' +
+                        '<button type="button" class="btn btn-primary btn-xs btn-add-field"><i class="fa fa-plus"></i> 添加字段</button> ' +
+                        '<button type="button" class="btn btn-info btn-xs btn-add-subtable"><i class="fa fa-plus"></i> 添加子表</button> ' +
+                        '<button type="button" class="btn btn-danger btn-xs btn-delete-section"><i class="fa fa-trash"></i></button>' +
+                        '</div>' +
+                        '</div>' +
+                        '<div class="section-fields" data-section-id="' + sectionId + '"></div>' +
+                        '<div class="section-subtables" data-section-id="' + sectionId + '"></div>' +
+                        '</div>';
+
+                    $("#formSections").append(sectionHtml);
+
+                    // 加载字段
+                    if (section.fields && section.fields.length > 0) {
+                        $.each(section.fields, function(j, field) {
+                            formEditor.fieldCounter++;
+                            var fieldId = "field_" + formEditor.fieldCounter;
+
+                            var fieldType = field.field_type || 'text';
+                            var fieldTypeIcon = '';
+
+                            // 根据字段类型选择图标
+                            switch(fieldType) {
+                                case 'text': fieldTypeIcon = 'fa-font'; break;
+                                case 'textarea': fieldTypeIcon = 'fa-align-left'; break;
+                                case 'number': fieldTypeIcon = 'fa-calculator'; break;
+                                case 'select': fieldTypeIcon = 'fa-list'; break;
+                                case 'radio': fieldTypeIcon = 'fa-dot-circle-o'; break;
+                                case 'checkbox': fieldTypeIcon = 'fa-check-square-o'; break;
+                                case 'date': fieldTypeIcon = 'fa-calendar'; break;
+                                case 'datetime': fieldTypeIcon = 'fa-clock-o'; break;
+                                case 'file': fieldTypeIcon = 'fa-file'; break;
+                                case 'image': fieldTypeIcon = 'fa-picture-o'; break;
+                                default: fieldTypeIcon = 'fa-font';
+                            }
+
+                            var fieldHtml = '<div class="field-item" data-id="' + fieldId + '" data-section-id="' + sectionId + '" data-type="field" ' +
+                                'data-field-id="' + field.id + '" ' +
+                                'data-field-code="' + field.field_code + '" ' +
+                                'data-field-name="' + field.field_name + '" ' +
+                                'data-field-type="' + fieldType + '" ' +
+                                'data-required="' + (field.is_required === 1 ? "1" : "0") + '" ' +
+                                'data-placeholder="' + (field.placeholder || "") + '" ' +
+                                'data-default="' + (field.default_value || "") + '" ' +
+                                'data-help="' + (field.help_text || "") + '" ' +
+                                'data-width="' + (field.field_width || "100%") + '" ' +
+                                'data-validation="' + (field.validation_rule || "") + '" ' +
+                                'data-validation-message="' + (field.validation_message || "") + '" ' +
+                                'data-dict="' + (field.dict_type || "") + '">' +
+                                '<i class="fa ' + fieldTypeIcon + ' field-drag-handle handle"></i> ' +
+                                '<span class="field-label">' + field.field_label + '</span>' +
+                                '<div class="field-actions">' +
+                                '<button type="button" class="btn btn-danger btn-xs btn-delete-field"><i class="fa fa-trash"></i></button>' +
+                                '</div>';
+
+                            // 如果有选项,保存选项数据
+                            if (field.options && field.options.length > 0) {
+                                var optionsData = [];
+                                $.each(field.options, function(k, option) {
+                                    optionsData.push({
+                                        id: option.id || "",
+                                        optionCode: option.option_code || "",
+                                        optionLabel: option.option_label || "",
+                                        optionValue: option.option_value || "",
+                                        isDefault: option.is_default === 1
+                                    });
+                                });
+                                fieldHtml += '<input type="hidden" class="field-options-data" value=\'' + JSON.stringify(optionsData) + '\'>';
+                            }
+
+                            fieldHtml += '</div>';
+
+                            $(".section-fields[data-section-id='" + sectionId + "']").append(fieldHtml);
+                        });
+                    }
+
+                    // 加载子表
+                    if (section.subtables && section.subtables.length > 0) {
+                        $.each(section.subtables, function(j, subtable) {
+                            formEditor.subtableCounter++;
+                            var subtableId = "subtable_" + formEditor.subtableCounter;
+
+                            var subtableHtml = '<div class="subtable-item" data-id="' + subtableId + '" data-section-id="' + sectionId + '" data-type="subtable" ' +
+                                'data-subtable-id="' + subtable.id + '" ' +
+                                'data-subtable-code="' + subtable.subtable_code + '" ' +
+                                'data-required="' + (subtable.is_required === 1 ? "1" : "0") + '" ' +
+                                'data-add-button-text="' + (subtable.add_button_text || "新增") + '" ' +
+                                'data-min-rows="' + (subtable.min_rows || "0") + '" ' +
+                                'data-max-rows="' + (subtable.max_rows || "") + '">' +
+                                '<div class="subtable-header">' +
+                                '<span>' + subtable.subtable_name + '</span>' +
+                                '<div class="subtable-actions">' +
+                                '<button type="button" class="btn btn-primary btn-xs btn-add-subtable-field"><i class="fa fa-plus"></i> 添加列</button> ' +
+                                '<button type="button" class="btn btn-danger btn-xs btn-delete-subtable"><i class="fa fa-trash"></i></button>' +
+                                '</div>' +
+                                '</div>' +
+                                '<div class="subtable-columns">';
+
+                            // 添加固定列
+                            subtableHtml += '<div class="subtable-column">序号</div>';
+
+                            // 添加字段列
+                            if (subtable.fields && subtable.fields.length > 0) {
+                                var fieldsData = [];
+                                $.each(subtable.fields, function(k, field) {
+                                    fieldsData.push({
+                                        id: field.id || "",
+                                        fieldCode: field.field_code || "",
+                                        fieldName: field.field_name || "",
+                                        fieldType: field.field_type || "text",
+                                        fieldLabel: field.field_label || "",
+                                        isRequired: field.is_required === 1,
+                                        displayOrder: field.display_order || 0
+                                    });
+
+                                    subtableHtml += '<div class="subtable-column" data-field-id="' + field.id + '">' + field.field_label + '</div>';
+                                });
+
+                                // 保存字段数据
+                                subtableHtml += '<input type="hidden" class="subtable-fields-data" value=\'' + JSON.stringify(fieldsData) + '\'>';
+                            }
+
+                            // 添加操作列
+                            subtableHtml += '<div class="subtable-column">操作</div>';
+
+                            subtableHtml += '</div></div>';
+
+                            $(".section-subtables[data-section-id='" + sectionId + "']").append(subtableHtml);
+                        });
+                    }
+                });
+            } else {
+                // 如果没有数据,显示提示
+                $("#formSections").html('<div class="alert alert-info">拖拽左侧组件到这里来设计表单</div>');
+            }
+
+            // 初始化字段排序
+            this.initSortable();
+        },
+
+        initSortable: function() {
+            // 使分组内的字段可排序
+            $(".section-fields").sortable({
+                handle: ".field-drag-handle",
+                placeholder: "placeholder",
+                connectWith: ".section-fields"
+            });
+
+            // 使子表内的列可排序
+            $(".subtable-columns").sortable({
+                items: ".subtable-column:not(:first-child):not(:last-child)",
+                placeholder: "placeholder"
+            });
+        },
+
+        bindEvents: function() {
+            // 绑定组件点击事件
+            $(document).on('click', '.section-item, .field-item, .subtable-item', function(e) {
+                e.stopPropagation();
+
+                // 先移除所有已选中的元素
+                $('.section-item, .field-item, .subtable-item').removeClass('selected');
+                $(this).addClass('selected');
+
+                // 更新当前选中的元素
+                formEditor.currentSelectedElement = $(this);
+
+                // 显示属性面板
+                formEditor.showProperties($(this));
+            });
+
+            // 绑定添加字段按钮事件
+            $(document).on('click', '.btn-add-field', function() {
+                var sectionId = $(this).closest('.section-item').data('id');
+                var fieldHtml = formEditor.createField(sectionId);
+                $(this).closest('.section-item').find('.section-fields').append(fieldHtml);
+            });
+
+            // 绑定添加子表按钮事件
+            $(document).on('click', '.btn-add-subtable', function() {
+                var sectionId = $(this).closest('.section-item').data('id');
+                var subtableHtml = formEditor.createSubtable(sectionId);
+                $(this).closest('.section-item').find('.section-subtables').append(subtableHtml);
+            });
+
+            // 绑定删除分组按钮事件
+            $(document).on('click', '.btn-delete-section', function() {
+                $(this).closest('.section-item').remove();
+                $('#propertyPanel').html('<h4>属性</h4><div class="alert alert-warning">选择表单元素来编辑其属性</div>');
+            });
+
+            // 绑定删除字段按钮事件
+            $(document).on('click', '.btn-delete-field', function() {
+                $(this).closest('.field-item').remove();
+                $('#propertyPanel').html('<h4>属性</h4><div class="alert alert-warning">选择表单元素来编辑其属性</div>');
+            });
+
+            // 绑定删除子表按钮事件
+            $(document).on('click', '.btn-delete-subtable', function() {
+                $(this).closest('.subtable-item').remove();
+                $('#propertyPanel').html('<h4>属性</h4><div class="alert alert-warning">选择表单元素来编辑其属性</div>');
+            });
+
+            // 绑定添加子表字段按钮事件
+            $(document).on('click', '.btn-add-subtable-field', function() {
+                formEditor.addSubtableField($(this).closest('.subtable-item'));
+            });
+
+            // 绑定画布点击事件,取消选中
+            $(document).on('click', '#formCanvas', function(e) {
+                if ($(e.target).is('#formCanvas') || $(e.target).is('#formSections')) {
+                    $('.section-item, .field-item, .subtable-item').removeClass('selected');
+                    formEditor.currentSelectedElement = null;
+                    $('#propertyPanel').html('<h4>属性</h4><div class="alert alert-warning">选择表单元素来编辑其属性</div>');
+                }
+            });
+        },
+
+        createSection: function() {
+            this.sectionCounter++;
+            var sectionId = "section_" + this.sectionCounter;
+
+            var html = '<div class="section-item" data-id="' + sectionId + '" data-type="section">' +
+                '<div class="section-header">' +
+                '<span><i class="fa fa-bars section-drag-handle handle"></i> 分组 ' + this.sectionCounter + '</span>' +
+                '<div class="section-actions">' +
+                '<button type="button" class="btn btn-primary btn-xs btn-add-field"><i class="fa fa-plus"></i> 添加字段</button> ' +
+                '<button type="button" class="btn btn-info btn-xs btn-add-subtable"><i class="fa fa-plus"></i> 添加子表</button> ' +
+                '<button type="button" class="btn btn-danger btn-xs btn-delete-section"><i class="fa fa-trash"></i></button>' +
+                '</div>' +
+                '</div>' +
+                '<div class="section-fields" data-section-id="' + sectionId + '"></div>' +
+                '<div class="section-subtables" data-section-id="' + sectionId + '"></div>' +
+                '</div>';
+
+            return html;
+        },
+
+        createField: function(sectionId) {
+            this.fieldCounter++;
+            var fieldId = "field_" + this.fieldCounter;
+
+            var html = '<div class="field-item" data-id="' + fieldId + '" data-section-id="' + sectionId + '" data-type="field" data-field-type="text">' +
+                '<i class="fa fa-font field-drag-handle handle"></i> <span class="field-label">字段 ' + this.fieldCounter + '</span>' +
+                '<div class="field-actions">' +
+                '<button type="button" class="btn btn-danger btn-xs btn-delete-field"><i class="fa fa-trash"></i></button>' +
+                '</div>' +
+                '</div>';
+
+            return html;
+        },
+
+        createSubtable: function(sectionId) {
+            this.subtableCounter++;
+            var subtableId = "subtable_" + this.subtableCounter;
+
+            var html = '<div class="subtable-item" data-id="' + subtableId + '" data-section-id="' + sectionId + '" data-type="subtable">' +
+                '<div class="subtable-header">' +
+                '<span>子表 ' + this.subtableCounter + '</span>' +
+                '<div class="subtable-actions">' +
+                '<button type="button" class="btn btn-primary btn-xs btn-add-subtable-field"><i class="fa fa-plus"></i> 添加列</button> ' +
+                '<button type="button" class="btn btn-danger btn-xs btn-delete-subtable"><i class="fa fa-trash"></i></button>' +
+                '</div>' +
+                '</div>' +
+                '<div class="subtable-columns">' +
+                '<div class="subtable-column">序号</div>' +
+                '<div class="subtable-column">操作</div>' +
+                '</div>' +
+                '</div>';
+
+            return html;
+        },
+
+        addSubtableField: function(subtable) {
+            // 打开字段添加对话框
+            var subtableId = subtable.data('id');
+
+            // 这里可以使用modal弹窗来添加字段,或者直接添加一个默认字段
+            // 简单实现:直接添加一个默认列
+            var columnCount = subtable.find('.subtable-column').length - 2; // 减去序号和操作列
+            var newColumn = $('<div class="subtable-column">新列 ' + (columnCount + 1) + '</div>');
+
+            // 插入到操作列之前
+            subtable.find('.subtable-columns').children().last().before(newColumn);
+
+            // 选中这个字段以便编辑属性
+            newColumn.trigger('click');
+        },
+
+        showProperties: function(element) {
+            var type = element.data('type');
+            var propertyHtml = '';
+
+            if (type === 'section') {
+                propertyHtml = this.getSectionProperties(element);
+            } else if (type === 'field') {
+                propertyHtml = this.getFieldProperties(element);
+            } else if (type === 'subtable') {
+                propertyHtml = this.getSubtableProperties(element);
+            }
+
+            $('#propertyPanel').html('<h4>属性</h4>' + propertyHtml);
+
+            // 初始化下拉框等UI组件
+            $('.selectpicker').selectpicker();
+        },
+
+        getSectionProperties: function(section) {
+            var sectionName = section.find('.section-header span').text().trim().replace(/^.*?\s/, '');
+            var isCollapsible = section.data('collapsible') === '1';
+            var isRequired = section.data('required') === '1';
+            var sectionCode = section.data('section-code') || '';
+
+            var html = '<div class="property-group">' +
+                '<div class="property-group-title">基本属性</div>' +
+                '<div class="property-group-content">' +
+                '<div class="property-row">' +
+                '<label>分组名称</label>' +
+                '<input type="text" class="form-control" id="section_name" value="' + sectionName + '" ' +
+                'onchange="formEditor.updateSectionProperty(\'name\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>分组编码</label>' +
+                '<input type="text" class="form-control" id="section_code" value="' + sectionCode + '" ' +
+                'onchange="formEditor.updateSectionProperty(\'code\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>是否可折叠</label>' +
+                '<select class="form-control selectpicker" id="section_collapsible" ' +
+                'onchange="formEditor.updateSectionProperty(\'collapsible\', this.value)">' +
+                '<option value="0" ' + (!isCollapsible ? 'selected' : '') + '>否</option>' +
+                '<option value="1" ' + (isCollapsible ? 'selected' : '') + '>是</option>' +
+                '</select>' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>是否必填分组</label>' +
+                '<select class="form-control selectpicker" id="section_required" ' +
+                'onchange="formEditor.updateSectionProperty(\'required\', this.value)">' +
+                '<option value="0" ' + (!isRequired ? 'selected' : '') + '>否</option>' +
+                '<option value="1" ' + (isRequired ? 'selected' : '') + '>是</option>' +
+                '</select>' +
+                '</div>' +
+                '</div>' +
+                '</div>';
+
+            return html;
+        },
+
+        getFieldProperties: function(field) {
+            var fieldLabel = field.find('.field-label').text().trim();
+            var fieldType = field.data('field-type') || 'text';
+            var isRequired = field.data('required') === '1';
+            var placeholder = field.data('placeholder') || '';
+            var defaultValue = field.data('default') || '';
+            var helpText = field.data('help') || '';
+            var fieldWidth = field.data('width') || '100%';
+            var fieldCode = field.data('field-code') || '';
+            var fieldName = field.data('field-name') || '';
+            var validationRule = field.data('validation') || '';
+            var validationMessage = field.data('validation-message') || '';
+            var dictType = field.data('dict') || '';
+
+            var html = '<div class="property-group">' +
+                '<div class="property-group-title">基本属性</div>' +
+                '<div class="property-group-content">' +
+                '<div class="property-row">' +
+                '<label>字段标签</label>' +
+                '<input type="text" class="form-control" id="field_label" value="' + fieldLabel + '" ' +
+                'onchange="formEditor.updateFieldProperty(\'label\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>字段名称</label>' +
+                '<input type="text" class="form-control" id="field_name" value="' + fieldName + '" ' +
+                'onchange="formEditor.updateFieldProperty(\'name\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>字段编码</label>' +
+                '<input type="text" class="form-control" id="field_code" value="' + fieldCode + '" ' +
+                'onchange="formEditor.updateFieldProperty(\'code\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>字段类型</label>' +
+                '<select class="form-control selectpicker" id="field_type" ' +
+                'onchange="formEditor.updateFieldProperty(\'type\', this.value)">' +
+                '<option value="text" ' + (fieldType === 'text' ? 'selected' : '') + '>单行文本</option>' +
+                '<option value="textarea" ' + (fieldType === 'textarea' ? 'selected' : '') + '>多行文本</option>' +
+                '<option value="number" ' + (fieldType === 'number' ? 'selected' : '') + '>数字</option>' +
+                '<option value="select" ' + (fieldType === 'select' ? 'selected' : '') + '>下拉选择</option>' +
+                '<option value="radio" ' + (fieldType === 'radio' ? 'selected' : '') + '>单选按钮</option>' +
+                '<option value="checkbox" ' + (fieldType === 'checkbox' ? 'selected' : '') + '>复选框</option>' +
+                '<option value="date" ' + (fieldType === 'date' ? 'selected' : '') + '>日期</option>' +
+                '<option value="datetime" ' + (fieldType === 'datetime' ? 'selected' : '') + '>日期时间</option>' +
+                '<option value="file" ' + (fieldType === 'file' ? 'selected' : '') + '>文件上传</option>' +
+                '<option value="image" ' + (fieldType === 'image' ? 'selected' : '') + '>图片上传</option>' +
+                '</select>' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>是否必填</label>' +
+                '<select class="form-control selectpicker" id="field_required" ' +
+                'onchange="formEditor.updateFieldProperty(\'required\', this.value)">' +
+                '<option value="0" ' + (!isRequired ? 'selected' : '') + '>否</option>' +
+                '<option value="1" ' + (isRequired ? 'selected' : '') + '>是</option>' +
+                '</select>' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>默认值</label>' +
+                '<input type="text" class="form-control" id="field_default" value="' + defaultValue + '" ' +
+                'onchange="formEditor.updateFieldProperty(\'default\', this.value)">' +
+                '</div>' +
+                '</div>' +
+                '</div>';
+
+            // 对于选择类型字段,添加选项编辑区域
+            if (fieldType === 'select' || fieldType === 'radio' || fieldType === 'checkbox') {
+                // 获取字段选项数据
+                var optionsData = [];
+                if (field.find('.field-options-data').length > 0) {
+                    try {
+                        optionsData = JSON.parse(field.find('.field-options-data').val());
+                    } catch (e) {
+                        console.error('解析选项数据错误', e);
+                    }
+                }
+
+                // 转换为文本
+                var optionsText = '';
+                if (optionsData.length > 0) {
+                    $.each(optionsData, function(i, option) {
+                        optionsText += option.optionValue + '=' + option.optionLabel + '\n';
+                    });
+                }
+
+                html += '<div class="property-group">' +
+                    '<div class="property-group-title">选项设置</div>' +
+                    '<div class="property-group-content">' +
+                    '<div class="property-row">' +
+                    '<label>选项列表</label>' +
+                    '<textarea class="form-control" id="field_options" rows="5" ' +
+                    'placeholder="每行一个选项,格式:值=标签" ' +
+                    'onchange="formEditor.updateFieldProperty(\'options\', this.value)">' + optionsText + '</textarea>' +
+                    '</div>' +
+                    '</div>' +
+                    '</div>';
+            }
+
+            html += '<div class="property-group">' +
+                '<div class="property-group-title">高级设置</div>' +
+                '<div class="property-group-content">' +
+                '<div class="property-row">' +
+                '<label>字段宽度</label>' +
+                '<select class="form-control selectpicker" id="field_width" ' +
+                'onchange="formEditor.updateFieldProperty(\'width\', this.value)">' +
+                '<option value="100%" ' + (fieldWidth === '100%' ? 'selected' : '') + '>100%</option>' +
+                '<option value="75%" ' + (fieldWidth === '75%' ? 'selected' : '') + '>75%</option>' +
+                '<option value="50%" ' + (fieldWidth === '50%' ? 'selected' : '') + '>50%</option>' +
+                '<option value="25%" ' + (fieldWidth === '25%' ? 'selected' : '') + '>25%</option>' +
+                '</select>' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>占位文本</label>' +
+                '<input type="text" class="form-control" id="field_placeholder" value="' + placeholder + '" ' +
+                'onchange="formEditor.updateFieldProperty(\'placeholder\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>帮助文本</label>' +
+                '<input type="text" class="form-control" id="field_help" value="' + helpText + '" ' +
+                'onchange="formEditor.updateFieldProperty(\'help\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>验证规则</label>' +
+                '<input type="text" class="form-control" id="field_validation" value="' + validationRule + '" ' +
+                'onchange="formEditor.updateFieldProperty(\'validation\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>验证提示</label>' +
+                '<input type="text" class="form-control" id="field_validation_message" value="' + validationMessage + '" ' +
+                'onchange="formEditor.updateFieldProperty(\'validationMessage\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>数据字典</label>' +
+                '<input type="text" class="form-control" id="field_dict" value="' + dictType + '" ' +
+                'onchange="formEditor.updateFieldProperty(\'dict\', this.value)">' +
+                '</div>' +
+                '</div>' +
+                '</div>';
+
+            return html;
+        },
+
+        getSubtableProperties: function(subtable) {
+            var subtableName = subtable.find('.subtable-header span').text().trim();
+            var isRequired = subtable.data('required') === '1';
+            var addButtonText = subtable.data('add-button-text') || '新增';
+            var minRows = subtable.data('min-rows') || '0';
+            var maxRows = subtable.data('max-rows') || '';
+            var subtableCode = subtable.data('subtable-code') || '';
+
+            var html = '<div class="property-group">' +
+                '<div class="property-group-title">基本属性</div>' +
+                '<div class="property-group-content">' +
+                '<div class="property-row">' +
+                '<label>子表名称</label>' +
+                '<input type="text" class="form-control" id="subtable_name" value="' + subtableName + '" ' +
+                'onchange="formEditor.updateSubtableProperty(\'name\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>子表编码</label>' +
+                '<input type="text" class="form-control" id="subtable_code" value="' + subtableCode + '" ' +
+                'onchange="formEditor.updateSubtableProperty(\'code\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>添加按钮文本</label>' +
+                '<input type="text" class="form-control" id="subtable_add_text" value="' + addButtonText + '" ' +
+                'onchange="formEditor.updateSubtableProperty(\'addButtonText\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>是否必填</label>' +
+                '<select class="form-control selectpicker" id="subtable_required" ' +
+                'onchange="formEditor.updateSubtableProperty(\'required\', this.value)">' +
+                '<option value="0" ' + (!isRequired ? 'selected' : '') + '>否</option>' +
+                '<option value="1" ' + (isRequired ? 'selected' : '') + '>是</option>' +
+                '</select>' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>最少行数</label>' +
+                '<input type="number" class="form-control" id="subtable_min_rows" value="' + minRows + '" min="0" ' +
+                'onchange="formEditor.updateSubtableProperty(\'minRows\', this.value)">' +
+                '</div>' +
+                '<div class="property-row">' +
+                '<label>最多行数</label>' +
+                '<input type="number" class="form-control" id="subtable_max_rows" value="' + maxRows + '" min="0" ' +
+                'onchange="formEditor.updateSubtableProperty(\'maxRows\', this.value)">' +
+                '</div>' +
+                '</div>' +
+                '</div>';
+
+            // 显示子表字段列表
+            var fieldsData = [];
+            if (subtable.find('.subtable-fields-data').length > 0) {
+                try {
+                    fieldsData = JSON.parse(subtable.find('.subtable-fields-data').val());
+                } catch (e) {
+                    console.error('解析子表字段数据错误', e);
+                }
+            }
+
+            if (fieldsData.length > 0) {
+                html += '<div class="property-group">' +
+                    '<div class="property-group-title">字段列表</div>' +
+                    '<div class="property-group-content">' +
+                    '<div class="table-responsive">' +
+                    '<table class="table table-bordered">' +
+                    '<thead><tr><th>标签</th><th>名称</th><th>类型</th><th>必填</th></tr></thead>' +
+                    '<tbody>';
+
+                $.each(fieldsData, function(i, field) {
+                    html += '<tr>' +
+                        '<td>' + field.fieldLabel + '</td>' +
+                        '<td>' + field.fieldName + '</td>' +
+                        '<td>' + field.fieldType + '</td>' +
+                        '<td>' + (field.isRequired ? '是' : '否') + '</td>' +
+                        '</tr>';
+                });
+
+                html += '</tbody></table></div></div></div>';
+            }
+
+            return html;
+        },
+
+        updateSectionProperty: function(property, value) {
+            if (!this.currentSelectedElement) return;
+
+            var section = this.currentSelectedElement;
+
+            if (property === 'name') {
+                section.find('.section-header span').text(value);
+            } else if (property === 'code') {
+                section.data('section-code', value);
+            } else if (property === 'collapsible') {
+                section.data('collapsible', value);
+            } else if (property === 'required') {
+                section.data('required', value);
+            }
+        },
+
+        updateFieldProperty: function(property, value) {
+            if (!this.currentSelectedElement) return;
+
+            var field = this.currentSelectedElement;
+
+            if (property === 'label') {
+                field.find('.field-label').text(value);
+            } else if (property === 'name') {
+                field.data('field-name', value);
+            } else if (property === 'code') {
+                field.data('field-code', value);
+            } else if (property === 'type') {
+                field.data('field-type', value);
+
+                // 更新字段图标
+                var icon = 'fa-font';
+                switch(value) {
+                    case 'textarea': icon = 'fa-align-left'; break;
+                    case 'number': icon = 'fa-calculator'; break;
+                    case 'select': icon = 'fa-list'; break;
+                    case 'radio': icon = 'fa-dot-circle-o'; break;
+                    case 'checkbox': icon = 'fa-check-square-o'; break;
+                    case 'date': icon = 'fa-calendar'; break;
+                    case 'datetime': icon = 'fa-clock-o'; break;
+                    case 'file': icon = 'fa-file'; break;
+                    case 'image': icon = 'fa-picture-o'; break;
+                }
+                field.find('.field-drag-handle').removeClass().addClass('fa ' + icon + ' field-drag-handle handle');
+
+                // 刷新属性面板以显示类型相关的设置
+                this.showProperties(field);
+            } else if (property === 'required') {
+                field.data('required', value);
+            } else if (property === 'default') {
+                field.data('default', value);
+            } else if (property === 'options') {
+                // 解析选项文本
+                var optionsLines = value.split('\n');
+                var options = [];
+
+                $.each(optionsLines, function(i, line) {
+                    if (line.trim()) {
+                        var parts = line.split('=');
+                        var optionValue = parts[0].trim();
+                        var optionLabel = parts.length > 1 ? parts[1].trim() : optionValue;
+
+                        options.push({
+                            optionCode: 'option_' + (Math.floor(Math.random() * 10000)),
+                            optionLabel: optionLabel,
+                            optionValue: optionValue,
+                            isDefault: false
+                        });
+                    }
+                });
+
+                // 更新字段的选项数据
+                field.find('.field-options-data').remove();
+                field.append('<input type="hidden" class="field-options-data" value=\'' + JSON.stringify(options) + '\'>');
+            } else if (property === 'width') {
+                field.data('width', value);
+            } else if (property === 'placeholder') {
+                field.data('placeholder', value);
+            } else if (property === 'help') {
+                field.data('help', value);
+            } else if (property === 'validation') {
+                field.data('validation', value);
+            } else if (property === 'validationMessage') {
+                field.data('validation-message', value);
+            } else if (property === 'dict') {
+                field.data('dict', value);
+            }
+        },
+
+        updateSubtableProperty: function(property, value) {
+            if (!this.currentSelectedElement) return;
+
+            var subtable = this.currentSelectedElement;
+
+            if (property === 'name') {
+                subtable.find('.subtable-header span').text(value);
+            } else if (property === 'code') {
+                subtable.data('subtable-code', value);
+            } else if (property === 'addButtonText') {
+                subtable.data('add-button-text', value);
+            } else if (property === 'required') {
+                subtable.data('required', value);
+            } else if (property === 'minRows') {
+                subtable.data('min-rows', value);
+            } else if (property === 'maxRows') {
+                subtable.data('max-rows', value);
+            }
+        },
+
+        getFormData: function() {
+            var formData = {
+                sections: []
+            };
+
+            // 遍历所有分组
+            $('.section-item').each(function(sectionIndex) {
+                var section = $(this);
+                var sectionData = {
+                    id: section.data('section-id') || '',
+                    section_code: section.data('section-code') || ('section_' + (sectionIndex + 1)),
+                    section_name: section.find('.section-header span').text().trim(),
+                    section_type: 'normal',
+                    display_order: sectionIndex,
+                    is_collapsible: section.data('collapsible') === '1' ? 1 : 0,
+                    is_required: section.data('required') === '1' ? 1 : 0,
+                    fields: [],
+                    subtables: []
+                };
+
+                // 遍历分组内的字段
+                section.find('.field-item').each(function(fieldIndex) {
+                    var field = $(this);
+
+                    var fieldType = field.data('field-type') || 'text';
+                    var fieldData = {
+                        id: field.data('field-id') || '',
+                        field_code: field.data('field-code') || ('field_' + (sectionIndex + 1) + '_' + (fieldIndex + 1)),
+                        field_name: field.data('field-name') || ('field_' + (sectionIndex + 1) + '_' + (fieldIndex + 1)),
+                        field_type: fieldType,
+                        field_label: field.find('.field-label').text().trim(),
+                        placeholder: field.data('placeholder') || '',
+                        default_value: field.data('default') || '',
+                        is_required: field.data('required') === '1' ? 1 : 0,
+                        validation_rule: field.data('validation') || '',
+                        validation_message: field.data('validation-message') || '',
+                        dict_type: field.data('dict') || '',
+                        display_order: fieldIndex,
+                        field_width: field.data('width') || '100%',
+                        help_text: field.data('help') || '',
+                        is_editable: 1
+                    };
+
+                    // 处理选项
+                    if (fieldType === 'select' || fieldType === 'radio' || fieldType === 'checkbox') {
+                        fieldData.options = [];
+
+                        if (field.find('.field-options-data').length > 0) {
+                            try {
+                                var optionsData = JSON.parse(field.find('.field-options-data').val());
+
+                                $.each(optionsData, function(i, option) {
+                                    fieldData.options.push({
+                                        id: option.id || '',
+                                        option_code: option.optionCode || ('option_' + (fieldIndex + 1) + '_' + (i + 1)),
+                                        option_label: option.optionLabel,
+                                        option_value: option.optionValue,
+                                        display_order: i,
+                                        is_default: option.isDefault ? 1 : 0
+                                    });
+                                });
+                            } catch (e) {
+                                console.error('处理字段选项时出错', e);
+                            }
+                        }
+                    }
+
+                    sectionData.fields.push(fieldData);
+                });
+
+                // 遍历分组内的子表
+                section.find('.subtable-item').each(function(subtableIndex) {
+                    var subtable = $(this);
+
+                    var subtableData = {
+                        id: subtable.data('subtable-id') || '',
+                        subtable_code: subtable.data('subtable-code') || ('subtable_' + (sectionIndex + 1) + '_' + (subtableIndex + 1)),
+                        subtable_name: subtable.find('.subtable-header span').text().trim(),
+                        display_order: subtableIndex,
+                        add_button_text: subtable.data('add-button-text') || '新增',
+                        is_required: subtable.data('required') === '1' ? 1 : 0,
+                        min_rows: parseInt(subtable.data('min-rows') || '0'),
+                        max_rows: subtable.data('max-rows') ? parseInt(subtable.data('max-rows')) : null,
+                        fields: []
+                    };
+
+                    // 处理子表字段
+                    if (subtable.find('.subtable-fields-data').length > 0) {
+                        try {
+                            var fieldsData = JSON.parse(subtable.find('.subtable-fields-data').val());
+
+                            $.each(fieldsData, function(i, field) {
+                                subtableData.fields.push({
+                                    id: field.id || '',
+                                    field_code: field.fieldCode || ('subtable_field_' + (subtableIndex + 1) + '_' + (i + 1)),
+                                    field_name: field.fieldName || ('subtable_field_' + (subtableIndex + 1) + '_' + (i + 1)),
+                                    field_type: field.fieldType || 'text',
+                                    field_label: field.fieldLabel,
+                                    is_required: field.isRequired ? 1 : 0,
+                                    display_order: field.displayOrder || i,
+                                    is_editable: 1
+                                });
+                            });
+                        } catch (e) {
+                            console.error('处理子表字段数据时出错', e);
+                        }
+                    }
+
+                    sectionData.subtables.push(subtableData);
+                });
+
+                formData.sections.push(sectionData);
+            });
+
+            return formData;
+        }
+    };
+
+    // 初始化表单编辑器
+    $(function() {
+        formEditor.init();
+    });
+
+    function submitForm() {
+        // 获取表单基本数据
+        var templateId = $("#templateId").val();
+        var templateName = $("input[name='template_name']").val();
+        var templateCode = $("input[name='template_code']").val();
+        var templateType = $("select[name='template_type']").val();
+        var version = $("input[name='version']").val();
+        var description = $("textarea[name='description']").val();
+
+        // 获取表单设计数据
+        var formDesignData = formEditor.getFormData();
+
+        // 构建完整表单数据
+        var data = {
+            "id": templateId,
+            "template_name": templateName,
+            "template_code": templateCode,
+            "template_type": templateType,
+            "version": version,
+            "description": description,
+            "formData": formDesignData
+        };
+
+        // 提交表单
+        $.operate.save(prefix + "/edit", data);
+    }
+</script>
+</body>
+</html>

+ 38 - 2
health-admin/src/main/resources/templates/gxhpz/hospitalAdd.html

@@ -20,9 +20,15 @@
                 <label>医院电话:</label>
                 <input name="phone" placeholder="请输入医院电话" id="phone" class="styled-input" type="text">
             </div>
+<!--            <div class="customize-form-group">-->
+<!--                <label>所属门店:</label>-->
+<!--                <input name="storeName" placeholder="请输入所属门店" id="storeName" class="styled-input" type="text">-->
+<!--            </div>-->
             <div class="customize-form-group">
                 <label>所属门店:</label>
-                <input name="storeName" placeholder="请输入所属门店" id="storeName" class="styled-input" type="text">
+                <select name="storeName" id="storeSelect" class="styled-input">
+                    <option value="">全部</option>
+                </select>
             </div>
         </div>
     </form>
@@ -30,7 +36,11 @@
 <th:block th:include="include :: footer" />
 <th:block th:include="include :: ztree-js" />
 <script type="text/javascript">
-
+    var prefix_task = ctx + "task/followTask";
+    //初始化加载
+    $(document).ready(function() {
+        findTaskStoreList();
+    });
     function submitHandler() {
         if ($.validate.form()) {
             add();
@@ -53,6 +63,32 @@
             }
         });
     }
+    // 异步加载所有门店信息并填充下拉框
+    function findTaskStoreList() {
+        $.ajax({
+            url: prefix_task+ "/findTaskStoreList", // 这是获取所有门店的API端点
+            type: 'POST',
+            cache: false, // 设置为 false 防止缓存
+            processData: false, // 告诉 jQuery 不要处理数据(非常重要)
+            contentType: false, // 告诉 jQuery 不要设置 contentType(非常重要)
+            async: true, // 异步请求更为推荐,除非有特殊原因需要同步
+            success: function (data) {
+                var select = $('#storeSelect');
+                // 清空除了默认选项外的所有选项
+                select.find('option:not(:first)').remove();
+                if (data && data.data) {
+                    data.data.forEach(function (store) {
+                        select.append(new Option(store.dept_name, store.id));
+                    });
+                } else {
+                    console.error('Unexpected response format:', data);
+                }
+            },
+            error: function () {
+                $.modal.alertError('加载门店信息失败');
+            }
+        });
+    }
 </script>
 </body>
 </html>

+ 1 - 1
health-admin/src/main/resources/templates/gxhpz/hospitalEdit.html

@@ -26,7 +26,7 @@
                 </div>
                 <div class="customize-form-group">
                     <label>所属门店:</label>
-                    <input name="storeName" placeholder="请输入所属门店" id="storeName" th:value="${storeName}" class="styled-input" type="text">
+                    <input name="storeName" placeholder="请输入所属门店" id="storeName" th:value="${storeName}" class="styled-input" type="text" readonly>
                 </div>
             </div>
         </div>

+ 41 - 4
health-admin/src/main/resources/templates/gxhpz/hospitalList.html

@@ -27,12 +27,18 @@
                     <label>医院名称:</label>
                     <input type="text" class="styled-input" placeholder="请输入医院名称" name="standardName"/>
                 </div>
+                    <div class="customize-form-group">
+                        <label>门店:</label>
+                        <select name="storeName" id="storeSelect" class="styled-input">
+                            <option value="">全部</option>
+                        </select>
+                    </div>
              </div>
              </form>
             </div>
 
             <div class="btn-group-sm" id="toolbar" role="group">
-                <a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="system:user:add">
+                <a class="btn btn-success" onclick='$.operate.addSetwh(0,1000,250)' shiro:hasPermission="gxhpz:yyk:add">
                     <i class="fa fa-plus"></i> 新增
                 </a>
             </div>
@@ -49,9 +55,14 @@
 <th:block th:include="include :: bootstrap-table-fixed-columns-js" />
 <th:block th:include="include :: ztree-js" />
 <script th:inline="javascript">
-    var editFlag = [[${@permission.hasPermi('gxhpz:hospital:edit')}]];
-    var removeFlag = [[${@permission.hasPermi('gxhpz:hospital:remove')}]];
+    var editFlag = [[${@permission.hasPermi('gxhpz:yyk:edit')}]];
+    var removeFlag = [[${@permission.hasPermi('gxhpz:yyk:remove')}]];
     var prefix = ctx + "gxhpz/hospital";
+    var prefix_task = ctx + "task/followTask";
+    //初始化加载
+    $(document).ready(function() {
+        findTaskStoreList();
+    });
     $(function() {
         var panehHidden = false;
         if ($(this).width() < 1590) {
@@ -81,7 +92,7 @@
             importTemplateUrl: prefix + "/importTemplate",*/
             sortName: "id",
             sortOrder: "asc",
-            modalName: "药品",
+            modalName: "医院",
             fitColumns: true,
             striped: true,
             autoRowHeight: true,
@@ -165,6 +176,32 @@
             $.operate.post(prefix + "/changeStatus", { "id": id, "status": 1 });
         })
     }
+    // 异步加载所有门店信息并填充下拉框
+    function findTaskStoreList() {
+        $.ajax({
+            url: prefix_task+ "/findTaskStoreList", // 这是获取所有门店的API端点
+            type: 'POST',
+            cache: false, // 设置为 false 防止缓存
+            processData: false, // 告诉 jQuery 不要处理数据(非常重要)
+            contentType: false, // 告诉 jQuery 不要设置 contentType(非常重要)
+            async: true, // 异步请求更为推荐,除非有特殊原因需要同步
+            success: function (data) {
+                var select = $('#storeSelect');
+                // 清空除了默认选项外的所有选项
+                select.find('option:not(:first)').remove();
+                if (data && data.data) {
+                    data.data.forEach(function (store) {
+                        select.append(new Option(store.dept_name, store.dept_name));
+                    });
+                } else {
+                    console.error('Unexpected response format:', data);
+                }
+            },
+            error: function () {
+                $.modal.alertError('加载门店信息失败');
+            }
+        });
+    }
 </script>
 </body>
 

+ 41 - 5
health-admin/src/main/resources/templates/gxhpz/pharmacistsAdd.html

@@ -24,17 +24,27 @@
                     <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
                 </select>
             </div>
-            <div class="customize-form-group">
-                <label>所属门店:</label>
-                <input name="storeName" placeholder="请输入所属门店" id="storeName" class="styled-input" type="text">
-            </div>
+<!--            <div class="customize-form-group">-->
+<!--                <label>所属门店:</label>-->
+<!--                <input name="storeName" placeholder="请输入所属门店" id="storeName" class="styled-input" type="text">-->
+<!--            </div>-->
+                <div class="customize-form-group">
+                    <label>所属门店:</label>
+                    <select name="storeName" id="storeSelect" class="styled-input">
+                        <option value="">全部</option>
+                    </select>
+                </div>
         </div>
     </form>
 </div>
 <th:block th:include="include :: footer" />
 <th:block th:include="include :: ztree-js" />
 <script type="text/javascript">
-
+    var prefix_task = ctx + "task/followTask";
+    //初始化加载
+    $(document).ready(function() {
+        findTaskStoreList();
+    });
     function submitHandler() {
         if ($.validate.form()) {
             add();
@@ -57,6 +67,32 @@
             }
         });
     }
+    // 异步加载所有门店信息并填充下拉框
+    function findTaskStoreList() {
+        $.ajax({
+            url: prefix_task+ "/findTaskStoreList", // 这是获取所有门店的API端点
+            type: 'POST',
+            cache: false, // 设置为 false 防止缓存
+            processData: false, // 告诉 jQuery 不要处理数据(非常重要)
+            contentType: false, // 告诉 jQuery 不要设置 contentType(非常重要)
+            async: true, // 异步请求更为推荐,除非有特殊原因需要同步
+            success: function (data) {
+                var select = $('#storeSelect');
+                // 清空除了默认选项外的所有选项
+                select.find('option:not(:first)').remove();
+                if (data && data.data) {
+                    data.data.forEach(function (store) {
+                        select.append(new Option(store.dept_name, store.dept_name));
+                    });
+                } else {
+                    console.error('Unexpected response format:', data);
+                }
+            },
+            error: function () {
+                $.modal.alertError('加载门店信息失败');
+            }
+        });
+    }
 </script>
 </body>
 </html>

+ 1 - 1
health-admin/src/main/resources/templates/gxhpz/pharmacistsEdit.html

@@ -40,7 +40,7 @@
                     </div>
                     <div class="customize-form-group">
                         <label>所属门店:</label>
-                        <input name="storeName" placeholder="请输入所属门店" id="storeName" th:value="${storeName}" class="styled-input" type="text">
+                        <input name="storeName" placeholder="请输入所属门店" id="storeName" th:value="${storeName}" class="styled-input" type="text" readonly>
                     </div>
                     <div class="customize-form-group" id="statusDIv">
                         <label>在职状态:</label>

+ 15 - 4
health-admin/src/main/resources/templates/gxhpz/pharmacistsList.html

@@ -27,15 +27,26 @@
                     <label>姓名:</label>
                     <input type="text" class="styled-input" placeholder="请输入药师或任务跟进人姓名" name="pharmacistName"/>
                 </div>
+                    <div class="customize-form-group">
+                        <label>电话:</label>
+                        <input type="text" class="styled-input" placeholder="请输入电话" name="phone"/>
+                    </div>
+                <div class="customize-form-group">
+                    <label>职位:</label>
+                    <select name="position" id="position" class="styled-input" th:with="type=${@dict.getType('sys_gxhpz_yppz_roles')}" required>
+                        <option value="">请选择职位</option>
+                        <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictLabel}"></option>
+                    </select>
+                </div>
              </div>
              </form>
             </div>
 
             <div class="btn-group-sm" id="toolbar" role="group">
-                <a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="system:user:add">
+                <a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="gxhpz:yspz:add">
                     <i class="fa fa-plus"></i> 新增药师
                 </a>
-                <a class="btn btn-success" onclick="$.operate.add2()" shiro:hasPermission="system:user:add">
+                <a class="btn btn-success" onclick="$.operate.add2()" shiro:hasPermission="gxhpz:regjrpz:add">
                     <i class="fa fa-plus"></i> 新增任务跟进人
                 </a>
             </div>
@@ -51,8 +62,8 @@
 <th:block th:include="include :: bootstrap-table-fixed-columns-js" />
 <th:block th:include="include :: ztree-js" />
 <script th:inline="javascript">
-    var editFlag = [[${@permission.hasPermi('gxhpz:hospital:edit')}]];
-    var removeFlag = [[${@permission.hasPermi('gxhpz:hospital:remove')}]];
+    var editFlag = [[${@permission.hasPermi('gxhpz:ysregjrpz:edit')}]];
+    var removeFlag = [[${@permission.hasPermi('gxhpz:ysregjrpz:remove')}]];
     var prefix = ctx + "gxhpz/pharmacists";
     $(function() {
         var panehHidden = false;

+ 282 - 0
health-admin/src/main/resources/templates/gxhpz/preview.html

@@ -0,0 +1,282 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org">
+<head>
+    <th:block th:include="include :: header('表单预览')" />
+    <th:block th:include="include :: bootstrap-select-css" />
+    <th:block th:include="include :: bootstrap-select-js" />
+    <th:block th:include="include :: bootstrap-fileinput-css" />
+    <th:block th:include="include :: bootstrap-fileinput-js" />
+    <th:block th:include="include :: datetimepicker-css" />
+    <th:block th:include="include :: datetimepicker-js" />
+    <style>
+        .form-container {
+            padding: 20px;
+            background-color: #fff;
+            border-radius: 4px;
+            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
+        }
+        .form-section {
+            margin-bottom: 20px;
+            padding: 15px;
+            background-color: #f9f9f9;
+            border-radius: 4px;
+            border: 1px solid #eee;
+        }
+        .form-section-header {
+            border-bottom: 1px solid #eee;
+            padding-bottom: 10px;
+            margin-bottom: 15px;
+            font-weight: bold;
+            font-size: 16px;
+            color: #333;
+        }
+        .form-fields {
+            margin-bottom: 15px;
+        }
+        .form-field {
+            margin-bottom: 15px;
+        }
+        .field-label {
+            font-weight: bold;
+            margin-bottom: 5px;
+        }
+        .field-label.required:after {
+            content: ' *';
+            color: red;
+        }
+        .field-help {
+            font-size: 12px;
+            color: #999;
+            margin-top: 5px;
+        }
+        .form-subtable {
+            margin-top: 15px;
+            background-color: #fff;
+            padding: 10px;
+            border-radius: 4px;
+            border: 1px solid #ddd;
+        }
+        .subtable-header {
+            font-weight: bold;
+            margin-bottom: 10px;
+        }
+        .subtable-table {
+            width: 100%;
+            border-collapse: collapse;
+            margin-bottom: 10px;
+        }
+        .subtable-table th, .subtable-table td {
+            padding: 8px;
+            border: 1px solid #ddd;
+            text-align: center;
+        }
+        .subtable-table th {
+            background-color: #f5f5f5;
+            font-weight: bold;
+        }
+        .subtable-add-btn {
+            margin-top: 5px;
+        }
+        .form-actions {
+            margin-top: 20px;
+            text-align: center;
+        }
+        .collapsible .form-section-content {
+            display: none;
+        }
+        .collapsible .form-section-header {
+            cursor: pointer;
+        }
+        .collapsible .form-section-header:after {
+            content: '\f107';
+            font-family: 'FontAwesome';
+            float: right;
+        }
+        .collapsible.collapsed .form-section-header:after {
+            content: '\f105';
+        }
+    </style>
+</head>
+<body class="white-bg">
+<div class="wrapper wrapper-content animated fadeInRight">
+    <div class="row">
+        <div class="col-sm-12">
+            <div class="ibox">
+                <div class="ibox-title">
+                    <h5 th:text="${formData.template.template_name}">表单预览</h5>
+                    <div class="ibox-tools">
+                        <a class="btn btn-primary btn-xs" onclick="$.modal.closeTab()">关闭</a>
+                    </div>
+                </div>
+                <div class="ibox-content">
+                    <div class="form-container">
+                        <div class="alert alert-info">
+                            当前版本: <span th:text="${formData.template.version}">1.0</span>
+                        </div>
+
+                        <form id="formPreview" class="form-horizontal">
+                            <!-- 遍历所有分组 -->
+                            <div th:each="section : ${formData.sections}" class="form-section" th:classappend="${section.is_collapsible == 1 ? 'collapsible' : ''}">
+                                <div class="form-section-header" th:text="${section.section_name}">分组标题</div>
+                                <div class="form-section-content">
+                                    <div class="form-fields">
+                                        <!-- 遍历分组内的字段 -->
+                                        <div th:each="field : ${section.fields}" class="form-field">
+                                            <div class="field-label" th:classappend="${field.is_required == 1 ? 'required' : ''}" th:text="${field.field_label}">字段标签</div>
+
+                                            <!-- 根据字段类型渲染不同的控件 -->
+                                            <div th:if="${field.field_type == 'text'}">
+                                                <input type="text" class="form-control" th:placeholder="${field.placeholder != null ? field.placeholder : ''}"
+                                                       th:style="'width:' + ${field.field_width != null ? field.field_width : '100%'}" th:value="${field.default_value != null ? field.default_value : ''}">
+                                            </div>
+
+                                            <div th:if="${field.field_type == 'textarea'}">
+                                                <textarea class="form-control" th:placeholder="${field.placeholder != null ? field.placeholder : ''}"
+                                                          th:style="'width:' + ${field.field_width != null ? field.field_width : '100%'}" rows="4" th:text="${field.default_value != null ? field.default_value : ''}"></textarea>
+                                            </div>
+
+                                            <div th:if="${field.field_type == 'number'}">
+                                                <input type="number" class="form-control" th:placeholder="${field.placeholder != null ? field.placeholder : ''}"
+                                                       th:style="'width:' + ${field.field_width != null ? field.field_width : '100%'}" th:value="${field.default_value != null ? field.default_value : ''}">
+                                            </div>
+
+                                            <div th:if="${field.field_type == 'select'}">
+                                                <select class="form-control selectpicker" th:style="'width:' + ${field.field_width != null ? field.field_width : '100%'}">
+                                                    <option value="">请选择</option>
+                                                    <option th:each="option : ${field.options}" th:value="${option.option_value}"
+                                                            th:text="${option.option_label}" th:selected="${option.is_default == 1}"></option>
+                                                </select>
+                                            </div>
+
+                                            <div th:if="${field.field_type == 'radio'}">
+                                                <div class="radio-group">
+                                                    <label th:each="option : ${field.options}" class="radio-inline">
+                                                        <input type="radio" th:name="${field.field_name != null ? field.field_name : 'radio_' + field.field_id}" th:value="${option.option_value}" th:checked="${option.is_default == 1}">
+                                                        <span th:text="${option.option_label}"></span>
+                                                    </label>
+                                                </div>
+                                            </div>
+
+                                            <div th:if="${field.field_type == 'checkbox'}">
+                                                <div class="checkbox-group">
+                                                    <label th:each="option : ${field.options}" class="checkbox-inline">
+                                                        <input type="checkbox" th:name="${field.field_name != null ? field.field_name : 'checkbox_' + field.field_id}" th:value="${option.option_value}" th:checked="${option.is_default == 1}">
+                                                        <span th:text="${option.option_label}"></span>
+                                                    </label>
+                                                </div>
+                                            </div>
+
+                                            <div th:if="${field.field_type == 'date'}">
+                                                <div class="input-group date">
+                                                    <input type="text" class="form-control date-picker" th:placeholder="${field.placeholder != null ? field.placeholder : ''}"
+                                                           th:style="'width:' + ${field.field_width != null ? field.field_width : '100%'}" th:value="${field.default_value != null ? field.default_value : ''}">
+                                                    <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
+                                                </div>
+                                            </div>
+
+                                            <div th:if="${field.field_type == 'datetime'}">
+                                                <div class="input-group datetime">
+                                                    <input type="text" class="form-control datetime-picker" th:placeholder="${field.placeholder != null ? field.placeholder : ''}"
+                                                           th:style="'width:' + ${field.field_width != null ? field.field_width : '100%'}" th:value="${field.default_value != null ? field.default_value : ''}">
+                                                    <span class="input-group-addon"><i class="fa fa-calendar"></i></span>
+                                                </div>
+                                            </div>
+
+                                            <div th:if="${field.field_type == 'file'}">
+                                                <div class="file-input">
+                                                    <input type="file" class="file-control">
+                                                </div>
+                                            </div>
+
+                                            <div th:if="${field.field_type == 'image'}">
+                                                <div class="image-input">
+                                                    <input type="file" class="file-control" accept="image/*">
+                                                </div>
+                                            </div>
+
+                                            <div class="field-help" th:if="${field.help_text}" th:text="${field.help_text}">帮助文本</div>
+                                        </div>
+                                    </div>
+
+                                    <!-- 遍历分组内的子表 -->
+                                    <div th:each="subtable : ${section.subtables}" class="form-subtable">
+                                        <div class="subtable-header" th:text="${subtable.subtable_name}">子表标题</div>
+                                        <table class="subtable-table">
+                                            <thead>
+                                            <tr>
+                                                <th>序号</th>
+                                                <th th:each="field : ${subtable.fields}" th:text="${field.field_label}">列标题</th>
+                                                <th>操作</th>
+                                            </tr>
+                                            </thead>
+                                            <tbody>
+                                            <!-- 示例行 -->
+                                            <tr>
+                                                <td>1</td>
+                                                <td th:each="field : ${subtable.fields}">-</td>
+                                                <td>
+                                                    <a class="btn btn-danger btn-xs"><i class="fa fa-trash"></i></a>
+                                                </td>
+                                            </tr>
+                                            </tbody>
+                                        </table>
+                                        <button type="button" class="btn btn-primary btn-sm subtable-add-btn">
+                                            <i class="fa fa-plus"></i> <span th:text="${subtable.add_button_text != null ? subtable.add_button_text : '新增'}">新增</span>
+                                        </button>
+                                    </div>
+                                </div>
+                            </div>
+
+                            <div class="form-actions">
+                                <button type="button" class="btn btn-primary">提交</button>
+                                <button type="button" class="btn btn-default" onclick="$.modal.closeTab()">关闭</button>
+                            </div>
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<th:block th:include="include :: footer" />
+
+<script th:inline="javascript">
+    $(function() {
+        // 初始化下拉框
+        $('.selectpicker').selectpicker();
+
+        // 初始化日期选择器
+        $('.date-picker').datetimepicker({
+            format: 'yyyy-mm-dd',
+            minView: 2,
+            autoclose: true
+        });
+
+        // 初始化日期时间选择器
+        $('.datetime-picker').datetimepicker({
+            format: 'yyyy-mm-dd hh:ii:ss',
+            autoclose: true
+        });
+
+        // 初始化文件上传
+        $('.file-control').fileinput({
+            showPreview: false,
+            showUpload: false,
+            showRemove: true,
+            showCancel: false,
+            browseLabel: '选择文件'
+        });
+
+        // 分组折叠功能
+        $('.collapsible .form-section-header').click(function() {
+            var section = $(this).parent();
+            section.toggleClass('collapsed');
+            section.find('.form-section-content').slideToggle();
+        });
+
+        // 首次加载时展开所有分组
+        $('.form-section-content').show();
+    });
+</script>
+</body>
+</html>

+ 163 - 0
health-admin/src/main/resources/templates/gxhpz/template.html

@@ -0,0 +1,163 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+    <th:block th:include="include :: header('表单模板管理')" />
+    <th:block th:include="include :: layout-latest-css" />
+    <th:block th:include="include :: jasny-bootstrap-css" />
+    <th:block th:include="include :: ztree-css" />
+    <th:block th:include="include :: select2-css" />
+    <th:block th:include="include :: bootstrap-editable-css" />
+</head>
+<body class="gray-bg">
+<div class="container-div">
+    <div class="row">
+        <div class="col-sm-12 search-collapse">
+            <form id="template-form">
+                <div class="select-list">
+                    <ul>
+                        <li>
+                            <label>模板名称:</label>
+                            <input type="text" name="templateName"/>
+                        </li>
+                        <li>
+                            <label>模板类型:</label>
+                            <select name="templateType">
+                                <option value="">所有</option>
+                                <option value="常规随访">常规随访</option>
+                                <option value="脱落召回">脱落召回</option>
+                                <option value="其他">其他</option>
+                            </select>
+                        </li>
+                        <li>
+                            <label>状态:</label>
+                            <select name="status">
+                                <option value="">所有</option>
+                                <option value="1">启用</option>
+                                <option value="0">停用</option>
+                            </select>
+                        </li>
+                        <li>
+                            <a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
+                            <a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
+                        </li>
+                    </ul>
+                </div>
+            </form>
+        </div>
+
+        <div class="btn-group-sm" id="toolbar" role="group">
+            <a class="btn btn-success" onclick="$.operate.addTab()" shiro:hasPermission="form:template:add">
+                <i class="fa fa-plus"></i> 新增
+            </a>
+        </div>
+
+        <div class="col-sm-12 select-table table-striped">
+            <table id="bootstrap-table"></table>
+        </div>
+    </div>
+</div>
+<th:block th:include="include :: footer" />
+<!--<th:block th:include="include :: bootstrap-table-js" />-->
+<script th:inline="javascript">
+    var editFlag = [[${@permission.hasPermi('form:template:edit')}]];
+    var removeFlag = [[${@permission.hasPermi('form:template:remove')}]];
+    var designFlag = [[${@permission.hasPermi('form:template:design')}]];
+    var previewFlag = [[${@permission.hasPermi('form:template:preview')}]];
+    var prefix = ctx + "form/template";
+
+    $(function() {
+        var options = {
+            url: prefix + "/list",
+            createUrl: prefix + "/add",
+            updateUrl: prefix + "/edit/{id}",
+            removeUrl: prefix + "/remove",
+            modalName: "表单模板",
+            columns: [{
+                checkbox: true
+            },
+                {
+                    field: 'id',
+                    title: '模板ID',
+                    visible: false
+                },
+                {
+                    field: 'templateCode',
+                    title: '模板编码'
+                },
+                {
+                    field: 'templateName',
+                    title: '模板名称'
+                },
+                {
+                    field: 'templateType',
+                    title: '模板类型'
+                },
+                {
+                    field: 'businessBelonging',
+                    title: '业务归属'
+                },
+                {
+                    field: 'version',
+                    title: '版本号'
+                },
+                {
+                    field: 'status',
+                    title: '状态',
+                    formatter: function(value, row, index) {
+                        return value == 0 ? '<span class="badge badge-danger">停用</span>' :
+                            '<span class="badge badge-primary">启用</span>';
+                    }
+                },
+                {
+                    field: 'createdTime',
+                    title: '创建时间'
+                },
+                {
+                    title: '操作',
+                    align: 'center',
+                    formatter: function(value, row, index) {
+                        var actions = [];
+                        actions.push('<a class="btn btn-primary btn-xs ' + designFlag + '" href="javascript:void(0)" onclick="design(\'' + row.id + '\')"><i class="fa fa-edit"></i>表单设计</a> ');
+                        actions.push('<a class="btn btn-info btn-xs ' + previewFlag + '" href="javascript:void(0)" onclick="preview(\'' + row.id + '\')"><i class="fa fa-search"></i>预览</a> ');
+                        actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.editTab(\'' + row.id + '\')"><i class="fa fa-edit"></i>编辑</a> ');
+                        if (row.status == 0) {
+                            actions.push('<a class="btn btn-primary btn-xs" href="javascript:void(0)" onclick="enable(\'' + row.id + '\')"><i class="fa fa-check"></i>启用</a> ');
+                        } else {
+                            actions.push('<a class="btn btn-warning btn-xs" href="javascript:void(0)" onclick="disable(\'' + row.id + '\')"><i class="fa fa-ban"></i>停用</a> ');
+                        }
+                        actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.id + '\')"><i class="fa fa-remove"></i>删除</a>');
+                        return actions.join('');
+                    }
+                }]
+        };
+        $.table.init(options);
+    });
+
+    /* 表单设计 */
+    function design(id) {
+        var url = prefix + "/design/" + id;
+        $.modal.openTab("表单设计", url);
+    }
+
+    /* 表单预览 */
+    function preview(id) {
+        var url = prefix + "/preview/" + id;
+        $.modal.openTab("表单预览", url);
+    }
+
+    /* 启用模板 */
+    function enable(id) {
+        $.modal.confirm("确认要启用该表单模板吗?", function() {
+            $.operate.post(prefix + "/publish/" + id);
+        })
+    }
+
+    /* 停用模板 */
+    function disable(id) {
+        $.modal.confirm("确认要停用该表单模板吗?", function() {
+            $.operate.post(prefix + "/disable/" + id);
+        })
+    }
+</script>
+</body>
+</html>

+ 2 - 2
health-admin/src/main/resources/templates/index.html

@@ -53,12 +53,12 @@
                     <a class="menuItem" th:href="@{/system/main}"><i class="fa fa-home"></i> <span class="nav-label">首页</span> </a>
                 </li>
                 <li th:each="menu : ${menus}">
-                	<a th:if="${user.loginName=='admin'}" th:class="@{${!#strings.isEmpty(menu.url) && menu.url != '#'} ? ${menu.target}}" th:href="@{${#strings.isEmpty(menu.url)} ? |#| : ${menu.url}}" th:data-refresh="${menu.isRefresh == '0'}">
+                	<a th:if="${user.loginName=='1'}" th:class="@{${!#strings.isEmpty(menu.url) && menu.url != '#'} ? ${menu.target}}" th:href="@{${#strings.isEmpty(menu.url)} ? |#| : ${menu.url}}" th:data-refresh="${menu.isRefresh == '0'}">
                 		<i class="fa fa-bar-chart-o" th:class="${menu.icon}"></i>
                     	<span class="nav-label" th:text="${menu.menuName}">一级菜单</span>
                     	<span th:class="${#strings.isEmpty(menu.url) || menu.url == '#'} ? |fa arrow|"></span>
                 	</a>
-					<a th:if="${user.loginName!='admin'and menu.orderNum !='1' and menu.orderNum !='2' and menu.orderNum !='3'  }" th:class="@{${!#strings.isEmpty(menu.url) && menu.url != '#'} ? ${menu.target}}" th:href="@{${#strings.isEmpty(menu.url)} ? |#| : ${menu.url}}" th:data-refresh="${menu.isRefresh == '0'}">
+					<a  th:class="@{${!#strings.isEmpty(menu.url) && menu.url != '#'} ? ${menu.target}}" th:href="@{${#strings.isEmpty(menu.url)} ? |#| : ${menu.url}}" th:data-refresh="${menu.isRefresh == '0'}">
 						<i class="fa fa-bar-chart-o" th:class="${menu.icon}"></i>
 						<span class="nav-label" th:text="${menu.menuName}">一级菜单</span>
 						<span th:class="${#strings.isEmpty(menu.url) || menu.url == '#'} ? |fa arrow|"></span>

+ 224 - 0
health-admin/src/main/resources/templates/report/kpi/OverviewKPIList.html

@@ -0,0 +1,224 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+    <meta charset="UTF-8">
+    <meta name="format-detection" content="telephone=no">
+    <th:block th:include="include :: header('总览绩效数据')" />
+    <th:block th:include="include :: layout-latest-css" />
+    <th:block th:include="include :: ztree-css" />
+    <th:block th:include="include :: select2-css" />
+</head>
+
+<body class="gray-bg">
+<div class="ui-layout-center">
+    <div class="container-div">
+        <div class="row">
+            <div class="col-sm-12 search-collapse" >
+                <div class="query-condition-container">
+                    <h4 class="query-condition-title">查询条件</h4>
+                    <div class="query-buttons">
+                        <a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
+                        <a class="btn btn-warning btn-rounded btn-sm" onclick="resetPre()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
+                    </div>
+                </div>
+                <form id="SDaasChannelManagement-form" class="customize-search-form">
+                    <input name="storeId" type="hidden" id="treeId"/>
+                    <div class="customize-form-group-container">
+                            <div class="input-group customize-form-group">
+                                    <label>归属门店:</label>
+                                    <input name="deptName" onclick="selectDeptTree()" id="treeName" type="text" placeholder="请选择归属门店" class="styled-input">
+                                    <span class="input-group-addon"><i class="fa fa-search"></i></span>
+                            </div>
+                        <div class="customize-form-group">
+                            <label>患者ID:</label>
+                            <input type="text" class="styled-input" placeholder="请输入患者ID" name="patientId" autocomplete="off"/>
+                        </div>
+                        <div class="customize-form-group">
+                            <label>药品编码:</label>
+                            <input type="text" class="styled-input" placeholder="请输入药品编码" name="mdmCode" autocomplete="off"/>
+                        </div>
+                        <!-- 按月维度 -->
+                        <div class="customize-form-group">
+                            <label>按月维度:</label>
+                            <select id="month" name="month" class="time-input time-input2">
+                                <option value="">请选择月份维度</option>
+                                <option value="S">上月</option>
+                                <option value="B">本月</option>
+                            </select>
+                        </div>
+                        <div class="customize-form-group">
+                            <label>月份范围:</label>
+                            <input type="text" class="time-input time-input2" id="beginTime" placeholder="开始时间" name="startDate">
+                            <span>-</span>
+                            <input type="text" class="time-input time-input2" id="endTime" placeholder="结束时间" name="endDate">
+                        </div>
+                    </div>
+                </form>
+            </div>
+
+            <div class="btn-group-sm" id="toolbar" role="group">
+                <a class="btn btn-primary" onclick="visualizeData()" shiro:hasPermission="report:kpi:view">
+                    <i class="fa fa-bar-chart"></i> 数据可视化
+                </a>
+                <a class="btn btn-warning" onclick="$.table.exportExcel()" shiro:hasPermission="report:kpi:export">
+                    <i class="fa fa-download"></i> 导出
+                </a>
+            </div>
+
+            <div class="col-sm-12 select-table table-striped" style="width: 100%; overflow-x: hidden;">
+                <table id="bootstrap-table" class="fixed-layout-table"></table>
+            </div>
+        </div>
+    </div>
+</div>
+
+<th:block th:include="include :: footer" />
+<th:block th:include="include :: layout-latest-js" />
+<th:block th:include="include :: bootstrap-table-fixed-columns-js" />
+<th:block th:include="include :: ztree-js" />
+<th:block th:include="include :: select2-js" />
+<script th:inline="javascript">
+    var prefix = ctx + "report/kpi";
+    $(function() {
+        var panehHidden = false;
+        if ($(this).width() < 1590) {
+            panehHidden = true;
+        }
+        $('body').layout({ initClosed: panehHidden, west__size: 185, resizeWithWindow: false });
+        // 回到顶部绑定
+        if ($.fn.toTop !== undefined) {
+            var opt = {
+                win:$('.ui-layout-center'),
+                doc:$('.ui-layout-center')
+            };
+            $('#scroll-up').toTop(opt);
+        }
+        queryArchivesList();
+    });
+        // /Total/export /drugstore/export /patient/export
+    function queryArchivesList() {
+        var options = {
+            url: prefix + "/OverviewKPIList",//实际也是门店级别的查询
+            viewUrl: prefix + "/visualizationDetail/{id}",
+            exportUrl: prefix + "/Total/export",
+            importUrl: prefix + "/importData",
+            importTemplateUrl: prefix + "/importTemplate",
+            //sortName: "id",
+            //sortOrder: "asc",
+            modalName: "总览绩效数据",
+            fitColumns: true,
+            striped: true,
+            autoRowHeight: true,
+            rowNumbers: true,
+            showFooter:true,  //是否显示表格底部区域。
+            clickToSelect: true, //是否启用点击行时选中整行的功能。
+            singleSelect: false, //是否仅允许选择一行
+            fixedColumns: true,
+            //fixedNumber: 3,
+            fixedRightNumber: 1,
+            columns: [{
+                checkbox: true
+            },
+                { field: 'lvl', title: '级别', align: 'center' },
+                { field: 'month', title: '月份', align: 'center' },
+                { field: 'platform_name', title: '平台', align: 'center' },
+                { field: 'project_company_name', title: '项目公司', align: 'center' },
+                { field: 'store_cd', title: '门店编码', align: 'center' },
+                { field: 'store_nm', title: '门店名称', align: 'center' },
+                { field: 'on_time_repurchase_rate', title: '按时复购率', align: 'center' },
+                { field: 'churn_rate', title: '脱落率', align: 'center' },
+                { field: 'churn_recall_rate', title: '脱落召回率', align: 'center' },
+                { field: 'loss_rate', title: '流失率', align: 'center' },
+                { field: 'due_for_repurchase_count', title: '应复购患者数', align: 'center' },
+                { field: 'on_time_repurchased_count', title: '按时复购患者数', align: 'center' },
+                { field: 'churned_count', title: '脱落患者数', align: 'center' },
+                { field: 'recalled_count', title: '脱落召回患者数', align: 'center' },
+                { field: 'lost_count', title: '流失患者数', align: 'center' },
+                { field: 'medication_patients', title: '购药患者数', align: 'center' },
+                { field: 'new_patients', title: '新患数', align: 'center' },
+                { field: 'old_patients', title: '老患数', align: 'center' },
+                { field: 'outbound_calls', title: '外呼次数', align: 'center' , visible: false},
+                { field: 'connected_calls', title: '外呼接通次数', align: 'center' , visible: false},
+                { field: 'call_connect_rate', title: '外呼接通率', align: 'center', visible: false },
+                { field: 'avg_connect_duration_min', title: '平均接通时长-分', align: 'center' , visible: false},
+                { field: 'est_follow_tasks', title: '预计随访任务数', align: 'center' },
+                { field: 'est_outbound_task_count', title: '预计随访任务中使用外呼的任务数', align: 'center',width: '190px', visible: false },
+                { field: 'outbound_usage_rate', title: '外呼使用率', align: 'center' , visible: false},
+                { field: 'est_regular_follow_tasks', title: '预计常规随访任务数', align: 'center' },
+                { field: 'est_regular_outbound_task_count', title: '预计常规随访任务中使用外呼的任务数', align: 'center',width: '210px', visible: false  },
+                { field: 'regular_task_outbound_usage_rate', title: '常规任务外呼使用率', align: 'center', visible: false },
+                { field: 'first_regular_outbound_rate', title: '首次常规外呼使用率', align: 'center', visible: false },
+                { field: 'est_recall_follow_tasks', title: '预计召回随访任务数', align: 'center' },
+                { field: 'est_recall_outbound_task_count', title: '预计召回随访任务中使用外呼的任务数', align: 'center' ,width: '190px', visible: false },
+                { field: 'recall_task_outbound_usage_rate', title: '召回任务外呼使用率', align: 'center', visible: false },
+                { field: 'completed_follow_tasks', title: '预计随访中完成的任务数', align: 'center' ,width: '190px'},
+                { field: 'task_completion_rate', title: '随访任务完成率', align: 'center' },
+                { field: 'actual_completed_regular_tasks', title: '实际完成常规任务数', align: 'center' },
+                { field: 'regular_summary_sent_count', title: '常规任务随访小结发送数', align: 'center', visible: false },
+                { field: 'regular_summary_send_rate', title: '常规任务随访小结发送率', align: 'center' , visible: false},
+                { field: 'actual_completed_recall_tasks', title: '实际完成召回任务数', align: 'center' },
+                { field: 'actual_completed_tasks', title: '实际完成任务数', align: 'center' },
+                { field: 'actual_completed_outbound_tasks', title: '实际完成且使用了外呼的任务数', align: 'center', width: '190px', visible: false },
+                { field: 'evaluated_tasks', title: '有评价任务数', align: 'center' , visible: false},
+                { field: 'satisfied_times', title: '满意次数', align: 'center' , visible: false},
+                { field: 'follow_evaluation_rate', title: '随访评价率', align: 'center' , visible: false},
+                { field: 'evaluation_satisfaction', title: '评价满意度', align: 'center', visible: false },
+                { field: 'actual_follow_patients', title: '实际随访患者数', align: 'center' },
+                { field: 'actual_follow_pharmacists', title: '实际随访药师数', align: 'center' },
+
+                {
+                    title: '操作',
+                    align: 'center',
+                    formatter: function(value, row, index) {
+                        if (row.store_cd) {
+                            var actions = [];
+                            actions.push('<a class="btn btn-info btn-xs" href="javascript:void(0)" onclick="$.operate.view(\'' + row.store_cd + '\')"><i class="fa fa-eye"></i>详情</a> ');
+                            return actions.join('');
+                        } else {
+                            return "";
+                        }
+                    }
+                }]
+        };
+        $.table.init(options);
+    }
+    /* 跳转到数据可视化页面 */
+    function visualizeData() {
+        var params = $.common.formToJSON("SDaasChannelManagement-form");
+        var url = prefix + "/visualization?" + $.param(params);
+        $.modal.openTab("绩效数据可视化", url);
+    }
+    /* 自定义重置-表单重置/隐藏框/树节点选择色/搜索 */
+    function resetPre() {
+        resetDate();
+        $("#SDaasChannelManagement-form")[0].reset();
+        $.table.search();
+        _refresh();
+    }
+    /* 用户管理-新增-选择门店树 */
+    function selectDeptTree() {
+        var treeId = $("#treeId").val();
+        var deptId = $.common.isEmpty(treeId) ? "100" : $("#treeId").val();
+        var url = ctx + "system/user/selectDeptTree/" + deptId;
+        var options = {
+            title: '选择门店',
+            width: "380",
+            url: url,
+            callBack: doSubmit
+        };
+        $.modal.openOptions(options);
+    }
+
+    function doSubmit(index, layero){
+        var body = $.modal.getChildFrame(index);
+        $("#treeId").val(body.find('#treeId').val());
+        $("#treeName").val(body.find('#treeName').val());
+        $.modal.close(index);
+    }
+
+
+
+</script>
+</body>
+
+</html>

+ 446 - 0
health-admin/src/main/resources/templates/report/kpi/OverviewKPIListView.html

@@ -0,0 +1,446 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+	<meta charset="UTF-8">
+	<meta name="format-detection" content="telephone=no">
+	<th:block th:include="include :: header('绩效数据详情')" />
+	<th:block th:include="include :: bootstrap-select-css" />
+	<style>
+		.detail-section {
+			margin-bottom: 20px;
+			border: 1px solid #ddd;
+			border-radius: 4px;
+			padding: 15px;
+		}
+		.detail-title {
+			font-size: 18px;
+			font-weight: bold;
+			margin-bottom: 20px;
+			border-bottom: 1px solid #eee;
+			padding-bottom: 10px;
+		}
+		.detail-item {
+			margin-bottom: 10px;
+		}
+		.detail-label {
+			font-weight: bold;
+		}
+		.detail-value {
+			margin-left: 10px;
+		}
+		.chart-container {
+			height: 400px;
+			width: 100%;
+			margin-top: 20px;
+		}
+	</style>
+</head>
+
+<body class="gray-bg">
+<div class="container-div">
+	<div class="row">
+		<div class="col-sm-12">
+			<div class="card">
+				<div class="card-header">
+					<h3 class="card-title">门店绩效详情</h3>
+				</div>
+				<div class="card-body">
+					<!-- 基本信息区域 -->
+					<div class="detail-section">
+						<div class="detail-title">基本信息</div>
+						<div class="row">
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">门店编码:</span>
+								<span class="detail-value" th:text="${detail['store_cd']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">门店名称:</span>
+								<span class="detail-value" th:text="${detail['store_nm']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">月份:</span>
+								<span class="detail-value" th:text="${detail['month']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">项目公司:</span>
+								<span class="detail-value" th:text="${detail['project_company_name']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">平台:</span>
+								<span class="detail-value" th:text="${detail['platform_name']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">级别:</span>
+								<span class="detail-value" th:text="${detail['lvl']}"></span>
+							</div>
+						</div>
+					</div>
+
+					<!-- 复购数据区域 -->
+					<div class="detail-section">
+						<div class="detail-title">复购数据</div>
+						<div class="row">
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">按时复购率:</span>
+								<span class="detail-value" th:text="${detail['on_time_repurchase_rate']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">脱落率:</span>
+								<span class="detail-value" th:text="${detail['churn_rate']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">脱落召回率:</span>
+								<span class="detail-value" th:text="${detail['churn_recall_rate']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">流失率:</span>
+								<span class="detail-value" th:text="${detail['loss_rate']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">应复购患者数:</span>
+								<span class="detail-value" th:text="${detail['due_for_repurchase_count']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">按时复购患者数:</span>
+								<span class="detail-value" th:text="${detail['on_time_repurchased_count']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">脱落患者数:</span>
+								<span class="detail-value" th:text="${detail['churned_count']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">脱落召回患者数:</span>
+								<span class="detail-value" th:text="${detail['recalled_count']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">流失患者数:</span>
+								<span class="detail-value" th:text="${detail['lost_count']}"></span>
+							</div>
+						</div>
+						<!-- 复购率图表 -->
+						<div class="chart-container" id="purchaseRateChart"></div>
+					</div>
+
+					<!-- 患者数据区域 -->
+					<div class="detail-section">
+						<div class="detail-title">患者数据</div>
+						<div class="row">
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">购药患者数:</span>
+								<span class="detail-value" th:text="${detail['medication_patients']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">新患数:</span>
+								<span class="detail-value" th:text="${detail['new_patients']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">老患数:</span>
+								<span class="detail-value" th:text="${detail['old_patients']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">实际随访患者数:</span>
+								<span class="detail-value" th:text="${detail['actual_follow_patients']}"></span>
+							</div>
+						</div>
+						<!-- 患者数据图表 -->
+						<div class="chart-container" id="patientsChart"></div>
+					</div>
+
+					<!-- 随访任务数据区域 -->
+					<div class="detail-section">
+						<div class="detail-title">随访任务数据</div>
+						<div class="row">
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">预计随访任务数:</span>
+								<span class="detail-value" th:text="${detail['est_follow_tasks']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">预计常规随访任务数:</span>
+								<span class="detail-value" th:text="${detail['est_regular_follow_tasks']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">预计召回随访任务数:</span>
+								<span class="detail-value" th:text="${detail['est_recall_follow_tasks']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">预计随访中完成的任务数:</span>
+								<span class="detail-value" th:text="${detail['completed_follow_tasks']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">随访任务完成率:</span>
+								<span class="detail-value" th:text="${detail['task_completion_rate']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">实际完成常规任务数:</span>
+								<span class="detail-value" th:text="${detail['actual_completed_regular_tasks']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">实际完成召回任务数:</span>
+								<span class="detail-value" th:text="${detail['actual_completed_recall_tasks']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">实际完成任务数:</span>
+								<span class="detail-value" th:text="${detail['actual_completed_tasks']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">实际随访药师数:</span>
+								<span class="detail-value" th:text="${detail['actual_follow_pharmacists']}"></span>
+							</div>
+						</div>
+						<!-- 任务完成率图表 -->
+						<div class="chart-container" id="taskCompletionChart"></div>
+					</div>
+
+					<!-- 外呼数据区域 (默认隐藏) -->
+					<div class="detail-section" style="display: none;">
+						<div class="detail-title">外呼数据</div>
+						<div class="row">
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">外呼次数:</span>
+								<span class="detail-value" th:text="${detail['outbound_calls']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">外呼接通次数:</span>
+								<span class="detail-value" th:text="${detail['connected_calls']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">外呼接通率:</span>
+								<span class="detail-value" th:text="${detail['call_connect_rate']}"></span>
+							</div>
+							<div class="col-md-4 detail-item">
+								<span class="detail-label">平均接通时长-分:</span>
+								<span class="detail-value" th:text="${detail['avg_connect_duration_min']}"></span>
+							</div>
+						</div>
+					</div>
+
+				</div>
+				<div class="card-footer">
+					<a class="btn btn-sm btn-primary" href="javascript:void(0)" onclick="$.modal.close()">返回</a>
+				</div>
+			</div>
+		</div>
+	</div>
+</div>
+
+<th:block th:include="include :: footer" />
+<th:block th:include="include :: bootstrap-select-js" />
+<th:block th:include="include :: echarts-js" />
+
+<script th:inline="javascript">
+	var prefix = ctx + "report/kpi";
+	/*<![CDATA[*/
+	var detailData = /*[[${detail}]]*/ '';
+	/*]]>*/
+	$(function() {
+		// 渲染图表
+		renderPurchaseRateChart();
+		renderPatientsChart();
+		renderTaskCompletionChart();
+	});
+
+	// 复购率图表
+	function renderPurchaseRateChart() {
+		var purchaseRateChart = echarts.init(document.getElementById('purchaseRateChart'));
+		var option = {
+			title: {
+				text: '复购率数据分析',
+				left: 'center'
+			},
+			tooltip: {
+				trigger: 'axis',
+				axisPointer: {
+					type: 'shadow'
+				}
+			},
+			legend: {
+				data: ['按时复购率', '脱落率', '脱落召回率', '流失率'],
+				bottom: 10
+			},
+			grid: {
+				left: '3%',
+				right: '4%',
+				bottom: '15%',
+				containLabel: true
+			},
+			xAxis: {
+				type: 'value',
+				boundaryGap: [0, 0.01],
+				axisLabel: {
+					formatter: '{value}%'
+				},
+				max: 100
+			},
+			yAxis: {
+				type: 'category',
+				data: ['比率']
+			},
+			series: [
+				{
+					name: '按时复购率',
+					type: 'bar',
+					data: [parseFloat(detailData.on_time_repurchase_rate) || 0]
+				},
+				{
+					name: '脱落率',
+					type: 'bar',
+					data: [parseFloat(detailData.churn_rate) || 0]
+				},
+				{
+					name: '脱落召回率',
+					type: 'bar',
+					data: [parseFloat(detailData.churn_recall_rate) || 0]
+				},
+				{
+					name: '流失率',
+					type: 'bar',
+					data: [parseFloat(detailData.loss_rate) || 0]
+				}
+			]
+		};
+
+		purchaseRateChart.setOption(option);
+		window.addEventListener('resize', function() {
+			purchaseRateChart.resize();
+		});
+	}
+
+	// 患者数据图表
+	function renderPatientsChart() {
+		var patientsChart = echarts.init(document.getElementById('patientsChart'));
+		var option = {
+			title: {
+				text: '患者数据分析',
+				left: 'center'
+			},
+			tooltip: {
+				trigger: 'item',
+				formatter: '{a} <br/>{b}: {c} ({d}%)'
+			},
+			legend: {
+				orient: 'horizontal',
+				bottom: 10,
+				data: ['新患数', '老患数', '应复购患者', '实际随访患者']
+			},
+			series: [ {
+				name: '患者数据',
+				type: 'pie',
+				radius: ['30%', '50%'],
+				avoidLabelOverlap: false,
+				label: {
+					show: false,
+					position: 'center'
+				},
+				emphasis: {
+					label: {
+						show: true,
+						fontSize: '18',
+						fontWeight: 'bold'
+					}
+				},
+				labelLine: {
+					show: false
+				},
+				data: [
+					{value: parseInt(detailData.new_patients) || 0, name: '新患数'},
+					{value: parseInt(detailData.old_patients) || 0, name: '老患数'},
+					{value: parseInt(detailData.due_for_repurchase_count) || 0, name: '应复购患者'},
+					{value: parseInt(detailData.actual_follow_patients) || 0, name: '实际随访患者'}
+				]
+			} ]
+		};
+
+		patientsChart.setOption(option);
+		window.addEventListener('resize', function() {
+			patientsChart.resize();
+		});
+	}
+
+	// 任务完成率图表
+	function renderTaskCompletionChart() {
+		var taskCompletionChart = echarts.init(document.getElementById('taskCompletionChart'));
+		var option = {
+			title: {
+				text: '随访任务完成情况',
+				left: 'center'
+			},
+			tooltip: {
+				trigger: 'axis',
+				axisPointer: {
+					type: 'shadow'
+				}
+			},
+			legend: {
+				data: ['预计任务数', '实际完成数', '完成率'],
+				bottom: 10
+			},
+			grid: {
+				left: '3%',
+				right: '4%',
+				bottom: '15%',
+				containLabel: true
+			},
+			xAxis: [ {
+				type: 'category',
+				axisTick: {show: false},
+				data: ['总体随访', '常规随访', '召回随访']
+			} ],
+			yAxis: [
+				{
+					type: 'value',
+					name: '任务数',
+					min: 0,
+					position: 'left'
+				},
+				{
+					type: 'value',
+					name: '完成率',
+					min: 0,
+					max: 100,
+					position: 'right',
+					axisLabel: {
+						formatter: '{value}%'
+					}
+				}
+			],
+			series: [
+				{
+					name: '预计任务数',
+					type: 'bar',
+					barGap: 0,
+					data: [
+						parseInt(detailData.est_follow_tasks) || 0,
+						parseInt(detailData.est_regular_follow_tasks) || 0,
+						parseInt(detailData.est_recall_follow_tasks) || 0
+					]
+				},
+				{
+					name: '实际完成数',
+					type: 'bar',
+					data: [
+						parseInt(detailData.actual_completed_tasks) || 0,
+						parseInt(detailData.actual_completed_regular_tasks) || 0,
+						parseInt(detailData.actual_completed_recall_tasks) || 0
+					]
+				},
+				{
+					name: '完成率',
+					type: 'line',
+					yAxisIndex: 1,
+					data: [
+						parseFloat(detailData.task_completion_rate) || 0,
+						parseFloat(detailData.actual_completed_regular_tasks / detailData.est_regular_follow_tasks * 100 || 0).toFixed(2),
+						parseFloat(detailData.actual_completed_recall_tasks / detailData.est_recall_follow_tasks * 100 || 0).toFixed(2)
+					]
+				}
+			]
+		};
+
+		taskCompletionChart.setOption(option);
+		window.addEventListener('resize', function() {
+			taskCompletionChart.resize();
+		});
+	}
+</script>
+</body>
+</html>

+ 239 - 0
health-admin/src/main/resources/templates/report/kpi/TrkPilotMktTbl.html

@@ -0,0 +1,239 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+    <meta charset="UTF-8">
+    <meta name="format-detection" content="telephone=no">
+    <th:block th:include="include :: header('复购品详情登记')" />
+    <th:block th:include="include :: layout-latest-css" />
+    <th:block th:include="include :: ztree-css" />
+    <th:block th:include="include :: select2-css" />
+</head>
+
+<body class="gray-bg">
+<div class="ui-layout-center">
+    <div class="container-div">
+        <div class="row">
+            <div class="col-sm-12 search-collapse" >
+                <div class="query-condition-container">
+                    <h4 class="query-condition-title">查询条件</h4>
+                    <div class="query-buttons">
+                        <a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
+                        <a class="btn btn-warning btn-rounded btn-sm" onclick="resetPre()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
+                    </div>
+                </div>
+                <form id="SDaasChannelManagement-form" class="customize-search-form">
+                    <input name="storeId" type="hidden" id="treeId"/>
+                    <div class="customize-form-group-container">
+                        <!-- 门店级 -->
+                        <div class="customize-form-group">
+                            <label>门店编码:</label>
+                            <input type="text" class="styled-input" placeholder="请输入门店编码" name="storeId" autocomplete="off"/>
+                        </div>
+                        <div class="input-group customize-form-group">
+                            <label>归属门店:</label>
+                            <input name="deptName" onclick="selectDeptTree()" id="treeName" type="text" placeholder="请选择归属门店" class="styled-input">
+                            <span class="input-group-addon"><i class="fa fa-search"></i></span>
+                        </div>
+
+                        <div class="customize-form-group">
+                            <label>d值品id:</label>
+                            <input type="text" class="styled-input" placeholder="请输入d值品id" name="dValueCode" autocomplete="off"/>
+                        </div>
+                        <div class="customize-form-group">
+                            <label>患者姓名:</label>
+                            <input type="text" class="styled-input" placeholder="请输入患者姓名" name="patientName" autocomplete="off"/>
+                        </div>
+                        <!-- 按月维度 S=上月,B=本月 -->
+<!--                        <div class="customize-form-group">-->
+<!--                            <label>按月维度:</label>-->
+<!--                            <select id="month" name="month" class="time-input time-input2">-->
+<!--                                <option value="">请选择月份维度</option>-->
+<!--                                <option value="S">上月</option>-->
+<!--                                <option value="B">本月</option>-->
+<!--                            </select>-->
+<!--                        </div>-->
+
+<!--                        <div class="customize-form-group">-->
+<!--                            <label>月份范围:</label>-->
+<!--                            <input type="text" class="time-input time-input2" id="beginTime" placeholder="开始时间" name="startDate">-->
+<!--                            <span>-</span>-->
+<!--                            <input type="text" class="time-input time-input2" id="endTime" placeholder="结束时间" name="endDate">-->
+<!--                        </div>-->
+                    </div>
+                </form>
+            </div>
+
+            <div class="btn-group-sm" id="toolbar" role="group">
+                <a class="btn btn-primary" onclick="visualizeData()" shiro:hasPermission="report:kpi:view">
+                    <i class="fa fa-bar-chart"></i> 数据可视化
+                </a>
+                <a class="btn btn-warning" onclick="$.table.exportExcel()" shiro:hasPermission="system:user:export">
+                    <i class="fa fa-download"></i> 导出
+                </a>
+            </div>
+
+            <div class="col-sm-12 select-table table-striped" style="width: 100%; overflow-x: hidden;">
+                <table id="bootstrap-table" class="fixed-layout-table"></table>
+            </div>
+        </div>
+    </div>
+</div>
+
+<th:block th:include="include :: footer" />
+<th:block th:include="include :: layout-latest-js" />
+<th:block th:include="include :: bootstrap-table-fixed-columns-js" />
+<th:block th:include="include :: ztree-js" />
+<th:block th:include="include :: select2-js" />
+<script th:inline="javascript">
+    var prefix = ctx + "report/kpi";
+    $(function() {
+        var panehHidden = false;
+        if ($(this).width() < 1590) {
+            panehHidden = true;
+        }
+        $('body').layout({ initClosed: panehHidden, west__size: 185, resizeWithWindow: false });
+        // 回到顶部绑定
+        if ($.fn.toTop !== undefined) {
+            var opt = {
+                win:$('.ui-layout-center'),
+                doc:$('.ui-layout-center')
+            };
+            $('#scroll-up').toTop(opt);
+        }
+        queryArchivesList();
+    });
+
+    function queryArchivesList() {
+        var options = {
+            url: prefix + "/TrkPilotMktTblList",
+            viewUrl: prefix + "/TrkPilotMktTblView/{id}",
+            exportUrl: prefix + "/patient/export",
+            importUrl: prefix + "/importData",
+            importTemplateUrl: prefix + "/importTemplate",
+            //sortName: "id",
+            //sortOrder: "asc",
+            modalName: "门店+患者+D值品统计数据",
+            fitColumns: true,
+            striped: true,
+            autoRowHeight: true,
+            rowNumbers: true,
+            showFooter:true,  //是否显示表格底部区域。
+            clickToSelect: true, //是否启用点击行时选中整行的功能。
+            singleSelect: false, //是否仅允许选择一行
+            fixedColumns: true,
+            //fixedNumber: 3,
+            fixedRightNumber: 1,
+            columns: [{
+                checkbox: true
+            },
+                { field: 'platform_name', title: '平台', align: 'center' },
+                { field: 'project_company_name', title: '项目公司', align: 'center' },
+                { field: 'store_cd', title: '门店编码', align: 'center' },
+                { field: 'store_nm', title: '门店名称', align: 'center' },
+                { field: 'drug_id', title: 'D值品ID', align: 'center' },
+                { field: 'drug_nm', title: 'D值品名称', align: 'center' },
+                { field: 'patient_id', title: '患者ID', align: 'center' },
+                { field: 'patient_nm', title: '患者姓名', align: 'center' },
+                { field: 'gender', title: '性别', align: 'center' ,
+                    formatter: function(value, row, index) {
+                        switch (value) {
+                            case 0:
+                                return "男";
+                            case 1:
+                                return "女";
+                            default:
+                                return "-";
+                        }
+                    }
+                    },
+                { field: 'age', title: '年龄', align: 'center' },
+                { field: 'medicare_type', title: '医保类型', align: 'center' },
+                { field: 'disease_type', title: '病种', align: 'center' },
+                { field: 'hospital', title: '医院', align: 'center' },
+                { field: 'doctor_nm', title: '医生姓名', align: 'center' },
+                { field: 'remaining_days', title: '剩余用药天数', align: 'center' },
+                { field: 'medication_status', title: '用药状态', align: 'center' },
+                { field: 'first_purchase_date', title: '首次购药日期', align: 'center' },
+                { field: 'second_last_purchase_date', title: '倒数第二次购药日期', align: 'center' },
+                { field: 'last_purchase_date', title: '最近一次购药日期', align: 'center' },
+                { field: 'days_between_last_two_purchases', title: '最近两次购药间隔天数', align: 'center' },
+                { field: 'last_order_id', title: '最近一次订单编号', align: 'center' },
+                { field: 'total_sales_quantity', title: '总销售数量', align: 'center' },
+                { field: 'total_orders', title: '总订单数', align: 'center' },
+                { field: 'businessBelonging', title: '业务归属', align: 'center' },
+                { field: 'last_follow_adherence', title: '最近一次随访依从性', align: 'center',width: '190px;'},
+                { field: 'pharmacist_response', title: '药师解答', align: 'center' },
+                { field: 'is_close_regular_follow', title: '是否关闭计划', align: 'center',width: '180px;' ,
+                    formatter: function(value, row, index) {
+                        switch (value) {
+                            case 0:
+                                return "否";
+                            case 1:
+                                return "是";
+                            default:
+                                return "无";
+                        }
+                    }},
+
+                { field: 'close_reason', title: '计划关闭原因', align: 'center' },
+                { field: 'close_date', title: '计划关闭日期', align: 'center' },
+
+                {
+                    title: '操作',
+                    align: 'center',
+                    visible: false,
+                    formatter: function(value, row, index) {
+                        if (row.store_cd) {
+                            var actions = [];
+                            actions.push('<a class="btn btn-info btn-xs" href="javascript:void(0)" onclick="$.operate.view(\'' + row.store_cd + '\')"><i class="fa fa-eye"></i>详情</a> ');
+                            return actions.join('');
+                        } else {
+                            return "";
+                        }
+                    }
+                }]
+        };
+        $.table.init(options);
+    }
+    /* 自定义重置-表单重置/隐藏框/树节点选择色/搜索 */
+    function resetPre() {
+        resetDate();
+        $("#SDaasChannelManagement-form")[0].reset();
+        $.table.search();
+        _refresh();
+    }
+    /* 跳转到数据可视化页面 */
+    function visualizeData() {
+        var params = $.common.formToJSON("SDaasChannelManagement-form");
+        var url = prefix + "/visualization-patient?" + $.param(params);
+        $.modal.openTab("患者维度数据可视化", url);
+    }
+
+    /* 用户管理-新增-选择门店树 */
+    function selectDeptTree() {
+        var treeId = $("#treeId").val();
+        var deptId = $.common.isEmpty(treeId) ? "100" : $("#treeId").val();
+        var url = ctx + "system/user/selectDeptTree/" + deptId;
+        var options = {
+            title: '选择门店',
+            width: "380",
+            url: url,
+            callBack: doSubmit
+        };
+        $.modal.openOptions(options);
+    }
+
+    function doSubmit(index, layero){
+        var body = $.modal.getChildFrame(index);
+        $("#treeId").val(body.find('#treeId').val());
+        $("#treeName").val(body.find('#treeName').val());
+        $.modal.close(index);
+    }
+
+
+
+
+</script>
+</body>
+
+</html>

+ 139 - 0
health-admin/src/main/resources/templates/report/kpi/TrkPilotMktTblView.html

@@ -0,0 +1,139 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
+<head>
+	<th:block th:include="include :: header('药品配置详情')" />
+	<th:block th:include="include :: jsonview-css" />
+</head>
+<body class="white-bg">
+	<div class="wrapper wrapper-content animated fadeInRight ibox-content">
+	<form class="form-horizontal m-t" id="signupForm" th:object="${detail}" style="font-family: SimSun;font-size: 16px;">
+		<div class="row">
+			<!-- 第一列 -->
+			<div class="col-sm-6">
+				<div class="form-group">
+					<label class="col-sm-4 control-label">商品名:</label>
+					<div class="col-sm-8 form-control-static" th:text="${productName}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">通用名:</label>
+					<div class="col-sm-8 form-control-static" th:text="${genericName}"></div>
+				</div>
+
+				<div class="form-group">
+					<label class="col-sm-4 control-label">药品编码:</label>
+					<div class="col-sm-8 form-control-static" th:text="${mdmCode}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">D值品编码:</label>
+					<div class="col-sm-8 form-control-static" th:text="${d_value_code}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">D值品名称:</label>
+					<div class="col-sm-8 form-control-static" th:text="${dValueName}"></div>
+				</div>
+
+				<div class="form-group">
+					<label class="col-sm-4 control-label">说明书适应症:</label>
+					<div class="col-sm-8 form-control-static" th:text="${indicationDescription}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">厂家:</label>
+					<div class="col-sm-8 form-control-static" th:text="${manufacturer}"></div>
+				</div>
+
+				<div class="form-group">
+					<label class="col-sm-4 control-label">包装:</label>
+					<div class="col-sm-8 form-control-static" th:text="${packaging}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">D值关联更新时间:</label>
+					<div class="col-sm-8 form-control-static" th:text="${dvupdatedTime}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">D值关联时间:</label>
+					<div class="col-sm-8 form-control-static" th:text="${#temporals.format(dvcreatedTime, 'yyyy-MM-dd HH:mm:ss')}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">D值更新人:</label>
+					<div class="col-sm-8 form-control-static" th:text="${dvupdatedby}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">状态:</label>
+					<div class="col-sm-8 form-control-static" th:text="${status == 1 ? '启用' : '停用'}"></div>
+				</div>
+			</div>
+
+			<!-- 第二列 -->
+			<div class="col-sm-6">
+				<div class="form-group">
+					<label class="col-sm-4 control-label">规格:</label>
+					<div class="col-sm-8 form-control-static" th:text="${specification}"></div>
+				</div>
+
+				<div class="form-group">
+					<label class="col-sm-4 control-label">是否随访管理品:</label>
+					<div class="col-sm-8 form-control-static" th:text="${isFollowUpManaged == 1 ? '是' : '否'}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">是否冷链管理品:</label>
+					<div class="col-sm-8 form-control-static" th:text="${isColdChainManaged == 1 ? '是' : '否'}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">是否登记管理品:</label>
+					<div class="col-sm-8 form-control-static" th:text="${isRegisteredManaged == 1 ? '是' : '否'}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">是否慈善援助管理品:</label>
+					<div class="col-sm-8 form-control-static" th:text="${isCharityAidManaged == 1 ? '是' : '否'}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">企业流向管理方式:</label>
+					<div class="col-sm-8 form-control-static" th:text="${enterpriseFlowManagement}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">厂家简称:</label>
+					<div class="col-sm-8 form-control-static" th:text="${manufacturerShortName}"></div>
+				</div>
+
+
+				<div class="form-group">
+					<label class="col-sm-4 control-label">给药方式:</label>
+					<div class="col-sm-8 form-control-static" th:text="${administrationMethod}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">创建人:</label>
+					<div class="col-sm-8 form-control-static" th:text="${createdBy}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">创建时间:</label>
+					<div class="col-sm-8 form-control-static" th:text="${#temporals.format(createdTime, 'yyyy-MM-dd HH:mm:ss')}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">更新人:</label>
+					<div class="col-sm-8 form-control-static" th:text="${updatedBy}"></div>
+				</div>
+				<div class="form-group">
+					<label class="col-sm-4 control-label">更新时间:</label>
+					<div class="col-sm-8 form-control-static" th:text="${#temporals.format(updatedTime, 'yyyy-MM-dd HH:mm:ss')}"></div>
+				</div>
+
+			</div>
+		</div>
+	</form>
+    </div>
+    <th:block th:include="include :: footer" />
+    <th:block th:include="include :: jsonview-js" />
+    <script th:inline="javascript">
+		$(function() {
+
+			// var operParam = [[${oorderData}]];
+			// if ($.common.isNotEmpty(operParam) && operParam.length < 2000) {
+			// 	$("#operParam").JSONView(operParam);
+			// } else {
+			// 	$("#operParam").text(operParam);
+			// }
+
+		});
+    </script>
+</body>
+</html>

+ 871 - 0
health-admin/src/main/resources/templates/report/kpi/prescriptionAnalysis.html

@@ -0,0 +1,871 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+    <meta charset="UTF-8">
+    <meta name="format-detection" content="telephone=no">
+    <th:block th:include="include :: header('处方登记情况分析')" />
+    <th:block th:include="include :: layout-latest-css" />
+    <th:block th:include="include :: ztree-css" />
+    <th:block th:include="include :: select2-css" />
+    <th:block th:include="include :: datetimepicker-css" />
+<!--    <th:block th:include="include :: datetimepicker-js" />-->
+<!--    <th:block th:include="include :: bootstrap-daterangepicker-css" />-->
+<!--    <th:block th:include="include :: echart-css" />-->
+    <style>
+        .card {
+            border-radius: 8px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+            margin-bottom: 20px;
+            background: #fff;
+        }
+        .card-header {
+            border-bottom: 1px solid #eee;
+            padding: 15px 20px;
+            font-weight: 600;
+            color: #333;
+            background: #f8f9fa;
+            border-radius: 8px 8px 0 0;
+        }
+        .card-body {
+            padding: 20px;
+        }
+        .metric-card {
+            text-align: center;
+            padding: 15px;
+            border-right: 1px solid #f0f0f0;
+        }
+        .metric-card:last-child {
+            border-right: none;
+        }
+        .metric-value {
+            font-size: 24px;
+            font-weight: 600;
+            margin: 10px 0;
+        }
+        .metric-title {
+            font-size: 14px;
+            color: #666;
+        }
+        .chart-container {
+            height: 350px;
+            width: 100%;
+        }
+        .recent-prescriptions {
+            max-height: 350px;
+            overflow-y: auto;
+        }
+        .prescription-item {
+            border-bottom: 1px solid #f0f0f0;
+            padding: 10px 0;
+        }
+        .prescription-item:last-child {
+            border-bottom: none;
+        }
+        .prescription-header {
+            font-weight: 600;
+            margin-bottom: 5px;
+        }
+        .prescription-meta {
+            font-size: 13px;
+            color: #666;
+        }
+        .status-badge {
+            display: inline-block;
+            padding: 3px 8px;
+            border-radius: 12px;
+            font-size: 12px;
+            color: white;
+        }
+        .status-completed {
+            background-color: #67C23A;
+        }
+        .status-pending {
+            background-color: #E6A23C;
+        }
+        .status-rejected {
+            background-color: #F56C6C;
+        }
+    </style>
+</head>
+
+<body class="gray-bg">
+<div class="container-div">
+    <div class="row">
+        <div class="col-sm-12 search-collapse">
+            <div class="query-condition-container">
+                <h4 class="query-condition-title">查询条件</h4>
+                <div class="query-buttons">
+                    <a class="btn btn-primary btn-rounded btn-sm" onclick="searchData()">
+                        <i class="fa fa-search"></i>&nbsp;搜索
+                    </a>
+                    <a class="btn btn-warning btn-rounded btn-sm" onclick="resetForm()">
+                        <i class="fa fa-refresh"></i>&nbsp;重置
+                    </a>
+                </div>
+            </div>
+
+            <form id="prescription-form" class="customize-search-form">
+                <div class="row customize-form-group-container">
+
+                    <!-- 医院 -->
+                    <div class="customize-form-group">
+                        <label>医院:</label>
+                        <input type="text" class="styled-input form-control input-sm" placeholder="请输入医院" name="hospital" autocomplete="off"/>
+                    </div>
+
+                    <!-- 科室 -->
+                    <div class="customize-form-group">
+                        <label>科室:</label>
+                        <input type="text" class="styled-input form-control input-sm" placeholder="请输入科室" name="department" autocomplete="off"/>
+                    </div>
+
+                    <!-- 处方医生 -->
+                    <div class="customize-form-group">
+                        <label>处方医生:</label>
+                        <input type="text" class="styled-input form-control input-sm" placeholder="请输入处方医生" name="prescribingDoctor" autocomplete="off"/>
+                    </div>
+
+                    <!-- 归属门店 -->
+                    <div class="customize-form-group">
+                        <label>归属门店:</label>
+                        <div class="input-group">
+                            <input name="deptName" onclick="selectDeptTree()" id="treeName" type="text" placeholder="请选择归属门店" class="styled-input form-control input-sm">
+                            <span class="input-group-addon" style="cursor: pointer;"><i class="fa fa-search"></i></span>
+                        </div>
+                        <input name="storeId" type="hidden" id="treeId"/>
+                    </div>
+
+                    <!-- 处方状态 -->
+                    <div class="customize-form-group">
+                        <label>处方状态:</label>
+                        <select name="status" class="styled-input form-control input-sm">
+                            <option value="">全部</option>
+                            <option value="1">订单已完成</option>
+                            <option value="2">待上传处方</option>
+                            <option value="3">待确认信息</option>
+                            <option value="4">待处方登记</option>
+                            <option value="5">待订单销售</option>
+                            <option value="6">待绑定患者</option>
+                            <option value="7">处方已完成</option>
+                            <option value="8">订单已退款</option>
+                        </select>
+                    </div>
+
+                    <div class="customize-form-group">
+                        <label>处方日期:</label>
+                        <input type="text" class="time-input time-input2" id="completeBeginTime" placeholder="开始时间" name="startDate">
+                        <span>-</span>
+                        <input type="text" class="time-input time-input2" id="completeEndTime" placeholder="结束时间" name="endDate">
+                    </div>
+                    <!-- 显示模式 -->
+                    <div class="customize-form-group">
+                        <label>显示模式:</label>
+                        <label class="radio-inline">
+                            <input type="radio" name="displayMode" value="count" checked> 订单量模式
+                        </label>
+                        <label class="radio-inline">
+                            <input type="radio" name="displayMode" value="percent"> 百分比模式
+                        </label>
+                    </div>
+
+                    <!-- 时间周期 -->
+                    <div class="customize-form-group">
+                        <label>时间周期:</label><br/>
+                        <label class="radio-inline">
+                            <input type="radio" name="timeRange" value="week" checked> 本周
+                        </label>
+                        <label class="radio-inline">
+                            <input type="radio" name="timeRange" value="month"> 本月
+                        </label>
+                        <label class="radio-inline">
+                            <input type="radio" name="timeRange" value="lastMonth"> 上月
+                        </label>
+                        <label class="radio-inline">
+                            <input type="radio" name="timeRange" value="year"> 本年
+                        </label>
+                    </div>
+
+                </div>
+            </form>
+        </div>
+
+        <!-- 总览指标卡片 -->
+        <div class="col-sm-12">
+            <div class="card">
+                <div class="card-header">
+                    <i class="fa fa-dashboard"></i> 处方概览
+                </div>
+                <div class="card-body">
+                    <div class="row">
+                        <div class="col-sm-3 metric-card">
+                            <div class="metric-title">总处方数</div>
+                            <div class="metric-value" id="totalPrescriptions">0</div>
+                        </div>
+                        <div class="col-sm-3 metric-card">
+                            <div class="metric-title">已完成处方</div>
+                            <div class="metric-value" id="completedPrescriptions">0</div>
+                        </div>
+                        <div class="col-sm-3 metric-card">
+                            <div class="metric-title">处理中处方</div>
+                            <div class="metric-value" id="pendingPrescriptions">0</div>
+                        </div>
+                        <div class="col-sm-3 metric-card">
+                            <div class="metric-title">已退款处方</div>
+                            <div class="metric-value" id="rejectedPrescriptions">0</div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <!-- 门店处方量对比卡片 -->
+        <div class="col-sm-12">
+            <div class="card">
+                <div class="card-header">
+                    <i class="fa fa-bar-chart"></i> DTP处方登记概览
+                    <div class="pull-right">
+                        <a class="btn btn-xs btn-primary" onclick="exportExcel()">
+                            <i class="fa fa-download"></i> 导出EXCEL
+                        </a>
+                    </div>
+                </div>
+                <div class="card-body">
+                    <div id="storeComparisonChart" style="height: 400px;"></div>
+
+                    <!-- 门店数据表格 -->
+                    <div class="table-responsive mt-3">
+                        <table class="table table-hover table-striped">
+                            <thead>
+                            <tr>
+                                <th>连锁店</th>
+                                <th>订单量</th>
+                                <th>待完成订单数量</th>
+                                <th>已完成订单数量</th>
+                                <th>订单完成率(%)</th>
+                            </tr>
+                            </thead>
+                            <tbody id="storeTableBody">
+                            <!-- 动态生成 -->
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <!-- 图表区域 -->
+        <div class="col-sm-6">
+            <div class="card">
+                <div class="card-header">
+                    <i class="fa fa-pie-chart"></i> 处方状态分布
+                </div>
+                <div class="card-body">
+                    <div id="statusChart" class="chart-container"></div>
+                </div>
+            </div>
+        </div>
+
+        <div class="col-sm-6">
+            <div class="card">
+                <div class="card-header">
+                    <i class="fa fa-bar-chart"></i> 医院分布
+                </div>
+                <div class="card-body">
+                    <div id="hospitalChart" class="chart-container"></div>
+                </div>
+            </div>
+        </div>
+
+        <div class="col-sm-6">
+            <div class="card">
+                <div class="card-header">
+                    <i class="fa fa-line-chart"></i> 月度处方趋势
+                </div>
+                <div class="card-body">
+                    <div id="monthlyChart" class="chart-container"></div>
+                </div>
+            </div>
+        </div>
+
+        <div class="col-sm-6">
+            <div class="card">
+                <div class="card-header">
+                    <i class="fa fa-pie-chart"></i> 处方诊断分布
+                </div>
+                <div class="card-body">
+                    <div id="drugTypeChart" class="chart-container"></div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 最近处方列表 -->
+        <div class="col-sm-12">
+            <div class="card">
+                <div class="card-header">
+                    <i class="fa fa-list"></i> 最近处方
+                    <div class="pull-right">
+                        <a class="btn btn-xs btn-warning" onclick="$.table.exportExcel()">
+                            <i class="fa fa-download"></i> 导出全部
+                        </a>
+                    </div>
+                </div>
+                <div class="card-body">
+                    <div class="recent-prescriptions" id="recentPrescriptions">
+                        <!-- 动态加载内容 -->
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<th:block th:include="include :: footer" />
+<th:block th:include="include :: layout-latest-js" />
+<th:block th:include="include :: echarts-js" />
+<script th:inline="javascript">
+    var prefix = ctx + "report/kpi";
+    var echarts;
+    var statusChart, hospitalChart, monthlyChart, drugTypeChart;
+    // 初始化门店对比图表
+    var storeChart;
+    $(function() {
+        // 初始化日期选择器
+        // laydate.render({
+        //     elem: '#beginTime',
+        //     type: 'date'
+        // });
+        // laydate.render({
+        //     elem: '#endTime',
+        //     type: 'date'
+        // });
+
+        // 初始化图表
+        initCharts();
+
+        // 加载数据
+        loadData();
+
+        // 初始化门店对比图表
+        storeChart = echarts.init(document.getElementById('storeComparisonChart'));
+
+        // 监听模式和时间范围选择变化
+        $('input[name="displayMode"], input[name="timeRange"]').change(function() {
+            loadStoreComparisonData();
+        });
+
+        // 加载门店对比数据
+        loadStoreComparisonData();
+
+
+    });
+
+    function initCharts() {
+        // 状态分布图表
+        statusChart = echarts.init(document.getElementById('statusChart'));
+
+        // 医院分布图表
+        hospitalChart = echarts.init(document.getElementById('hospitalChart'));
+
+        // 月度趋势图表
+        monthlyChart = echarts.init(document.getElementById('monthlyChart'));
+
+        // 药品类型分布图表
+        drugTypeChart = echarts.init(document.getElementById('drugTypeChart'));
+
+        // 窗口大小变化时重绘图表
+        window.addEventListener('resize', function() {
+            statusChart.resize();
+            hospitalChart.resize();
+            monthlyChart.resize();
+            drugTypeChart.resize();
+        });
+    }
+
+    function loadData() {
+        var formData = $("#prescription-form").serialize();
+        $.ajax({
+            url: prefix + "/prescriptionAnalysis/data",
+            type: "post",
+            data: formData,
+            success: function(result) {
+                if (result.code == 0) {
+                    var data = result.data;
+                    updateDashboard(data);
+                } else {
+                    $.modal.alertError("获取数据失败:" + result.msg);
+                }
+            },
+            error: function() {
+                $.modal.alertError("获取数据失败,请稍后重试");
+            }
+        });
+    }
+
+    function updateDashboard(data) {
+        // 更新指标数据
+        $("#totalPrescriptions").text(data.totalPrescriptions || 0);
+        $("#completedPrescriptions").text(data.completedPrescriptions || 0);
+        $("#pendingPrescriptions").text(data.pendingPrescriptions || 0);
+        $("#rejectedPrescriptions").text(data.rejectedPrescriptions || 0);
+
+        // 更新状态分布图表
+        updateStatusChart(data.statusDistribution || {});
+
+        // 更新医院分布图表
+        updateHospitalChart(data.hospitalDistribution || {});
+
+        // 更新月度趋势图表
+        updateMonthlyChart(data.monthlyDistribution || {});
+
+        // 更新处方诊断分布图表
+        updateDrugTypeChart(data.drugTypeDistribution || {});
+
+        // 更新最近处方列表
+        updateRecentPrescriptions(data.recentPrescriptions || []);
+    }
+
+    function updateStatusChart(statusData) {
+        var chartData = [];
+        var colors = ['#67C23A', '#E6A23C', '#F56C6C', '#909399', '#409EFF', '#DCDFE6', '#FF9900', '#CC6633'];
+
+        var index = 0;
+        for (var status in statusData) {
+            chartData.push({
+                name: status,
+                value: statusData[status],
+                itemStyle: {
+                    color: colors[index % colors.length]
+                }
+            });
+            index++;
+        }
+
+        var option = {
+            tooltip: {
+                trigger: 'item',
+                formatter: '{a} <br/>{b}: {c} ({d}%)'
+            },
+            legend: {
+                orient: 'vertical',
+                left: 10,
+                data: Object.keys(statusData)
+            },
+            series: [
+                {
+                    name: '处方状态',
+                    type: 'pie',
+                    radius: ['50%', '70%'],
+                    avoidLabelOverlap: false,
+                    label: {
+                        show: false,
+                        position: 'center'
+                    },
+                    emphasis: {
+                        label: {
+                            show: true,
+                            fontSize: '18',
+                            fontWeight: 'bold'
+                        }
+                    },
+                    labelLine: {
+                        show: false
+                    },
+                    data: chartData
+                }
+            ]
+        };
+
+        statusChart.setOption(option);
+    }
+
+    function updateHospitalChart(hospitalData) {
+        // 排序并取前10家医院
+        var sortedData = Object.entries(hospitalData)
+            .sort((a, b) => b[1] - a[1])
+            .slice(0, 10);
+
+        var hospitals = sortedData.map(item => item[0]);
+        var counts = sortedData.map(item => item[1]);
+
+        var option = {
+            tooltip: {
+                trigger: 'axis',
+                axisPointer: {
+                    type: 'shadow'
+                }
+            },
+            grid: {
+                left: '3%',
+                right: '4%',
+                bottom: '3%',
+                containLabel: true
+            },
+            xAxis: {
+                type: 'value'
+            },
+            yAxis: {
+                type: 'category',
+                data: hospitals,
+                axisLabel: {
+                    interval: 0,
+                    rotate: 30
+                }
+            },
+            series: [
+                {
+                    name: '处方数量',
+                    type: 'bar',
+                    data: counts,
+                    itemStyle: {
+                        color: '#409EFF'
+                    }
+                }
+            ]
+        };
+
+        hospitalChart.setOption(option);
+    }
+
+    function updateMonthlyChart(monthlyData) {
+        var months = Object.keys(monthlyData).sort();
+        var counts = months.map(month => monthlyData[month]);
+
+        var option = {
+            tooltip: {
+                trigger: 'axis'
+            },
+            xAxis: {
+                type: 'category',
+                data: months,
+                axisLabel: {
+                    interval: 0,
+                    rotate: 45
+                }
+            },
+            yAxis: {
+                type: 'value'
+            },
+            series: [
+                {
+                    name: '处方数量',
+                    type: 'line',
+                    data: counts,
+                    smooth: true,
+                    itemStyle: {
+                        color: '#67C23A'
+                    },
+                    areaStyle: {
+                        color: {
+                            type: 'linear',
+                            x: 0,
+                            y: 0,
+                            x2: 0,
+                            y2: 1,
+                            colorStops: [{
+                                offset: 0, color: 'rgba(103,194,58,0.5)'
+                            }, {
+                                offset: 1, color: 'rgba(103,194,58,0.1)'
+                            }]
+                        }
+                    }
+                }
+            ]
+        };
+
+        monthlyChart.setOption(option);
+    }
+
+    function updateDrugTypeChart(drugTypeData) {
+        var chartData = [];
+        var colors = ['#67C23A', '#E6A23C', '#F56C6C', '#909399', '#409EFF', '#DCDFE6', '#FF9900', '#CC6633'];
+
+        var index = 0;
+        for (var type in drugTypeData) {
+            if (type && type !== 'null' && type !== 'undefined') {
+                chartData.push({
+                    name: type,
+                    value: drugTypeData[type],
+                    itemStyle: {
+                        color: colors[index % colors.length]
+                    }
+                });
+                index++;
+            }
+        }
+
+        var option = {
+            tooltip: {
+                trigger: 'item',
+                formatter: '{a} <br/>{b}: {c} ({d}%)'
+            },
+            legend: {
+                orient: 'vertical',
+                left: 10,
+                data: chartData.map(item => item.name)
+            },
+            series: [
+                {
+                    name: '处方诊断',
+                    type: 'pie',
+                    radius: '55%',
+                    center: ['50%', '50%'],
+                    data: chartData,
+                    emphasis: {
+                        itemStyle: {
+                            shadowBlur: 10,
+                            shadowOffsetX: 0,
+                            shadowColor: 'rgba(0, 0, 0, 0.5)'
+                        }
+                    }
+                }
+            ]
+        };
+
+        drugTypeChart.setOption(option);
+    }
+
+    function updateRecentPrescriptions(prescriptions) {
+        var html = '';
+
+        if (prescriptions.length === 0) {
+            html = '<div class="text-center p-3">暂无数据</div>';
+        } else {
+            for (var i = 0; i < prescriptions.length; i++) {
+                var p = prescriptions[i];
+                var statusClass = getStatusClass(p.status);
+                var statusText = getStatusText(p.status);
+                var date = p.prescriptionIssueDate ? new Date(p.prescriptionIssueDate).toLocaleDateString() : '-';
+
+                html += '<div class="prescription-item">';
+                html += '  <div class="prescription-header">';
+                html += '    <span>' + (p.patientName || '未知患者') + '</span>';
+                html += '    <span class="pull-right status-badge ' + statusClass + '">' + statusText + '</span>';
+                html += '  </div>';
+                html += '  <div class="prescription-meta">';
+                html += '    <span>处方号: ' + (p.prescriptionNumber || '-') + '</span>';
+                html += '    <span class="m-l-10">医院: ' + (p.hospital || '-') + '</span>';
+                html += '    <span class="m-l-10">科室: ' + (p.department || '-') + '</span>';
+                html += '    <span class="m-l-10">日期: ' + date + '</span>';
+                // html += '    <a href="' + prefix + '/prescriptionDetail/' + p.id + '" class="btn btn-xs btn-info pull-right">详情</a>';
+                html += '  </div>';
+                html += '</div>';
+            }
+        }
+
+        $("#recentPrescriptions").html(html);
+    }
+
+    function getStatusClass(status) {
+        if (status == 1 || status == 7) {
+            return 'status-completed';
+        } else if (status == 8) {
+            return 'status-rejected';
+        } else {
+            return 'status-pending';
+        }
+    }
+
+    function getStatusText(status) {
+        switch (parseInt(status)) {
+            case 1: return '订单已完成';
+            case 2: return '待上传处方';
+            case 3: return '待确认信息';
+            case 4: return '待处方登记';
+            case 5: return '待订单销售';
+            case 6: return '待绑定患者';
+            case 7: return '处方已完成';
+            case 8: return '订单已退款';
+            default: return '待确认信息';
+        }
+    }
+
+    function searchData() {
+        loadData();
+    }
+
+    function resetForm() {
+        $("#prescription-form")[0].reset();
+        $("#treeId").val("");
+        $("#treeName").val("");
+        loadData();
+    }
+
+    /* 用户管理-新增-选择门店树 */
+    function selectDeptTree() {
+        var treeId = $("#treeId").val();
+        var deptId = $.common.isEmpty(treeId) ? "100" : $("#treeId").val();
+        var url = ctx + "system/user/selectDeptTree/" + deptId;
+        var options = {
+            title: '选择门店',
+            width: "380",
+            url: url,
+            callBack: doSubmit
+        };
+        $.modal.openOptions(options);
+    }
+
+    function doSubmit(index, layero){
+        var body = $.modal.getChildFrame(index);
+        $("#treeId").val(body.find('#treeId').val());
+        $("#treeName").val(body.find('#treeName').val());
+        $.modal.close(index);
+    }
+
+
+
+
+
+
+
+    function loadStoreComparisonData() {
+        var displayMode = $('input[name="displayMode"]:checked').val();
+        var timeRange = $('input[name="timeRange"]:checked').val();
+
+        $.ajax({
+            url: prefix + "/prescriptionAnalysis/storeComparison",
+            type: "post",
+            data: {
+                displayMode: displayMode,
+                timeRange: timeRange,
+                hospital: $('input[name="hospital"]').val(),
+                department: $('input[name="department"]').val(),
+                prescribingDoctor: $('input[name="prescribingDoctor"]').val(),
+                storeId: $('#treeId').val(),
+                status: $('select[name="status"]').val(),
+                startDate: $('#beginTime').val(),
+                endDate: $('#endTime').val()
+            },
+            success: function(result) {
+                if (result.code == 0) {
+                    updateStoreComparison(result.data, displayMode);
+                } else {
+                    $.modal.alertError("获取门店对比数据失败:" + result.msg);
+                }
+            }
+        });
+    }
+
+    function updateStoreComparison(data, displayMode) {
+        var storeData = data.storeData || [];
+        var isPercentMode = displayMode === 'percent';
+
+        // 准备图表数据
+        var storeNames = [];
+        var completedData = [];
+        var pendingData = [];
+
+        for (var i = 0; i < storeData.length; i++) {
+            var store = storeData[i];
+            storeNames.push(store.storeName);
+
+            if (isPercentMode) {
+                // 百分比模式
+                var total = parseFloat(store.orderCount);
+                var completed = parseFloat(store.completedCount);
+                var pending = parseFloat(store.pendingCount);
+
+                completedData.push((completed / total * 100).toFixed(2));
+                pendingData.push((pending / total * 100).toFixed(2));
+            } else {
+                // 订单量模式
+                completedData.push(parseInt(store.completedCount));
+                pendingData.push(parseInt(store.pendingCount));
+            }
+        }
+
+        // 配置图表选项
+        var option = {
+            tooltip: {
+                trigger: 'axis',
+                axisPointer: {
+                    type: 'shadow'
+                }
+            },
+            legend: {
+                data: ['已完成订单', '待处理订单']
+            },
+            grid: {
+                left: '3%',
+                right: '4%',
+                bottom: '3%',
+                containLabel: true
+            },
+            xAxis: {
+                type: 'category',
+                data: storeNames,
+                axisLabel: {
+                    interval: 0,
+                    rotate: 30
+                }
+            },
+            yAxis: {
+                type: 'value',
+                name: isPercentMode ? '百分比 (%)' : '订单数量'
+            },
+            series: [
+                {
+                    name: '已完成订单',
+                    type: 'bar',
+                    stack: 'total',
+                    data: completedData,
+                    itemStyle: {
+                        color: '#0000FF' // 蓝色
+                    }
+                },
+                {
+                    name: '待处理订单',
+                    type: 'bar',
+                    stack: 'total',
+                    data: pendingData,
+                    itemStyle: {
+                        color: '#FF0000' // 红色
+                    }
+                }
+            ]
+        };
+
+        storeChart.setOption(option);
+
+        // 更新表格数据
+        var tableHtml = '';
+        for (var i = 0; i < storeData.length; i++) {
+            var store = storeData[i];
+            tableHtml += '<tr>';
+            tableHtml += '<td>' + store.storeName + '</td>';
+            tableHtml += '<td>' + store.orderCount + '</td>';
+            tableHtml += '<td>' + store.pendingCount + '</td>';
+            tableHtml += '<td>' + store.completedCount + '</td>';
+            tableHtml += '<td>' + store.completionRate + '%</td>';
+            tableHtml += '</tr>';
+        }
+
+        $('#storeTableBody').html(tableHtml);
+    }
+
+    function exportExcel() {
+        var displayMode = $('input[name="displayMode"]:checked').val();
+        var timeRange = $('input[name="timeRange"]:checked').val();
+
+        var params = {
+            displayMode: displayMode,
+            timeRange: timeRange,
+            hospital: $('input[name="hospital"]').val(),
+            department: $('input[name="department"]').val(),
+            prescribingDoctor: $('input[name="prescribingDoctor"]').val(),
+            storeId: $('#treeId').val(),
+            status: $('select[name="status"]').val(),
+            startDate: $('#beginTime').val(),
+            endDate: $('#endTime').val()
+        };
+
+        $.modal.loading("正在导出数据,请稍候...");
+        $.post(prefix + "/prescription/export", params, function(result) {
+            if (result.code == 0) {
+                window.location.href = ctx + "common/download?fileName=" + encodeURI(result.msg) + "&delete=" + true;
+            } else {
+                $.modal.alertError(result.msg);
+            }
+            $.modal.closeLoading();
+        });
+    }
+</script>
+</body>
+</html>

+ 257 - 0
health-admin/src/main/resources/templates/report/kpi/prescriptionDetail.html

@@ -0,0 +1,257 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+    <meta charset="UTF-8">
+    <meta name="format-detection" content="telephone=no">
+    <th:block th:include="include :: header('处方详情')" />
+    <th:block th:include="include :: layout-latest-css" />
+    <th:block th:include="include :: ztree-css" />
+    <th:block th:include="include :: select2-css" />
+<!--    <th:block th:include="include :: bootstrap-daterangepicker-css" />-->
+    <style>
+        .card {
+            border-radius: 8px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+            margin-bottom: 20px;
+            background: #fff;
+        }
+        .card-header {
+            border-bottom: 1px solid #eee;
+            padding: 15px 20px;
+            font-weight: 600;
+            color: #333;
+            background: #f8f9fa;
+            border-radius: 8px 8px 0 0;
+        }
+        .card-body {
+            padding: 20px;
+        }
+        .info-label {
+            font-weight: 600;
+            color: #555;
+            width: 120px;
+            display: inline-block;
+        }
+        .info-value {
+            color: #333;
+        }
+        .drug-item {
+            border-bottom: 1px solid #f0f0f0;
+            padding: 10px 0;
+        }
+        .drug-item:last-child {
+            border-bottom: none;
+        }
+        .drug-name {
+            font-weight: 600;
+            margin-bottom: 5px;
+        }
+        .drug-meta {
+            font-size: 13px;
+            color: #666;
+        }
+        .status-badge {
+            display: inline-block;
+            padding: 5px 10px;
+            border-radius: 4px;
+            font-size: 13px;
+            color: white;
+        }
+        .status-completed {
+            background-color: #67C23A;
+        }
+        .status-pending {
+            background-color: #E6A23C;
+        }
+        .status-rejected {
+            background-color: #F56C6C;
+        }
+        .prescription-image {
+            max-width: 100%;
+            border: 1px solid #eee;
+            border-radius: 4px;
+            padding: 5px;
+        }
+        .btn-back {
+            margin-bottom: 20px;
+        }
+    </style>
+</head>
+
+<body class="gray-bg">
+<div class="container-div">
+    <div class="row">
+        <div class="col-sm-12">
+            <a href="javascript:history.back()" class="btn btn-default btn-rounded btn-sm btn-back">
+                <i class="fa fa-arrow-left"></i> 返回列表
+            </a>
+        </div>
+
+        <!-- 处方基本信息 -->
+        <div class="col-sm-12">
+            <div class="card">
+                <div class="card-header">
+                    <i class="fa fa-file-text-o"></i> 处方基本信息
+                    <div class="pull-right">
+                        <span th:if="${detail.status == 1 || detail.status == 7}" class="status-badge status-completed" th:text="${detail.status == 1 ? '订单已完成' : '处方已完成'}"></span>
+                        <span th:if="${detail.status == 8}" class="status-badge status-rejected">订单已退款</span>
+                        <span th:if="${detail.status != 1 && detail.status != 7 && detail.status != 8}" class="status-badge status-pending" th:text="${
+                            detail.status == 2 ? '待上传处方' :
+                            detail.status == 3 ? '待确认信息' :
+                            detail.status == 4 ? '待处方登记' :
+                            detail.status == 5 ? '待订单销售' :
+                            detail.status == 6 ? '待绑定患者' : '待确认信息'
+                        }"></span>
+                    </div>
+                </div>
+                <div class="card-body">
+                    <div class="row">
+                        <div class="col-sm-6">
+                            <p><span class="info-label">处方单号:</span><span class="info-value" th:text="${detail.prescriptionNumber}">-</span></p>
+                            <p><span class="info-label">处方日期:</span><span class="info-value" th:text="${detail.prescriptionIssueDate}">-</span></p>
+                            <p><span class="info-label">医院:</span><span class="info-value" th:text="${detail.hospital}">-</span></p>
+                            <p><span class="info-label">科室:</span><span class="info-value" th:text="${detail.department}">-</span></p>
+                            <p><span class="info-label">处方医生:</span><span class="info-value" th:text="${detail.prescribingDoctor}">-</span></p>
+                            <p><span class="info-label">登记日期:</span><span class="info-value" th:text="${detail.registrationDate}">-</span></p>
+                        </div>
+                        <div class="col-sm-6">
+                            <p><span class="info-label">销售单号:</span><span class="info-value" th:text="${detail.salesOrderNumber}">-</span></p>
+                            <p><span class="info-label">销售日期:</span><span class="info-value" th:text="${detail.saleDate}">-</span></p>
+                            <p><span class="info-label">销售门店:</span><span class="info-value" th:text="${detail.sales_storeName}">-</span></p>
+                            <p><span class="info-label">药品品种数:</span><span class="info-value" th:text="${detail.drugVarietyCount}">-</span></p>
+                            <p><span class="info-label">药品数量:</span><span class="info-value" th:text="${detail.drugQuantity}">-</span></p>
+                            <p><span class="info-label">审核状态:</span><span class="info-value" th:text="${
+                                detail.reviewStatus == 0 ? '审核不通过' :
+                                detail.reviewStatus == 1 ? '审核通过' :
+                                detail.reviewStatus == 2 ? '待审核' : '审核通过'
+                            }">-</span></p>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 患者信息 -->
+        <div class="col-sm-6">
+            <div class="card">
+                <div class="card-header">
+                    <i class="fa fa-user"></i> 患者信息
+                </div>
+                <div class="card-body">
+                    <p><span class="info-label">患者姓名:</span><span class="info-value" th:text="${detail.patientName}">-</span></p>
+                    <p><span class="info-label">患者ID:</span><span class="info-value" th:text="${detail.patientId}">-</span></p>
+                    <p><span class="info-label">联系电话:</span><span class="info-value" th:text="${detail.patientPhone}">-</span></p>
+                    <p><span class="info-label">性别:</span><span class="info-value" th:text="${
+                        detail.gender == 0 ? '男' :
+                        detail.gender == 1 ? '女' : '未知'
+                    }">-</span></p>
+                    <p><span class="info-label">年龄:</span><span class="info-value" th:text="${detail.age}">-</span></p>
+                </div>
+            </div>
+        </div>
+
+        <!-- 诊断信息 -->
+        <div class="col-sm-6">
+            <div class="card">
+                <div class="card-header">
+                    <i class="fa fa-stethoscope"></i> 诊断信息
+                </div>
+                <div class="card-body">
+                    <p><span class="info-label">处方诊断:</span><span class="info-value" th:text="${detail.prescriptionDiagnosis}">-</span></p>
+                    <p><span class="info-label">临床诊断:</span><span class="info-value" th:text="${detail.clinicalDiagnosis}">-</span></p>
+                    <p><span class="info-label">诊断大类:</span><span class="info-value" th:text="${detail.dl}">-</span></p>
+                    <p><span class="info-label">诊断小类:</span><span class="info-value" th:text="${detail.xl}">-</span></p>
+                    <p><span class="info-label">用药途径:</span><span class="info-value" th:text="${detail.drugRoute}">-</span></p>
+                </div>
+            </div>
+        </div>
+
+        <!-- 处方图片 -->
+        <div th:if="${detail.prescriptionImageUrl != null && detail.prescriptionImageUrl != ''}" class="col-sm-6">
+            <div class="card">
+                <div class="card-header">
+                    <i class="fa fa-image"></i> 处方图片
+                </div>
+                <div class="card-body text-center">
+                    <img th:src="${detail.prescriptionImageUrl}" alt="处方图片" class="prescription-image" onclick="$.modal.openFull('处方图片预览', '${detail.prescriptionImageUrl}')">
+                </div>
+            </div>
+        </div>
+
+        <!-- 发票图片 -->
+        <div th:if="${detail.invoiceImageUrl != null && detail.invoiceImageUrl != ''}" class="col-sm-6">
+            <div class="card">
+                <div class="card-header">
+                    <i class="fa fa-file-text"></i> 发票图片
+                </div>
+                <div class="card-body text-center">
+                    <img th:src="${detail.invoiceImageUrl}" alt="发票图片" class="prescription-image" onclick="$.modal.openFull('发票图片预览', '${detail.invoiceImageUrl}')">
+                </div>
+            </div>
+        </div>
+
+        <!-- 药品信息 -->
+        <div class="col-sm-12">
+            <div class="card">
+                <div class="card-header">
+                    <i class="fa fa-medkit"></i> 药品信息
+                </div>
+                <div class="card-body">
+                    <div id="drugList">
+                        <div th:each="drug : ${drugList}" class="drug-item">
+                            <div class="drug-name">
+                                <span th:text="${drug.productName}">药品名称</span>
+                                <span th:if="${drug.genericName}" class="m-l-10 text-muted" th:text="'(' + ${drug.genericName} + ')'"></span>
+                            </div>
+                            <div class="row drug-meta">
+                                <div class="col-sm-4">
+                                    <p><span class="info-label">药品编码:</span><span th:text="${drug.mdmCode}">-</span></p>
+                                    <p><span class="info-label">规格:</span><span th:text="${drug.specification}">-</span></p>
+                                    <p><span class="info-label">厂家:</span><span th:text="${drug.manufacturerShortName}">-</span></p>
+                                </div>
+                                <div class="col-sm-4">
+                                    <p><span class="info-label">批号:</span><span th:text="${drug.drug_batch_number}">-</span></p>
+                                    <p><span class="info-label">数量:</span><span th:text="${drug.packageQuantity}">-</span></p>
+                                    <p><span class="info-label">单次剂量:</span><span th:text="${drug.singleDoseValue} + ' ' + ${drug.singleDoseUnit}">-</span></p>
+                                </div>
+                                <div class="col-sm-4">
+                                    <p><span class="info-label">用药频次:</span><span th:text="${drug.dosageFrequency}">-</span></p>
+                                    <p><span class="info-label">用药途径:</span><span th:text="${drug.medicationRoute}">-</span></p>
+                                    <p><span class="info-label">给药方式:</span><span th:text="${drug.administrationMethod}">-</span></p>
+                                </div>
+                            </div>
+                        </div>
+                        <div th:if="${#lists.isEmpty(drugList)}" class="text-center p-3">
+                            暂无药品信息
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 备注信息 -->
+        <div th:if="${detail.remarks != null && detail.remarks != ''}" class="col-sm-12">
+            <div class="card">
+                <div class="card-header">
+                    <i class="fa fa-comments"></i> 备注信息
+                </div>
+                <div class="card-body">
+                    <p th:text="${detail.remarks}">-</p>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<th:block th:include="include :: footer" />
+<th:block th:include="include :: layout-latest-js" />
+<script th:inline="javascript">
+    var prefix = ctx + "report/kpi";
+
+    $(function() {
+        // 适配布局
+        $('body').layout({ resizeWithWindow: false });
+    });
+</script>
+</body>
+</html>

+ 218 - 0
health-admin/src/main/resources/templates/report/kpi/storeDValProd4Rates.html

@@ -0,0 +1,218 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+    <meta charset="UTF-8">
+    <meta name="format-detection" content="telephone=no">
+    <th:block th:include="include :: header('复购品门店维度数据')" />
+    <th:block th:include="include :: layout-latest-css" />
+    <th:block th:include="include :: ztree-css" />
+    <th:block th:include="include :: select2-css" />
+</head>
+
+<body class="gray-bg">
+<div class="ui-layout-center">
+    <div class="container-div">
+        <div class="row">
+            <div class="col-sm-12 search-collapse" >
+                <div class="query-condition-container">
+                    <h4 class="query-condition-title">查询条件</h4>
+                    <div class="query-buttons">
+                        <a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i class="fa fa-search"></i>&nbsp;搜索</a>
+                        <a class="btn btn-warning btn-rounded btn-sm" onclick="resetPre()"><i class="fa fa-refresh"></i>&nbsp;重置</a>
+                    </div>
+                </div>
+                <form id="SDaasChannelManagement-form" class="customize-search-form">
+                    <input name="storeId" type="hidden" id="treeId"/>
+                    <div class="customize-form-group-container">
+                        <!-- 门店级 -->
+                        <div class="customize-form-group">
+                            <label>门店编码:</label>
+                            <input type="text" class="styled-input" placeholder="请输入门店编码" name="storeId" autocomplete="off"/>
+                        </div>
+<!--                        <div class="customize-form-group">-->
+<!--                            <label>门店名称:</label>-->
+<!--                            <input type="text" class="styled-input" placeholder="请输入门店名称" name="store_nm" autocomplete="off"/>-->
+<!--                        </div>-->
+                        <div class="input-group customize-form-group">
+                            <label>归属门店:</label>
+                            <input name="deptName" onclick="selectDeptTree()" id="treeName" type="text" placeholder="请选择归属门店" class="styled-input">
+                            <span class="input-group-addon"><i class="fa fa-search"></i></span>
+                        </div>
+                        <div class="customize-form-group">
+                            <label>药品编码:</label>
+                            <input type="text" class="styled-input" placeholder="请输入药品编码" name="mdmCode" autocomplete="off"/>
+                        </div>
+                        <div class="customize-form-group">
+                            <label>d值品id:</label>
+                            <input type="number" class="styled-input" placeholder="请输入d值品id" name="dValueCode" autocomplete="off"/>
+                        </div>
+                        <div class="customize-form-group">
+                            <label>d值品名称:</label>
+                            <input type="text" class="styled-input" placeholder="请输入d值品名称" name="dValueName" autocomplete="off"/>
+                        </div>
+                        <!-- 按月维度 -->
+                        <div class="customize-form-group">
+                            <label>按月维度:</label>
+                            <select id="month" name="month" class="time-input time-input2">
+                                <option value="">请选择月份维度</option>
+                                <option value="S">上月</option>
+                                <option value="B">本月</option>
+                            </select>
+                        </div>
+
+                        <div class="customize-form-group">
+                            <label>月份范围:</label>
+                            <input type="text" class="time-input time-input2" id="beginTime" placeholder="开始时间" name="startDate">
+                            <span>-</span>
+                            <input type="text" class="time-input time-input2" id="endTime" placeholder="结束时间" name="endDate">
+                        </div>
+                    </div>
+                </form>
+            </div>
+
+            <div class="btn-group-sm" id="toolbar" role="group">
+                <a class="btn btn-primary" onclick="visualizeData()" shiro:hasPermission="system:user:view">
+                    <i class="fa fa-bar-chart"></i> 可视化浏览
+                </a>
+                <a class="btn btn-warning" onclick="$.table.exportExcel()" shiro:hasPermission="system:user:export">
+                    <i class="fa fa-download"></i> 导出
+                </a>
+            </div>
+
+            <div class="col-sm-12 select-table table-striped" style="width: 100%; overflow-x: hidden;">
+                <table id="bootstrap-table" class="fixed-layout-table"></table>
+            </div>
+        </div>
+    </div>
+</div>
+
+<th:block th:include="include :: footer" />
+<th:block th:include="include :: layout-latest-js" />
+<th:block th:include="include :: bootstrap-table-fixed-columns-js" />
+<th:block th:include="include :: ztree-js" />
+<th:block th:include="include :: select2-js" />
+<script th:inline="javascript">
+    var prefix = ctx + "report/kpi";
+    $(function() {
+        var panehHidden = false;
+        if ($(this).width() < 1590) {
+            panehHidden = true;
+        }
+        $('body').layout({ initClosed: panehHidden, west__size: 185, resizeWithWindow: false });
+        // 回到顶部绑定
+        if ($.fn.toTop !== undefined) {
+            var opt = {
+                win:$('.ui-layout-center'),
+                doc:$('.ui-layout-center')
+            };
+            $('#scroll-up').toTop(opt);
+        }
+        queryArchivesList();
+    });
+    // /Total/export /drugstore/export /patient/export
+    function queryArchivesList() {
+        var options = {
+            url: prefix + "/storeDValProd4RatesList",
+            viewUrl: prefix + "/storeDValProd4RatesView/{id}",
+            exportUrl: prefix + "/drugstore/export",
+            importUrl: prefix + "/importData",
+            importTemplateUrl: prefix + "/importTemplate",
+            //sortName: "id",
+            //sortOrder: "asc",
+            modalName: "门店维度数据",
+            fitColumns: true,
+            striped: true,
+            autoRowHeight: true,
+            rowNumbers: true,
+            showFooter:true,  //是否显示表格底部区域。
+            clickToSelect: true, //是否启用点击行时选中整行的功能。
+            singleSelect: false, //是否仅允许选择一行
+            fixedColumns: true,
+            //fixedNumber: 3,
+            fixedRightNumber: 1,
+            columns: [{
+                checkbox: true
+            },
+                { field: 'month', title: '月份', align: 'center' },
+                { field: 'platform_name', title: '平台', align: 'center' },
+                { field: 'project_company_name', title: '连锁', align: 'center' },
+                { field: 'store_cd', title: '门店编码', align: 'center' },
+                { field: 'store_nm', title: '门店名称', align: 'center' },
+                { field: 'drug_id', title: 'd值品id', align: 'center' },
+                { field: 'drug_nm', title: 'd值品名称', align: 'center' },
+                { field: 'on_time_repurchase_rate', title: '按时复购率', align: 'center' },
+                { field: 'churn_rate', title: '脱落率', align: 'center' },
+                { field: 'churn_recall_rate', title: '脱落召回率', align: 'center' },
+                { field: 'loss_rate', title: '流失率', align: 'center' },
+                { field: 'due_for_repurchase_count', title: '应复购人次', align: 'center' },
+                { field: 'on_time_repurchased_count', title: '按时复购人次', align: 'center' },
+                { field: 'churned_count', title: '脱落人次', align: 'center' },
+                { field: 'recalled_count', title: '召回人次', align: 'center' },
+                { field: 'lost_count', title: '流失人次', align: 'center' },
+                { field: 'churn_amount', title: '脱落金额', align: 'center' },
+                { field: 'loss_amount', title: '流失金额', align: 'center' },
+                { field: 'first_order_amount', title: '首单金额', align: 'center' },
+                { field: 'on_time_repurchase_amount', title: '按时复购金额', align: 'center' },
+                { field: 'churn_recall_amount', title: '脱落召回金额', align: 'center' },
+                { field: 'loss_recall_amount', title: '流失召回金额', align: 'center' },
+
+                {
+                    title: '操作',
+                    align: 'center',
+                    visible: true,
+                    formatter: function(value, row, index) {
+                        if (row.id) {
+                            var actions = [];
+                            actions.push('<a class="btn btn-info btn-xs" href="javascript:void(0)" onclick="$.operate.view(\'' + row.id + '\')"><i class="fa fa-eye"></i>详情</a> ');
+                            return actions.join('');
+                        } else {
+                            return "";
+                        }
+                    }
+                }]
+        };
+        $.table.init(options);
+    }
+
+    /* 自定义重置-表单重置/隐藏框/树节点选择色/搜索 */
+    function resetPre() {
+        resetDate();
+        $("#SDaasChannelManagement-form")[0].reset();
+        $.table.search();
+        _refresh();
+    }
+
+    /* 用户管理-新增-选择门店树 */
+    function selectDeptTree() {
+        var treeId = $("#treeId").val();
+        var deptId = $.common.isEmpty(treeId) ? "100" : $("#treeId").val();
+        var url = ctx + "system/user/selectDeptTree/" + deptId;
+        var options = {
+            title: '选择门店',
+            width: "380",
+            url: url,
+            callBack: doSubmit
+        };
+        $.modal.openOptions(options);
+    }
+
+    function doSubmit(index, layero){
+        var body = $.modal.getChildFrame(index);
+        $("#treeId").val(body.find('#treeId').val());
+        $("#treeName").val(body.find('#treeName').val());
+        $.modal.close(index);
+    }
+    /* 跳转到可视化页面 */
+    function visualizeData() {
+        var url = prefix + "/storeDValProd4RatesVisualize";
+        // 获取当前查询条件
+        var queryParams = $("#SDaasChannelManagement-form").serialize();
+        // 带着查询条件跳转,确保可视化的是当前筛选的数据
+        $.modal.openTab("复购品门店维度数据可视化", url + "?" + queryParams);
+    }
+
+
+</script>
+</body>
+
+</html>

+ 366 - 0
health-admin/src/main/resources/templates/report/kpi/storeDValProd4RatesView.html

@@ -0,0 +1,366 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+	<meta charset="UTF-8">
+	<meta name="format-detection" content="telephone=no">
+	<title th:text="'复购品门店维度数据详情'">复购品门店维度数据详情</title>
+
+	<!-- Direct CSS includes instead of Thymeleaf fragments -->
+	<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
+	<link rel="stylesheet" th:href="@{/css/font-awesome.min.css}">
+	<link rel="stylesheet" th:href="@{/css/animate.min.css}">
+	<link rel="stylesheet" th:href="@{/css/style.min.css}">
+	<th:block th:include="include :: jsonview-css" />
+<!--	<link rel="stylesheet" th:href="@{/css/layout.min.css}">
+	<link rel="stylesheet" th:href="@{/css/plugins/ztree/metroStyle/metroStyle.css}">
+	<link rel="stylesheet" th:href="@{/css/plugins/select2/select2.min.css}">
+	<link rel="stylesheet" th:href="@{/css/plugins/bootstrap-daterangepicker/daterangepicker.css}">
+	<link rel="stylesheet" th:href="@{/css/plugins/bootstrap-duallistbox/bootstrap-duallistbox.min.css}">
+	<link rel="stylesheet" th:href="@{/css/plugins/echarts/echarts.css}">-->
+
+	<style>
+		.card {
+			border-radius: 8px;
+			box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+			margin-bottom: 20px;
+			background: #fff;
+		}
+		.card-header {
+			border-bottom: 1px solid #eee;
+			padding: 15px 20px;
+			font-weight: 600;
+			color: #333;
+			background: #f8f9fa;
+			border-radius: 8px 8px 0 0;
+		}
+		.card-body {
+			padding: 20px;
+		}
+		.metric-card {
+			text-align: center;
+			padding: 15px;
+			border-right: 1px solid #f0f0f0;
+		}
+		.metric-card:last-child {
+			border-right: none;
+		}
+		.metric-value {
+			font-size: 24px;
+			font-weight: 600;
+			margin: 10px 0;
+		}
+		.metric-title {
+			font-size: 14px;
+			color: #666;
+		}
+		.info-label {
+			font-weight: 600;
+			color: #555;
+			width: 120px;
+			display: inline-block;
+		}
+		.info-value {
+			color: #333;
+		}
+		.chart-container {
+			height: 350px;
+			width: 100%;
+		}
+		.btn-back {
+			margin-bottom: 20px;
+		}
+		.positive-rate {
+			color: #28a745;
+		}
+		.negative-rate {
+			color: #dc3545;
+		}
+	</style>
+</head>
+
+<body class="gray-bg">
+<div class="container-div">
+	<form class="form-horizontal m-t" id="signupForm"  style="font-family: SimSun;font-size: 16px;">
+		<div class="row">
+	<!--			<div class="col-sm-12">-->
+	<!--				<a href="javascript:history.back()" class="btn btn-default btn-rounded btn-sm btn-back">-->
+	<!--					<i class="fa fa-arrow-left"></i> 返回列表-->
+	<!--				</a>-->
+	<!--			</div>-->
+
+			<!-- 基本信息卡片 -->
+			<div class="col-sm-12">
+				<div class="card">
+					<div class="card-header">
+						<i class="fa fa-info-circle"></i> 基本信息
+					</div>
+					<div class="card-body">
+						<div class="row">
+							<div class="col-sm-6">
+								<p><span class="info-label">门店编码:</span><span class="info-value" th:text="${store_cd}">ST10001</span></p>
+								<p><span class="info-label">d值品ID:</span><span class="info-value" th:text="${drug_id}">D12345</span></p>
+								<p><span class="info-label">应复购人次:</span><span class="info-value" th:text="${due_for_repurchase_count}">120</span></p>
+							</div>
+							<div class="col-sm-6">
+								<p><span class="info-label">平台:</span><span class="info-value" th:text="${platform_name}">健康平台</span></p>
+								<p><span class="info-label">门店名称:</span><span class="info-value" th:text="${store_nm}">某某药店</span></p>
+								<p><span class="info-label">d值品名称:</span><span class="info-value" th:text="${drug_nm}">某药品</span></p>
+								<p><span class="info-label">连锁:</span><span class="info-value" th:text="${project_company_name}">某某连锁</span></p>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+
+			<!-- 关键指标卡片 -->
+			<div class="col-sm-12">
+				<div class="card">
+					<div class="card-header">
+						<i class="fa fa-line-chart"></i> 核心指标
+					</div>
+					<div class="card-body">
+						<div class="row">
+							<div class="col-sm-3 metric-card">
+								<div class="metric-title">按时复购率</div>
+								<div class="metric-value positive-rate" th:text="${on_time_repurchase_rate + '%'}">65.8%</div>
+							</div>
+							<div class="col-sm-3 metric-card">
+								<div class="metric-title">脱落率</div>
+								<div class="metric-value negative-rate" th:text="${churn_rate + '%'}">22.5%</div>
+							</div>
+							<div class="col-sm-3 metric-card">
+								<div class="metric-title">脱落召回率</div>
+								<div class="metric-value positive-rate" th:text="${churn_recall_rate + '%'}">15.3%</div>
+							</div>
+							<div class="col-sm-3 metric-card">
+								<div class="metric-title">流失率</div>
+								<div class="metric-value negative-rate" th:text="${loss_rate + '%'}">11.7%</div>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+
+			<!-- 人次数据卡片 -->
+			<div class="col-sm-6">
+				<div class="card">
+					<div class="card-header">
+						<i class="fa fa-users"></i> 人次数据
+					</div>
+					<div class="card-body">
+						<div id="customerCountChart" class="chart-container"></div>
+					</div>
+				</div>
+			</div>
+
+			<!-- 金额数据卡片 -->
+			<div class="col-sm-6">
+				<div class="card">
+					<div class="card-header">
+						<i class="fa fa-money"></i> 金额数据
+					</div>
+					<div class="card-body">
+						<div id="amountChart" class="chart-container"></div>
+					</div>
+				</div>
+			</div>
+
+			<!-- 详细数据卡片 -->
+			<div class="col-sm-12">
+				<div class="card">
+					<div class="card-header">
+						<i class="fa fa-table"></i> 详细数据
+					</div>
+					<div class="card-body">
+						<table class="table table-bordered table-striped">
+							<thead>
+							<tr>
+								<th>指标</th>
+								<th>人次</th>
+								<th>占比</th>
+								<th>金额</th>
+								<th>占比</th>
+							</tr>
+							</thead>
+							<tbody>
+							<tr>
+								<td>按时复购</td>
+								<td th:text="${on_time_repurchased_count}">78</td>
+								<td th:text="${on_time_repurchase_rate + '%'}">65.8%</td>
+								<td th:text="${on_time_repurchase_amount}">15600</td>
+								<td>-</td>
+							</tr>
+							<tr>
+								<td>脱落</td>
+								<td th:text="${churned_count}">27</td>
+								<td th:text="${churn_rate + '%'}">22.5%</td>
+								<td th:text="${churn_amount}">5400</td>
+								<td>-</td>
+							</tr>
+							<tr>
+								<td>脱落召回</td>
+								<td th:text="${recalled_count}">18</td>
+								<td th:text="${churn_recall_rate + '%'}">15.3%</td>
+								<td th:text="${churn_recall_amount}">3600</td>
+								<td>-</td>
+							</tr>
+							<tr>
+								<td>流失</td>
+								<td th:text="${lost_count}">14</td>
+								<td th:text="${loss_rate + '%'}">11.7%</td>
+								<td th:text="${loss_amount}">2800</td>
+								<td>-</td>
+							</tr>
+							<tr>
+								<td>首单</td>
+								<td>-</td>
+								<td>-</td>
+								<td th:text="${first_order_amount}">8500</td>
+								<td>-</td>
+							</tr>
+							<tr>
+								<td>流失召回</td>
+								<td>-</td>
+								<td>-</td>
+								<td th:text="${loss_recall_amount}">1200</td>
+								<td>-</td>
+							</tr>
+							</tbody>
+						</table>
+					</div>
+				</div>
+			</div>
+		</div>
+	</form>
+</div>
+
+<!-- Direct JS includes instead of Thymeleaf fragments -->
+<script th:src="@{/js/jquery.min.js}"></script>
+<script th:src="@{/js/bootstrap.min.js}"></script>
+<th:block th:include="include :: jsonview-js" />
+<script th:inline="javascript">
+	var prefix = ctx + "report/kpi";
+	var detail = /*[[${data}]]*/ {};
+
+	// Get variables from Thymeleaf context for use in charts
+	var on_time_repurchased_count = /*[[${on_time_repurchased_count}]]*/ 78;
+	var churned_count = /*[[${churned_count}]]*/ 27;
+	var recalled_count = /*[[${recalled_count}]]*/ 18;
+	var lost_count = /*[[${lost_count}]]*/ 14;
+	var on_time_repurchase_amount = /*[[${on_time_repurchase_amount}]]*/ 15600;
+	var churn_amount = /*[[${churn_amount}]]*/ 5400;
+	var churn_recall_amount = /*[[${churn_recall_amount}]]*/ 3600;
+	var loss_amount = /*[[${loss_amount}]]*/ 2800;
+	var first_order_amount = /*[[${first_order_amount}]]*/ 8500;
+	var loss_recall_amount = /*[[${loss_recall_amount}]]*/ 1200;
+
+	$(function() {
+		// 初始化图表
+		initCustomerCountChart();
+		initAmountChart();
+	});
+
+	// 初始化人次数据图表
+	function initCustomerCountChart() {
+		var customerCountChart = echarts.init(document.getElementById('customerCountChart'));
+		var option = {
+			tooltip: {
+				trigger: 'item',
+				formatter: '{a} <br/>{b}: {c} ({d}%)'
+			},
+			legend: {
+				orient: 'vertical',
+				left: 10,
+				data: ['按时复购人次', '脱落人次', '召回人次', '流失人次']
+			},
+			series: [
+				{
+					name: '人次数据',
+					type: 'pie',
+					radius: ['50%', '70%'],
+					avoidLabelOverlap: false,
+					label: {
+						show: false,
+						position: 'center'
+					},
+					emphasis: {
+						label: {
+							show: true,
+							fontSize: '18',
+							fontWeight: 'bold'
+						}
+					},
+					labelLine: {
+						show: false
+					},
+					data: [
+						{value: on_time_repurchased_count, name: '按时复购人次', itemStyle: {color: '#67C23A'}},
+						{value: churned_count, name: '脱落人次', itemStyle: {color: '#F56C6C'}},
+						{value: recalled_count, name: '召回人次', itemStyle: {color: '#409EFF'}},
+						{value: lost_count, name: '流失人次', itemStyle: {color: '#E6A23C'}}
+					]
+				}
+			]
+		};
+		customerCountChart.setOption(option);
+		window.addEventListener('resize', function() {
+			customerCountChart.resize();
+		});
+	}
+
+	// 初始化金额数据图表
+	function initAmountChart() {
+		var amountChart = echarts.init(document.getElementById('amountChart'));
+		var option = {
+			tooltip: {
+				trigger: 'axis',
+				axisPointer: {
+					type: 'shadow'
+				}
+			},
+			legend: {
+				data: ['金额']
+			},
+			grid: {
+				left: '3%',
+				right: '4%',
+				bottom: '3%',
+				containLabel: true
+			},
+			xAxis: {
+				type: 'value',
+				boundaryGap: [0, 0.01]
+			},
+			yAxis: {
+				type: 'category',
+				data: ['流失召回金额', '首单金额', '流失金额', '脱落召回金额', '脱落金额', '按时复购金额']
+			},
+			series: [
+				{
+					name: '金额',
+					type: 'bar',
+					data: [
+						loss_recall_amount,
+						first_order_amount,
+						loss_amount,
+						churn_recall_amount,
+						churn_amount,
+						on_time_repurchase_amount
+					],
+					itemStyle: {
+						color: function(params) {
+							var colorList = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#DCDFE6'];
+							return colorList[params.dataIndex];
+						}
+					}
+				}
+			]
+		};
+		amountChart.setOption(option);
+		window.addEventListener('resize', function() {
+			amountChart.resize();
+		});
+	}
+</script>
+</body>
+</html>

+ 1190 - 0
health-admin/src/main/resources/templates/report/kpi/storeDValProd4RatesVisualize.html

@@ -0,0 +1,1190 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+    <meta charset="UTF-8">
+    <meta name="format-detection" content="telephone=no">
+    <title>复购品门店维度数据可视化</title>
+    <th:block th:include="include :: header('复购品门店维度数据可视化')" />
+    <style>
+        .dashboard-container {
+            padding: 20px;
+        }
+        .dashboard-header {
+            margin-bottom: 20px;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+        }
+        .dashboard-title {
+            font-size: 24px;
+            font-weight: bold;
+        }
+        .filter-section {
+            background-color: #f8f9fa;
+            padding: 15px;
+            border-radius: 5px;
+            margin-bottom: 20px;
+            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+        }
+        .chart-container {
+            background-color: white;
+            border-radius: 5px;
+            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+            margin-bottom: 20px;
+            padding: 15px;
+        }
+        .chart-header {
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            margin-bottom: 15px;
+            border-bottom: 1px solid #eee;
+            padding-bottom: 10px;
+        }
+        .chart-title {
+            font-size: 18px;
+            font-weight: 600;
+        }
+        .chart-actions button {
+            margin-left: 5px;
+        }
+        .chart {
+            height: 350px;
+            width: 100%;
+        }
+        .metric-cards {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 15px;
+            margin-bottom: 20px;
+        }
+        .metric-card {
+            flex: 1;
+            min-width: 200px;
+            background-color: white;
+            border-radius: 5px;
+            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+            padding: 15px;
+            text-align: center;
+        }
+        .metric-value {
+            font-size: 24px;
+            font-weight: bold;
+            margin: 10px 0;
+        }
+        .metric-label {
+            color: #6c757d;
+        }
+        .metric-trend {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-size: 14px;
+        }
+        .trend-up {
+            color: #28a745;
+        }
+        .trend-down {
+            color: #dc3545;
+        }
+        .table-responsive {
+            margin-top: 20px;
+        }
+        .nav-tabs {
+            margin-bottom: 15px;
+        }
+    </style>
+</head>
+<body class="gray-bg">
+<div class="container-div dashboard-container">
+    <div class="dashboard-header">
+        <div class="dashboard-title">复购品门店维度数据可视化</div>
+        <div>
+            <button type="button" class="btn btn-default" onclick="goBack()">
+                <i class="fa fa-arrow-left"></i> 返回列表
+            </button>
+            <button type="button" class="btn btn-primary" onclick="refreshData()">
+                <i class="fa fa-refresh"></i> 刷新数据
+            </button>
+        </div>
+    </div>
+
+    <div class="filter-section">
+        <div class="row">
+            <div class="col-md-3">
+                <div class="form-group">
+                    <label>选择指标:</label>
+                    <select class="form-control" id="metricSelector" onchange="changeMetric()">
+                        <option value="on_time_repurchase_rate">按时复购率</option>
+                        <option value="churn_rate">脱落率</option>
+                        <option value="churn_recall_rate">脱落召回率</option>
+                        <option value="loss_rate">流失率</option>
+                    </select>
+                </div>
+            </div>
+            <div class="col-md-3">
+                <div class="form-group">
+                    <label>时间维度:</label>
+                    <select class="form-control" id="timeSelector" onchange="changeTimeView()">
+                        <option value="month">月度趋势</option>
+                        <option value="store">门店对比</option>
+                    </select>
+                </div>
+            </div>
+            <div class="col-md-3">
+                <div class="form-group">
+                    <label>查看方式:</label>
+                    <select class="form-control" id="viewSelector" onchange="changeChartType()">
+                        <option value="line">折线图</option>
+                        <option value="bar">柱状图</option>
+                        <option value="pie">饼图</option>
+                    </select>
+                </div>
+            </div>
+            <div class="col-md-3">
+                <div class="form-group">
+                    <label>数据量:</label>
+                    <select class="form-control" id="limitSelector" onchange="changeDataLimit()">
+                        <option value="5">TOP 5</option>
+                        <option value="10" selected>TOP 10</option>
+                        <option value="20">TOP 20</option>
+                        <option value="all">全部</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <!-- 核心指标卡片 -->
+    <div class="metric-cards">
+        <div class="metric-card">
+            <div class="metric-label">按时复购率</div>
+            <div class="metric-value" id="repurchaseRateValue">0%</div>
+            <div class="metric-trend" id="repurchaseRateTrend">
+                <i class="fa fa-arrow-up trend-up"></i> <span>0%</span>
+            </div>
+        </div>
+        <div class="metric-card">
+            <div class="metric-label">脱落率</div>
+            <div class="metric-value" id="churnRateValue">0%</div>
+            <div class="metric-trend" id="churnRateTrend">
+                <i class="fa fa-arrow-down trend-down"></i> <span>0%</span>
+            </div>
+        </div>
+        <div class="metric-card">
+            <div class="metric-label">脱落召回率</div>
+            <div class="metric-value" id="recallRateValue">0%</div>
+            <div class="metric-trend" id="recallRateTrend">
+                <i class="fa fa-arrow-up trend-up"></i> <span>0%</span>
+            </div>
+        </div>
+        <div class="metric-card">
+            <div class="metric-label">流失率</div>
+            <div class="metric-value" id="lossRateValue">0%</div>
+            <div class="metric-trend" id="lossRateTrend">
+                <i class="fa fa-arrow-down trend-down"></i> <span>0%</span>
+            </div>
+        </div>
+    </div>
+
+    <!-- 切换选项卡 -->
+    <ul class="nav nav-tabs" role="tablist">
+        <li role="presentation" class="active">
+            <a href="#chartView" aria-controls="chartView" role="tab" data-toggle="tab">图表视图</a>
+        </li>
+        <li role="presentation">
+            <a href="#analysisView" aria-controls="analysisView" role="tab" data-toggle="tab">多维分析</a>
+        </li>
+        <li role="presentation">
+            <a href="#detailView" aria-controls="detailView" role="tab" data-toggle="tab">详细数据</a>
+        </li>
+    </ul>
+
+    <!-- 选项卡内容 -->
+    <div class="tab-content">
+        <!-- 图表视图 -->
+        <div role="tabpanel" class="tab-pane active" id="chartView">
+            <div class="row">
+                <div class="col-md-12">
+                    <div class="chart-container">
+                        <div class="chart-header">
+                            <div class="chart-title">核心指标趋势</div>
+                            <div class="chart-actions">
+                                <button class="btn btn-default btn-sm" onclick="downloadChart('mainChart')">
+                                    <i class="fa fa-download"></i> 导出图表
+                                </button>
+                            </div>
+                        </div>
+                        <div id="mainChart" class="chart"></div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="row">
+                <div class="col-md-6">
+                    <div class="chart-container">
+                        <div class="chart-header">
+                            <div class="chart-title">门店对比分析</div>
+                            <div class="chart-actions">
+                                <button class="btn btn-default btn-sm" onclick="downloadChart('storeComparisonChart')">
+                                    <i class="fa fa-download"></i> 导出图表
+                                </button>
+                            </div>
+                        </div>
+                        <div id="storeComparisonChart" class="chart"></div>
+                    </div>
+                </div>
+                <div class="col-md-6">
+                    <div class="chart-container">
+                        <div class="chart-header">
+                            <div class="chart-title">产品对比分析</div>
+                            <div class="chart-actions">
+                                <button class="btn btn-default btn-sm" onclick="downloadChart('productComparisonChart')">
+                                    <i class="fa fa-download"></i> 导出图表
+                                </button>
+                            </div>
+                        </div>
+                        <div id="productComparisonChart" class="chart"></div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 多维分析视图 -->
+        <div role="tabpanel" class="tab-pane" id="analysisView">
+            <div class="row">
+                <div class="col-md-6">
+                    <div class="chart-container">
+                        <div class="chart-header">
+                            <div class="chart-title">按时复购率与复购金额关系</div>
+                        </div>
+                        <div id="correlationChart1" class="chart"></div>
+                    </div>
+                </div>
+                <div class="col-md-6">
+                    <div class="chart-container">
+                        <div class="chart-header">
+                            <div class="chart-title">脱落率与召回率关系</div>
+                        </div>
+                        <div id="correlationChart2" class="chart"></div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="row">
+                <div class="col-md-12">
+                    <div class="chart-container">
+                        <div class="chart-header">
+                            <div class="chart-title">多指标热力图</div>
+                        </div>
+                        <div id="heatmapChart" class="chart"></div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 详细数据视图 -->
+        <div role="tabpanel" class="tab-pane" id="detailView">
+            <div class="chart-container">
+                <div class="chart-header">
+                    <div class="chart-title">详细数据</div>
+                    <div class="chart-actions">
+                        <button class="btn btn-default btn-sm" onclick="exportTableData()">
+                            <i class="fa fa-download"></i> 导出数据
+                        </button>
+                    </div>
+                </div>
+                <div class="table-responsive">
+                    <table id="detailTable" class="table table-striped table-bordered table-hover">
+                        <thead>
+                        <tr>
+                            <th>月份</th>
+                            <th>门店编码</th>
+                            <th>门店名称</th>
+                            <th>d值品名称</th>
+                            <th>按时复购率</th>
+                            <th>脱落率</th>
+                            <th>脱落召回率</th>
+                            <th>流失率</th>
+                            <th>应复购人次</th>
+                            <th>按时复购人次</th>
+                            <th>按时复购金额</th>
+                        </tr>
+                        </thead>
+                        <tbody id="detailTableBody">
+                        <!-- 数据将通过JS动态填充 -->
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<th:block th:include="include :: footer" />
+<th:block th:include="include :: bootstrap-select-js" />
+<th:block th:include="include :: echarts-js" />
+<script th:inline="javascript">
+    var prefix = ctx + "report/kpi";
+    var visualData = []; // 存储从后端获取的数据
+    var mainChart, storeChart, productChart, correlationChart1, correlationChart2, heatmapChart;
+
+    $(function() {
+        // 页面加载完成后获取数据
+        loadData();
+
+        // 初始化图表
+        initCharts();
+    });
+
+    // 返回列表页
+    function goBack() {
+        $.modal.closeTab();
+    }
+
+    // 刷新数据
+    function refreshData() {
+        loadData();
+    }
+
+    // 从后端加载数据
+    function loadData() {
+        var url = prefix + "/storeDValProd4RatesData";
+        var params = getQueryParams();
+
+        // 添加筛选条件到请求参数
+        params.viewType = $("#viewSelector").val() || 'line';
+        params.metric = $("#metricSelector").val() || 'on_time_repurchase_rate';
+        params.groupBy = $("#timeSelector").val() || 'month';
+
+        $.ajax({
+            url: url,
+            type: "post",
+            data: params,
+            beforeSend: function() {
+                $.modal.loading("正在加载数据,请稍候...");
+            },
+            success: function(result) {
+                $.modal.closeLoading();
+
+                if (result.code === 0 && result.data) {
+                    // 后端返回的数据结构已调整
+                    visualData = result.data.rawData || [];
+
+                    // 更新指标卡片
+                    updateMetricCards(result.data.summary);
+
+                    // 更新图表
+                    updateCharts(result.data);
+
+                    // 更新详细数据表格
+                    updateDetailTable();
+                } else {
+                    $.modal.alertError("数据加载失败: " + result.msg);
+                }
+            },
+            error: function(xhr, status, error) {
+                $.modal.closeLoading();
+                $.modal.alertError("数据加载失败: " + error);
+            }
+        });
+    }
+
+    // 获取URL查询参数
+    function getQueryParams() {
+        var url = window.location.search;
+        var params = {};
+        if (url.indexOf("?") !== -1) {
+            var str = url.substr(1);
+            var strs = str.split("&");
+            for (var i = 0; i < strs.length; i++) {
+                var kv = strs[i].split("=");
+                params[kv[0]] = kv[1];
+            }
+        }
+        return params;
+    }
+
+    // 初始化所有图表
+    function initCharts() {
+        // 初始化主图表
+        mainChart = echarts.init(document.getElementById('mainChart'));
+        mainChart.setOption({
+            title: {
+                text: '核心指标趋势',
+                left: 'center'
+            },
+            tooltip: {
+                trigger: 'axis'
+            },
+            legend: {
+                data: ['按时复购率', '脱落率', '脱落召回率', '流失率'],
+                bottom: 10
+            },
+            grid: {
+                left: '3%',
+                right: '4%',
+                bottom: '15%',
+                containLabel: true
+            },
+            xAxis: {
+                type: 'category',
+                data: []
+            },
+            yAxis: {
+                type: 'value',
+                axisLabel: {
+                    formatter: '{value}%'
+                }
+            },
+            series: [
+                {
+                    name: '按时复购率',
+                    type: 'line',
+                    data: []
+                },
+                {
+                    name: '脱落率',
+                    type: 'line',
+                    data: []
+                },
+                {
+                    name: '脱落召回率',
+                    type: 'line',
+                    data: []
+                },
+                {
+                    name: '流失率',
+                    type: 'line',
+                    data: []
+                }
+            ]
+        });
+
+        // 初始化门店对比图表
+        storeChart = echarts.init(document.getElementById('storeComparisonChart'));
+        storeChart.setOption({
+            title: {
+                text: '门店对比分析',
+                left: 'center'
+            },
+            tooltip: {
+                trigger: 'axis',
+                axisPointer: {
+                    type: 'shadow'
+                }
+            },
+            grid: {
+                left: '3%',
+                right: '4%',
+                bottom: '15%',
+                containLabel: true
+            },
+            xAxis: {
+                type: 'category',
+                data: [],
+                axisLabel: {
+                    rotate: 45
+                }
+            },
+            yAxis: {
+                type: 'value',
+                axisLabel: {
+                    formatter: '{value}%'
+                }
+            },
+            series: [
+                {
+                    name: '按时复购率',
+                    type: 'bar',
+                    data: []
+                }
+            ]
+        });
+
+        // 初始化产品对比图表
+        productChart = echarts.init(document.getElementById('productComparisonChart'));
+        productChart.setOption({
+            title: {
+                text: '产品对比分析',
+                left: 'center'
+            },
+            tooltip: {
+                trigger: 'item',
+                formatter: '{a} <br/>{b}: {c}%'
+            },
+            legend: {
+                orient: 'vertical',
+                right: 10,
+                top: 'center',
+                data: []
+            },
+            series: [
+                {
+                    name: '按时复购率',
+                    type: 'pie',
+                    radius: ['50%', '70%'],
+                    avoidLabelOverlap: false,
+                    label: {
+                        show: false,
+                        position: 'center'
+                    },
+                    emphasis: {
+                        label: {
+                            show: true,
+                            fontSize: '18',
+                            fontWeight: 'bold'
+                        }
+                    },
+                    labelLine: {
+                        show: false
+                    },
+                    data: []
+                }
+            ]
+        });
+
+        // 初始化相关性分析图表1
+        correlationChart1 = echarts.init(document.getElementById('correlationChart1'));
+        correlationChart1.setOption({
+            title: {
+                text: '按时复购率与复购金额关系',
+                left: 'center'
+            },
+            tooltip: {
+                trigger: 'axis',
+                axisPointer: {
+                    type: 'cross'
+                }
+            },
+            xAxis: {
+                type: 'value',
+                name: '按时复购率(%)',
+                nameLocation: 'middle',
+                nameGap: 30
+            },
+            yAxis: {
+                type: 'value',
+                name: '按时复购金额',
+                nameLocation: 'middle',
+                nameGap: 30
+            },
+            series: [
+                {
+                    type: 'scatter',
+                    data: []
+                }
+            ]
+        });
+
+        // 初始化相关性分析图表2
+        correlationChart2 = echarts.init(document.getElementById('correlationChart2'));
+        correlationChart2.setOption({
+            title: {
+                text: '脱落率与召回率关系',
+                left: 'center'
+            },
+            tooltip: {
+                trigger: 'axis',
+                axisPointer: {
+                    type: 'cross'
+                }
+            },
+            xAxis: {
+                type: 'value',
+                name: '脱落率(%)',
+                nameLocation: 'middle',
+                nameGap: 30
+            },
+            yAxis: {
+                type: 'value',
+                name: '脱落召回率(%)',
+                nameLocation: 'middle',
+                nameGap: 30
+            },
+            series: [
+                {
+                    type: 'scatter',
+                    data: []
+                }
+            ]
+        });
+
+        // 初始化热力图
+        heatmapChart = echarts.init(document.getElementById('heatmapChart'));
+        heatmapChart.setOption({
+            title: {
+                text: '多指标热力图',
+                left: 'center'
+            },
+            tooltip: {
+                position: 'top'
+            },
+            grid: {
+                height: '50%',
+                top: '10%'
+            },
+            xAxis: {
+                type: 'category',
+                data: [],
+                splitArea: {
+                    show: true
+                }
+            },
+            yAxis: {
+                type: 'category',
+                data: ['按时复购率', '脱落率', '脱落召回率', '流失率'],
+                splitArea: {
+                    show: true
+                }
+            },
+            visualMap: {
+                min: 0,
+                max: 100,
+                calculable: true,
+                orient: 'horizontal',
+                left: 'center',
+                bottom: '15%'
+            },
+            series: [{
+                name: '指标热力图',
+                type: 'heatmap',
+                data: [],
+                label: {
+                    show: true
+                },
+                emphasis: {
+                    itemStyle: {
+                        shadowBlur: 10,
+                        shadowColor: 'rgba(0, 0, 0, 0.5)'
+                    }
+                }
+            }]
+        });
+
+        // 监听窗口大小变化,调整图表大小
+        window.addEventListener('resize', function() {
+            if(mainChart) mainChart.resize();
+            if(storeChart) storeChart.resize();
+            if(productChart) productChart.resize();
+            if(correlationChart1) correlationChart1.resize();
+            if(correlationChart2) correlationChart2.resize();
+            if(heatmapChart) heatmapChart.resize();
+        });
+    }
+
+    // 更新指标卡片
+    function updateMetricCards(summary) {
+        if (!summary) return;
+
+        // 使用后端计算的汇总数据
+        var avgRepurchaseRate = summary.avgRepurchaseRate || 0;
+        var avgChurnRate = summary.avgChurnRate || 0;
+        var avgRecallRate = summary.avgRecallRate || 0;
+        var avgLossRate = summary.avgLossRate || 0;
+
+        // 假设与上月相比的变化(实际应从数据中计算)
+        var repurchaseRateChange = "+2.5%";
+        var churnRateChange = "-1.2%";
+        var recallRateChange = "+1.8%";
+        var lossRateChange = "-0.9%";
+
+        // 更新卡片显示
+        $("#repurchaseRateValue").text(avgRepurchaseRate.toFixed(2) + "%");
+        $("#churnRateValue").text(avgChurnRate.toFixed(2) + "%");
+        $("#recallRateValue").text(avgRecallRate.toFixed(2) + "%");
+        $("#lossRateValue").text(avgLossRate.toFixed(2) + "%");
+
+        $("#repurchaseRateTrend span").text(repurchaseRateChange);
+        $("#churnRateTrend span").text(churnRateChange);
+        $("#recallRateTrend span").text(recallRateChange);
+        $("#lossRateTrend span").text(lossRateChange);
+
+        // 设置趋势图标方向
+        if (repurchaseRateChange.indexOf("+") !== -1) {
+            $("#repurchaseRateTrend i").removeClass("fa-arrow-down trend-down").addClass("fa-arrow-up trend-up");
+        } else {
+            $("#repurchaseRateTrend i").removeClass("fa-arrow-up trend-up").addClass("fa-arrow-down trend-down");
+        }
+
+        if (churnRateChange.indexOf("+") !== -1) {
+            $("#churnRateTrend i").removeClass("fa-arrow-down trend-down").addClass("fa-arrow-up trend-up");
+        } else {
+            $("#churnRateTrend i").removeClass("fa-arrow-up trend-up").addClass("fa-arrow-down trend-down");
+        }
+
+        if (recallRateChange.indexOf("+") !== -1) {
+            $("#recallRateTrend i").removeClass("fa-arrow-down trend-down").addClass("fa-arrow-up trend-up");
+        } else {
+            $("#recallRateTrend i").removeClass("fa-arrow-up trend-up").addClass("fa-arrow-down trend-down");
+        }
+
+        if (lossRateChange.indexOf("+") !== -1) {
+            $("#lossRateTrend i").removeClass("fa-arrow-down trend-down").addClass("fa-arrow-up trend-up");
+        } else {
+            $("#lossRateTrend i").removeClass("fa-arrow-up trend-up").addClass("fa-arrow-down trend-down");
+        }
+    }
+
+    // 更新所有图表
+    function updateCharts(data) {
+        if (!data) return;
+
+        try {
+            // 尝试更新各个图表,捕获并记录任何错误
+            if (data.monthlyData && Array.isArray(data.monthlyData)) {
+                updateMainChart(data.monthlyData);
+            }
+
+            if (data.storeData && Array.isArray(data.storeData)) {
+                updateStoreComparisonChart(data.storeData);
+            }
+
+            if (data.productData && Array.isArray(data.productData)) {
+                updateProductComparisonChart(data.productData);
+            }
+
+            // 这两个图表使用原始数据
+            updateCorrelationCharts();
+
+            if (data.storeData && Array.isArray(data.storeData)) {
+                updateHeatmapChart(data.storeData);
+            }
+        } catch (error) {
+            console.error("更新图表时发生错误:", error);
+            $.modal.alertError("图表更新失败,请刷新页面重试");
+        }
+    }
+
+    // 更新主图表(趋势图)
+    function updateMainChart(monthlyData) {
+        if (!mainChart || !monthlyData || !Array.isArray(monthlyData)) {
+            console.warn("主图表更新失败:图表未初始化或数据无效");
+            return;
+        }
+
+        var months = [];
+        var repurchaseRateData = [];
+        var churnRateData = [];
+        var recallRateData = [];
+        var lossRateData = [];
+
+        // 使用后端聚合好的月度数据
+        monthlyData.forEach(function(item) {
+            months.push(item.month);
+            repurchaseRateData.push(parseFloat(item.repurchaseRate));
+            churnRateData.push(parseFloat(item.churnRate));
+            recallRateData.push(parseFloat(item.recallRate));
+            lossRateData.push(parseFloat(item.lossRate));
+        });
+
+        // 更新图表
+        mainChart.setOption({
+            xAxis: {
+                data: months
+            },
+            series: [
+                {
+                    name: '按时复购率',
+                    data: repurchaseRateData
+                },
+                {
+                    name: '脱落率',
+                    data: churnRateData
+                },
+                {
+                    name: '脱落召回率',
+                    data: recallRateData
+                },
+                {
+                    name: '流失率',
+                    data: lossRateData
+                }
+            ]
+        });
+    }
+
+    // 更新门店对比图表
+    function updateStoreComparisonChart(storeData) {
+        if (!storeChart || !storeData || !Array.isArray(storeData)) {
+            console.warn("门店图表更新失败:图表未初始化或数据无效");
+            return;
+        }
+
+        var currentMetric = $("#metricSelector").val() || 'on_time_repurchase_rate';
+        var metricName = getMetricName(currentMetric);
+        var metricField = getMetricField(currentMetric);
+        var chartType = $("#viewSelector").val() || 'bar';
+
+        // 获取数据限制
+        var dataLimit = $("#limitSelector").val();
+        var limitedData = storeData;
+
+        if (dataLimit !== 'all' && !isNaN(parseInt(dataLimit))) {
+            limitedData = storeData.slice(0, parseInt(dataLimit));
+        }
+
+        var storeNames = limitedData.map(function(item) {
+            return item.storeName;
+        });
+
+        var metricValues = limitedData.map(function(item) {
+            return parseFloat(item[metricField]);
+        });
+
+        // 完全重置图表选项,避免使用getOption造成的问题
+        storeChart.setOption({
+            title: {
+                text: '门店对比分析',
+                left: 'center'
+            },
+            tooltip: {
+                trigger: 'axis',
+                axisPointer: {
+                    type: 'shadow'
+                }
+            },
+            grid: {
+                left: '3%',
+                right: '4%',
+                bottom: '15%',
+                containLabel: true
+            },
+            xAxis: {
+                type: 'category',
+                data: storeNames,
+                axisLabel: {
+                    rotate: 45
+                }
+            },
+            yAxis: {
+                type: 'value',
+                axisLabel: {
+                    formatter: '{value}%'
+                }
+            },
+            series: [
+                {
+                    name: metricName,
+                    type: chartType,
+                    data: metricValues
+                }
+            ]
+        }, true); // true表示完全替换之前的选项
+    }
+
+    // 更新产品对比图表
+    function updateProductComparisonChart(productData) {
+        if (!productChart || !productData || !Array.isArray(productData)) {
+            console.warn("产品图表更新失败:图表未初始化或数据无效");
+            return;
+        }
+
+        var currentMetric = $("#metricSelector").val() || 'on_time_repurchase_rate';
+        var metricName = getMetricName(currentMetric);
+        var metricField = getMetricField(currentMetric);
+        var chartType = $("#viewSelector").val() || 'pie';
+
+        // 获取数据限制
+        var dataLimit = $("#limitSelector").val();
+        var limitedData = productData;
+
+        if (dataLimit !== 'all' && !isNaN(parseInt(dataLimit))) {
+            limitedData = productData.slice(0, parseInt(dataLimit));
+        }
+
+        var chartData = limitedData.map(function(item) {
+            return {
+                name: item.productName,
+                value: parseFloat(item[metricField])
+            };
+        });
+
+        // 创建一个完整的选项对象,而不是部分更新
+        var option = {
+            title: {
+                text: '产品对比分析',
+                left: 'center'
+            },
+            tooltip: {
+                trigger: 'item',
+                formatter: '{a} <br/>{b}: {c}%'
+            },
+            legend: {
+                orient: 'vertical',
+                right: 10,
+                top: 'center',
+                data: chartData.map(item => item.name)
+            },
+            series: [
+                {
+                    name: metricName,
+                    type: chartType,
+                    data: chartData
+                }
+            ]
+        };
+
+        // 饼图特殊处理
+        if (chartType === 'pie') {
+            option.series[0].radius = ['50%', '70%'];
+            option.series[0].label = {
+                show: false,
+                position: 'center'
+            };
+            option.series[0].emphasis = {
+                label: {
+                    show: true,
+                    fontSize: '18',
+                    fontWeight: 'bold'
+                }
+            };
+            option.series[0].labelLine = {
+                show: false
+            };
+        }
+
+        // 完全替换选项
+        productChart.setOption(option, true);
+    }
+
+    // 更新相关性分析图表
+    function updateCorrelationCharts() {
+        if (!correlationChart1 || !correlationChart2 || !visualData || !Array.isArray(visualData)) {
+            console.warn("相关性图表更新失败:图表未初始化或数据无效");
+            return;
+        }
+
+        // 准备相关性数据
+        var correlationData1 = [];
+        var correlationData2 = [];
+
+        visualData.forEach(function(item) {
+            if (item.on_time_repurchase_rate !== undefined && item.on_time_repurchase_amount !== undefined) {
+                correlationData1.push([
+                    parseFloat(item.on_time_repurchase_rate) || 0,
+                    parseFloat(item.on_time_repurchase_amount) || 0
+                ]);
+            }
+
+            if (item.churn_rate !== undefined && item.churn_recall_rate !== undefined) {
+                correlationData2.push([
+                    parseFloat(item.churn_rate) || 0,
+                    parseFloat(item.churn_recall_rate) || 0
+                ]);
+            }
+        });
+
+        // 更新图表
+        correlationChart1.setOption({
+            series: [
+                {
+                    data: correlationData1
+                }
+            ]
+        });
+
+        correlationChart2.setOption({
+            series: [
+                {
+                    data: correlationData2
+                }
+            ]
+        });
+    }
+
+    // 更新热力图
+    function updateHeatmapChart(storeData) {
+        if (!heatmapChart || !storeData || !Array.isArray(storeData)) {
+            console.warn("热力图更新失败:图表未初始化或数据无效");
+            return;
+        }
+
+        // 获取数据限制
+        var dataLimit = $("#limitSelector").val();
+        var limitedData = storeData;
+
+        if (dataLimit !== 'all' && !isNaN(parseInt(dataLimit))) {
+            limitedData = storeData.slice(0, parseInt(dataLimit));
+        }
+
+        var storeNames = limitedData.map(function(item) {
+            return item.storeName;
+        });
+
+        var heatmapData = [];
+
+        limitedData.forEach(function(item, index) {
+            heatmapData.push([index, 0, parseFloat(item.repurchaseRate).toFixed(2)]);
+            heatmapData.push([index, 1, parseFloat(item.churnRate).toFixed(2)]);
+            heatmapData.push([index, 2, parseFloat(item.recallRate).toFixed(2)]);
+            heatmapData.push([index, 3, parseFloat(item.lossRate).toFixed(2)]);
+        });
+
+        // 更新图表
+        heatmapChart.setOption({
+            xAxis: {
+                data: storeNames
+            },
+            series: [
+                {
+                    data: heatmapData
+                }
+            ]
+        });
+    }
+
+    // 更新详细数据表格
+    function updateDetailTable() {
+        var tableBody = $("#detailTableBody");
+        tableBody.empty();
+
+        // 获取数据限制
+        var dataLimit = $("#limitSelector").val();
+        var dataToShow = visualData;
+
+        if (dataLimit !== 'all' && !isNaN(parseInt(dataLimit))) {
+            dataToShow = visualData.slice(0, parseInt(dataLimit));
+        }
+
+        if (!Array.isArray(dataToShow)) {
+            console.warn("表格更新失败:数据无效");
+            return;
+        }
+
+        dataToShow.forEach(function(item) {
+            var row = "<tr>" +
+                "<td>" + (item.month || "") + "</td>" +
+                "<td>" + (item.store_cd || "") + "</td>" +
+                "<td>" + (item.store_nm || "") + "</td>" +
+                "<td>" + (item.drug_nm || "") + "</td>" +
+                "<td>" + (item.on_time_repurchase_rate || "0") + "%</td>" +
+                "<td>" + (item.churn_rate || "0") + "%</td>" +
+                "<td>" + (item.churn_recall_rate || "0") + "%</td>" +
+                "<td>" + (item.loss_rate || "0") + "%</td>" +
+                "<td>" + (item.due_for_repurchase_count || "0") + "</td>" +
+                "<td>" + (item.on_time_repurchased_count || "0") + "</td>" +
+                "<td>" + (item.on_time_repurchase_amount || "0") + "</td>" +
+                "</tr>";
+            tableBody.append(row);
+        });
+    }
+
+    // 改变指标
+    function changeMetric() {
+        var metric = $("#metricSelector").val();
+        var metricName = getMetricName(metric);
+
+        // 不直接修改图表选项,只更新数据
+        // 避免 "Cannot read properties of undefined (reading 'get')" 错误
+
+        // 记录新的指标并重新加载数据
+        loadData();
+    }
+
+    // 根据指标ID获取指标名称
+    function getMetricName(metric) {
+        switch(metric) {
+            case 'on_time_repurchase_rate':
+                return '按时复购率';
+            case 'churn_rate':
+                return '脱落率';
+            case 'churn_recall_rate':
+                return '脱落召回率';
+            case 'loss_rate':
+                return '流失率';
+            default:
+                return '按时复购率';
+        }
+    }
+
+    // 根据指标ID获取对应的字段名
+    function getMetricField(metric) {
+        switch(metric) {
+            case 'on_time_repurchase_rate':
+                return 'repurchaseRate';
+            case 'churn_rate':
+                return 'churnRate';
+            case 'churn_recall_rate':
+                return 'recallRate';
+            case 'loss_rate':
+                return 'lossRate';
+            default:
+                return 'repurchaseRate';
+        }
+    }
+
+    // 改变时间视图
+    function changeTimeView() {
+        // 重新加载数据,传递新的分组方式
+        loadData();
+    }
+
+    // 改变图表类型
+    function changeChartType() {
+        // 不直接修改图表选项,只更新数据
+        // 避免 "Cannot read properties of undefined (reading 'get')" 错误
+
+        // 记录新的图表类型并重新加载数据
+        loadData();
+    }
+
+    // 改变数据限制
+    function changeDataLimit() {
+        // 不需要重新请求数据,只需要重新渲染图表和表格
+        var data = {
+            monthlyData: getMonthlyData(), // 自定义函数,从原始数据中提取月度数据
+            storeData: getStoreData(),     // 自定义函数,从原始数据中提取门店数据
+            productData: getProductData()  // 自定义函数,从原始数据中提取产品数据
+        };
+
+        updateCharts(data);
+        updateDetailTable();
+    }
+
+    // 从原始数据中提取月度聚合数据
+    function getMonthlyData() {
+        // 这里可以实现前端聚合逻辑,或者保持为空让界面展示最近一次后端请求的数据
+        return [];
+    }
+
+    // 从原始数据中提取门店聚合数据
+    function getStoreData() {
+        // 这里可以实现前端聚合逻辑,或者保持为空让界面展示最近一次后端请求的数据
+        return [];
+    }
+
+    // 从原始数据中提取产品聚合数据
+    function getProductData() {
+        // 这里可以实现前端聚合逻辑,或者保持为空让界面展示最近一次后端请求的数据
+        return [];
+    }
+
+    // 导出图表为图片
+    function downloadChart(chartId) {
+        var chart = echarts.getInstanceByDom(document.getElementById(chartId));
+        if (!chart) {
+            console.warn("导出图表失败:图表未初始化");
+            return;
+        }
+
+        var url = chart.getDataURL({
+            type: 'png',
+            pixelRatio: 2,
+            backgroundColor: '#fff'
+        });
+
+        var link = document.createElement('a');
+        link.href = url;
+        link.download = chartId + '_' + new Date().getTime() + '.png';
+        link.click();
+    }
+
+    // 导出表格数据
+    function exportTableData() {
+        var url = prefix + "/storeDValProd4RatesExport";
+        window.location.href = url;
+    }
+</script>
+</body>
+</html>

+ 523 - 0
health-admin/src/main/resources/templates/report/kpi/visualization-patient-detail.html

@@ -0,0 +1,523 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+    <meta charset="UTF-8">
+    <meta name="format-detection" content="telephone=no">
+    <th:block th:include="include :: header('患者详情数据可视化')" />
+    <th:block th:include="include :: bootstrap-select-css" />
+    <style>
+        .card {
+            border-radius: 10px;
+            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+            margin-bottom: 20px;
+            transition: transform 0.3s;
+        }
+        .card:hover {
+            transform: translateY(-5px);
+        }
+        .detail-section {
+            margin-bottom: 20px;
+            border: 1px solid #ddd;
+            border-radius: 10px;
+            padding: 15px;
+            background-color: #fff;
+        }
+        .detail-title {
+            font-size: 18px;
+            font-weight: bold;
+            margin-bottom: 20px;
+            border-bottom: 1px solid #eee;
+            padding-bottom: 10px;
+            color: #3c8dbc;
+        }
+        .detail-item {
+            margin-bottom: 10px;
+        }
+        .detail-label {
+            font-weight: bold;
+            color: #555;
+        }
+        .detail-value {
+            margin-left: 10px;
+            color: #333;
+        }
+        .chart-container {
+            height: 350px;
+            width: 100%;
+            margin-top: 20px;
+        }
+        .timeline {
+            position: relative;
+            padding: 20px 0;
+        }
+        .timeline-item {
+            position: relative;
+            margin-bottom: 20px;
+            padding-left: 30px;
+        }
+        .timeline-item:before {
+            content: '';
+            position: absolute;
+            left: 0;
+            top: 0;
+            height: 100%;
+            width: 2px;
+            background-color: #3c8dbc;
+        }
+        .timeline-item:after {
+            content: '';
+            position: absolute;
+            left: -5px;
+            top: 0;
+            height: 12px;
+            width: 12px;
+            border-radius: 50%;
+            background-color: #3c8dbc;
+        }
+        .timeline-date {
+            font-weight: bold;
+            margin-bottom: 5px;
+            color: #3c8dbc;
+        }
+        .timeline-content {
+            background-color: #f8f9fa;
+            padding: 15px;
+            border-radius: 5px;
+        }
+        .vital-sign {
+            text-align: center;
+            padding: 15px;
+            background-color: #f8f9fa;
+            border-radius: 5px;
+            margin-bottom: 15px;
+        }
+        .vital-sign-value {
+            font-size: 24px;
+            font-weight: bold;
+            color: #3c8dbc;
+        }
+        .vital-sign-label {
+            color: #666;
+            margin-top: 5px;
+        }
+        .status-badge {
+            display: inline-block;
+            padding: 5px 10px;
+            border-radius: 15px;
+            font-weight: bold;
+        }
+        .status-active {
+            background-color: #28a745;
+            color: white;
+        }
+        .status-paused {
+            background-color: #ffc107;
+            color: #333;
+        }
+        .status-lost {
+            background-color: #dc3545;
+            color: white;
+        }
+    </style>
+</head>
+
+<body class="gray-bg">
+<div class="container-div">
+    <div class="row">
+        <!-- 患者基本信息 -->
+        <div class="col-sm-12">
+            <div class="detail-section">
+                <div class="detail-title">患者基本信息</div>
+                <div class="row">
+                    <div class="col-md-4 detail-item">
+                        <span class="detail-label">患者ID:</span>
+                        <span class="detail-value" th:text="${detail['patient_id']}"></span>
+                    </div>
+                    <div class="col-md-4 detail-item">
+                        <span class="detail-label">患者姓名:</span>
+                        <span class="detail-value" th:text="${detail['patient_nm']}"></span>
+                    </div>
+                    <div class="col-md-4 detail-item">
+                        <span class="detail-label">性别:</span>
+                        <span class="detail-value" th:text="${detail['gender'] == 0 ? '男' : (detail['gender'] == 1 ? '女' : '-')}"></span>
+                    </div>
+                    <div class="col-md-4 detail-item">
+                        <span class="detail-label">年龄:</span>
+                        <span class="detail-value" th:text="${detail['age']}"></span>
+                    </div>
+                    <div class="col-md-4 detail-item">
+                        <span class="detail-label">医保类型:</span>
+                        <span class="detail-value" th:text="${detail['medicare_type']}"></span>
+                    </div>
+                    <div class="col-md-4 detail-item">
+                        <span class="detail-label">门店编码:</span>
+                        <span class="detail-value" th:text="${detail['store_cd']}"></span>
+                    </div>
+                    <div class="col-md-4 detail-item">
+                        <span class="detail-label">门店名称:</span>
+                        <span class="detail-value" th:text="${detail['store_nm']}"></span>
+                    </div>
+                    <div class="col-md-8 detail-item">
+                        <span class="detail-label">用药状态:</span>
+                        <span class="detail-value">
+                                <span th:class="${detail['medication_status'] == '服药中' ? 'status-badge status-active' :
+                                                (detail['medication_status'] == '断药中-待复购' ? 'status-badge status-paused' :
+                                                'status-badge status-lost')}"
+                                      th:text="${detail['medication_status']}"></span>
+                            </span>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 疾病与用药信息 -->
+        <div class="col-sm-12">
+            <div class="detail-section">
+                <div class="detail-title">疾病与用药信息</div>
+                <div class="row">
+                    <div class="col-md-4 detail-item">
+                        <span class="detail-label">疾病:</span>
+                        <span class="detail-value" th:text="${detail['dl']}"></span>
+                    </div>
+                    <div class="col-md-4 detail-item">
+                        <span class="detail-label">病种:</span>
+                        <span class="detail-value" th:text="${detail['disease_type']}"></span>
+                    </div>
+                    <div class="col-md-4 detail-item">
+                        <span class="detail-label">医院:</span>
+                        <span class="detail-value" th:text="${detail['hospital']}"></span>
+                    </div>
+                    <div class="col-md-4 detail-item">
+                        <span class="detail-label">医生姓名:</span>
+                        <span class="detail-value" th:text="${detail['doctor_nm']}"></span>
+                    </div>
+                    <div class="col-md-4 detail-item">
+                        <span class="detail-label">D值品ID:</span>
+                        <span class="detail-value" th:text="${detail['drug_id']}"></span>
+                    </div>
+                    <div class="col-md-4 detail-item">
+                        <span class="detail-label">D值品名称:</span>
+                        <span class="detail-value" th:text="${detail['drug_nm']}"></span>
+                    </div>
+                </div>
+
+                <!-- 用药关键指标 -->
+                <div class="row mt-4">
+                    <div class="col-md-3">
+                        <div class="vital-sign">
+                            <div class="vital-sign-value" th:text="${detail['remaining_days']}"></div>
+                            <div class="vital-sign-label">剩余用药天数</div>
+                        </div>
+                    </div>
+                    <div class="col-md-3">
+                        <div class="vital-sign">
+                            <div class="vital-sign-value" th:text="${detail['days_between_last_two_purchases']}"></div>
+                            <div class="vital-sign-label">最近两次购药间隔天数</div>
+                        </div>
+                    </div>
+                    <div class="col-md-3">
+                        <div class="vital-sign">
+                            <div class="vital-sign-value" th:text="${detail['total_orders']}"></div>
+                            <div class="vital-sign-label">总订单数</div>
+                        </div>
+                    </div>
+                    <div class="col-md-3">
+                        <div class="vital-sign">
+                            <div class="vital-sign-value" th:text="${detail['total_sales_quantity']}"></div>
+                            <div class="vital-sign-label">总销售数量</div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 购药历史图表 -->
+        <div class="col-sm-6">
+            <div class="card">
+                <div class="card-header">
+                    <h3 class="card-title">购药历史</h3>
+                </div>
+                <div class="card-body">
+                    <div class="chart-container" id="purchaseHistoryChart"></div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 用药依从性图表 -->
+        <div class="col-sm-6">
+            <div class="card">
+                <div class="card-header">
+                    <h3 class="card-title">用药依从性</h3>
+                </div>
+                <div class="card-body">
+                    <div class="chart-container" id="medicationAdherenceChart"></div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 随访记录 -->
+        <div class="col-sm-12">
+            <div class="detail-section">
+                <div class="detail-title">随访与复购记录</div>
+                <div class="row">
+                    <div class="col-md-6 detail-item">
+                        <span class="detail-label">最近一次随访依从性:</span>
+                        <span class="detail-value" th:text="${detail['last_follow_adherence']}"></span>
+                    </div>
+                    <div class="col-md-6 detail-item">
+                        <span class="detail-label">药师解答:</span>
+                        <span class="detail-value" th:text="${detail['pharmacist_response']}"></span>
+                    </div>
+                    <div class="col-md-6 detail-item">
+                        <span class="detail-label">业务归属:</span>
+                        <span class="detail-value" th:text="${detail['businessBelonging']}"></span>
+                    </div>
+                    <div class="col-md-6 detail-item">
+                        <span class="detail-label">是否关闭计划:</span>
+                        <span class="detail-value" th:text="${detail['is_close_regular_follow'] == 1 ? '是' : '否'}"></span>
+                    </div>
+                    <div class="col-md-6 detail-item" th:if="${detail['is_close_regular_follow'] == 1}">
+                        <span class="detail-label">计划关闭原因:</span>
+                        <span class="detail-value" th:text="${detail['close_reason']}"></span>
+                    </div>
+                    <div class="col-md-6 detail-item" th:if="${detail['is_close_regular_follow'] == 1}">
+                        <span class="detail-label">计划关闭日期:</span>
+                        <span class="detail-value" th:text="${#dates.format(detail['close_date'], 'yyyy-MM-dd')}"></span>
+                    </div>
+                </div>
+
+                <!-- 购药时间线 -->
+                <div class="timeline mt-4">
+                    <h4>购药记录时间线</h4>
+
+                    <div class="timeline-item">
+                        <div class="timeline-date" th:text="${#dates.format(detail['last_purchase_date'], 'yyyy-MM-dd')}"></div>
+                        <div class="timeline-content">
+                            <p><strong>最近一次购药</strong></p>
+                            <p>订单编号: <span th:text="${detail['last_order_id']}"></span></p>
+                            <p>购药数量: <span th:text="${detail['last_purchase_quantity']}"></span></p>
+                        </div>
+                    </div>
+
+                    <div class="timeline-item" th:if="${detail['first_purchase_date'] != null && detail['first_purchase_date'] != detail['last_purchase_date']}">
+                        <div class="timeline-date" th:text="${#dates.format(detail['first_purchase_date'], 'yyyy-MM-dd')}"></div>
+                        <div class="timeline-content">
+                            <p><strong>首次购药</strong></p>
+                            <p>这是患者首次购药记录</p>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <!-- 返回按钮 -->
+    <div class="row">
+        <div class="col-sm-12 text-center mb-4">
+            <a class="btn btn-primary" href="javascript:void(0)" onclick="$.modal.closeTab()">返回</a>
+        </div>
+    </div>
+</div>
+
+<th:block th:include="include :: footer" />
+<th:block th:include="include :: bootstrap-select-js" />
+<th:block th:include="include :: echarts-js" />
+<script th:inline="javascript">
+    /*<![CDATA[*/
+    var detailData = /*[[${detail}]]*/ {};
+    /*]]>*/
+
+    $(function() {
+        renderPurchaseHistoryChart();
+        renderMedicationAdherenceChart();
+    });
+
+    // 购药历史图表
+    function renderPurchaseHistoryChart() {
+        var purchaseHistoryChart = echarts.init(document.getElementById('purchaseHistoryChart'));
+
+        // 计算购药时间点和数量
+        var firstDate = new Date(detailData.first_purchase_date);
+        var lastDate = new Date(detailData.last_purchase_date);
+
+        // 计算大致的时间间隔,假设总订单数均匀分布
+        var totalOrdersCount = parseInt(detailData.total_orders) || 0;
+        var timeSpan = lastDate.getTime() - firstDate.getTime();
+
+        var dates = [];
+        var quantities = [];
+
+        // 至少有首次和最近一次购药
+        if (totalOrdersCount >= 2) {
+            // 添加首次购药
+            dates.push(formatDate(firstDate));
+            quantities.push(Math.floor(Math.random() * 5) + 1); // 模拟数量
+
+            // 添加中间购药记录
+            if (totalOrdersCount > 2) {
+                var middleOrdersCount = totalOrdersCount - 2;
+                var timeInterval = timeSpan / (totalOrdersCount - 1);
+
+                for (var i = 1; i < totalOrdersCount - 1; i++) {
+                    var intervalDate = new Date(firstDate.getTime() + timeInterval * i);
+                    dates.push(formatDate(intervalDate));
+                    quantities.push(Math.floor(Math.random() * 5) + 1); // 模拟数量
+                }
+            }
+
+            // 添加最近一次购药
+            dates.push(formatDate(lastDate));
+            quantities.push(parseInt(detailData.last_purchase_quantity) || Math.floor(Math.random() * 5) + 1);
+        } else {
+            // 只有一次购药记录
+            dates.push(formatDate(lastDate));
+            quantities.push(parseInt(detailData.last_purchase_quantity) || 1);
+        }
+
+        var option = {
+            tooltip: {
+                trigger: 'axis',
+                axisPointer: {
+                    type: 'cross',
+                    label: {
+                        backgroundColor: '#6a7985'
+                    }
+                }
+            },
+            legend: {
+                data: ['购药数量']
+            },
+            grid: {
+                left: '3%',
+                right: '4%',
+                bottom: '3%',
+                containLabel: true
+            },
+            xAxis: [
+                {
+                    type: 'category',
+                    boundaryGap: false,
+                    data: dates
+                }
+            ],
+            yAxis: [
+                {
+                    type: 'value',
+                    name: '数量'
+                }
+            ],
+            series: [
+                {
+                    name: '购药数量',
+                    type: 'line',
+                    stack: '总量',
+                    areaStyle: {},
+                    emphasis: {
+                        focus: 'series'
+                    },
+                    data: quantities
+                }
+            ]
+        };
+
+        purchaseHistoryChart.setOption(option);
+
+        window.addEventListener('resize', function() {
+            purchaseHistoryChart.resize();
+        });
+    }
+
+    // 用药依从性图表
+    function renderMedicationAdherenceChart() {
+        var medicationAdherenceChart = echarts.init(document.getElementById('medicationAdherenceChart'));
+
+        // 计算依从性数据(这里使用模拟数据)
+        var adherenceRate = Math.random() * 30 + 70; // 70-100% 之间的随机数
+        var missedDoses = Math.floor(Math.random() * 5); // 0-4 次漏服
+        var delayedDoses = Math.floor(Math.random() * 7); // 0-6 次延迟服药
+
+        var option = {
+            tooltip: {
+                formatter: '{a} <br/>{b} : {c}%'
+            },
+            series: [
+                {
+                    name: '用药依从性',
+                    type: 'gauge',
+                    detail: { formatter: '{value}%' },
+                    data: [{ value: adherenceRate.toFixed(2), name: '依从率' }],
+                    axisLine: {
+                        lineStyle: {
+                            width: 30,
+                            color: [
+                                [0.3, '#ff4500'],
+                                [0.7, '#ffcc00'],
+                                [1, '#5cb85c']
+                            ]
+                        }
+                    },
+                    pointer: {
+                        itemStyle: {
+                            color: 'auto'
+                        }
+                    },
+                    axisTick: {
+                        distance: -30,
+                        length: 8,
+                        lineStyle: {
+                            color: '#fff',
+                            width: 2
+                        }
+                    },
+                    splitLine: {
+                        distance: -30,
+                        length: 30,
+                        lineStyle: {
+                            color: '#fff',
+                            width: 4
+                        }
+                    },
+                    axisLabel: {
+                        distance: -20,
+                        color: 'auto',
+                        fontSize: 20
+                    },
+                    detail: {
+                        valueAnimation: true,
+                        formatter: '{value}%',
+                        color: 'auto',
+                        fontSize: 30
+                    }
+                }
+            ]
+        };
+
+        medicationAdherenceChart.setOption(option);
+
+        // 添加漏服和延迟服药的说明文字
+        var cardBody = medicationAdherenceChart.getDom().parentNode;
+        if (!document.getElementById('adherence-stats')) {
+            var statsDiv = document.createElement('div');
+            statsDiv.id = 'adherence-stats';
+            statsDiv.style.marginTop = '20px';
+            statsDiv.style.textAlign = 'center';
+            statsDiv.innerHTML = '<p>估计漏服次数: <strong>' + missedDoses + '</strong> 次</p>' +
+                '<p>估计延迟服药: <strong>' + delayedDoses + '</strong> 次</p>';
+            cardBody.appendChild(statsDiv);
+        }
+
+        window.addEventListener('resize', function() {
+            medicationAdherenceChart.resize();
+        });
+    }
+
+    // 格式化日期
+    function formatDate(date) {
+        var year = date.getFullYear();
+        var month = ('0' + (date.getMonth() + 1)).slice(-2);
+        var day = ('0' + date.getDate()).slice(-2);
+        return year + '-' + month + '-' + day;
+    }
+</script>
+</body>
+</html>

+ 691 - 0
health-admin/src/main/resources/templates/report/kpi/visualization-patient.html

@@ -0,0 +1,691 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+    <meta charset="UTF-8">
+    <meta name="format-detection" content="telephone=no">
+    <th:block th:include="include :: header('患者维度数据可视化')" />
+    <th:block th:include="include :: bootstrap-select-css" />
+    <style>
+        .card {
+            border-radius: 10px;
+            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+            margin-bottom: 20px;
+            transition: transform 0.3s;
+        }
+        .card:hover {
+            transform: translateY(-5px);
+        }
+        .stat-card {
+            text-align: center;
+            padding: 20px;
+        }
+        .stat-title {
+            font-size: 16px;
+            color: #666;
+            margin-bottom: 10px;
+        }
+        .stat-value {
+            font-size: 30px;
+            font-weight: bold;
+            color: #3c8dbc;
+        }
+        .chart-container {
+            height: 400px;
+            width: 100%;
+            margin-top: 10px;
+        }
+        .data-table-container {
+            margin-top: 20px;
+        }
+        .filters-container {
+            padding: 15px;
+            background-color: #f8f9fa;
+            border-radius: 10px;
+            margin-bottom: 20px;
+        }
+    </style>
+</head>
+
+<body class="gray-bg">
+<div class="container-div">
+    <div class="row">
+        <!-- 筛选条件 -->
+        <div class="col-sm-12">
+            <div class="filters-container">
+                <div class="row">
+                    <div class="col-md-3">
+                        <div class="form-group">
+                            <label for="storeFilter">门店筛选:</label>
+                            <select class="form-control" id="storeFilter">
+                                <option value="">全部门店</option>
+                            </select>
+
+<!--                      <select name="storeId" id="storeSelect" class="styled-input">-->
+<!--                        <option value="">全部</option>-->
+<!--                      </select>-->
+                        </div>
+                    </div>
+                    <div class="col-md-3">
+                        <div class="form-group">
+                            <label for="drugFilter">D值品筛选:</label>
+                            <select class="form-control" id="drugFilter">
+                                <option value="">全部D值品</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="col-md-3">
+                        <div class="form-group">
+                            <label for="statusFilter">用药状态筛选:</label>
+                            <select class="form-control" id="statusFilter">
+                                <option value="">全部状态</option>
+                                <option value="服药中">服药中</option>
+                                <option value="断药中-待复购">断药中-待复购</option>
+                                <option value="脱落">脱落</option>
+                                <option value="流失">流失</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="col-md-3">
+                        <div class="form-group">
+                            <label>&nbsp;</label>
+                            <button type="button" class="btn btn-primary btn-block" onclick="refreshData()">应用筛选</button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 统计卡片 -->
+        <div class="col-sm-12">
+            <div class="row">
+                <div class="col-md-3">
+                    <div class="card stat-card">
+                        <div class="stat-title">总患者数</div>
+                        <div class="stat-value" id="totalPatients">0</div>
+                    </div>
+                </div>
+                <div class="col-md-3">
+                    <div class="card stat-card">
+                        <div class="stat-title">男性患者</div>
+                        <div class="stat-value" id="malePatients">0</div>
+                    </div>
+                </div>
+                <div class="col-md-3">
+                    <div class="card stat-card">
+                        <div class="stat-title">女性患者</div>
+                        <div class="stat-value" id="femalePatients">0</div>
+                    </div>
+                </div>
+                <div class="col-md-3">
+                    <div class="card stat-card">
+                        <div class="stat-title">平均年龄</div>
+                        <div class="stat-value" id="avgAge">0</div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 图表区域 -->
+        <div class="col-sm-12">
+            <div class="row">
+                <!-- 用药状态分布 -->
+                <div class="col-md-6">
+                    <div class="card">
+                        <div class="card-header">
+                            <h3 class="card-title">用药状态分布</h3>
+                        </div>
+                        <div class="card-body">
+                            <div class="chart-container" id="medicationStatusChart"></div>
+                        </div>
+                    </div>
+                </div>
+
+                <!-- 性别分布 -->
+                <div class="col-md-6">
+                    <div class="card">
+                        <div class="card-header">
+                            <h3 class="card-title">性别分布</h3>
+                        </div>
+                        <div class="card-body">
+                            <div class="chart-container" id="genderChart"></div>
+                        </div>
+                    </div>
+                </div>
+
+                <!-- 年龄分布 -->
+                <div class="col-md-6">
+                    <div class="card">
+                        <div class="card-header">
+                            <h3 class="card-title">年龄分布</h3>
+                        </div>
+                        <div class="card-body">
+                            <div class="chart-container" id="ageChart"></div>
+                        </div>
+                    </div>
+                </div>
+
+                <!-- 疾病类型分布 -->
+                <div class="col-md-6">
+                    <div class="card">
+                        <div class="card-header">
+                            <h3 class="card-title">疾病类型分布</h3>
+                        </div>
+                        <div class="card-body">
+                            <div class="chart-container" id="diseaseTypeChart"></div>
+                        </div>
+                    </div>
+                </div>
+
+                <!-- 用药趋势 -->
+                <div class="col-md-12">
+                    <div class="card">
+                        <div class="card-header">
+                            <h3 class="card-title">剩余用药天数分布</h3>
+                        </div>
+                        <div class="card-body">
+                            <div class="chart-container" id="remainingDaysChart"></div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 数据表格 -->
+        <div class="col-sm-12">
+            <div class="card">
+                <div class="card-header">
+                    <h3 class="card-title">患者详情列表</h3>
+                </div>
+                <div class="card-body data-table-container">
+                    <div class="table-responsive">
+                        <table id="patientDataTable" class="table table-striped table-bordered table-hover"></table>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<th:block th:include="include :: footer" />
+<th:block th:include="include :: bootstrap-select-js" />
+<th:block th:include="include :: bootstrap-table-fixed-columns-js" />
+<th:block th:include="include :: bootstrap-table-export-js" />
+<th:block th:include="include :: echarts-js" />
+
+<script th:inline="javascript">
+    var prefix = ctx + "report/kpi";
+    var charts = [];
+    var patientData = [];
+
+    $(function() {
+        loadData();
+        $(window).resize(function() {
+            charts.forEach(function(chart) {
+                chart.resize();
+            });
+        });
+    });
+
+    function loadData() {
+        var loading = layer.load(1, {shade: [0.3, '#000']});
+
+        $.ajax({
+            url: prefix + "/visualization-patient/data",
+            type: "post",
+            dataType: "json",
+            success: function(res) {
+                layer.close(loading);
+
+                if (res.code === 0) {
+                    patientData = res.data.detailData;
+
+                    // 更新统计卡片
+                    $("#totalPatients").text(res.data.totalPatients);
+                    $("#malePatients").text(res.data.malePatients);
+                    $("#femalePatients").text(res.data.femalePatients);
+
+                    // 计算平均年龄
+                    var totalAge = 0;
+                    patientData.forEach(function(item) {
+                        totalAge += parseInt(item.age || 0);
+                    });
+                    var avgAge = patientData.length > 0 ? Math.round(totalAge / patientData.length) : 0;
+                    $("#avgAge").text(avgAge);
+
+                    // 渲染图表
+                    renderMedicationStatusChart(res.data.medicationStatusDistribution);
+                    renderGenderChart(res.data.malePatients, res.data.femalePatients);
+                    renderAgeChart(res.data.ageGroupDistribution);
+                    renderDiseaseTypeChart(res.data.diseaseTypeDistribution);
+                    renderRemainingDaysChart(res.data.remainingDaysDistribution);
+
+                    // 初始化表格
+                    initTable(patientData);
+
+                    // 初始化筛选下拉框
+                    initFilters(patientData);
+                } else {
+                    $.modal.alertError(res.msg);
+                }
+            },
+            error: function() {
+                layer.close(loading);
+                $.modal.alertError("获取数据失败");
+            }
+        });
+    }
+    // 异步加载所有门店信息并填充下拉框
+    function findTaskStoreList() {
+        $.ajax({
+            url: prefix_task+ "/findTaskStoreList", // 这是获取所有门店的API端点
+            type: 'POST',
+            cache: false, // 设置为 false 防止缓存
+            processData: false, // 告诉 jQuery 不要处理数据(非常重要)
+            contentType: false, // 告诉 jQuery 不要设置 contentType(非常重要)
+            async: true, // 异步请求更为推荐,除非有特殊原因需要同步
+            success: function (data) {
+                var select = $('#storeSelect');
+                // 清空除了默认选项外的所有选项
+                select.find('option:not(:first)').remove();
+                if (data && data.data) {
+                    data.data.forEach(function (store) {
+                        select.append(new Option(store.dept_name, store.id));
+                    });
+                } else {
+                    console.error('Unexpected response format:', data);
+                }
+            },
+            error: function () {
+                $.modal.alertError('加载门店信息失败');
+            }
+        });
+    }
+    function renderMedicationStatusChart(data) {
+        var chart = echarts.init(document.getElementById('medicationStatusChart'));
+        charts.push(chart);
+
+        var options = {
+            tooltip: {
+                trigger: 'item',
+                formatter: '{a} <br/>{b}: {c} ({d}%)'
+            },
+            legend: {
+                orient: 'vertical',
+                left: 'left',
+                data: Object.keys(data)
+            },
+            series: [
+                {
+                    name: '用药状态',
+                    type: 'pie',
+                    radius: '70%',
+                    center: ['50%', '50%'],
+                    data: Object.keys(data).map(function(key) {
+                        return {
+                            name: key,
+                            value: data[key]
+                        };
+                    }),
+                    emphasis: {
+                        itemStyle: {
+                            shadowBlur: 10,
+                            shadowOffsetX: 0,
+                            shadowColor: 'rgba(0, 0, 0, 0.5)'
+                        }
+                    }
+                }
+            ]
+        };
+
+        chart.setOption(options);
+    }
+
+    function renderGenderChart(male, female) {
+        var chart = echarts.init(document.getElementById('genderChart'));
+        charts.push(chart);
+
+        var options = {
+            tooltip: {
+                trigger: 'item',
+                formatter: '{a} <br/>{b}: {c} ({d}%)'
+            },
+            legend: {
+                orient: 'vertical',
+                left: 'left',
+                data: ['男性', '女性']
+            },
+            series: [
+                {
+                    name: '性别',
+                    type: 'pie',
+                    radius: '70%',
+                    center: ['50%', '50%'],
+                    data: [
+                        {name: '男性', value: male},
+                        {name: '女性', value: female}
+                    ],
+                    emphasis: {
+                        itemStyle: {
+                            shadowBlur: 10,
+                            shadowOffsetX: 0,
+                            shadowColor: 'rgba(0, 0, 0, 0.5)'
+                        }
+                    }
+                }
+            ]
+        };
+
+        chart.setOption(options);
+    }
+
+    function renderAgeChart(data) {
+        var chart = echarts.init(document.getElementById('ageChart'));
+        charts.push(chart);
+
+        var keys = Object.keys(data);
+        var values = keys.map(function(key) {
+            return data[key];
+        });
+
+        var options = {
+            tooltip: {
+                trigger: 'axis',
+                axisPointer: {
+                    type: 'shadow'
+                }
+            },
+            grid: {
+                left: '3%',
+                right: '4%',
+                bottom: '3%',
+                containLabel: true
+            },
+            xAxis: {
+                type: 'category',
+                data: keys,
+                axisLabel: {
+                    rotate: 45,
+                    interval: 0
+                }
+            },
+            yAxis: {
+                type: 'value'
+            },
+            series: [
+                {
+                    name: '年龄分布',
+                    type: 'bar',
+                    data: values,
+                    itemStyle: {
+                        color: function(params) {
+                            var colorList = ['#c23531','#2f4554', '#61a0a8', '#d48265', '#91c7ae','#749f83'];
+                            return colorList[params.dataIndex % colorList.length];
+                        }
+                    }
+                }
+            ]
+        };
+
+        chart.setOption(options);
+    }
+
+    function renderDiseaseTypeChart(data) {
+        var chart = echarts.init(document.getElementById('diseaseTypeChart'));
+        charts.push(chart);
+
+        // 获取前7个疾病类型
+        var keys = Object.keys(data).sort(function(a, b) {
+            return data[b] - data[a];
+        }).slice(0, 7);
+
+        var values = keys.map(function(key) {
+            return data[key];
+        });
+
+        var options = {
+            tooltip: {
+                trigger: 'axis',
+                axisPointer: {
+                    type: 'shadow'
+                }
+            },
+            grid: {
+                left: '3%',
+                right: '4%',
+                bottom: '3%',
+                containLabel: true
+            },
+            xAxis: {
+                type: 'value'
+            },
+            yAxis: {
+                type: 'category',
+                data: keys
+            },
+            series: [
+                {
+                    name: '患者数',
+                    type: 'bar',
+                    data: values,
+                    itemStyle: {
+                        color: function(params) {
+                            var colorList = ['#c23531','#2f4554', '#61a0a8', '#d48265', '#91c7ae','#749f83', '#ca8622'];
+                            return colorList[params.dataIndex % colorList.length];
+                        }
+                    }
+                }
+            ]
+        };
+
+        chart.setOption(options);
+    }
+
+    function renderRemainingDaysChart(data) {
+        var chart = echarts.init(document.getElementById('remainingDaysChart'));
+        charts.push(chart);
+
+        var keys = Object.keys(data);
+        var values = keys.map(function(key) {
+            return data[key];
+        });
+
+        var options = {
+            tooltip: {
+                trigger: 'item',
+                formatter: '{a} <br/>{b}: {c} ({d}%)'
+            },
+            legend: {
+                orient: 'vertical',
+                left: 'left',
+                data: keys
+            },
+            series: [
+                {
+                    name: '剩余用药天数',
+                    type: 'pie',
+                    radius: '70%',
+                    center: ['50%', '50%'],
+                    data: keys.map(function(key, index) {
+                        return {
+                            name: key,
+                            value: values[index]
+                        };
+                    }),
+                    emphasis: {
+                        itemStyle: {
+                            shadowBlur: 10,
+                            shadowOffsetX: 0,
+                            shadowColor: 'rgba(0, 0, 0, 0.5)'
+                        }
+                    }
+                }
+            ]
+        };
+
+        chart.setOption(options);
+    }
+
+    function initTable(data) {
+        $('#patientDataTable').bootstrapTable('destroy').bootstrapTable({
+            data: data,
+            pagination: true,
+            pageSize: 10,
+            pageList: [10, 25, 50, 100],
+            search: true,
+            showRefresh: false,
+            showToggle: false,
+            showColumns: true,
+            clickToSelect: true,
+            uniqueId: "patient_id",
+            columns: [
+                {field: 'store_nm', title: '门店名称'},
+                {field: 'patient_id', title: '患者ID'},
+                {field: 'patient_nm', title: '患者姓名'},
+                {
+                    field: 'gender',
+                    title: '性别',
+                    formatter: function(value) {
+                        return value == 0 ? '男' : (value == 1 ? '女' : '-');
+                    }
+                },
+                {field: 'age', title: '年龄'},
+                {field: 'drug_nm', title: 'D值品名称'},
+                {field: 'drug_id', title: 'D值品ID'},
+                {field: 'disease_type', title: '病种'},
+                {field: 'medication_status', title: '用药状态'},
+                {field: 'remaining_days', title: '剩余用药天数'},
+                {field: 'last_purchase_date', title: '最近一次购药日期'},
+                {
+                    field: 'operate',
+                    title: '操作',
+                    align: 'center',
+                    formatter: function(value, row) {
+                        return '<a class="btn btn-info btn-xs" href="javascript:void(0)" onclick="viewDetail(\'' + row.patient_id + '\', \'' + row.drug_id + '\')"><i class="fa fa-eye"></i>详情</a>';
+                    }
+                }
+            ]
+        });
+    }
+
+    function initFilters(data) {
+        // 门店筛选
+        var stores = {};
+        data.forEach(function(item) {
+            if (item.store_nm) {
+                stores[item.store_nm] = 1;
+            }
+        });
+
+        var storeFilter = $("#storeFilter");
+        storeFilter.empty().append('<option value="">全部门店</option>');
+
+        Object.keys(stores).sort().forEach(function(store) {
+            storeFilter.append('<option value="' + store + '">' + store + '</option>');
+        });
+
+        // D值品筛选
+        var drugs = {};
+        data.forEach(function(item) {
+            if (item.drug_nm) {
+                drugs[item.drug_nm] = 1;
+            }
+        });
+
+        var drugFilter = $("#drugFilter");
+        drugFilter.empty().append('<option value="">全部D值品</option>');
+
+        Object.keys(drugs).sort().forEach(function(drug) {
+            drugFilter.append('<option value="' + drug + '">' + drug + '</option>');
+        });
+    }
+
+    function refreshData() {
+        var store = $("#storeFilter").val();
+        var drug = $("#drugFilter").val();
+        var status = $("#statusFilter").val();
+
+        var filteredData = patientData.filter(function(item) {
+            return (store === "" || item.store_nm === store) &&
+                (drug === "" || item.drug_nm === drug) &&
+                (status === "" || item.medication_status === status);
+        });
+
+        // 重新计算并更新统计数据
+        var maleCount = 0, femaleCount = 0, totalAge = 0;
+        var medicationStatus = {}, ageGroups = {}, diseaseTypes = {};
+        var remainingDaysStatus = {'服药中': 0, '断药中': 0};
+
+        filteredData.forEach(function(item) {
+            // 性别统计
+            if (item.gender == 0) {
+                maleCount++;
+            } else if (item.gender == 1) {
+                femaleCount++;
+            }
+
+            // 年龄统计
+            totalAge += parseInt(item.age || 0);
+
+            // 计算年龄段
+            var age = parseInt(item.age || 0);
+            var ageGroup = getAgeGroup(age);
+            ageGroups[ageGroup] = (ageGroups[ageGroup] || 0) + 1;
+
+            // 用药状态统计
+            var status = item.medication_status;
+            medicationStatus[status] = (medicationStatus[status] || 0) + 1;
+
+            // 疾病类型统计
+            var disease = item.disease_type || '未知';
+            diseaseTypes[disease] = (diseaseTypes[disease] || 0) + 1;
+
+            // 剩余用药天数统计
+            if (parseInt(item.remaining_days) > 0) {
+                remainingDaysStatus['服药中']++;
+            } else {
+                remainingDaysStatus['断药中']++;
+            }
+        });
+
+        // 更新统计卡片
+        $("#totalPatients").text(filteredData.length);
+        $("#malePatients").text(maleCount);
+        $("#femalePatients").text(femaleCount);
+
+        var avgAge = filteredData.length > 0 ? Math.round(totalAge / filteredData.length) : 0;
+        $("#avgAge").text(avgAge);
+
+        // 重新渲染图表
+        renderMedicationStatusChart(medicationStatus);
+        renderGenderChart(maleCount, femaleCount);
+        renderAgeChart(ageGroups);
+        renderDiseaseTypeChart(diseaseTypes);
+        renderRemainingDaysChart(remainingDaysStatus);
+
+        // 更新表格
+        $('#patientDataTable').bootstrapTable('load', filteredData);
+    }
+
+    function getAgeGroup(age) {
+        if (age < 18) {
+            return "未成年(<18岁)";
+        } else if (age < 30) {
+            return "青年(18-29岁)";
+        } else if (age < 45) {
+            return "中青年(30-44岁)";
+        } else if (age < 60) {
+            return "中年(45-59岁)";
+        } else if (age < 75) {
+            return "老年(60-74岁)";
+        } else {
+            return "高龄老年(≥75岁)";
+        }
+    }
+
+    function viewDetail(patientId,drugId) {
+        var url = prefix + "/visualization-patient-detail?patientId=" + patientId + "&drugId=" + drugId;
+        $.modal.openTab("患者详情", url);
+    }
+</script>
+</body>
+</html>

+ 771 - 0
health-admin/src/main/resources/templates/report/kpi/visualization.html

@@ -0,0 +1,771 @@
+<!DOCTYPE html>
+<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>绩效数据可视化</title>
+    <th:block th:include="include :: header('绩效数据可视化')" />
+    <th:block th:include="include :: bootstrap-select-css" />
+<!--    <th:block th:include="include :: bootstrap-table-fixed-columns-css" />-->
+<!--    <th:block th:include="include :: echarts-css" />-->
+<!--    <th:block th:include="include :: header('百度ECharts')" />-->
+    <style>
+        .visualization-container {
+            padding: 20px;
+        }
+        .chart-container {
+            background-color: #fff;
+            padding: 20px;
+            border-radius: 5px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.05);
+            margin-bottom: 20px;
+            height: 400px;
+        }
+        .chart-title {
+            font-size: 16px;
+            font-weight: bold;
+            margin-bottom: 15px;
+            color: #333;
+            padding-bottom: 10px;
+            border-bottom: 1px solid #eee;
+        }
+        .filter-container {
+            background-color: #fff;
+            padding: 15px;
+            border-radius: 5px;
+            box-shadow: 0 2px 10px rgba(0,0,0,0.05);
+            margin-bottom: 20px;
+        }
+        .filter-title {
+            font-size: 14px;
+            font-weight: bold;
+            margin-bottom: 15px;
+        }
+        .filter-form {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 15px;
+        }
+        .filter-item {
+            min-width: 200px;
+        }
+    </style>
+</head>
+<body class="gray-bg">
+<div class="visualization-container">
+    <!-- 筛选条件 -->
+    <div class="filter-container">
+        <div class="filter-title">筛选条件</div>
+        <form id="filter-form" class="filter-form">
+
+            <div class="filter-item">
+<!--                <label for="storeFilter">门店筛选:</label>-->
+<!--                <select class="form-control" id="storeFilter">-->
+<!--                    <option value="">全部门店</option>-->
+<!--                </select>-->
+                <div class="input-group customize-form-group">
+                    <label>门店筛选:</label>
+                    <select name="storeId" id="storeSelect" class="styled-input">
+                        <option value="">全部</option>
+                    </select>
+                </div>
+            </div>
+            <div class="filter-item">
+                <div class="input-group customize-form-group">
+                <label>时间范围:</label>
+                <select id="timeRangeSelect" class="form-control">
+                    <option value="">请选择时间范围</option>
+                    <option value="B">本月</option>
+                    <option value="S">上月</option>
+
+                </select>
+            </div>
+            </div>
+            <div class="filter-item" id="customDateRange" style="display: none;">
+                <input type="text" class="time-input" id="startDate" placeholder="开始日期">
+                <span>-</span>
+                <input type="text" class="time-input" id="endDate" placeholder="结束日期">
+            </div>
+            <div class="filter-item">
+                <button type="button" id="search-btn" class="btn btn-primary btn-sm"><i class="fa fa-search"></i> 查询</button>
+                <button type="button" id="reset-btn" class="btn btn-warning btn-sm"><i class="fa fa-refresh"></i> 重置</button>
+            </div>
+        </form>
+    </div>
+
+    <!-- 关键指标概览 -->
+    <div class="row">
+        <div class="col-md-3">
+            <div class="widget style1 navy-bg">
+                <div class="row">
+                    <div class="col-xs-4">
+                        <i class="fa fa-shopping-cart fa-5x"></i>
+                    </div>
+                    <div class="col-xs-8 text-right">
+                        <span>购药患者数</span>
+                        <h2 class="font-bold" id="total-patients">0</h2>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="col-md-3">
+            <div class="widget style1 lazur-bg">
+                <div class="row">
+                    <div class="col-xs-4">
+                        <i class="fa fa-user-plus fa-5x"></i>
+                    </div>
+                    <div class="col-xs-8 text-right">
+                        <span>新患数</span>
+                        <h2 class="font-bold" id="new-patients">0</h2>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="col-md-3">
+            <div class="widget style1 yellow-bg">
+                <div class="row">
+                    <div class="col-xs-4">
+                        <i class="fa fa-line-chart fa-5x"></i>
+                    </div>
+                    <div class="col-xs-8 text-right">
+                        <span>按时复购率</span>
+                        <h2 class="font-bold" id="repurchase-rate">0%</h2>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="col-md-3">
+            <div class="widget style1 red-bg">
+                <div class="row">
+                    <div class="col-xs-4">
+                        <i class="fa fa-tasks fa-5x"></i>
+                    </div>
+                    <div class="col-xs-8 text-right">
+                        <span>任务完成率</span>
+                        <h2 class="font-bold" id="task-completion">0%</h2>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <!-- 图表区域 -->
+    <div class="row">
+        <!-- 客户行为分析图表 -->
+        <div class="col-md-6">
+            <div class="chart-container">
+                <div class="chart-title">客户行为分析</div>
+                <div id="customer-behavior-chart" style="height: 320px;"></div>
+            </div>
+        </div>
+
+        <!-- 复购率与脱落率趋势图 -->
+        <div class="col-md-6">
+            <div class="chart-container">
+                <div class="chart-title">复购率与脱落率趋势</div>
+                <div id="rates-trend-chart" style="height: 320px;"></div>
+            </div>
+        </div>
+    </div>
+
+    <div class="row">
+        <!-- 患者构成分析 -->
+        <div class="col-md-6">
+            <div class="chart-container">
+                <div class="chart-title">患者构成分析</div>
+                <div id="patient-composition-chart" style="height: 320px;"></div>
+            </div>
+        </div>
+
+        <!-- 随访任务完成情况 -->
+        <div class="col-md-6">
+            <div class="chart-container">
+                <div class="chart-title">随访任务完成情况</div>
+                <div id="task-completion-chart" style="height: 320px;"></div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<th:block th:include="include :: footer" />
+<script th:src="@{/health/js/echarts.min.js}"></script>
+<th:block th:include="include :: bootstrap-select-js" />
+<th:block th:include="include :: echarts-js" />
+
+<script th:inline="javascript">
+    var prefix_task = ctx + "task/followTask";
+    var prefix = ctx + "report/kpi";
+    var data = [];
+
+    $(document).ready(function() {
+        // 初始化日期选择器
+        layui.use('laydate', function(){
+            var laydate = layui.laydate;
+            laydate.render({ elem: '#startDate', theme: '#393D49' });
+            laydate.render({ elem: '#endDate', theme: '#393D49' });
+        });
+
+        // 初始化下拉选择框
+        $('.selectpicker').selectpicker();
+
+        // 时间范围选择变化监听
+        $('#timeRangeSelect').change(function() {
+            if ($(this).val() === 'custom') {
+                $('#customDateRange').show();
+            } else {
+                $('#customDateRange').hide();
+            }
+        });
+
+        // 加载门店数据
+        findTaskStoreList();
+
+        // 初始化查询
+        loadData();
+
+        // 按钮绑定
+        $('#search-btn').click(function() {
+            loadData();
+        });
+
+        $('#reset-btn').click(function() {
+            $('#filter-form')[0].reset();
+            $('.selectpicker').selectpicker('refresh');
+            $('#customDateRange').hide();
+            loadData();
+        });
+    });
+
+    // 异步加载所有门店信息并填充下拉框
+    function findTaskStoreList() {
+        $.ajax({
+            url: prefix_task+ "/findTaskStoreList", // 这是获取所有门店的API端点
+            type: 'POST',
+            cache: false, // 设置为 false 防止缓存
+            processData: false, // 告诉 jQuery 不要处理数据(非常重要)
+            contentType: false, // 告诉 jQuery 不要设置 contentType(非常重要)
+            async: true, // 异步请求更为推荐,除非有特殊原因需要同步
+            success: function (data) {
+                var select = $('#storeSelect');
+                // 清空除了默认选项外的所有选项
+                select.find('option:not(:first)').remove();
+                if (data && data.data) {
+                    data.data.forEach(function (store) {
+                        select.append(new Option(store.dept_name, store.id));
+                    });
+                } else {
+                    console.error('Unexpected response format:', data);
+                }
+            },
+            error: function () {
+                $.modal.alertError('加载门店信息失败');
+            }
+        });
+    }
+
+    // 加载数据
+    function loadData() {
+        var params = {
+            storeId: $('#storeSelect').val(),
+            month: $('#timeRangeSelect').val() !== 'custom' ? $('#timeRangeSelect').val() : '',
+            startDate: $('#startDate').val(),
+            endDate: $('#endDate').val()
+        };
+
+        $.ajax({
+            url: prefix + "/OverviewKPIList",
+            type: "post",
+            data: params,
+            success: function(res) {
+                data = res.rows || [];
+                renderDashboard();
+                renderCharts();
+            }
+        });
+    }
+
+    // 渲染关键指标
+    function renderDashboard() {
+        var totalPatients = 0;
+        var newPatients = 0;
+        var repurchaseRate = 0;
+        var taskCompletion = 0;
+
+        if (data.length > 0) {
+            // 计算总计值
+            data.forEach(function(item) {
+                totalPatients += parseInt(item.medication_patients || 0);
+                newPatients += parseInt(item.new_patients || 0);
+            });
+
+            // 计算平均值
+            var sumRepurchaseRate = 0;
+            var sumTaskCompletion = 0;
+            var countRepurchase = 0;
+            var countTask = 0;
+
+            data.forEach(function(item) {
+                if (item.on_time_repurchase_rate) {
+                    var rateStr = item.on_time_repurchase_rate.replace('%', '');
+                    sumRepurchaseRate += parseFloat(rateStr) / 100.0;
+                    countRepurchase++;
+                }
+                if (item.task_completion_rate) {
+                    var rateStr = item.task_completion_rate.replace('%', '');
+                    sumTaskCompletion += parseFloat(rateStr) / 100.0;
+                    countTask++;
+                }
+            });
+
+            repurchaseRate = countRepurchase > 0 ? (sumRepurchaseRate / countRepurchase) : 0;
+            taskCompletion = countTask > 0 ? (sumTaskCompletion / countTask) : 0;
+        }
+
+        $('#total-patients').text(totalPatients);
+        $('#new-patients').text(newPatients);
+        $('#repurchase-rate').text((repurchaseRate * 100).toFixed(2) + '%');
+        $('#task-completion').text((taskCompletion * 100).toFixed(2) + '%');
+    }
+
+    // 渲染图表
+    function renderCharts() {
+        renderCustomerBehaviorChart();
+        renderRatesTrendChart();
+        renderPatientCompositionChart();
+        renderTaskCompletionChart();
+    }
+
+    // 客户行为分析图表
+    function renderCustomerBehaviorChart() {
+        var chartDom = document.getElementById('customer-behavior-chart');
+        if (!chartDom) return;
+
+        var chart = echarts.init(chartDom);
+
+        // 数据处理
+        var stores = [];
+        var repurchaseData = [];
+        var churnData = [];
+        var lossData = [];
+
+        // 选取前10个门店数据
+        var topStores = data.slice(0, 10);
+
+        topStores.forEach(function(item) {
+            stores.push(item.store_nm || '未知门店');
+            repurchaseData.push(parseInt(item.on_time_repurchased_count || 0));
+            churnData.push(parseInt(item.churned_count || 0));
+            lossData.push(parseInt(item.lost_count || 0));
+        });
+
+        var option = {
+            tooltip: {
+                trigger: 'axis',
+                axisPointer: {
+                    type: 'shadow'
+                }
+            },
+            legend: {
+                data: ['按时复购', '脱落', '流失'],
+                top: 0,
+                right: 10
+            },
+            grid: {
+                left: '3%',
+                right: '4%',
+                bottom: '3%',
+                containLabel: true
+            },
+            xAxis: {
+                type: 'category',
+                data: stores,
+                axisLabel: {
+                    interval: 0,
+                    rotate: 30,
+                    textStyle: {
+                        fontSize: 10
+                    }
+                }
+            },
+            yAxis: {
+                type: 'value'
+            },
+            series: [
+                {
+                    name: '按时复购',
+                    type: 'bar',
+                    stack: 'total',
+                    emphasis: {
+                        focus: 'series'
+                    },
+                    data: repurchaseData,
+                    itemStyle: {
+                        color: '#91cc75'
+                    }
+                },
+                {
+                    name: '脱落',
+                    type: 'bar',
+                    stack: 'total',
+                    emphasis: {
+                        focus: 'series'
+                    },
+                    data: churnData,
+                    itemStyle: {
+                        color: '#fac858'
+                    }
+                },
+                {
+                    name: '流失',
+                    type: 'bar',
+                    stack: 'total',
+                    emphasis: {
+                        focus: 'series'
+                    },
+                    data: lossData,
+                    itemStyle: {
+                        color: '#ee6666'
+                    }
+                }
+            ]
+        };
+
+        chart.setOption(option);
+
+        // 浏览器窗口大小变化时,重置图表大小
+        window.addEventListener('resize', function() {
+            chart.resize();
+        });
+    }
+
+    // 复购率与脱落率趋势图
+    function renderRatesTrendChart() {
+        var chartDom = document.getElementById('rates-trend-chart');
+        if (!chartDom) return;
+
+        var chart = echarts.init(chartDom);
+
+        // 根据月份分组数据
+        var monthGroups = {};
+
+        data.forEach(function(item) {
+            var month = item.month || '未知月份';
+            if (!monthGroups[month]) {
+                monthGroups[month] = {
+                    repurchaseRate: [],
+                    churnRate: [],
+                    recallRate: [],
+                    lossRate: []
+                };
+            }
+
+            if (item.on_time_repurchase_rate) {
+                var rateStr = item.on_time_repurchase_rate.replace('%', '');
+                monthGroups[month].repurchaseRate.push(parseFloat(rateStr));
+            }
+            if (item.churn_rate) {
+                var rateStr = item.churn_rate.replace('%', '');
+                monthGroups[month].churnRate.push(parseFloat(rateStr));
+            }
+            if (item.churn_recall_rate) {
+                var rateStr = item.churn_recall_rate.replace('%', '');
+                monthGroups[month].recallRate.push(parseFloat(rateStr));
+            }
+            if (item.loss_rate) {
+                var rateStr = item.loss_rate.replace('%', '');
+                monthGroups[month].lossRate.push(parseFloat(rateStr));
+            }
+        });
+
+        // 计算每个月份的平均值
+        var months = [];
+        var avgRepurchaseRate = [];
+        var avgChurnRate = [];
+        var avgRecallRate = [];
+        var avgLossRate = [];
+
+        for (var month in monthGroups) {
+            months.push(month);
+
+            var repurchaseRates = monthGroups[month].repurchaseRate;
+            var churnRates = monthGroups[month].churnRate;
+            var recallRates = monthGroups[month].recallRate;
+            var lossRates = monthGroups[month].lossRate;
+
+            var avgRepurchase = repurchaseRates.length > 0 ?
+                repurchaseRates.reduce(function(a, b) { return a + b; }, 0) / repurchaseRates.length : 0;
+            var avgChurn = churnRates.length > 0 ?
+                churnRates.reduce(function(a, b) { return a + b; }, 0) / churnRates.length : 0;
+            var avgRecall = recallRates.length > 0 ?
+                recallRates.reduce(function(a, b) { return a + b; }, 0) / recallRates.length : 0;
+            var avgLoss = lossRates.length > 0 ?
+                lossRates.reduce(function(a, b) { return a + b; }, 0) / lossRates.length : 0;
+
+            avgRepurchaseRate.push(avgRepurchase.toFixed(2));
+            avgChurnRate.push(avgChurn.toFixed(2));
+            avgRecallRate.push(avgRecall.toFixed(2));
+            avgLossRate.push(avgLoss.toFixed(2));
+        }
+
+        var option = {
+            tooltip: {
+                trigger: 'axis',
+                formatter: function(params) {
+                    var result = params[0].name + '<br/>';
+                    params.forEach(function(param) {
+                        result += param.marker + ' ' + param.seriesName + ': ' + param.value + '%<br/>';
+                    });
+                    return result;
+                }
+            },
+            legend: {
+                data: ['按时复购率', '脱落率', '脱落召回率', '流失率'],
+                top: 0,
+                right: 10
+            },
+            grid: {
+                left: '3%',
+                right: '4%',
+                bottom: '3%',
+                containLabel: true
+            },
+            xAxis: {
+                type: 'category',
+                boundaryGap: false,
+                data: months
+            },
+            yAxis: {
+                type: 'value',
+                axisLabel: {
+                    formatter: '{value}%'
+                }
+            },
+            series: [
+                {
+                    name: '按时复购率',
+                    type: 'line',
+                    data: avgRepurchaseRate,
+                    itemStyle: {
+                        color: '#91cc75'
+                    }
+                },
+                {
+                    name: '脱落率',
+                    type: 'line',
+                    data: avgChurnRate,
+                    itemStyle: {
+                        color: '#fac858'
+                    }
+                },
+                {
+                    name: '脱落召回率',
+                    type: 'line',
+                    data: avgRecallRate,
+                    itemStyle: {
+                        color: '#5470c6'
+                    }
+                },
+                {
+                    name: '流失率',
+                    type: 'line',
+                    data: avgLossRate,
+                    itemStyle: {
+                        color: '#ee6666'
+                    }
+                }
+            ]
+        };
+
+        chart.setOption(option);
+
+        // 浏览器窗口大小变化时,重置图表大小
+        window.addEventListener('resize', function() {
+            chart.resize();
+        });
+    }
+
+    // 患者构成分析
+    function renderPatientCompositionChart() {
+        var chartDom = document.getElementById('patient-composition-chart');
+        if (!chartDom) return;
+
+        var chart = echarts.init(chartDom);
+
+        // 数据汇总
+        var totalMedication = 0;
+        var totalNew = 0;
+        var totalOld = 0;
+
+        data.forEach(function(item) {
+            totalMedication += parseInt(item.medication_patients || 0);
+            totalNew += parseInt(item.new_patients || 0);
+            totalOld += parseInt(item.old_patients || 0);
+        });
+
+        var option = {
+            tooltip: {
+                trigger: 'item',
+                formatter: '{a} <br/>{b}: {c} ({d}%)'
+            },
+            legend: {
+                orient: 'vertical',
+                left: 10,
+                data: ['新患', '老患']
+            },
+            series: [
+                {
+                    name: '患者构成',
+                    type: 'pie',
+                    radius: ['50%', '70%'],
+                    avoidLabelOverlap: false,
+                    label: {
+                        show: false,
+                        position: 'center'
+                    },
+                    emphasis: {
+                        label: {
+                            show: true,
+                            fontSize: '18',
+                            fontWeight: 'bold'
+                        }
+                    },
+                    labelLine: {
+                        show: false
+                    },
+                    data: [
+                        { value: totalNew, name: '新患', itemStyle: { color: '#5470c6' } },
+                        { value: totalOld, name: '老患', itemStyle: { color: '#91cc75' } }
+                    ]
+                }
+            ]
+        };
+
+        chart.setOption(option);
+
+        // 添加中心文本
+        chart.setOption({
+            graphic: {
+                elements: [{
+                    type: 'text',
+                    left: 'center',
+                    top: 'center',
+                    style: {
+                        text: '总患者数\n' + totalMedication,
+                        fontSize: 20,
+                        fontWeight: 'bold',
+                        lineDash: [0, 0],
+                        fill: '#333',
+                        textAlign: 'center'
+                    }
+                }]
+            }
+        });
+
+        // 浏览器窗口大小变化时,重置图表大小
+        window.addEventListener('resize', function() {
+            chart.resize();
+        });
+    }
+
+    // 随访任务完成情况
+    function renderTaskCompletionChart() {
+        var chartDom = document.getElementById('task-completion-chart');
+        if (!chartDom) return;
+
+        var chart = echarts.init(chartDom);
+
+        // 数据汇总
+        var totalEstTasks = 0;
+        var totalCompletedTasks = 0;
+        var totalRegularTasks = 0;
+        var totalCompletedRegular = 0;
+        var totalRecallTasks = 0;
+        var totalCompletedRecall = 0;
+
+        data.forEach(function(item) {
+            totalEstTasks += parseInt(item.est_follow_tasks || 0);
+            totalCompletedTasks += parseInt(item.completed_follow_tasks || 0);
+            totalRegularTasks += parseInt(item.est_regular_follow_tasks || 0);
+            totalCompletedRegular += parseInt(item.actual_completed_regular_tasks || 0);
+            totalRecallTasks += parseInt(item.est_recall_follow_tasks || 0);
+            totalCompletedRecall += parseInt(item.actual_completed_recall_tasks || 0);
+        });
+
+        var option = {
+            tooltip: {
+                trigger: 'axis',
+                axisPointer: {
+                    type: 'shadow'
+                }
+            },
+            legend: {
+                data: ['计划任务数', '已完成任务数'],
+                top: 0,
+                right: 10
+            },
+            grid: {
+                left: '3%',
+                right: '4%',
+                bottom: '3%',
+                containLabel: true
+            },
+            xAxis: {
+                type: 'category',
+                data: ['总任务', '常规随访', '召回随访']
+            },
+            yAxis: {
+                type: 'value'
+            },
+            series: [
+                {
+                    name: '计划任务数',
+                    type: 'bar',
+                    data: [totalEstTasks, totalRegularTasks, totalRecallTasks],
+                    itemStyle: {
+                        color: '#5470c6'
+                    }
+                },
+                {
+                    name: '已完成任务数',
+                    type: 'bar',
+                    data: [totalCompletedTasks, totalCompletedRegular, totalCompletedRecall],
+                    itemStyle: {
+                        color: '#91cc75'
+                    }
+                }
+            ]
+        };
+
+        chart.setOption(option);
+
+        // 浏览器窗口大小变化时,重置图表大小
+        window.addEventListener('resize', function() {
+            chart.resize();
+        });
+    }
+
+
+    /* 用户管理-新增-选择门店树 */
+    function selectDeptTree() {
+        var treeId = $("#treeId").val();
+        var deptId = $.common.isEmpty(treeId) ? "100" : $("#treeId").val();
+        var url = ctx + "system/user/selectDeptTree/" + deptId;
+        var options = {
+            title: '选择门店',
+            width: "380",
+            url: url,
+            callBack: doSubmit
+        };
+        $.modal.openOptions(options);
+    }
+
+    function doSubmit(index, layero){
+        var body = $.modal.getChildFrame(index);
+        $("#treeId").val(body.find('#treeId').val());
+        $("#treeName").val(body.find('#treeName').val());
+        $.modal.close(index);
+    }
+
+</script>
+</body>
+</html>

+ 37 - 3
health-admin/src/main/resources/templates/spgl/SPProductinfoList.html

@@ -32,12 +32,46 @@
                     <label>商品名称:</label>
                     <input type="text" class="styled-input" placeholder="请输入商品名称" name="product_name"/>
                 </div>
+                    <!-- 是否随访管理品 -->
+                    <div class="customize-form-group">
+                        <label>是否随访管理品:</label>
+                        <select name="isFollowUpManaged" class="styled-input" th:with="type=${@dict.getType('sys_gxhpz_yes_no')}">
+                            <option value="">请选择</option>
+                            <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+                        </select>
+                    </div>
+                    <!-- 是否冷链管理品 -->
+                    <div class="customize-form-group">
+                        <label>是否冷链管理品:</label>
+                        <select name="isColdChainManaged" class="styled-input" th:with="type=${@dict.getType('sys_gxhpz_yes_no')}">
+                            <option value="">请选择</option>
+                            <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+                        </select>
+                    </div>
+
+                    <!-- 是否登记管理品 -->
+                    <div class="customize-form-group">
+                        <label>是否登记管理品:</label>
+                        <select name="isRegisteredManaged" class="styled-input" th:with="type=${@dict.getType('sys_gxhpz_yes_no')}">
+                            <option value="">请选择</option>
+                            <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+                        </select>
+                    </div>
+
+                    <!-- 是否慈善援助管理品 -->
+                    <div class="customize-form-group">
+                        <label>是否慈善援助管理品:</label>
+                        <select name="isCharityAidManaged" class="styled-input" th:with="type=${@dict.getType('sys_gxhpz_yes_no')}">
+                            <option value="">请选择</option>
+                            <option th:each="dict : ${type}" th:text="${dict.dictLabel}" th:value="${dict.dictValue}"></option>
+                        </select>
+                    </div>
              </div>
              </form>
             </div>
 
             <div class="btn-group-sm" id="toolbar" role="group">
-                <a class="btn btn-success" onclick="$.operate.addTab()" shiro:hasPermission="sp:sp:add">
+                <a class="btn btn-success" onclick="$.operate.addTab()" shiro:hasPermission="gxhpz:ypk:add">
                     <i class="fa fa-plus"></i> 新增商品
                 </a>
 <!--                <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="system:user:remove">-->
@@ -68,8 +102,8 @@
 <!--拖拽表格-->
 <th:block th:include="include :: ztree-js" />
 <script th:inline="javascript">
-    var editFlag = [[${@permission.hasPermi('sp:sp:edit')}]];
-    var removeFlag = [[${@permission.hasPermi('sp:sp:remove')}]];
+    var editFlag = [[${@permission.hasPermi('gxhpz:ypk:edit')}]];
+    var removeFlag = [[${@permission.hasPermi('gxhpz:ypk:remove')}]];
     var prefix = ctx + "sp/sp";
     $(function() {
         var panehHidden = false;

+ 5 - 5
health-admin/src/main/resources/templates/zlgl/SZlglCfdjSaleprescriptioninfoList.html

@@ -36,13 +36,13 @@
             </div>
 
             <div class="btn-group-sm" id="toolbar" role="group">
-                <a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="system:user:add">
+                <a class="btn btn-success" onclick="$.operate.add()" shiro:hasPermission="zlgl:zl:add">
                     <i class="fa fa-plus"></i> 新增
                 </a>
-                 <a class="btn btn-primary single disabled" onclick="$.operate.edit()" shiro:hasPermission="system:user:edit">
+                 <a class="btn btn-primary single disabled" onclick="$.operate.edit()" shiro:hasPermission="zlgl:zl:edit">
                     <i class="fa fa-edit"></i> 修改
                 </a>
-                <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="system:user:remove">
+                <a class="btn btn-danger multiple disabled" onclick="$.operate.removeAll()" shiro:hasPermission="zlgl:zl:remove">
                     <i class="fa fa-remove"></i> 删除
                 </a>
             </div>
@@ -59,8 +59,8 @@
 <th:block th:include="include :: bootstrap-table-fixed-columns-js" />
 <th:block th:include="include :: ztree-js" />
 <script th:inline="javascript">
-    var editFlag = [[${@permission.hasPermi('dtp:pmService:edit')}]];
-    var removeFlag = [[${@permission.hasPermi('dtp:pmService:remove')}]];
+    var editFlag = [[${@permission.hasPermi('zlgl:zl:edit')}]];
+    var removeFlag = [[${@permission.hasPermi('zlgl:zl:remove')}]];
     var prefix = ctx + "zlgl/szlglcfdjsaleprescriptioninfo";
     $(function() {
         var panehHidden = false;

+ 11 - 3
health-common/pom.xml

@@ -16,7 +16,6 @@
     </description>
 
     <dependencies>
-
         <!-- Spring框架基本的核心工具 -->
         <dependency>
             <groupId>org.springframework</groupId>
@@ -100,13 +99,22 @@
             <artifactId>lombok</artifactId>
         </dependency>
 
-
-
         <dependency>
             <groupId>log4j</groupId>
             <artifactId>log4j</artifactId>
         </dependency>
 
+        <!-- 添加HttpClient依赖 -->
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+        </dependency>
+
+        <!-- 添加HttpCore依赖 -->
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpcore</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 104 - 0
health-common/src/main/java/com/bzd/common/core/domain/entity/StoreDrugPerformanceExport.java

@@ -0,0 +1,104 @@
+package com.bzd.common.core.domain.entity;
+
+import com.bzd.common.annotation.Excel;
+import com.bzd.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.math.BigDecimal;
+/**
+ * 品门店维度数据导出实体
+ *
+ * @author wsp
+ */
+@Data
+public class StoreDrugPerformanceExport extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+    private Long id;
+    /** 月份 */
+    @Excel(name = "月份")
+    private String month;
+
+    /** 平台 */
+    @Excel(name = "平台")
+    private String platformName;
+
+    /** 连锁 */
+    @Excel(name = "连锁")
+    private String projectCompanyName;
+
+    /** 门店编码 */
+    @Excel(name = "门店编码")
+    private String storeCd;
+
+    /** 门店名称 */
+    @Excel(name = "门店名称")
+    private String storeNm;
+
+    /** d值品id */
+    @Excel(name = "d值品id")
+    private String drugId;
+
+    /** d值品名称 */
+    @Excel(name = "d值品名称")
+    private String drugNm;
+
+    /** 按时复购率 */
+    @Excel(name = "按时复购率")
+    private String onTimeRepurchaseRate;
+
+    /** 脱落率 */
+    @Excel(name = "脱落率")
+    private String churnRate;
+
+    /** 脱落召回率 */
+    @Excel(name = "脱落召回率")
+    private String churnRecallRate;
+
+    /** 流失率 */
+    @Excel(name = "流失率")
+    private String lossRate;
+
+    /** 应复购人次 */
+    @Excel(name = "应复购人次")
+    private Integer dueForRepurchaseCount;
+
+    /** 按时复购人次 */
+    @Excel(name = "按时复购人次")
+    private Integer onTimeRepurchasedCount;
+
+    /** 脱落人次 */
+    @Excel(name = "脱落人次")
+    private Integer churnedCount;
+
+    /** 召回人次 */
+    @Excel(name = "召回人次")
+    private Integer recalledCount;
+
+    /** 流失人次 */
+    @Excel(name = "流失人次")
+    private Integer lostCount;
+
+    /** 脱落金额 */
+    @Excel(name = "脱落金额")
+    private BigDecimal churnAmount;
+
+    /** 流失金额 */
+    @Excel(name = "流失金额")
+    private BigDecimal lossAmount;
+
+    /** 首单金额 */
+    @Excel(name = "首单金额")
+    private BigDecimal firstOrderAmount;
+
+    /** 按时复购金额 */
+    @Excel(name = "按时复购金额")
+    private BigDecimal onTimeRepurchaseAmount;
+
+    /** 脱落召回金额 */
+    @Excel(name = "脱落召回金额")
+    private BigDecimal churnRecallAmount;
+
+    /** 流失召回金额 */
+    @Excel(name = "流失召回金额")
+    private BigDecimal lossRecallAmount;
+}

+ 137 - 0
health-common/src/main/java/com/bzd/common/core/domain/entity/StorePatientDrugStatisticExport.java

@@ -0,0 +1,137 @@
+package com.bzd.common.core.domain.entity;
+
+import com.bzd.common.annotation.Excel;
+import com.bzd.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 门店+患者+D值品统计数据导出实体类
+ *
+ * @author wsp
+ */
+@Data
+public class StorePatientDrugStatisticExport extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+    private Long id;
+    /** 平台 */
+    @Excel(name = "平台")
+    private String platformName;
+
+    /** 项目公司 */
+    @Excel(name = "项目公司")
+    private String projectCompanyName;
+
+    /** 门店编码 */
+    @Excel(name = "门店编码")
+    private String storeCd;
+
+    /** 门店名称 */
+    @Excel(name = "门店名称")
+    private String storeNm;
+
+    /** 疾病 */
+    @Excel(name = "疾病")
+    private String dl;
+
+    /** D值品ID */
+    @Excel(name = "D值品ID")
+    private String drugId;
+
+    /** D值品名称 */
+    @Excel(name = "D值品名称")
+    private String drugNm;
+
+    /** 患者ID */
+    @Excel(name = "患者ID")
+    private String patientId;
+
+    /** 患者姓名 */
+    @Excel(name = "患者姓名")
+    private String patientNm;
+
+    /** 性别 */
+    @Excel(name = "性别", readConverterExp = "0=男,1=女")
+    private Integer gender;
+
+    /** 年龄 */
+    @Excel(name = "年龄")
+    private Integer age;
+
+    /** 医保类型 */
+    @Excel(name = "医保类型")
+    private String medicareType;
+
+    /** 病种 */
+    @Excel(name = "病种")
+    private String diseaseType;
+
+    /** 医院 */
+    @Excel(name = "医院")
+    private String hospital;
+
+    /** 医生姓名 */
+    @Excel(name = "医生姓名")
+    private String doctorNm;
+
+    /** 剩余用药天数 */
+    @Excel(name = "剩余用药天数")
+    private Integer remainingDays;
+
+    /** 用药状态 */
+    @Excel(name = "用药状态")
+    private String medicationStatus;
+
+    /** 首次购药日期 */
+    @Excel(name = "首次购药日期", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date firstPurchaseDate;
+
+    /** 最近一次购药日期 */
+    @Excel(name = "最近一次购药日期", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date lastPurchaseDate;
+
+    /** 最近两次购药间隔天数 */
+    @Excel(name = "最近两次购药间隔天数")
+    private Integer daysBetweenLastTwoPurchases;
+
+    /** 最近一次订单编号 */
+    @Excel(name = "最近一次订单编号")
+    private String lastOrderId;
+
+    /** 最近一次购药数量 */
+    @Excel(name = "最近一次购药数量")
+    private Integer lastPurchaseQuantity;
+
+    /** 总销售数量 */
+    @Excel(name = "总销售数量")
+    private Integer totalSalesQuantity;
+
+    /** 总订单数 */
+    @Excel(name = "总订单数")
+    private Integer totalOrders;
+
+    /** 业务归属 */
+    @Excel(name = "业务归属")
+    private String businessBelonging;
+
+    /** 最近一次随访依从性 */
+    @Excel(name = "最近一次随访依从性")
+    private String lastFollowAdherence;
+
+    /** 药师解答 */
+    @Excel(name = "药师解答")
+    private String pharmacistResponse;
+
+    /** 是否关闭计划 */
+    @Excel(name = "是否关闭计划", readConverterExp = "0=否,1=是")
+    private Integer isCloseRegularFollow;
+
+    /** 计划关闭原因 */
+    @Excel(name = "计划关闭原因")
+    private String closeReason;
+
+    /** 计划关闭日期 */
+    @Excel(name = "计划关闭日期", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date closeDate;
+}

+ 130 - 0
health-common/src/main/java/com/bzd/common/core/domain/entity/TotalPerformanceExport.java

@@ -0,0 +1,130 @@
+package com.bzd.common.core.domain.entity;
+
+import com.bzd.common.annotation.Excel;
+import com.bzd.common.core.domain.BaseEntity;
+import lombok.Data;
+/**
+ * 总览绩效数据导出实体类
+ *
+ * @author wsp
+ */
+@Data
+public class TotalPerformanceExport extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+    private Long id;
+    /** 门店编码 */
+    @Excel(name = "门店编码")
+    private String storeCd;
+
+    /** 门店名称 */
+    @Excel(name = "门店名称")
+    private String storeNm;
+
+    /** D值品名称 */
+    @Excel(name = "D值品名称")
+    private String drugNm;
+
+    /** D值品ID */
+    @Excel(name = "D值品ID")
+    private String drugId;
+
+    /** 平台 */
+    @Excel(name = "平台")
+    private String platformName;
+
+    /** 项目公司 */
+    @Excel(name = "项目公司")
+    private String projectCompanyName;
+
+    /** 按时复购率 */
+    @Excel(name = "按时复购率")
+    private String onTimeRepurchaseRate;
+
+    /** 脱落率 */
+    @Excel(name = "脱落率")
+    private String churnRate;
+
+    /** 脱落召回率 */
+    @Excel(name = "脱落召回率")
+    private String churnRecallRate;
+
+    /** 流失率 */
+    @Excel(name = "流失率")
+    private String lossRate;
+
+    /** 应复购患者数 */
+    @Excel(name = "应复购患者数")
+    private Integer dueForRepurchaseCount;
+
+    /** 按时复购患者数 */
+    @Excel(name = "按时复购患者数")
+    private Integer onTimeRepurchasedCount;
+
+    /** 脱落患者数 */
+    @Excel(name = "脱落患者数")
+    private Integer churnedCount;
+
+    /** 脱落召回患者数 */
+    @Excel(name = "脱落召回患者数")
+    private Integer recalledCount;
+
+    /** 流失患者数 */
+    @Excel(name = "流失患者数")
+    private Integer lostCount;
+
+    /** 购药患者数 */
+    @Excel(name = "购药患者数")
+    private Integer medicationPatients;
+
+    /** 新患数 */
+    @Excel(name = "新患数")
+    private Integer newPatients;
+
+    /** 老患数 */
+    @Excel(name = "老患数")
+    private Integer oldPatients;
+
+    /** 预计随访任务数 */
+    @Excel(name = "预计随访任务数")
+    private Integer estFollowTasks;
+
+    /** 预计随访中完成的任务数 */
+    @Excel(name = "预计随访中完成的任务数")
+    private Integer completedFollowTasks;
+
+    /** 随访任务完成率 */
+    @Excel(name = "随访任务完成率")
+    private String taskCompletionRate;
+
+    /** 实际完成常规任务数 */
+    @Excel(name = "实际完成常规任务数")
+    private Integer actualCompletedRegularTasks;
+
+    /** 实际完成召回任务数 */
+    @Excel(name = "实际完成召回任务数")
+    private Integer actualCompletedRecallTasks;
+
+    /** 实际完成任务数 */
+    @Excel(name = "实际完成任务数")
+    private Integer actualCompletedTasks;
+
+    /** 实际随访患者数 */
+    @Excel(name = "实际随访患者数")
+    private Integer actualFollowPatients;
+
+    /** 实际随访药师数 */
+    @Excel(name = "实际随访药师数")
+    private Integer actualFollowPharmacists;
+
+    /** 预计常规随访任务数 */
+    @Excel(name = "预计常规随访任务数")
+    private Integer estRegularFollowTasks;
+
+    /** 预计召回随访任务数 */
+    @Excel(name = "预计召回随访任务数")
+    private Integer estRecallFollowTasks;
+
+    /** 月份 */
+    @Excel(name = "月份")
+    private String month;
+}

+ 46 - 0
health-common/src/main/java/com/bzd/common/utils/IDCardValidator.java

@@ -0,0 +1,46 @@
+package com.bzd.common.utils;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+public class IDCardValidator {
+    private static final Set<String> PROVINCE_CODES = new HashSet<>();
+    static {
+        String[] codes = {"11","12","13","14","15","21","22","23","31","32","33","34","35","36","37","41","42","43","44","45","46","50","51","52","53","54","61","62","63","64","65","71","81","82"};
+        for (String code : codes) {
+            PROVINCE_CODES.add(code);
+        }
+    }
+
+    public static boolean validateIDCard(String id) {
+        if (id == null || id.length() != 18) return false;
+        id = id.toUpperCase();
+        if (!id.matches("^\\d{17}[\\dX]$")) return false;
+
+        // 行政区划代码校验
+        String provinceCode = id.substring(0, 2);
+        if (!PROVINCE_CODES.contains(provinceCode)) return false;
+
+        // 出生日期校验
+        String birthDateStr = id.substring(6, 14);
+        try {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
+            sdf.setLenient(false); // 禁止自动纠正日期
+            sdf.parse(birthDateStr);
+        } catch (ParseException e) {
+            return false;
+        }
+
+        // 校验码计算
+        char[] idChars = id.toCharArray();
+        int[] weights = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
+        char[] checkCodes = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
+        int sum = 0;
+        for (int i = 0; i < 17; i++) {
+            sum += (idChars[i] - '0') * weights[i];
+        }
+        return idChars[17] == checkCodes[sum % 11];
+    }
+}

+ 107 - 0
health-common/src/main/java/com/bzd/common/utils/submail/MessageXsend.java

@@ -0,0 +1,107 @@
+package com.bzd.common.utils.submail;
+
+//import com.bzd.common.json.JSONObject;
+import com.alibaba.fastjson.JSONObject;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+
+import java.io.IOException;
+import java.util.TreeMap;
+
+public class MessageXsend {
+    public static final String TIMESTAMP = "https://api-v4.mysubmail.com/service/timestamp";
+    private static final String URL = "https://api-v4.mysubmail.com/sms/xsend";
+    public static final String TYPE_MD5 = "md5";
+    public static final String TYPE_SHA1 = "sha1";
+    public static final  String appid = "94309";// 宝智达冷链
+    public static final String appkey = "8c79de85cc4bc61d93b36ff89862e3e6";
+    public static final  String project = "3HQxS3";
+    public static final  String sign_type = "md5";
+    public static final  String sign_version = "2";
+    //获取时间戳
+    public static String sendMessage(String to, Integer dvaluedays, String userName, String productName, String storeName) {
+        TreeMap<String, String> requestData = new TreeMap<String, String>();
+        JSONObject vars = new JSONObject();
+        vars.put("userName", userName);
+        vars.put("productName", productName);
+        vars.put("dvaluedays", dvaluedays.toString());
+        vars.put("storeName", storeName);
+        requestData.put("appid", appid);
+        requestData.put("to", to);
+        requestData.put("project", project);
+
+        if (sign_type.equals(TYPE_MD5) || sign_type.equals(TYPE_SHA1)) {
+            try {
+                requestData.put("timestamp", getTimestamp());
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            requestData.put("sign_type", sign_type);
+            requestData.put("sign_version", sign_version);
+            String signStr = appid + appkey + RequestEncoder.formatRequest(requestData) + appid + appkey;
+            System.out.println(signStr);
+            requestData.put("signature", RequestEncoder.encode(sign_type, signStr));
+        } else {
+            requestData.put("signature", appkey);
+        }
+        requestData.put("vars", vars.toJSONString());
+
+        HttpPost httpPost = new HttpPost(URL);
+        httpPost.setHeader("Accept", "application/json");
+        httpPost.setHeader("Content-Type", "application/json");
+        StringEntity entity = new StringEntity(JSONObject.toJSONString(requestData), "UTF-8");
+        httpPost.setEntity(entity);
+        try {
+            CloseableHttpClient closeableHttpClient = HttpClientBuilder.create().build();
+            HttpResponse response = closeableHttpClient.execute(httpPost);
+            HttpEntity httpEntity = response.getEntity();
+            if (httpEntity != null) {
+                String jsonStr = EntityUtils.toString(httpEntity, "UTF-8");
+                System.out.println(jsonStr);
+                return jsonStr;
+            }
+        } catch (ClientProtocolException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+
+
+
+    //获取时间戳
+    private static String getTimestamp() throws IOException {
+        CloseableHttpClient closeableHttpClient = HttpClientBuilder.create().build();
+        HttpGet httpget = new HttpGet(TIMESTAMP);
+        try {
+            HttpResponse response = closeableHttpClient.execute(httpget);
+            HttpEntity httpEntity = response.getEntity();
+            String jsonStr = EntityUtils.toString(httpEntity, "UTF-8");
+            if (jsonStr != null) {
+                JSONObject json = JSONObject.parseObject(jsonStr);
+                return json.getString("timestamp");
+            }
+        } catch (ClientProtocolException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+
+
+
+ }

+ 53 - 0
health-common/src/main/java/com/bzd/common/utils/submail/RequestEncoder.java

@@ -0,0 +1,53 @@
+package com.bzd.common.utils.submail;
+
+import java.security.MessageDigest;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.TreeMap;
+
+public class RequestEncoder {
+    private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5',
+            '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+    public static String encode(String algorithm, String str) {
+        if (str == null) {
+            return null;
+        }
+        try {
+            MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
+            messageDigest.update(str.getBytes("UTF-8"));
+            return getFormattedText(messageDigest.digest());
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    private static String getFormattedText(byte[] bytes) {
+        int len = bytes.length;
+        StringBuilder buf = new StringBuilder(len * 2);
+        for (int j = 0; j < len; j++) {
+            buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
+            buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
+        }
+        return buf.toString();
+    }
+
+    public static String formatRequest(TreeMap<String, String> data) {
+        Set<String> keySet = data.keySet();
+        Iterator<String> it = keySet.iterator();
+        StringBuffer sb = new StringBuffer();
+        while (it.hasNext()) {
+            String key = it.next();
+            Object value = data.get(key);
+            if (value instanceof String) {
+                sb.append(key + "=" + value + "&");
+            }
+        }
+        if (sb.length() != 0) {
+            System.out.println("sb.substring(0, sb.length() - 1) = " + sb.substring(0, sb.length() - 1));
+            return sb.substring(0, sb.length() - 1);
+        }
+        return null;
+    }
+}

+ 84 - 0
health-common/src/main/java/com/bzd/common/utils/submail/SubMailConfig.java

@@ -0,0 +1,84 @@
+package com.bzd.common.utils.submail;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class SubMailConfig {
+    @Value("${submail.timestamp-url}")
+    private String timestampUrl;
+
+    @Value("${submail.url}")
+    private String url;
+
+    @Value("${submail.appid}")
+    private String appid;
+
+    @Value("${submail.appkey}")
+    private String appkey;
+    @Value("${submail.project}")
+    private String project;
+    @Value("${submail.sign-type}")
+    private String signType;
+    @Value("${submail.sign-version}")
+    private String signVersion;
+
+
+    // Getters and Setters
+    public String getTimestampUrl() {
+        return timestampUrl;
+    }
+
+    public void setTimestampUrl(String timestampUrl) {
+        this.timestampUrl = timestampUrl;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public String getAppid() {
+        return appid;
+    }
+
+    public void setAppid(String appid) {
+        this.appid = appid;
+    }
+
+    public String getAppkey() {
+        return appkey;
+    }
+
+    public void setAppkey(String appkey) {
+        this.appkey = appkey;
+    }
+
+    public String getProject() {
+        return project;
+    }
+
+    public void setProject(String project) {
+        this.project = project;
+    }
+
+    public String getSignType() {
+        return signType;
+    }
+
+    public void setSignType(String signType) {
+        this.signType = signType;
+    }
+
+    public String getSignVersion() {
+        return signVersion;
+    }
+
+    public void setSignVersion(String signVersion) {
+        this.signVersion = signVersion;
+    }
+}

+ 31 - 0
health-quartz/src/main/java/com/bzd/quartz/domain/SysJob.java

@@ -77,6 +77,37 @@ public class SysJob extends BaseEntity implements Serializable
 
     /** 上一个任务执行时间 */
     private String LastTaskExecutionTime;
+    /** 任务创建id唯一标识 */
+    private Integer createId;
+    /** 执行动作类型 */
+    private String actions;
+    /** 节点标记 */
+    private String nodeFlag;
+
+
+    public Integer getCreateId() {
+        return createId;
+    }
+
+    public void setCreateId(Integer createId) {
+        this.createId = createId;
+    }
+
+    public String getActions() {
+        return actions;
+    }
+
+    public void setActions(String actions) {
+        this.actions = actions;
+    }
+
+    public String getNodeFlag() {
+        return nodeFlag;
+    }
+
+    public void setNodeFlag(String nodeFlag) {
+        this.nodeFlag = nodeFlag;
+    }
 
     public Integer getRemainingExecutions() {
         return remainingExecutions;

+ 50 - 0
health-quartz/src/main/java/com/bzd/quartz/task/FilterCondition.java

@@ -0,0 +1,50 @@
+package com.bzd.quartz.task;
+
+public class FilterCondition {
+    private String traits;
+    private String traitValue;
+    private String judgingCondition;
+    private String judgmentValue;
+    private Integer rowIndex;
+
+    // Getters and Setters
+    public String getTraits() {
+        return traits;
+    }
+
+    public void setTraits(String traits) {
+        this.traits = traits;
+    }
+
+    public String getTraitValue() {
+        return traitValue;
+    }
+
+    public void setTraitValue(String traitValue) {
+        this.traitValue = traitValue;
+    }
+
+    public String getJudgingCondition() {
+        return judgingCondition;
+    }
+
+    public void setJudgingCondition(String judgingCondition) {
+        this.judgingCondition = judgingCondition;
+    }
+
+    public String getJudgmentValue() {
+        return judgmentValue;
+    }
+
+    public void setJudgmentValue(String judgmentValue) {
+        this.judgmentValue = judgmentValue;
+    }
+
+    public Integer getRowIndex() {
+        return rowIndex;
+    }
+
+    public void setRowIndex(Integer rowIndex) {
+        this.rowIndex = rowIndex;
+    }
+}

+ 856 - 28
health-quartz/src/main/java/com/bzd/quartz/task/RyTask.java

@@ -3,18 +3,24 @@ package com.bzd.quartz.task;
 import com.bzd.common.config.dao.DaoSupport;
 import com.bzd.common.config.dao.PageData;
 import com.bzd.common.constant.ScheduleConstants;
-import com.bzd.common.utils.DateUtils;
+import com.bzd.common.utils.uuid.IdUtils;
 import com.bzd.quartz.domain.SysJob;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.bzd.common.utils.DateUtils;
 import com.bzd.quartz.service.ISysJobService;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import com.bzd.common.utils.StringUtils;
 
 import javax.annotation.Resource;
-import java.sql.Date;
-import java.util.List;
+import java.io.IOException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
 
 import static com.bzd.common.utils.ShiroUtils.getSysUser;
+import static com.bzd.common.utils.submail.MessageXsend.sendMessage;
 
 /**
  * 定时任务调度测试
@@ -28,6 +34,8 @@ public class RyTask
     private DaoSupport daoSupport;
     @Autowired
     ISysJobService jobService;
+
+
     public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i)
     {
         System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i));
@@ -60,8 +68,18 @@ public class RyTask
         int planResult= daoSupport.update("followTaskMapper.openPlanByPlanId", plan);
         System.out.println("触发计划:更新方法<openPlanByPlanId(plan)>:plan="+plan+"结果:"+planResult);
     }
+    /**
+     * 根据创建时间和激活天数生成 Cron 表达式。
+     *
+     * @param createdTime   创建计划的时间字符串,格式为 "yyyy-MM-dd HH:mm:ss"
+     * @param activateWhichDay 激活节点在创建后的第几天
+     * @return 生成的 Cron 表达式
+     */
+    private static final SimpleDateFormat SDF_DATE_ONLY = new SimpleDateFormat("yyyy-MM-dd");
+    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss");
     //触发任务
-    public void triggerTask(String appointmentDate, String nodeFlag, String action, String mdmCode, String storeId , String patientId , String templateId , Integer planId , Integer taskId)throws Exception {
+    public void triggerTask(String filterCondition ,String taskActionTaskType,String deliveryChannel,String appointmentDate, String nodeFlag, String action, String mdmCode, String storeId , String patientId , String templateId , Integer planId , Integer taskId,Integer createId)throws Exception {
         System.out.println("触发任务了=========>执行有参方法:triggerTask parameters"+nodeFlag+action+mdmCode+storeId+patientId+templateId+planId+taskId);
         System.out.println("执行时间:"+DateUtils.getTime());
         //        ST 时间条件+单次节点
@@ -70,43 +88,161 @@ public class RyTask
         //        PE 事件条件+周期节点
         //        生成任务 action=create
         //        执行任务 action=execute
-        if("create".equals(action)){
-            if("PT".equals(nodeFlag) || "PE".equals(nodeFlag)){
-                updateJobStatusByTaskId(taskId,patientId,storeId);
-            }
-            PageData    task=new PageData();
-            task.put("planId",planId);
-            task.put("taskId",taskId);
-            task.put("storeId",storeId);
-            task.put("patientId",patientId);
-            task.put("taskStatus","待执行");//待执行,已下发,已完成,未完成,未下发,已取消
-            task.put("updatedAt",DateUtils.getTime());
-            task.put("operator","系统触发");
-            task.put("appointmentDate",appointmentDate);//预约日期
-            System.out.println("触发任务:更新方法<openTaskByTaskId(task)>:预约日期="+appointmentDate);
-            int taskResult= daoSupport.update("followTaskMapper.openTaskByTaskId", task);
-            System.out.println("触发任务:更新方法<openTaskByTaskId(task)>:task="+task+"结果:"+taskResult);
-        }if("execute".equals(action)){
-            System.out.println("执行时间:发短信了,你好:小爱同学,需要什么帮助吗,你的药吃完了没?"+DateUtils.getTime()+"古币啊");
+        //        任务动作 taskActionTaskType=触达任务 表单任务
+        //        下发渠道 deliveryChannel=药师工作台 短信
+        if("表单任务".equals(taskActionTaskType)){
+            if(action.equals("create")){//打开任务开关
+                if("PT".equals(nodeFlag)){//时间+周期
+                    Integer NewtaskId = (Integer) IdUtils.get10randomNumber();
+                    updateJobStatusByTaskId(NewtaskId,taskId,patientId,storeId,createId,action);
+                }
+                if("PE".equals(nodeFlag)){//事件+周期 最近订单D值 剩余用药天数
+                    PageData pd1 = new PageData();
+                    pd1.put("taskId",taskId);
+                    pd1.put("templateId",templateId);
+                    pd1.put("patientId", patientId);
+                    pd1.put("mdmCode", mdmCode);
+                    pd1.put("storeId", storeId);
+                    PageData node = (PageData) daoSupport.findForObject("followTaskMapper.selectOneBytt", pd1);
+                    int createTaskExecutionTimes = (int) node.get("createTaskExecutionTimes"); // 共执行几次任务
+                    int generationDaysAfter = (int) node.get("generationDaysAfter"); // 执行任务(生成后第几天执行任务)
+                    int createTaskTriggerValue = (int) node.get("createTaskTriggerValue"); // 剩余用药天数
+                    String generationHMS = (String) node.get("generationHMS"); // 执行任务时分秒
+                    PageData D3Conditions = (PageData) daoSupport.findForObject("dvalueMapper.selectLinkDValuesBy3Conditions2", pd1);// 查询关联的D值购药用药天数
+                    Integer sum_total = (Integer) D3Conditions.get("adjusted_sum_total"); // 实际剩余用药天数
+
+                    // 检查是否满足创建任务的条件
+                    if (sum_total != null && sum_total == createTaskTriggerValue) {
+                        Date startDate = new Date();//第几天生成任务的基准时间
+                            // 延迟1秒
+                            Thread.sleep(1000);
+                            // 通过生成任务的时间计算执行任务的时间
+                            Calendar calendar3 = Calendar.getInstance();
+                            calendar3.setTime(startDate);
+                            calendar3.add(Calendar.DAY_OF_MONTH, generationDaysAfter); // 根据 generationDaysAfter 来决定执行任务的时间
+                            Date executeTaskTime = calendar3.getTime();
+                            // 执行任务 定时器
+                            node.put("action", "execute");
+                            SysJob job3 = createSysJob(node, mdmCode, storeId, patientId, templateId, planId, taskId, generationDaysAfter, generationHMS, executeTaskTime.toString(), 0);
+                            Date ExCutDate = parseStringToDateWithMultipleFormats(job3.getLastTaskExecutionTime());
+                            job3.setExpiryDate(ExCutDate);
+                            PageData tk = new PageData();
+                            tk.put("taskId", taskId);
+                            tk.put("appointmentDate", ExCutDate);
+                            int FollowTask = daoSupport.update("followTaskMapper.updateFollowTaskAppointmentDateByTaskId", tk); // 更新任务随访预约日期
+                            System.out.println("更新任务 随访预约日期结果:" + FollowTask);
+
+                            job3.setMaxExecutions(createTaskExecutionTimes); // 设置最大执行次数
+                            job3.setCurrentExecutionCount(0); // 初始化当前执行次数为0
+                            job3.setRemainingExecutions(createTaskExecutionTimes); // 设置剩余执行次数
+                            int PEA = jobService.insertJob2(job3);
+                            System.out.println("满足生成任务的条件: createTaskTriggerValue 等于 sum_total 执行任务 定时器结果:"+PEA);
+
+                    } else {
+                        System.out.println("不满足生成任务的条件: createTaskTriggerValue 不等于 sum_total.");
+                        return ;
+
+                    }
+                    Integer NewtaskId = (Integer) IdUtils.get10randomNumber();
+                    updateJobStatusByTaskId(NewtaskId,taskId,patientId,storeId,createId,action);
+                }
+                PageData    task=new PageData();
+                task.put("planId",planId);
+                task.put("taskId",taskId);
+                task.put("storeId",storeId);
+                task.put("createId",createId);
+                task.put("actions",action);
+                task.put("patientId",patientId);
+                task.put("taskStatus","待执行");//待执行,已下发,已完成,未完成,未下发,已取消
+                task.put("updatedAt",DateUtils.getTime());
+                task.put("operator","系统触发");
+                int taskResult= daoSupport.update("followTaskMapper.openTaskByTaskId", task);//激活开启任务,显示任务
+                System.out.println("触发生成任务:="+taskActionTaskType+"结果:"+taskResult);
+            }
+            if(action.equals("execute")){//执行任务
+                if(StringUtils.isNotEmpty(filterCondition)){
+                    //处理有过滤条件的逻辑
+                        List<FilterCondition> conditions = parseFilterCondition(filterCondition);
+                        boolean shouldExecute = evaluateConditions(conditions, patientId, storeId, planId, taskId, createId,mdmCode);
+                        if (!shouldExecute) {
+                            System.out.println("表单任务:满足过滤条件,不执行任务");
+                            return;
+                        }
+                }
+//                if("PT".equals(nodeFlag) || "PE".equals(nodeFlag)){
+//                    updateJobStatusByTaskId(taskId,taskId,patientId,storeId,createId,action);
+//                }
+                  System.out.println("在这里执行表单任务动作");
+
+            }
+
+        }if("触达任务".equals(taskActionTaskType)){
+            if(action.equals("create")) {
+                if("PT".equals(nodeFlag) || "PE".equals(nodeFlag)){
+
+                    updateJobStatusByTaskId(null,taskId,patientId,storeId,createId,action);
+                }
+                //处理创建动作的逻辑
+            }if(action.equals("execute")){
+                if(StringUtils.isNotEmpty(filterCondition)){
+                //处理有过滤条件的逻辑
+                    List<FilterCondition> conditions = parseFilterCondition(filterCondition);
+                    boolean shouldExecute = evaluateConditions(conditions, patientId, storeId, planId, taskId, createId,mdmCode);
+                    if (!shouldExecute) {
+                        System.out.println("触达任务:满足过滤条件,不执行任务");
+                        return;
+                    }
+                }
+                     System.out.println("没有过滤条件,我继续执行任务;在这里触发一个动作去通知或怎改变任务表的一些状态");
+                    if(StringUtils.isNotEmpty(deliveryChannel)){
+                         PageData    taskData=new PageData();
+                                        taskData.put("mdmCode",mdmCode);
+                                        taskData.put("patientId",patientId);
+                        PageData    jobResult= (PageData)daoSupport.findForObject("followTaskMapper.getSendInfoByPM", taskData);
+                        if(StringUtils.isNotNull(jobResult)){
+                            String userName = jobResult.getString("name");
+                            String productName = jobResult.getString("productName");
+                            String storeName = jobResult.getString("dept_name");
+                            if(StringUtils.isEmpty(storeName)){
+                                storeName=storeId;//storeId
+                            }
+                            // 先安全地转换为 Long 类型
+                            Long adjustedSumTotalLong = (Long) jobResult.get("adjusted_sum_total");
+                            // 再转换为 Integer 类型
+                            Integer dvaluedays = adjustedSumTotalLong != null ? adjustedSumTotalLong.intValue() : null;
+                            String phoneNumber = (String) jobResult.get("phoneNumber");
+                            String sendMessage = sendMessage(phoneNumber, dvaluedays, userName, productName, storeName);
+                            System.out.println("执行触达任务:响应结果:"+sendMessage);
+                        }
+                        System.out.println("执行触达任务:下发渠道为:"+deliveryChannel+",你好:小爱同学,需要什么帮助吗,你的药吃完了没?"+DateUtils.getTime()+"古币啊");
+
+                }
+            }
+
         }
 
     }
+
+
     /**
      * 更新任务状态并递减剩余执行次数。
      *
      * @param taskId 任务ID
      */
-    public void updateJobStatusByTaskId(Integer taskId, String storeId, String patientId) throws Exception {
+    public void updateJobStatusByTaskId(Integer NewtaskId,Integer taskId, String patientId,String storeId,Integer createId,String action) throws Exception {
         PageData    task=new PageData();
         task.put("taskId",taskId);
         task.put("storeId",storeId);
+        task.put("createId",createId);
         task.put("patientId",patientId);
-
+        task.put("action","execute");
         PageData    jobResult= (PageData)daoSupport.findForObject("followTaskMapper.getJobByTaskId", task);
+        PageData    jobResult2= (PageData)daoSupport.findForObject("followTaskMapper.getJobByCreateId", task);
+
+        if (StringUtils.isNotNull(jobResult) && StringUtils.isNull(NewtaskId)) {
+            int getRemainingExecutions=(int)   jobResult.get("remainingExecutions");
+            int currentExecutionCount=(int)   jobResult.get("currentExecutionCount");
 
-        if (StringUtils.isNotNull(jobResult)) {
-         int getRemainingExecutions=(int)   jobResult.get("remainingExecutions");
-         int currentExecutionCount=(int)   jobResult.get("currentExecutionCount");
             if (getRemainingExecutions>0) {
                 jobResult.put("remainingExecutions",getRemainingExecutions - 1);
                 jobResult.put("currentExecutionCount",currentExecutionCount + 1);
@@ -114,11 +250,703 @@ public class RyTask
                 jobResult.put("updateBy","SYSTEM");
                 jobResult.put("taskId",taskId);
                 jobResult.put("storeId",storeId);
+                jobResult.put("createId",createId);
                 jobResult.put("patientId",patientId);
+                jobResult.put("remark","非周期任务的更新");
                 int ret= daoSupport.update("followTaskMapper.updateJobStatusByTaskId", jobResult);
-                System.out.println("执行更行方法:更新任务状态并递减剩余执行次数<updateJobStatusByTaskId(String taskId)>:taskId="+taskId+"结果:"+ret);
+                System.out.println("非周期任务job的更新结果:"+ret);
+            }
+        }
+        if (StringUtils.isNotNull(jobResult2) && StringUtils.isNotNull(NewtaskId)) {
+            PageData    pdResult=new PageData();
+            pdResult.put("taskId",taskId);
+            PageData taskObj= (PageData) daoSupport.findForObject("followTaskMapper.getTaskByTaskId", pdResult);
+            PageData    taskResult=new PageData();
+            taskResult.put("appointmentDate",jobResult2.get("expiry_date"));  //预约日期
+            taskResult.put("createdAt",DateUtils.getTime()); //创建时间
+            taskResult.put("operator","系统触发");// 操作人
+            taskResult.put("taskId",NewtaskId);
+            taskResult.put("taskName","次周期任务"+NewtaskId);
+            taskResult.put("taskStatus","待执行");
+
+            taskResult.put("templeName",taskObj.get("templeName"));
+            taskResult.put("businessBelonging",taskObj.get("businessBelonging"));
+            taskResult.put("taskTheme",taskObj.get("taskTheme"));
+            taskResult.put("nodeId",taskObj.get("nodeId"));
+            taskResult.put("planId",taskObj.get("planId"));
+            taskResult.put("patientId",taskObj.get("patientId"));
+            taskResult.put("patientName",taskObj.get("patientName"));
+            taskResult.put("gender",taskObj.get("gender"));
+            taskResult.put("age",taskObj.get("age"));
+            taskResult.put("disease",taskObj.get("disease"));
+            taskResult.put("drugsLinkId",taskObj.get("drugsLinkId"));
+            taskResult.put("storeName",taskObj.get("storeName"));
+            taskResult.put("mdmCode",taskObj.get("mdmCode"));
+            taskResult.put("genericName",taskObj.get("genericName"));
+            taskResult.put("productName",taskObj.get("productName"));
+            taskResult.put("taskFollower",taskObj.get("taskFollower"));
+            taskResult.put("storeId",taskObj.get("storeId"));
+            int ret2= daoSupport.save("followTaskMapper.insertFollowTask", taskResult);//生成新任务 // 示例返回值
+            System.out.println("周期任务job需要更换新的taskId生成新任务结果:"+ret2);
+            int getRemainingExecutions=(int)   jobResult2.get("remainingExecutions");
+            int currentExecutionCount=(int)   jobResult2.get("currentExecutionCount");
+            if (getRemainingExecutions>0) {
+                jobResult2.put("remainingExecutions",getRemainingExecutions - 1);
+                jobResult2.put("currentExecutionCount",currentExecutionCount + 1);
+                jobResult2.put("updateTime",DateUtils.getTime());
+                jobResult2.put("updateBy","SYSTEM");
+                jobResult2.put("taskId",NewtaskId);
+                jobResult2.put("storeId",storeId);
+                jobResult2.put("createId",createId);
+                jobResult2.put("patientId",patientId);
+                jobResult2.put("remark","周期任务需要更换新的taskId");
+                int ret= daoSupport.update("followTaskMapper.updateJobStatusByTaskId", jobResult2);
+                System.out.println("周期任务job需要更换新的taskId结果:"+ret);
+
+            }
+        }
+
+    }
+
+
+    /**3 创建任务*/
+    private SysJob createSysJob(PageData pd, String mdmCode, String storeId, String patientId, String templateId, Integer planId, Integer taskId, int daysOffset, String generationHMS, String time, int eventDay) throws ParseException {
+        SysJob job = new SysJob();
+        job.setJobGroup(pd.get("jobGroup").toString()); //任务组名
+        job.setJobName(pd.get("jobName").toString()); // 任务名称
+        String action = (String) pd.get("action");
+        String nodeFlag = (String) pd.get("nodeFlag");
+        String taskActionTaskType = (String) pd.get("taskActionTaskType"); //任务动作 触达任务 表单任务
+        String deliveryChannel = (String) pd.get("deliveryChannel"); //下发渠道 药师工作台 短信
+        String filterCondition= (String) pd.get("filterCondition");//是否添加有过滤条件
+        if(StringUtils.isEmpty(filterCondition)){
+            filterCondition="";
+        }
+        Integer createId = (Integer) IdUtils.get10randomNumber();
+        String cronExpression="";
+        String LastTaskTime="";
+        if(nodeFlag.equals("PT") && action.equals("create")){
+            generationHMS="01:01:59";
+            cronExpression = generateCronExpression7(time, generationHMS,eventDay);
+            LastTaskTime=  zhuanhuaDirectly2(cronExpression);
+        } else if (nodeFlag.equals("PE") && action.equals("create")) {
+            cronExpression = generateCronExpression7(time, generationHMS,eventDay);
+        } else{
+            cronExpression = generateCronExpression4(time, daysOffset, generationHMS,false);
+            LastTaskTime=  zhuanhuaDirectly(cronExpression);
+        }
+        // 设置 Cron 表达式
+        System.out.println("创建任务时间表达式cronExpression:"+cronExpression);
+        // 把 lastTaskTimeStr 转成 Date 类型
+        Date lastTaskTimeDate = SDF.parse(LastTaskTime);
+        // 使用 SDF_DATE_ONLY 格式化 Date 对象,得到仅包含年月日的字符串
+        String formattedDate = SDF_DATE_ONLY.format(lastTaskTimeDate);
+        System.out.println("Formatted Date: " + formattedDate);
+        String invokeTarget = String.format("ryTask.triggerTask('%s','%s','%s', '%s','%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d)",
+                filterCondition,taskActionTaskType,deliveryChannel,formattedDate, nodeFlag, action, mdmCode, storeId, patientId, templateId, planId, taskId,createId);
+        job.setInvokeTarget(invokeTarget); // 设置调用目标字符串
+        job.setLastTaskExecutionTime(LastTaskTime);
+        job.setCronExpression(cronExpression); // 设置 Cron 表达式
+        job.setMisfirePolicy(ScheduleConstants.MISFIRE_FIRE_AND_PROCEED); // Cron 计划策略
+        job.setConcurrent("1"); // 是否并发执行(0允许 1禁止)
+        job.setStatus("0");
+        job.setPlanId(planId.toString());
+        job.setStoreId(storeId);
+        job.setPatientId(patientId);
+        job.setTaskId(taskId.toString());
+        job.setTaskId(taskId.toString());
+
+        job.setCreateId(createId);
+        job.setActions(action);
+        job.setNodeFlag(nodeFlag);
+        return job;
+    }
+    public static Date parseStringToDateWithMultipleFormats(String time) throws ParseException {
+        if (time == null || time.trim().isEmpty()) {
+            throw new IllegalArgumentException("Time string cannot be null or empty.");
+        }
+
+        // 定义多个可能的时间格式
+        String[] formats = {
+                "yyyy-MM-dd HH:mm:ss",
+                "yyyy/MM/dd HH:mm:ss",
+                "dd-MM-yyyy HH:mm:ss",
+                "MM/dd/yyyy HH:mm:ss"
+        };
+
+        // 尝试每种格式进行解析
+        for (String format : formats) {
+            SimpleDateFormat sdf = new SimpleDateFormat(format);
+            try {
+                return sdf.parse(time);
+            } catch (ParseException e) {
+                // 继续尝试下一个格式
+            }
+        }
+
+        // 如果所有格式都无法解析,则抛出异常
+        throw new ParseException("Unable to parse the date string with any of the provided formats.", 0);
+    }
+    public static String generateCronExpression7(String createdTime, String generationHMS, int eventDay) {
+        // 定义输入的日期格式
+        SimpleDateFormat sdfInput = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.US);
+
+        Calendar calendar = Calendar.getInstance();
+
+        try {
+            // 解析传入的创建时间
+            Date date = sdfInput.parse(createdTime);
+            calendar.setTime(date);
+
+            // 设置生成的时间部分(小时、分钟、秒)
+            String[] hmsParts = generationHMS.split(":");
+            int hour = Integer.parseInt(hmsParts[0]);
+            int minute = Integer.parseInt(hmsParts[1]);
+            int second = Integer.parseInt(hmsParts[2]);
+
+            // 设置具体的小时、分钟和秒
+            calendar.set(Calendar.HOUR_OF_DAY, hour);
+            calendar.set(Calendar.MINUTE, minute);
+            calendar.set(Calendar.SECOND, second);
+
+            // 确保时间不早于当前时间
+            if (calendar.getTime().before(new Date())) {
+                calendar.add(Calendar.DAY_OF_MONTH, eventDay); // 如果时间已经过去,则增加间隔天数
+            }
+
+            // 循环找到下一个符合条件的日期(每隔eventDay天)
+            while (calendar.getTime().before(new Date())) {
+                calendar.add(Calendar.DAY_OF_MONTH, eventDay);
+            }
+
+            // 获取年份、月份、日期
+            int year = calendar.get(Calendar.YEAR);
+            int month = calendar.get(Calendar.MONTH) + 1; // Calendar.MONTH 是从0开始的
+            int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
+
+            // 构建Cron表达式
+            String cronExpression = String.format("%d %d %d %d %d ? %d",
+                    second, // 秒
+                    minute, // 分钟
+                    hour,   // 小时
+                    dayOfMonth, // 日期
+                    month,  // 月份
+                    year);  // 年份
+
+            return cronExpression;
+        } catch (ParseException e) {
+            e.printStackTrace();
+            // 提供默认值或采取其他措施处理异常
+            return "59 30 19 18 3 ? 2099"; // 默认返回一个示例表达式
+        }
+    }
+
+
+
+    /**3 生成表达式*/
+    public static String generateCronExpression4(String createdTime, int activateWhichDay, String generationHMS, boolean isActivationJob) {
+        // 正确的日期格式
+        SimpleDateFormat sdfInput = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.US);
+        Calendar calendar = Calendar.getInstance();
+
+        try {
+            // 解析传入的创建时间
+            Date date = sdfInput.parse(createdTime);
+            calendar.setTime(date);
+
+            // 加上激活计划的天数
+            calendar.add(Calendar.DAY_OF_MONTH, activateWhichDay);
+
+            if (isActivationJob) {
+                // 对于激活任务,我们只关心日期部分
+                return String.format("%d %d %d %d %d ? %d",
+                        0, // 秒
+                        0, // 分钟
+                        0, // 小时
+                        calendar.get(Calendar.DAY_OF_MONTH), // 日
+                        calendar.get(Calendar.MONTH) + 1, // 月份(Calendar.MONTH 是从0开始计数的)
+                        calendar.get(Calendar.YEAR)); // 年份
+            } else {
+                // 设置具体执行的小时、分钟和秒
+                String[] timeParts = generationHMS.split(":");
+                calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(timeParts[0]));
+                calendar.set(Calendar.MINUTE, Integer.parseInt(timeParts[1]));
+                calendar.set(Calendar.SECOND, Integer.parseInt(timeParts[2]));
+
+                // 返回Cron表达式
+                return String.format("%d %d %d %d %d ? %d",
+                        calendar.get(Calendar.SECOND),
+                        calendar.get(Calendar.MINUTE),
+                        calendar.get(Calendar.HOUR_OF_DAY),
+                        calendar.get(Calendar.DAY_OF_MONTH),
+                        calendar.get(Calendar.MONTH) + 1,
+                        calendar.get(Calendar.YEAR)); // 添加年份字段
+            }
+        } catch (ParseException e) {
+            // 提供默认值或采取其他措施处理异常
+            e.printStackTrace();
+            // 这里可以设置一个默认的cron表达式,例如立即执行的任务
+            return "0 0 0 * * ? *"; // 默认每天午夜执行的任务,并且包含年份占位符
+        }
+    }
+
+
+
+    public static String zhuanhuaDirectly2(String cronExpression) throws ParseException {
+        try {
+            // 分割 Cron 表达式
+            String[] parts = cronExpression.trim().split("\\s+");
+            if (parts.length != 6 && parts.length != 7) {
+                throw new ParseException("Invalid Cron expression format. Expected 6 or 7 fields.", 0);
+            }
+
+            // 提取各部分
+            String second = parts[0];
+            String minute = parts[1];
+            String hour = parts[2];
+            String dayOfMonth = parts[3];
+            String month = parts[4];
+            String dayOfWeek = parts[5]; // 忽略 dayOfWeek 字段
+            String year = parts.length == 7 ? parts[6] : String.valueOf(Calendar.getInstance().get(Calendar.YEAR));
+
+            // 构建 Calendar 对象并设置各字段
+            Calendar calendar = Calendar.getInstance();
+            calendar.set(Calendar.YEAR, parseYear(year));
+            calendar.set(Calendar.MONTH, parseMonth(month) - 1); // Calendar.MONTH 是从 0 开始的
+            calendar.set(Calendar.HOUR_OF_DAY, parseHour(hour));
+            calendar.set(Calendar.MINUTE, parseMinute(minute));
+            calendar.set(Calendar.SECOND, parseSecond(second));
+
+            // 处理 dayOfMonth 中的间隔字段
+            if (dayOfMonth.startsWith("*/")) {
+                int interval = Integer.parseInt(dayOfMonth.substring(2));
+                int currentDay = calendar.get(Calendar.DAY_OF_MONTH);
+                int nextDay = ((currentDay + interval - 1) / interval) * interval + 1;
+                while (nextDay <= currentDay) {
+                    nextDay += interval;
+                }
+                calendar.set(Calendar.DAY_OF_MONTH, nextDay);
+            } else {
+                calendar.set(Calendar.DAY_OF_MONTH, parseDayOfMonth(dayOfMonth));
+            }
+
+            // 格式化时间为字符串
+            return SDF.format(calendar.getTime());
+        } catch (ParseException e) {
+            // 打印或记录异常信息
+            System.err.println("Error parsing Cron expression: " + e.getMessage());
+            throw e; // 或者你可以选择返回一个错误消息而不是重新抛出异常
+        }
+    }
+
+
+
+    public static String zhuanhuaDirectly(String cronExpression) throws ParseException {
+        try {
+            // 分割 Cron 表达式
+            String[] parts = cronExpression.split(" ");
+            if (parts.length != 7) {
+                throw new ParseException("Invalid Cron expression format. Expected 7 fields.", 0);
+            }
+
+            // 提取各部分
+            String second = parts[0];
+            String minute = parts[1];
+            String hour = parts[2];
+            String dayOfMonth = parts[3];
+            String month = parts[4];
+            String dayOfWeek = parts[5]; // 这里我们忽略 dayOfWeek 字段
+            String year = parts[6];
+
+            // 构建 Calendar 对象并设置各字段
+            Calendar calendar = Calendar.getInstance();
+            calendar.set(Calendar.YEAR, Integer.parseInt(year));
+            calendar.set(Calendar.MONTH, parseMonth(month) - 1); // Calendar.MONTH 是从 0 开始的
+            calendar.set(Calendar.DAY_OF_MONTH, parseDayOfMonth(dayOfMonth));
+            calendar.set(Calendar.HOUR_OF_DAY, parseHour(hour));
+            calendar.set(Calendar.MINUTE, parseMinute(minute));
+            calendar.set(Calendar.SECOND, parseSecond(second));
+
+            // 格式化时间为字符串
+            return SDF.format(calendar.getTime());
+        } catch (ParseException e) {
+            // 打印或记录异常信息
+            System.err.println("Error parsing Cron expression: " + e.getMessage());
+            throw e; // 或者你可以选择返回一个错误消息而不是重新抛出异常
+        }
+    }
+
+
+
+    private List<FilterCondition> parseFilterCondition(String filterCondition) throws IOException {
+        ObjectMapper objectMapper = new ObjectMapper();
+        return objectMapper.readValue(filterCondition, objectMapper.getTypeFactory().constructCollectionType(List.class, FilterCondition.class));
+    }
+    private boolean evaluateConditions(List<FilterCondition> conditions, String patientId, String storeId, Integer planId, Integer taskId, Integer createId,String mdmCode) throws Exception {
+        for (FilterCondition condition : conditions) {
+            switch (condition.getTraits()) {
+                case "patient-feature":
+                    if ("最近待执行随访距今天数".equals(condition.getTraitValue())) {
+                        // 查询sys_job表和随访任务主表
+                        int days = getDaysFromSysJobAndFollowUpMainTable(patientId, storeId, planId, taskId, createId,mdmCode);
+                        return evaluateNumericCondition(days, condition.getJudgingCondition(), Integer.parseInt(condition.getJudgmentValue()));
+                    } else if ("最近已完成随访距今天数".equals (condition.getTraitValue())) {
+                        // 查询sys_job表和随访任务主表
+                        int days = getDaysCompletedFollowUpMainTable(patientId, storeId, planId, taskId, createId,mdmCode);
+                        return evaluateNumericCondition(days, condition.getJudgingCondition(), Integer.parseInt(condition.getJudgmentValue()));
+                    }  else if ("永久停止随访(脱落召回)".equals(condition.getTraitValue())) {
+                        //查询业务归属
+                        String yes_no = getMedicationStopTL(patientId,mdmCode, taskId);
+                        return evaluateStringCondition(yes_no, condition.getJudgingCondition(), condition.getJudgmentValue());
+                    } else if ("永久停止随访(常规随访)".equals(condition.getTraitValue())) {
+                        //查询业务归属
+                        String yes_no = getMedicationStopCG(patientId,mdmCode, taskId);
+                        return evaluateStringCondition(yes_no, condition.getJudgingCondition(), condition.getJudgmentValue());
+                    } else if ("永久停止随访(全部)".equals(condition.getTraitValue())) {
+                        //查询业务归属
+                        String yes_no = getMedicationStopAll(patientId,mdmCode, taskId);
+                        return evaluateStringCondition(yes_no, condition.getJudgingCondition(), condition.getJudgmentValue());
+                    } else if ("随访不配合原因".equals(condition.getTraitValue())) {
+                        PageData HZ_INFO = getPhoneNumberFromPatientProfile(patientId);
+                        // 随访不配合原因
+                        String Reason = getReasonFromFollowUpTotalTable(patientId,mdmCode,taskId);
+                        return evaluateStringCondition(Reason, condition.getJudgingCondition(), condition.getJudgmentValue());
+                    }
+                    break;
+                case "patient-did-feature":
+                    if ("用药状态".equals(condition.getTraitValue())) {
+                        // 查询随访任务全量表
+                        String medicationStatus = getMedicationStatusFromFollowUpTotalTable(patientId,mdmCode,taskId);
+                        return evaluateStringCondition(medicationStatus, condition.getJudgingCondition(), condition.getJudgmentValue());
+                    }  else if ("永久停止随访(脱落召回)".equals(condition.getTraitValue())) {
+                        //查询业务归属
+                        String yes_no = getMedicationStopTL(patientId,mdmCode, taskId);
+                        return evaluateStringCondition(yes_no, condition.getJudgingCondition(), condition.getJudgmentValue());
+                    } else if ("永久停止随访(常规随访)".equals(condition.getTraitValue())) {
+                        //查询业务归属
+                        String yes_no = getMedicationStopCG(patientId,mdmCode, taskId);
+                        return evaluateStringCondition(yes_no, condition.getJudgingCondition(), condition.getJudgmentValue());
+                    } else if ("永久停止随访(全部)".equals(condition.getTraitValue())) {
+                        //查询业务归属
+                        String yes_no = getMedicationStopAll(patientId,mdmCode, taskId);
+                        return evaluateStringCondition(yes_no, condition.getJudgingCondition(), condition.getJudgmentValue());
+                    } else if ("最近订单距今天数".equals(condition.getTraitValue())) {
+                        // 查询处方表
+                        int days = getDaysFromPrescriptionTable(patientId,mdmCode);
+                        return evaluateNumericCondition(days, condition.getJudgingCondition(), Integer.parseInt(condition.getJudgmentValue()));
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+        return true; // 默认情况下继续执行任务
+    }
+
+    private boolean evaluateNumericCondition(int actualValue, String judgingCondition, int judgmentValue) {
+        switch (judgingCondition) {
+            case "大于": return actualValue > judgmentValue;
+            case "小于": return actualValue < judgmentValue;
+            case "等于": return actualValue == judgmentValue;
+            case "大于等于": return actualValue >= judgmentValue;
+            case "小于等于": return actualValue <= judgmentValue;
+            case "不等于": return actualValue != judgmentValue;
+            default: return false;
+        }
+    }
+
+    private boolean evaluateStringCondition(String actualValue, String judgingCondition, String judgmentValue) {
+        switch (judgingCondition) {
+            case "等于": return actualValue.equals(judgmentValue);
+            case "不等于": return !actualValue.equals(judgmentValue);
+            default: return false;
+        }
+    }
+
+    // 模拟从不同表中获取数据的方法
+    private int getDaysFromSysJobAndFollowUpMainTable(String patientId, String storeId, Integer planId, Integer taskId, Integer createId,String mdmCode) throws Exception {
+        // 实际应从sys_job和随访任务主表中查询数据  最近待执行随访距今天数
+        PageData task = new PageData();
+        task.put("patientId", patientId);
+        PageData    jobResult= (PageData)daoSupport.findForObject("followTaskMapper.selectRecentlyPendingFollowUpDays", task);
+        if(StringUtils.isNotNull(jobResult)){
+            return Integer.parseInt(jobResult.get("days").toString());
+        }
+        return 0; // 示例返回值
+    }
+    private int getDaysCompletedFollowUpMainTable(String patientId, String storeId, Integer planId, Integer taskId, Integer createId,String mdmCode) throws Exception {
+        // 实际应从sys_job和随访任务主表中查询数据 已完成随访距离今天
+        PageData task = new PageData();
+        task.put("patientId", patientId);
+        PageData    jobResult= (PageData)daoSupport.findForObject("followTaskMapper.selectRecentlyCompletedFollowUpDays", task);
+        if(StringUtils.isNotNull(jobResult)){
+            return Integer.parseInt(jobResult.get("days").toString());
+        }
+        return 0; // 示例返回值
+    }
+
+    private PageData getPhoneNumberFromPatientProfile(String patientId) throws Exception {
+        // 实际应从患者档案表中查询数据 触达手机号
+        PageData task = new PageData();
+        task.put("patientId", patientId);
+        PageData    jobResult= (PageData)daoSupport.findForObject("followTaskMapper.getPhoneNumberByPatientId", task);
+        if(StringUtils.isNotNull(jobResult)){
+            return jobResult;
+        }
+        return null; // 示例返回值
+    }
+
+    private String getMedicationStatusFromFollowUpTotalTable(String patientId,String mdmCode, Integer taskId) throws Exception {
+        // 实际应从随访任务全量表中查询数据 用药状态
+        PageData task = new PageData();
+        task.put("patientId", patientId);
+        task.put("taskId", taskId);
+        PageData    jobResult= (PageData)daoSupport.findForObject("followTaskMapper.getMedicationStatus", task);
+        if(StringUtils.isNotNull(jobResult)){
+            return jobResult.get("medicationStatus").toString();
+        }
+        return ""; // 示例返回值
+    }
+    private String getReasonFromFollowUpTotalTable(String patientId,String mdmCode, Integer taskId) throws Exception {
+        // 实际应从随访任务全量表中查询数据 用药状态
+        PageData task = new PageData();
+        task.put("patientId", patientId);
+        task.put("taskId", taskId);
+        PageData    jobResult= (PageData)daoSupport.findForObject("followTaskMapper.getMedicationStatus", task);
+        if(StringUtils.isNotNull(jobResult)){
+            String reasons_uncooperative = (String)jobResult.get("reasons_uncooperative");
+            if(StringUtils.isNotEmpty(reasons_uncooperative)){
+                return reasons_uncooperative;
             }
         }
 
+        return ""; // 示例返回值
+    }
+    private String getMedicationStopTL(String patientId,String mdmCode, Integer taskId) throws Exception {
+        PageData task = new PageData();
+        task.put("patientId", patientId);
+        task.put("businessBelonging", "脱落召回");
+        task.put("taskId", taskId);
+        task.put("medicationStatus", "永久停药");
+        task.put("stoped", "是");
+        PageData    jobResult= (PageData)daoSupport.findForObject("followTaskMapper.getMedicationBusinessBelonging", task);
+        if(StringUtils.isNotNull(jobResult)){
+            return jobResult.get("businessBelonging").toString();
+        }
+        return ""; // 示例返回值
+    }
+    private String getMedicationStopCG(String patientId,String mdmCode, Integer taskId) throws Exception {
+        // 实际应从随访任务全量表中查询数据
+       // return "常规随访"; // 示例返回值
+        PageData task = new PageData();
+        task.put("patientId", patientId);
+        task.put("businessBelonging", "常规随访");
+        task.put("taskId", taskId);
+        task.put("medicationStatus", "永久停药");
+        task.put("stoped", "是");
+        PageData    jobResult= (PageData)daoSupport.findForObject("followTaskMapper.getMedicationBusinessBelonging", task);
+        if(StringUtils.isNotNull(jobResult)){
+            return jobResult.get("businessBelonging").toString();
+        }
+        return ""; // 示例返回值
+    }
+    private String getMedicationStopAll(String patientId,String mdmCode, Integer taskId) throws Exception {
+        // 实际应从随访任务全量表中查询数据
+        PageData task = new PageData();
+        task.put("patientId", patientId);
+        task.put("taskId", taskId);
+        task.put("medicationStatus", "永久停药");
+        task.put("stoped", "是");
+        PageData    jobResult= (PageData)daoSupport.findForObject("followTaskMapper.getMedicationBusinessBelonging", task);
+        if(StringUtils.isNotNull(jobResult)){
+            return jobResult.get("businessBelonging").toString();
+        }
+        return ""; // 示例返回值
+    }
+    private int getDaysFromPrescriptionTable(String patientId,String mdmCode) throws Exception {
+        // 实际应从处方表中查询数据  最近订单距今天数
+        //selectRecentlyOrderDays 最近订单距今天数
+        PageData task = new PageData();
+        task.put("patientId", patientId);
+        PageData    jobResult= (PageData)daoSupport.findForObject("followTaskMapper.selectRecentlyOrderDays", task);
+        if(StringUtils.isNotNull(jobResult)){
+            return Integer.parseInt(jobResult.get("days").toString());
+        }
+        return 0; // 示例返回值
+    }
+
+
+
+    private boolean validateConditions(List<Map<String, String>> conditions, Map<String, String> patientData) {
+        for (Map<String, String> condition : conditions) {
+            String traits = condition.get("traits"); // 新增 traits 判断
+            String traitValue = condition.get("traitValue");
+            String judgingCondition = condition.get("judgingCondition");
+            String expectedValue = condition.containsKey("judgmentInputValue") ?
+                    condition.get("judgmentInputValue") :
+                    condition.get("judgmentSelectValue");
+
+            // 1. 根据 traits 和 traitValue 获取实际值(不同分类可能来源不同)
+            String actualValue = getActualValueByTraits(traits, traitValue, patientData);
+            if (actualValue == null) {
+                System.out.println("患者数据缺少字段: " + traits + " - " + traitValue);
+                return false;
+            }
+
+            // 2. 根据 traits 和 traitValue 决定比较逻辑
+            boolean result = evaluateCondition(traits, traitValue, actualValue, judgingCondition, expectedValue);
+            if (!result) {
+                System.out.println("条件不满足: " + traits + " - " + traitValue + " " + judgingCondition + " " + expectedValue);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // 根据 traits 和 traitValue 获取实际值(示例逻辑)
+    private String getActualValueByTraits(String traits, String traitValue, Map<String, String> patientData) {
+        switch (traits) {
+            case "patient-feature":
+                // 患者基础特征,直接从 patientData 获取
+                return patientData.get(traitValue);
+            case "patient-did-feature":
+                // 患者行为特征,可能需要从其他服务或扩展数据获取
+                return patientData.get(traitValue);
+            default:
+                return null;
+        }
+    }
+
+    // 根据 traits 和 traitValue 定义比较逻辑
+    private boolean evaluateCondition(String traits, String traitValue, String actualValue,
+                                      String judgingCondition, String expectedValue) {
+        try {
+            // 判断是否为数值型比较
+            boolean isNumeric = isNumericComparison(traits, traitValue);
+
+            if (isNumeric) {
+                double actual = Double.parseDouble(actualValue);
+                double expected = Double.parseDouble(expectedValue);
+
+                switch (judgingCondition) {
+                    case "大于等于": return actual >= expected;
+                    case "小于等于": return actual <= expected;
+                    case "大于": return actual > expected;
+                    case "小于": return actual < expected;
+                    case "等于": return actual == expected;
+                    case "不等于": return actual != expected;
+                    default: return false;
+                }
+            } else {
+                switch (judgingCondition) {
+                    case "等于": return actualValue.equals(expectedValue);
+                    case "不等于": return !actualValue.equals(expectedValue);
+                    default: return false;
+                }
+            }
+        } catch (NumberFormatException e) {
+            return false;
+        }
+    }
+
+    // 判断是否需要进行数值比较(根据 traits 和 traitValue)
+    private boolean isNumericComparison(String traits, String traitValue) {
+        // 示例规则:
+        // 1. patient-did-feature 下的 "最近订单距今天数" 是数值型
+        // 2. patient-feature 下的 "年龄" 是数值型
+        if ("patient-did-feature".equals(traits)) {
+            return traitValue.contains("距今天数") || traitValue.contains("天数");
+        } else if ("patient-feature".equals(traits)) {
+            return "距今天数".equals(traitValue) || "天数".equals(traitValue);
+        }
+        return false;
+    }
+
+
+
+    // 解析月份
+    private static int parseMonth(String month) throws ParseException {
+        switch (month.toLowerCase()) {
+            case "*":
+                return Calendar.getInstance().get(Calendar.MONTH) + 1; // 使用当前月份
+            case "jan": return 1;
+            case "feb": return 2;
+            case "mar": return 3;
+            case "apr": return 4;
+            case "may": return 5;
+            case "jun": return 6;
+            case "jul": return 7;
+            case "aug": return 8;
+            case "sep": return 9;
+            case "oct": return 10;
+            case "nov": return 11;
+            case "dec": return 12;
+            default:
+                try {
+                    return Integer.parseInt(month);
+                } catch (NumberFormatException e) {
+                    throw new ParseException("Invalid month format: " + month, 0);
+                }
+        }
+    }
+
+    // 解析日
+    private static int parseDayOfMonth(String dayOfMonth) throws ParseException {
+        if ("*".equals(dayOfMonth)) {
+            return Calendar.getInstance().get(Calendar.DAY_OF_MONTH); // 使用当前日
+        }
+        try {
+            return Integer.parseInt(dayOfMonth);
+        } catch (NumberFormatException e) {
+            throw new ParseException("Invalid day of month format: " + dayOfMonth, 0);
+        }
+    }
+
+    // 解析小时
+    private static int parseHour(String hour) throws ParseException {
+        if ("*".equals(hour)) {
+            return Calendar.getInstance().get(Calendar.HOUR_OF_DAY); // 使用当前小时
+        }
+        try {
+            return Integer.parseInt(hour);
+        } catch (NumberFormatException e) {
+            throw new ParseException("Invalid hour format: " + hour, 0);
+        }
+    }
+
+    // 解析分钟
+    private static int parseMinute(String minute) throws ParseException {
+        if ("*".equals(minute)) {
+            return Calendar.getInstance().get(Calendar.MINUTE); // 使用当前分钟
+        }
+        try {
+            return Integer.parseInt(minute);
+        } catch (NumberFormatException e) {
+            throw new ParseException("Invalid minute format: " + minute, 0);
+        }
+    }
+
+    // 解析秒
+    private static int parseSecond(String second) throws ParseException {
+        if ("*".equals(second)) {
+            return Calendar.getInstance().get(Calendar.SECOND); // 使用当前秒
+        }
+        try {
+            return Integer.parseInt(second);
+        } catch (NumberFormatException e) {
+            throw new ParseException("Invalid second format: " + second, 0);
+        }
+    }
+
+    // 解析年份
+    private static int parseYear(String year) throws ParseException {
+        if ("*".equals(year)) {
+            return Calendar.getInstance().get(Calendar.YEAR); // 使用当前年份
+        }
+        try {
+            return Integer.parseInt(year);
+        } catch (NumberFormatException e) {
+            throw new ParseException("Invalid year format: " + year, 0);
+        }
     }
 }

Some files were not shown because too many files changed in this diff