Ver código fonte

更新模块信息

dumingliang 1 semana atrás
pai
commit
a0498d3752
25 arquivos alterados com 1511 adições e 216 exclusões
  1. 18 13
      .mybatis/index.json
  2. 109 0
      docs/init-river-lake-doc.sql
  3. 71 0
      gw-admin/src/main/java/com/goldenwater/web/controller/gx/DocDirectoryController.java
  4. 131 0
      gw-admin/src/main/java/com/goldenwater/web/controller/gx/DocFileController.java
  5. 1 1
      gw-admin/src/main/java/com/goldenwater/web/controller/system/SysLoginController.java
  6. 3 3
      gw-admin/src/main/java/com/goldenwater/web/core/config/SwaggerConfig.java
  7. 4 0
      gw-gx/pom.xml
  8. 32 0
      gw-gx/src/main/java/com/goldenwater/gx/domain/DocDirectory.java
  9. 41 0
      gw-gx/src/main/java/com/goldenwater/gx/domain/DocFile.java
  10. 23 0
      gw-gx/src/main/java/com/goldenwater/gx/mapper/DocDirectoryMapper.java
  11. 21 0
      gw-gx/src/main/java/com/goldenwater/gx/mapper/DocFileMapper.java
  12. 20 0
      gw-gx/src/main/java/com/goldenwater/gx/service/IDocDirectoryService.java
  13. 18 0
      gw-gx/src/main/java/com/goldenwater/gx/service/IDocFileService.java
  14. 71 0
      gw-gx/src/main/java/com/goldenwater/gx/service/impl/DocDirectoryServiceImpl.java
  15. 41 0
      gw-gx/src/main/java/com/goldenwater/gx/service/impl/DocFileServiceImpl.java
  16. 64 0
      gw-gx/src/main/resources/mapper/gx/DocDirectoryMapper.xml
  17. 76 0
      gw-gx/src/main/resources/mapper/gx/DocFileMapper.xml
  18. 7 7
      gw-system/src/main/java/com/goldenwater/system/service/impl/SysUserOnlineServiceImpl.java
  19. 41 0
      gw-ui/src/api/gx/document.js
  20. 279 27
      gw-ui/src/views/front/DataCommon.vue
  21. 432 24
      gw-ui/src/views/front/Document.vue
  22. 0 69
      gw-ui/src/views/front/Home.vue
  23. 3 3
      gw-ui/src/views/gx/station/index.vue
  24. 0 69
      gw-ui/src/views/index.vue
  25. 5 0
      pom.xml

+ 18 - 13
.mybatis/index.json

@@ -1,6 +1,6 @@
 {
   "version": "1.0",
-  "timestamp": 1784552948950,
+  "timestamp": 1784560221776,
   "projectRoot": "d:\\guochan\\tba-gx-guochan",
   "entries": [
     {
@@ -9,28 +9,33 @@
       "size": 4257
     },
     {
-      "path": "gw-common\\target\\classes\\com\\goldenwater\\common\\config\\RuoYiConfig.class",
-      "mtime": 1784552659877.3826,
-      "size": 2656
+      "path": "gw-admin\\target\\classes\\com\\goldenwater\\GoldenwaterApplication.class",
+      "mtime": 1784552950828.1543,
+      "size": 1060
     },
     {
-      "path": "gw-system\\target\\classes\\com\\goldenwater\\system\\domain\\SysConfig.class",
-      "mtime": 1784552817135.2542,
-      "size": 3593
+      "path": "gw-framework\\target\\classes\\com\\goldenwater\\framework\\config\\SecurityConfig.class",
+      "mtime": 1784559893818.9417,
+      "size": 12386
     },
     {
       "path": "gw-framework\\target\\classes\\com\\goldenwater\\framework\\config\\ThreadPoolConfig.class",
-      "mtime": 1784552913658.5635,
-      "size": 2442
+      "mtime": 1784559974656.9731,
+      "size": 2450
     },
     {
-      "path": "gw-admin\\target\\classes\\com\\goldenwater\\GoldenwaterApplication.class",
-      "mtime": 1784552943458.064,
-      "size": 1060
+      "path": "gw-system\\target\\classes\\com\\goldenwater\\system\\domain\\SysConfig.class",
+      "mtime": 1784559997430.4497,
+      "size": 3593
+    },
+    {
+      "path": "gw-common\\target\\classes\\com\\goldenwater\\common\\config\\RuoYiConfig.class",
+      "mtime": 1784560049282.296,
+      "size": 2656
     },
     {
       "path": "gw-admin\\target\\classes\\com\\goldenwater\\web\\core\\config\\SwaggerConfig.class",
-      "mtime": 1784552943887.2979,
+      "mtime": 1784560177893.3743,
       "size": 3793
     }
   ]

+ 109 - 0
docs/init-river-lake-doc.sql

@@ -0,0 +1,109 @@
+-- ============================================================
+-- 河湖长制文档管理 - 数据库建表语句
+-- 达梦数据库(Database) SQL Script
+-- ============================================================
+
+-- 目录表
+CREATE TABLE DOC_DIRECTORY (
+    ID BIGINT PRIMARY KEY IDENTITY(1,1),
+    DIR_NAME VARCHAR2(200) NOT NULL COMMENT '目录名称',
+    PARENT_ID BIGINT DEFAULT 0 COMMENT '父目录ID,0表示根目录',
+    ORDER_NUM INT DEFAULT 0 COMMENT '排序号',
+    STATUS CHAR(1) DEFAULT '0' COMMENT '状态:0启用,1禁用',
+    REMARK VARCHAR2(500) COMMENT '备注',
+    CREATE_BY VARCHAR2(64) COMMENT '创建人',
+    CREATE_TIME DATETIME DEFAULT SYSDATE COMMENT '创建时间',
+    UPDATE_BY VARCHAR2(64) COMMENT '更新人',
+    UPDATE_TIME DATETIME DEFAULT SYSDATE COMMENT '更新时间'
+);
+
+-- 目录表索引
+CREATE INDEX IDX_DOC_DIRECTORY_PARENT ON DOC_DIRECTORY(PARENT_ID);
+CREATE INDEX IDX_DOC_DIRECTORY_STATUS ON DOC_DIRECTORY(STATUS);
+
+-- 文件表
+CREATE TABLE DOC_FILE (
+    ID BIGINT PRIMARY KEY IDENTITY(1,1),
+    DIR_ID BIGINT NOT NULL COMMENT '所属目录ID',
+    FILE_NAME VARCHAR2(200) NOT NULL COMMENT '文件名称',
+    FILE_PATH VARCHAR2(500) NOT NULL COMMENT '文件存储路径',
+    FILE_SIZE BIGINT DEFAULT 0 COMMENT '文件大小(字节)',
+    FILE_TYPE VARCHAR2(50) COMMENT '文件类型',
+    FILE_EXT VARCHAR2(20) COMMENT '文件扩展名',
+    UPLOAD_BY VARCHAR2(64) COMMENT '上传人',
+    UPLOAD_TIME DATETIME DEFAULT SYSDATE COMMENT '上传时间',
+    STATUS CHAR(1) DEFAULT '0' COMMENT '状态:0正常,1删除',
+    REMARK VARCHAR2(500) COMMENT '备注',
+    CREATE_BY VARCHAR2(64) COMMENT '创建人',
+    CREATE_TIME DATETIME DEFAULT SYSDATE COMMENT '创建时间',
+    UPDATE_BY VARCHAR2(64) COMMENT '更新人',
+    UPDATE_TIME DATETIME DEFAULT SYSDATE COMMENT '更新时间'
+);
+
+-- 文件表索引
+CREATE INDEX IDX_DOC_FILE_DIR ON DOC_FILE(DIR_ID);
+CREATE INDEX IDX_DOC_FILE_STATUS ON DOC_FILE(STATUS);
+CREATE INDEX IDX_DOC_FILE_UPLOAD_TIME ON DOC_FILE(UPLOAD_TIME);
+
+-- 初始化数据 - 根目录
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('河湖长制', 0, 1, '0', 'admin', SYSDATE);
+
+-- 初始化数据 - 一级目录
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('制度文件', 1, 1, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('工作方案', 1, 2, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('考核评估', 1, 3, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('巡河记录', 1, 4, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('问题台账', 1, 5, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('信息简报', 1, 6, '0', 'admin', SYSDATE);
+
+-- 初始化数据 - 二级目录
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('河长职责', 2, 1, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('湖长职责', 2, 2, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('会议制度', 2, 3, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('巡查制度', 2, 4, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('年度方案', 3, 1, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('专项方案', 3, 2, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('季度考核', 4, 1, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('年度考核', 4, 2, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('月度巡河', 5, 1, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('问题整改', 6, 1, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('督办记录', 6, 2, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('工作简报', 7, 1, '0', 'admin', SYSDATE);
+
+INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, CREATE_BY, CREATE_TIME)
+VALUES ('通报信息', 7, 2, '0', 'admin', SYSDATE);

+ 71 - 0
gw-admin/src/main/java/com/goldenwater/web/controller/gx/DocDirectoryController.java

@@ -0,0 +1,71 @@
+package com.goldenwater.web.controller.gx;
+
+import com.goldenwater.common.annotation.Anonymous;
+import com.goldenwater.common.annotation.Log;
+import com.goldenwater.common.core.controller.BaseController;
+import com.goldenwater.common.core.domain.AjaxResult;
+import com.goldenwater.gx.domain.DocDirectory;
+import com.goldenwater.gx.service.IDocDirectoryService;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.annotation.Resource;
+import java.util.List;
+
+@RestController
+@RequestMapping("/gx/directory")
+public class DocDirectoryController extends BaseController {
+
+    @Resource
+    private IDocDirectoryService docDirectoryService;
+
+    @Anonymous
+    @GetMapping("/front/tree")
+    public AjaxResult frontTree() {
+        List<DocDirectory> tree = docDirectoryService.selectDirectoryTree();
+        return AjaxResult.success(tree);
+    }
+
+    @PreAuthorize("@ss.hasPermi('gx:directory:list')")
+    @GetMapping("/tree")
+    public AjaxResult tree() {
+        List<DocDirectory> tree = docDirectoryService.selectDirectoryTree();
+        return AjaxResult.success(tree);
+    }
+
+    @PreAuthorize("@ss.hasPermi('gx:directory:list')")
+    @GetMapping("/list/{parentId}")
+    public AjaxResult list(@PathVariable Long parentId) {
+        List<DocDirectory> list = docDirectoryService.selectDirectoryByParentId(parentId);
+        return AjaxResult.success(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('gx:directory:query')")
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id) {
+        return AjaxResult.success(docDirectoryService.selectDirectoryById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('gx:directory:add')")
+    @Log(title = "目录管理", businessType = com.goldenwater.common.enums.BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody DocDirectory directory) {
+        directory.setCreateBy(getUsername());
+        return toAjax(docDirectoryService.insertDirectory(directory));
+    }
+
+    @PreAuthorize("@ss.hasPermi('gx:directory:edit')")
+    @Log(title = "目录管理", businessType = com.goldenwater.common.enums.BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody DocDirectory directory) {
+        directory.setUpdateBy(getUsername());
+        return toAjax(docDirectoryService.updateDirectory(directory));
+    }
+
+    @PreAuthorize("@ss.hasPermi('gx:directory:remove')")
+    @Log(title = "目录管理", businessType = com.goldenwater.common.enums.BusinessType.DELETE)
+    @DeleteMapping("/{id}")
+    public AjaxResult remove(@PathVariable Long id) {
+        return toAjax(docDirectoryService.deleteDirectoryById(id));
+    }
+}

+ 131 - 0
gw-admin/src/main/java/com/goldenwater/web/controller/gx/DocFileController.java

@@ -0,0 +1,131 @@
+package com.goldenwater.web.controller.gx;
+
+import com.goldenwater.common.annotation.Anonymous;
+import com.goldenwater.common.annotation.Log;
+import com.goldenwater.common.config.RuoYiConfig;
+import com.goldenwater.common.core.controller.BaseController;
+import com.goldenwater.common.core.domain.AjaxResult;
+import com.goldenwater.common.core.domain.R;
+import com.goldenwater.common.utils.file.FileUtils;
+import com.goldenwater.gx.domain.DocFile;
+import com.goldenwater.gx.service.IDocFileService;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/gx/file")
+public class DocFileController extends BaseController {
+
+    @Resource
+    private IDocFileService docFileService;
+
+    @Anonymous
+    @GetMapping("/front/list/{dirId}")
+    public AjaxResult frontList(@PathVariable Long dirId) {
+        List<DocFile> list = docFileService.selectFileList(dirId);
+        return AjaxResult.success(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('gx:file:list')")
+    @GetMapping("/list/{dirId}")
+    public AjaxResult list(@PathVariable Long dirId) {
+        List<DocFile> list = docFileService.selectFileList(dirId);
+        return AjaxResult.success(list);
+    }
+
+    @PreAuthorize("@ss.hasPermi('gx:file:query')")
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id) {
+        return AjaxResult.success(docFileService.selectFileById(id));
+    }
+
+    @PreAuthorize("@ss.hasPermi('gx:file:upload')")
+    @Log(title = "文件管理", businessType = com.goldenwater.common.enums.BusinessType.IMPORT)
+    @PostMapping("/upload")
+    public AjaxResult upload(@RequestParam("file") MultipartFile file, 
+                             @RequestParam("dirId") Long dirId) {
+        if (file.isEmpty()) {
+            return AjaxResult.error("上传文件不能为空");
+        }
+
+        String originalFilename = file.getOriginalFilename();
+        String fileExt = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
+        String fileName = UUID.randomUUID().toString() + "." + fileExt;
+        
+        String uploadDir = RuoYiConfig.getProfile() + "/doc/";
+        File dir = new File(uploadDir);
+        if (!dir.exists()) {
+            dir.mkdirs();
+        }
+
+        File dest = new File(uploadDir + fileName);
+        try {
+            file.transferTo(dest);
+        } catch (IOException e) {
+            return AjaxResult.error("文件上传失败");
+        }
+
+        DocFile docFile = new DocFile();
+        docFile.setDirId(dirId);
+        docFile.setFileName(originalFilename);
+        docFile.setFilePath("/doc/" + fileName);
+        docFile.setFileSize(file.getSize());
+        docFile.setFileType(file.getContentType());
+        docFile.setFileExt(fileExt);
+        docFile.setUploadBy(getUsername());
+        docFile.setStatus("0");
+        docFile.setCreateBy(getUsername());
+
+        docFileService.insertFile(docFile);
+        return AjaxResult.success("文件上传成功");
+    }
+
+    @Anonymous
+    @GetMapping("/download/{id}")
+    public void download(@PathVariable Long id, HttpServletResponse response) throws IOException {
+        DocFile file = docFileService.selectFileById(id);
+        if (file == null) {
+            response.getWriter().write("文件不存在");
+            return;
+        }
+
+        String filePath = RuoYiConfig.getProfile() + file.getFilePath();
+        File f = new File(filePath);
+        if (!f.exists()) {
+            response.getWriter().write("文件不存在");
+            return;
+        }
+
+        response.setContentType("application/octet-stream");
+        response.setCharacterEncoding("utf-8");
+        response.setHeader("Content-Disposition", 
+                "attachment; filename=" + URLEncoder.encode(file.getFileName(), StandardCharsets.UTF_8));
+
+        FileUtils.writeBytes(filePath, response.getOutputStream());
+    }
+
+    @PreAuthorize("@ss.hasPermi('gx:file:remove')")
+    @Log(title = "文件管理", businessType = com.goldenwater.common.enums.BusinessType.DELETE)
+    @DeleteMapping("/{id}")
+    public AjaxResult remove(@PathVariable Long id) {
+        DocFile file = docFileService.selectFileById(id);
+        if (file != null) {
+            String filePath = RuoYiConfig.getProfile() + file.getFilePath();
+            File f = new File(filePath);
+            if (f.exists()) {
+                f.delete();
+            }
+        }
+        return toAjax(docFileService.deleteFileById(id));
+    }
+}

+ 1 - 1
gw-admin/src/main/java/com/goldenwater/web/controller/system/SysLoginController.java

@@ -22,7 +22,7 @@ import com.goldenwater.framework.web.service.SysLoginService;
 import com.goldenwater.framework.web.service.SysPermissionService;
 import com.goldenwater.framework.web.service.TokenService;
 import com.goldenwater.system.service.ISysConfigService;
-import com.goldenwater.system.service.ISysMenuService;
+ import com.goldenwater.system.service.ISysMenuService;
 
 /**
  * 登录验证

+ 3 - 3
gw-admin/src/main/java/com/goldenwater/web/core/config/SwaggerConfig.java

@@ -21,7 +21,7 @@ public class SwaggerConfig
 {
     /** 系统基础配置 */
     @Autowired
-    private RuoYiConfig goldenwaterConfig;
+    private RuoYiConfig RuoyiConfig;
     
     /**
      * 自定义的 OpenAPI 对象
@@ -57,8 +57,8 @@ public class SwaggerConfig
             // 描述
             .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
             // 作者信息
-            .contact(new Contact().name(goldenwaterConfig.getName()))
+            .contact(new Contact().name(RuoyiConfig.getName()))
             // 版本
-            .version("版本号:" + goldenwaterConfig.getVersion());
+            .version("版本号:" + RuoyiConfig.getVersion());
     }
 }

+ 4 - 0
gw-gx/pom.xml

@@ -22,6 +22,10 @@
             <groupId>com.goldenwater</groupId>
             <artifactId>gw-common</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
 
     </dependencies>
 

+ 32 - 0
gw-gx/src/main/java/com/goldenwater/gx/domain/DocDirectory.java

@@ -0,0 +1,32 @@
+package com.goldenwater.gx.domain;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import lombok.Data;
+
+@Data
+public class DocDirectory {
+
+    private Long id;
+
+    private String dirName;
+
+    private Long parentId;
+
+    private Integer orderNum;
+
+    private String status;
+
+    private String remark;
+
+    private String createBy;
+
+    private java.util.Date createTime;
+
+    private String updateBy;
+
+    private java.util.Date updateTime;
+
+    private List<DocDirectory> children = new ArrayList<>();
+}

+ 41 - 0
gw-gx/src/main/java/com/goldenwater/gx/domain/DocFile.java

@@ -0,0 +1,41 @@
+package com.goldenwater.gx.domain;
+
+import lombok.Data;
+
+@Data
+public class DocFile {
+
+    private Long id;
+
+    private Long dirId;
+
+    private String fileName;
+
+    private String filePath;
+
+    private Long fileSize;
+
+    private String fileType;
+
+    private String fileExt;
+
+    private String uploadBy;
+
+    private java.util.Date uploadTime;
+
+    private String status;
+
+    private String remark;
+
+    private String createBy;
+
+    private java.util.Date createTime;
+
+    private String updateBy;
+
+    private java.util.Date updateTime;
+
+    private String dirName;
+
+    private String uploadByName;
+}

+ 23 - 0
gw-gx/src/main/java/com/goldenwater/gx/mapper/DocDirectoryMapper.java

@@ -0,0 +1,23 @@
+package com.goldenwater.gx.mapper;
+
+import com.goldenwater.gx.domain.DocDirectory;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+public interface DocDirectoryMapper {
+
+    List<DocDirectory> selectDirectoryTree();
+
+    List<DocDirectory> selectDirectoryByParentId(@Param("parentId") Long parentId);
+
+    DocDirectory selectDirectoryById(@Param("id") Long id);
+
+    int insertDirectory(DocDirectory directory);
+
+    int updateDirectory(DocDirectory directory);
+
+    int deleteDirectoryById(@Param("id") Long id);
+
+    int countChildByParentId(@Param("parentId") Long parentId);
+}

+ 21 - 0
gw-gx/src/main/java/com/goldenwater/gx/mapper/DocFileMapper.java

@@ -0,0 +1,21 @@
+package com.goldenwater.gx.mapper;
+
+import com.goldenwater.gx.domain.DocFile;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+public interface DocFileMapper {
+
+    List<DocFile> selectFileList(@Param("dirId") Long dirId);
+
+    DocFile selectFileById(@Param("id") Long id);
+
+    int insertFile(DocFile file);
+
+    int updateFile(DocFile file);
+
+    int deleteFileById(@Param("id") Long id);
+
+    int countByDirId(@Param("dirId") Long dirId);
+}

+ 20 - 0
gw-gx/src/main/java/com/goldenwater/gx/service/IDocDirectoryService.java

@@ -0,0 +1,20 @@
+package com.goldenwater.gx.service;
+
+import com.goldenwater.gx.domain.DocDirectory;
+
+import java.util.List;
+
+public interface IDocDirectoryService {
+
+    List<DocDirectory> selectDirectoryTree();
+
+    List<DocDirectory> selectDirectoryByParentId(Long parentId);
+
+    DocDirectory selectDirectoryById(Long id);
+
+    int insertDirectory(DocDirectory directory);
+
+    int updateDirectory(DocDirectory directory);
+
+    int deleteDirectoryById(Long id);
+}

+ 18 - 0
gw-gx/src/main/java/com/goldenwater/gx/service/IDocFileService.java

@@ -0,0 +1,18 @@
+package com.goldenwater.gx.service;
+
+import com.goldenwater.gx.domain.DocFile;
+
+import java.util.List;
+
+public interface IDocFileService {
+
+    List<DocFile> selectFileList(Long dirId);
+
+    DocFile selectFileById(Long id);
+
+    int insertFile(DocFile file);
+
+    int updateFile(DocFile file);
+
+    int deleteFileById(Long id);
+}

+ 71 - 0
gw-gx/src/main/java/com/goldenwater/gx/service/impl/DocDirectoryServiceImpl.java

@@ -0,0 +1,71 @@
+package com.goldenwater.gx.service.impl;
+
+import com.goldenwater.gx.domain.DocDirectory;
+import com.goldenwater.gx.mapper.DocDirectoryMapper;
+import com.goldenwater.gx.service.IDocDirectoryService;
+import org.springframework.stereotype.Service;
+
+import jakarta.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Service("docDirectoryService")
+public class DocDirectoryServiceImpl implements IDocDirectoryService {
+
+    @Resource
+    private DocDirectoryMapper docDirectoryMapper;
+
+    @Override
+    public List<DocDirectory> selectDirectoryTree() {
+        List<DocDirectory> allDirectories = docDirectoryMapper.selectDirectoryTree();
+        return buildTree(allDirectories, 0L);
+    }
+
+    @Override
+    public List<DocDirectory> selectDirectoryByParentId(Long parentId) {
+        return docDirectoryMapper.selectDirectoryByParentId(parentId);
+    }
+
+    @Override
+    public DocDirectory selectDirectoryById(Long id) {
+        return docDirectoryMapper.selectDirectoryById(id);
+    }
+
+    @Override
+    public int insertDirectory(DocDirectory directory) {
+        return docDirectoryMapper.insertDirectory(directory);
+    }
+
+    @Override
+    public int updateDirectory(DocDirectory directory) {
+        return docDirectoryMapper.updateDirectory(directory);
+    }
+
+    @Override
+    public int deleteDirectoryById(Long id) {
+        return docDirectoryMapper.deleteDirectoryById(id);
+    }
+
+    private List<DocDirectory> buildTree(List<DocDirectory> directories, Long parentId) {
+        Map<Long, List<DocDirectory>> map = directories.stream()
+                .collect(Collectors.groupingBy(DocDirectory::getParentId));
+
+        return buildTreeRecursive(map, parentId);
+    }
+
+    private List<DocDirectory> buildTreeRecursive(Map<Long, List<DocDirectory>> map, Long parentId) {
+        List<DocDirectory> result = new ArrayList<>();
+        List<DocDirectory> children = map.get(parentId);
+        
+        if (children != null) {
+            for (DocDirectory child : children) {
+                child.setChildren(buildTreeRecursive(map, child.getId()));
+                result.add(child);
+            }
+        }
+        
+        return result;
+    }
+}

+ 41 - 0
gw-gx/src/main/java/com/goldenwater/gx/service/impl/DocFileServiceImpl.java

@@ -0,0 +1,41 @@
+package com.goldenwater.gx.service.impl;
+
+import com.goldenwater.gx.domain.DocFile;
+import com.goldenwater.gx.mapper.DocFileMapper;
+import com.goldenwater.gx.service.IDocFileService;
+import org.springframework.stereotype.Service;
+
+import jakarta.annotation.Resource;
+import java.util.List;
+
+@Service("docFileService")
+public class DocFileServiceImpl implements IDocFileService {
+
+    @Resource
+    private DocFileMapper docFileMapper;
+
+    @Override
+    public List<DocFile> selectFileList(Long dirId) {
+        return docFileMapper.selectFileList(dirId);
+    }
+
+    @Override
+    public DocFile selectFileById(Long id) {
+        return docFileMapper.selectFileById(id);
+    }
+
+    @Override
+    public int insertFile(DocFile file) {
+        return docFileMapper.insertFile(file);
+    }
+
+    @Override
+    public int updateFile(DocFile file) {
+        return docFileMapper.updateFile(file);
+    }
+
+    @Override
+    public int deleteFileById(Long id) {
+        return docFileMapper.deleteFileById(id);
+    }
+}

+ 64 - 0
gw-gx/src/main/resources/mapper/gx/DocDirectoryMapper.xml

@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.goldenwater.gx.mapper.DocDirectoryMapper">
+
+    <resultMap type="com.goldenwater.gx.domain.DocDirectory" id="DocDirectoryMap">
+        <id property="id" column="ID" />
+        <result property="dirName" column="DIR_NAME" />
+        <result property="parentId" column="PARENT_ID" />
+        <result property="orderNum" column="ORDER_NUM" />
+        <result property="status" column="STATUS" />
+        <result property="remark" column="REMARK" />
+        <result property="createBy" column="CREATE_BY" />
+        <result property="createTime" column="CREATE_TIME" />
+        <result property="updateBy" column="UPDATE_BY" />
+        <result property="updateTime" column="UPDATE_TIME" />
+    </resultMap>
+
+    <select id="selectDirectoryTree" resultMap="DocDirectoryMap">
+        SELECT ID, DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, REMARK, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME
+        FROM DOC_DIRECTORY
+        WHERE STATUS = '0'
+        ORDER BY PARENT_ID, ORDER_NUM
+    </select>
+
+    <select id="selectDirectoryByParentId" resultMap="DocDirectoryMap">
+        SELECT ID, DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, REMARK, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME
+        FROM DOC_DIRECTORY
+        WHERE STATUS = '0'
+          AND PARENT_ID = #{parentId}
+        ORDER BY ORDER_NUM
+    </select>
+
+    <select id="selectDirectoryById" resultMap="DocDirectoryMap">
+        SELECT ID, DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, REMARK, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME
+        FROM DOC_DIRECTORY
+        WHERE ID = #{id}
+    </select>
+
+    <insert id="insertDirectory" parameterType="com.goldenwater.gx.domain.DocDirectory">
+        INSERT INTO DOC_DIRECTORY (DIR_NAME, PARENT_ID, ORDER_NUM, STATUS, REMARK, CREATE_BY, CREATE_TIME)
+        VALUES (#{dirName}, #{parentId}, #{orderNum}, #{status}, #{remark}, #{createBy}, SYSDATE)
+    </insert>
+
+    <update id="updateDirectory" parameterType="com.goldenwater.gx.domain.DocDirectory">
+        UPDATE DOC_DIRECTORY
+        SET DIR_NAME = #{dirName},
+            PARENT_ID = #{parentId},
+            ORDER_NUM = #{orderNum},
+            STATUS = #{status},
+            REMARK = #{remark},
+            UPDATE_BY = #{updateBy},
+            UPDATE_TIME = SYSDATE
+        WHERE ID = #{id}
+    </update>
+
+    <delete id="deleteDirectoryById" parameterType="Long">
+        DELETE FROM DOC_DIRECTORY WHERE ID = #{id}
+    </delete>
+
+    <select id="countChildByParentId" resultType="int">
+        SELECT COUNT(*) FROM DOC_DIRECTORY WHERE PARENT_ID = #{parentId} AND STATUS = '0'
+    </select>
+</mapper>

+ 76 - 0
gw-gx/src/main/resources/mapper/gx/DocFileMapper.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.goldenwater.gx.mapper.DocFileMapper">
+
+    <resultMap type="com.goldenwater.gx.domain.DocFile" id="DocFileMap">
+        <id property="id" column="ID" />
+        <result property="dirId" column="DIR_ID" />
+        <result property="fileName" column="FILE_NAME" />
+        <result property="filePath" column="FILE_PATH" />
+        <result property="fileSize" column="FILE_SIZE" />
+        <result property="fileType" column="FILE_TYPE" />
+        <result property="fileExt" column="FILE_EXT" />
+        <result property="uploadBy" column="UPLOAD_BY" />
+        <result property="uploadTime" column="UPLOAD_TIME" />
+        <result property="status" column="STATUS" />
+        <result property="remark" column="REMARK" />
+        <result property="createBy" column="CREATE_BY" />
+        <result property="createTime" column="CREATE_TIME" />
+        <result property="updateBy" column="UPDATE_BY" />
+        <result property="updateTime" column="UPDATE_TIME" />
+        <result property="dirName" column="DIR_NAME" />
+        <result property="uploadByName" column="UPLOAD_BY_NAME" />
+    </resultMap>
+
+    <select id="selectFileList" resultMap="DocFileMap">
+        SELECT f.ID, f.DIR_ID, f.FILE_NAME, f.FILE_PATH, f.FILE_SIZE, f.FILE_TYPE, f.FILE_EXT,
+               f.UPLOAD_BY, f.UPLOAD_TIME, f.STATUS, f.REMARK, f.CREATE_BY, f.CREATE_TIME,
+               f.UPDATE_BY, f.UPDATE_TIME, d.DIR_NAME, u.NICK_NAME AS UPLOAD_BY_NAME
+        FROM DOC_FILE f
+        LEFT JOIN DOC_DIRECTORY d ON f.DIR_ID = d.ID
+        LEFT JOIN SYS_USER u ON f.UPLOAD_BY = u.USER_NAME
+        WHERE f.STATUS = '0'
+          AND f.DIR_ID = #{dirId}
+        ORDER BY f.UPLOAD_TIME DESC
+    </select>
+
+    <select id="selectFileById" resultMap="DocFileMap">
+        SELECT ID, DIR_ID, FILE_NAME, FILE_PATH, FILE_SIZE, FILE_TYPE, FILE_EXT,
+               UPLOAD_BY, UPLOAD_TIME, STATUS, REMARK, CREATE_BY, CREATE_TIME,
+               UPDATE_BY, UPDATE_TIME
+        FROM DOC_FILE
+        WHERE ID = #{id}
+    </select>
+
+    <insert id="insertFile" parameterType="com.goldenwater.gx.domain.DocFile">
+        INSERT INTO DOC_FILE (DIR_ID, FILE_NAME, FILE_PATH, FILE_SIZE, FILE_TYPE, FILE_EXT,
+                              UPLOAD_BY, UPLOAD_TIME, STATUS, REMARK, CREATE_BY, CREATE_TIME)
+        VALUES (#{dirId}, #{fileName}, #{filePath}, #{fileSize}, #{fileType}, #{fileExt},
+                #{uploadBy}, SYSDATE, #{status}, #{remark}, #{createBy}, SYSDATE)
+    </insert>
+
+    <update id="updateFile" parameterType="com.goldenwater.gx.domain.DocFile">
+        UPDATE DOC_FILE
+        SET FILE_NAME = #{fileName},
+            FILE_PATH = #{filePath},
+            FILE_SIZE = #{fileSize},
+            FILE_TYPE = #{fileType},
+            FILE_EXT = #{fileExt},
+            UPLOAD_BY = #{uploadBy},
+            UPLOAD_TIME = SYSDATE,
+            STATUS = #{status},
+            REMARK = #{remark},
+            UPDATE_BY = #{updateBy},
+            UPDATE_TIME = SYSDATE
+        WHERE ID = #{id}
+    </update>
+
+    <delete id="deleteFileById" parameterType="Long">
+        DELETE FROM DOC_FILE WHERE ID = #{id}
+    </delete>
+
+    <select id="countByDirId" resultType="int">
+        SELECT COUNT(*) FROM DOC_FILE WHERE DIR_ID = #{dirId} AND STATUS = '0'
+    </select>
+</mapper>

+ 7 - 7
gw-system/src/main/java/com/goldenwater/system/service/impl/SysUserOnlineServiceImpl.java

@@ -8,7 +8,7 @@ import com.goldenwater.system.service.ISysUserOnlineService;
 
 /**
  * 在线用户 服务层处理
- * 
+ *
  * @author goldenwater
  */
 @Service
@@ -16,7 +16,7 @@ public class SysUserOnlineServiceImpl implements ISysUserOnlineService
 {
     /**
      * 通过登录地址查询信息
-     * 
+     *
      * @param ipaddr 登录地址
      * @param user 用户信息
      * @return 在线用户信息
@@ -33,7 +33,7 @@ public class SysUserOnlineServiceImpl implements ISysUserOnlineService
 
     /**
      * 通过用户名称查询信息
-     * 
+     *
      * @param userName 用户名称
      * @param user 用户信息
      * @return 在线用户信息
@@ -50,7 +50,7 @@ public class SysUserOnlineServiceImpl implements ISysUserOnlineService
 
     /**
      * 通过登录地址/用户名称查询信息
-     * 
+     *
      * @param ipaddr 登录地址
      * @param userName 用户名称
      * @param user 用户信息
@@ -68,7 +68,7 @@ public class SysUserOnlineServiceImpl implements ISysUserOnlineService
 
     /**
      * 设置在线用户信息
-     * 
+     *
      * @param user 用户信息
      * @return 在线用户
      */
@@ -87,9 +87,9 @@ public class SysUserOnlineServiceImpl implements ISysUserOnlineService
         sysUserOnline.setBrowser(user.getBrowser());
         sysUserOnline.setOs(user.getOs());
         sysUserOnline.setLoginTime(user.getLoginTime());
-        if (StringUtils.isNotNull(user.getUser().getUnit()))
+        if (StringUtils.isNotNull(user.getUser().getDept()))
         {
-            sysUserOnline.setDeptName(user.getUser().getUnit().getUnitName());
+            sysUserOnline.setDeptName(user.getUser().getDept().getDeptName());
         }
         return sysUserOnline;
     }

+ 41 - 0
gw-ui/src/api/gx/document.js

@@ -0,0 +1,41 @@
+import request from '@/utils/request'
+
+export function getDirectoryTree() {
+  return request({
+    url: '/gx/directory/front/tree',
+    method: 'get'
+  })
+}
+
+export function getFileList(dirId) {
+  return request({
+    url: '/gx/file/front/list/' + dirId,
+    method: 'get'
+  })
+}
+
+export function uploadFile(data) {
+  return request({
+    url: '/gx/file/upload',
+    method: 'post',
+    data: data,
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    }
+  })
+}
+
+export function downloadFile(id) {
+  return request({
+    url: '/gx/file/download/' + id,
+    method: 'get',
+    responseType: 'blob'
+  })
+}
+
+export function deleteFile(id) {
+  return request({
+    url: '/gx/file/' + id,
+    method: 'delete'
+  })
+}

+ 279 - 27
gw-ui/src/views/front/DataCommon.vue

@@ -1,25 +1,203 @@
 <template>
   <div class="page-container">
-    <div class="page-header">
-      <h1>数据查看</h1>
-      <p>通用数据表格查询,支持单位切换、业务类型切换、站名搜索</p>
-    </div>
     <div class="page-content">
-      <div class="data-placeholder">
-        <svg-icon icon-class="table" class="data-icon" />
-        <p>数据表格组件</p>
-        <p class="hint">单位选择 + 业务切换 + 站名搜索 + 动态表格列配置</p>
+      <div class="search-bar">
+        <div class="search-left">
+          <el-select v-model="selectedUnit" placeholder="请选择共享单位" @change="handleUnitChange" class="unit-select">
+            <el-option v-for="unit in unitList" :key="unit.unitCode" :label="unit.unitName" :value="unit.unitCode" />
+          </el-select>
+        </div>
+        <div class="search-center">
+          <div class="biz-buttons">
+            <el-button 
+              v-for="biz in bizList" 
+              :key="biz.bizCode"
+              :class="{ active: selectedBiz === biz.bizCode }"
+              @click="handleBizClick(biz.bizCode)"
+              size="default"
+            >
+              {{ biz.bizName }}
+              <span class="station-count">{{ biz.stationCount || 0 }}</span>
+            </el-button>
+          </div>
+        </div>
+        <div class="search-right">
+          <el-input v-model="searchKeyword" placeholder="请输入站名" @keyup.enter="loadStationData" clearable style="width: 200px">
+            <template #prefix>
+              <el-icon><Search /></el-icon>
+            </template>
+          </el-input>
+        </div>
+      </div>
+      
+      <div class="table-container">
+        <el-table :data="tableData" stripe border v-loading="loading">
+          <el-table-column prop="stcd" label="站码" width="100" />
+          <el-table-column prop="stnm" label="站名" width="150" />
+          <el-table-column prop="rvnm" label="河流名称" width="120" />
+          <el-table-column prop="lttd" label="纬度" width="100" />
+          <el-table-column prop="lgtd" label="经度" width="100" />
+          <el-table-column prop="latestDataTime" label="最新数据时间" width="170" />
+          <el-table-column prop="wt" label="水温(℃)" width="100" />
+          <el-table-column prop="ph" label="pH" width="80" />
+          <el-table-column prop="do" label="溶解氧(mg/L)" width="110" />
+          <el-table-column prop="nh3n" label="氨氮(mg/L)" width="100" />
+          <el-table-column prop="tp" label="总磷(mg/L)" width="100" />
+          <el-table-column prop="tn" label="总氮(mg/L)" width="100" />
+          <el-table-column prop="cod" label="COD(mg/L)" width="100" />
+          <el-table-column prop="bod" label="BOD(mg/L)" width="100" />
+        </el-table>
+        
+        <div v-if="tableData.length === 0 && !loading" class="empty-state">
+          <el-icon class="empty-icon"><DataAnalysis /></el-icon>
+          <p>暂无数据</p>
+        </div>
       </div>
     </div>
   </div>
 </template>
 
 <script setup>
+import { ref, onMounted } from 'vue'
+import { Search, DataAnalysis } from '@element-plus/icons-vue'
+import request from '@/utils/request'
+
+const selectedUnit = ref('TBA')
+const selectedBiz = ref('TBA_SW_1H2H')
+const searchKeyword = ref('')
+const loading = ref(false)
+
+const unitList = ref([])
+const bizList = ref([])
+const stationList = ref([])
+const tableData = ref([])
+
+onMounted(() => {
+  loadUnits()
+})
+
+function loadUnits() {
+  request({
+    url: '/gx/unit/front/list',
+    method: 'get'
+  }).then(res => {
+    unitList.value = res.data || []
+    loadBizList(selectedUnit.value)
+  })
+}
+
+function handleUnitChange(val) {
+  selectedBiz.value = ''
+  stationList.value = []
+  tableData.value = []
+  loadBizList(val)
+}
+
+function loadBizList(unitCode) {
+  request({
+    url: '/gx/biz/front/list',
+    method: 'get',
+    params: { bizUnit: unitCode }
+  }).then(res => {
+    const bizData = res.data || []
+    
+    Promise.all(bizData.map(biz => {
+      return request({
+        url: '/gx/station/front/listByBizCode/' + biz.bizCode,
+        method: 'get'
+      }).then(stationRes => {
+        const stations = stationRes.data || []
+        return { ...biz, stationCount: stations.length }
+      })
+    })).then(results => {
+      bizList.value = results
+      
+      if (bizList.value.length > 0) {
+        const defaultBiz = bizList.value.find(b => b.bizCode === selectedBiz.value)
+        if (defaultBiz) {
+          selectedBiz.value = defaultBiz.bizCode
+        } else {
+          selectedBiz.value = bizList.value[0].bizCode
+        }
+        handleBizClick(selectedBiz.value)
+      }
+    })
+  })
+}
+
+function handleBizClick(bizCode) {
+  selectedBiz.value = bizCode
+  loadStationData()
+}
+
+function loadStationData() {
+  loading.value = true
+  
+  request({
+    url: '/gx/station/front/listByBizCode/' + selectedBiz.value,
+    method: 'get'
+  }).then(res => {
+    stationList.value = res.data || []
+    
+    let filteredStations = stationList.value
+    if (searchKeyword.value) {
+      const keyword = searchKeyword.value.toLowerCase()
+      filteredStations = filteredStations.filter(s => 
+        s.stnm.toLowerCase().includes(keyword) || 
+        s.stcd.toLowerCase().includes(keyword)
+      )
+    }
+    
+    Promise.all(filteredStations.map(station => {
+      return request({
+        url: '/gx/quality/front/list',
+        method: 'get',
+        params: { stcd: station.stcd, pageSize: 1 }
+      }).then(qualityRes => {
+        const qualityData = qualityRes.data || []
+        const latestQuality = qualityData.length > 0 ? qualityData[0] : {}
+        return {
+          ...station,
+          latestDataTime: latestQuality.uploadTime || '-',
+          wt: latestQuality.wt || '-',
+          ph: latestQuality.ph || '-',
+          do: latestQuality.do || '-',
+          nh3n: latestQuality.nh3n || '-',
+          tp: latestQuality.tp || '-',
+          tn: latestQuality.tn || '-',
+          cod: latestQuality.cod || '-',
+          bod: latestQuality.bod || '-'
+        }
+      })
+    })).then(results => {
+      tableData.value = results
+      loading.value = false
+    }).catch(() => {
+      tableData.value = filteredStations.map(station => ({
+        ...station,
+        latestDataTime: '-',
+        wt: '-',
+        ph: '-',
+        do: '-',
+        nh3n: '-',
+        tp: '-',
+        tn: '-',
+        cod: '-',
+        bod: '-'
+      }))
+      loading.value = false
+    })
+  }).catch(() => {
+    loading.value = false
+  })
+}
 </script>
 
 <style scoped>
 .page-container {
   height: 100%;
+  display: flex;
+  flex-direction: column;
 }
 
 .page-header {
@@ -42,37 +220,111 @@
 }
 
 .page-content {
+  flex: 1;
   background: #fff;
   border-radius: 12px;
-  padding: 20px;
-  min-height: 500px;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
 }
 
-.data-placeholder {
+.search-bar {
+  padding: 15px 20px;
+  border-bottom: 1px solid #e4e7ed;
   display: flex;
-  flex-direction: column;
   align-items: center;
-  justify-content: center;
-  height: 500px;
-  background: #f8fafc;
-  border-radius: 8px;
+  justify-content: space-between;
 }
 
-.data-icon {
-  width: 80px;
-  height: 80px;
-  color: #0066cc;
-  margin-bottom: 20px;
+.search-left {
+  flex-shrink: 0;
 }
 
-.data-placeholder p {
-  font-size: 18px;
-  color: #333;
-  margin: 0 0 10px 0;
+.unit-select {
+  width: 220px;
+}
+
+.search-center {
+  flex: 1;
+  display: flex;
+  justify-content: center;
+  padding: 0 20px;
+}
+
+.biz-buttons {
+  display: flex;
+  gap: 10px;
+  flex-wrap: wrap;
+  justify-content: center;
 }
 
-.data-placeholder .hint {
+.biz-buttons :deep(.el-button) {
+  padding: 6px 16px;
+  border-radius: 4px;
   font-size: 14px;
-  color: #999;
+  background: #f5f7fa;
+  color: #606266;
+  border-color: #dcdfe6;
+  transition: all 0.3s ease;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.biz-buttons :deep(.el-button:hover) {
+  background: #ecf5ff;
+  border-color: #b3d8ff;
+  color: #1c97e7;
+}
+
+.biz-buttons :deep(.el-button.active) {
+  background: #1c97e7;
+  color: #fff;
+  border-color: #1c97e7;
+}
+
+.station-count {
+  font-size: 12px;
+  background: rgba(255, 255, 255, 0.2);
+  padding: 2px 6px;
+  border-radius: 10px;
+}
+
+.biz-buttons :deep(.el-button.active) .station-count {
+  background: rgba(255, 255, 255, 0.3);
+}
+
+.search-right {
+  flex-shrink: 0;
+}
+
+.table-container {
+  flex: 1;
+  overflow-y: auto;
+  padding: 20px;
+}
+
+.table-container :deep(.el-table) {
+  width: 100%;
+}
+
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 300px;
+  color: #909399;
+}
+
+.empty-icon {
+  font-size: 64px;
+  margin-bottom: 16px;
+  opacity: 0.5;
+}
+
+.empty-state p {
+  font-size: 16px;
+  margin: 0;
 }
 </style>

+ 432 - 24
gw-ui/src/views/front/Document.vue

@@ -1,25 +1,283 @@
 <template>
-  <div class="page-container">
+  <div class="document-page">
     <div class="page-header">
       <h1>河湖长制</h1>
-      <p>文档管理:目录树 + 文件搜索 + 下载</p>
+      <p>文档管理:目录树浏览 + 文件上传下载</p>
     </div>
+    
     <div class="page-content">
-      <div class="document-placeholder">
-        <svg-icon icon-class="documentation" class="document-icon" />
-        <p>河湖长制文档管理</p>
-        <p class="hint">目录树浏览、文件搜索、文档下载</p>
+      <div class="sidebar">
+        <div class="sidebar-header">
+          <span class="sidebar-title">目录树</span>
+        </div>
+        
+        <div class="tree-container">
+          <el-tree
+            :data="directoryTree"
+            :props="treeProps"
+            :default-expand-all="true"
+            @node-click="handleNodeClick"
+            highlight-current
+            class="directory-tree"
+          >
+            <template #default="{ node }">
+              <span class="tree-node">
+                <el-icon v-if="node.children && node.children.length > 0">
+                  <FolderOpened />
+                </el-icon>
+                <el-icon v-else>
+                  <Folder />
+                </el-icon>
+                <span class="node-label">{{ node.label }}</span>
+                <span v-if="getDirFileCount(node.data.id) > 0" class="file-count">
+                  {{ getDirFileCount(node.data.id) }}
+                </span>
+              </span>
+            </template>
+          </el-tree>
+        </div>
+      </div>
+      
+      <div class="main-content">
+        <div class="content-header">
+          <div class="current-dir">
+            <el-breadcrumb separator="/">
+              <el-breadcrumb-item v-for="(item, index) in breadcrumb" :key="item.id">
+                <span @click="goToDir(item.id)">{{ item.name }}</span>
+              </el-breadcrumb-item>
+            </el-breadcrumb>
+          </div>
+          <div class="action-buttons">
+            <el-upload
+              class="upload-btn"
+              :action="uploadUrl"
+              :data="{ dirId: selectedDirId }"
+              :on-success="handleUploadSuccess"
+              :on-error="handleUploadError"
+              :show-file-list="false"
+              accept=".doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt,.zip,.rar"
+            >
+              <el-button type="primary" icon="Upload">上传文件</el-button>
+            </el-upload>
+          </div>
+        </div>
+        
+        <div class="file-list-container">
+          <div v-if="fileList.length > 0" class="file-grid">
+            <div 
+              v-for="file in fileList" 
+              :key="file.id" 
+              class="file-card"
+              @click="previewFile(file)"
+            >
+              <div class="file-icon">
+                <el-icon :size="48">
+                  <component :is="getFileIcon(file.fileExt)" />
+                </el-icon>
+              </div>
+              <div class="file-info">
+                <div class="file-name">{{ file.fileName }}</div>
+                <div class="file-meta">
+                  <span>{{ formatFileSize(file.fileSize) }}</span>
+                  <span>{{ formatDate(file.uploadTime) }}</span>
+                </div>
+              </div>
+              <div class="file-actions">
+                <el-button 
+                  type="text" 
+                  icon="Download" 
+                  @click.stop="handleDownload(file.id)"
+                >下载</el-button>
+                <el-button 
+                  type="text" 
+                  icon="Delete" 
+                  @click.stop="handleDelete(file.id)"
+                >删除</el-button>
+              </div>
+            </div>
+          </div>
+          
+          <div v-else class="empty-state">
+            <el-icon class="empty-icon"><Folder /></el-icon>
+            <p>该目录下暂无文件</p>
+            <p class="hint">点击上方"上传文件"按钮添加文档</p>
+          </div>
+        </div>
       </div>
     </div>
   </div>
 </template>
 
 <script setup>
+import { ref, onMounted, computed } from 'vue'
+import { 
+  Folder, FolderOpened, Upload, Download, Delete,
+  Document
+} from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { getDirectoryTree, getFileList, downloadFile, deleteFile } from '@/api/gx/document'
+
+const directoryTree = ref([])
+const fileList = ref([])
+const selectedDirId = ref(1)
+const breadcrumb = ref([{ id: 1, name: '河湖长制' }])
+const uploadUrl = '/gx/file/upload'
+
+const treeProps = {
+  children: 'children',
+  label: 'dirName',
+  id: 'id'
+}
+
+const dirFileCount = ref({})
+
+onMounted(() => {
+  loadDirectoryTree()
+})
+
+function loadDirectoryTree() {
+  getDirectoryTree().then(res => {
+    directoryTree.value = res.data || []
+    if (directoryTree.value.length > 0) {
+      selectedDirId.value = directoryTree.value[0].id
+      loadFileList(selectedDirId.value)
+    }
+  })
+}
+
+function handleNodeClick(data) {
+  selectedDirId.value = data.id
+  loadFileList(data.id)
+  updateBreadcrumb(data)
+}
+
+function updateBreadcrumb(node) {
+  const path = []
+  findPath(directoryTree.value, node.id, path)
+  breadcrumb.value = path.reverse()
+}
+
+function findPath(nodes, targetId, path) {
+  for (const node of nodes) {
+    if (node.id === targetId) {
+      path.push({ id: node.id, name: node.dirName })
+      return true
+    }
+    if (node.children && node.children.length > 0) {
+      if (findPath(node.children, targetId, path)) {
+        path.push({ id: node.id, name: node.dirName })
+        return true
+      }
+    }
+  }
+  return false
+}
+
+function goToDir(dirId) {
+  selectedDirId.value = dirId
+  loadFileList(dirId)
+  
+  const index = breadcrumb.value.findIndex(item => item.id === dirId)
+  breadcrumb.value = breadcrumb.value.slice(0, index + 1)
+}
+
+function loadFileList(dirId) {
+  getFileList(dirId).then(res => {
+    fileList.value = res.data || []
+    updateDirFileCount()
+  })
+}
+
+function updateDirFileCount() {
+  dirFileCount.value = {}
+}
+
+function getDirFileCount(dirId) {
+  return fileList.value.filter(f => f.dirId === dirId).length
+}
+
+function handleUploadSuccess(response) {
+  if (response.code === 200) {
+    ElMessage.success('文件上传成功')
+    loadFileList(selectedDirId.value)
+  } else {
+    ElMessage.error(response.msg || '上传失败')
+  }
+}
+
+function handleUploadError(error) {
+  ElMessage.error('文件上传失败')
+}
+
+function handleDownload(fileId) {
+  downloadFile(fileId).then(response => {
+    const blob = new Blob([response.data])
+    const url = window.URL.createObjectURL(blob)
+    const a = document.createElement('a')
+    a.href = url
+    a.download = getFileName(fileId)
+    document.body.appendChild(a)
+    a.click()
+    window.URL.revokeObjectURL(url)
+    document.body.removeChild(a)
+  }).catch(() => {
+    ElMessage.error('下载失败')
+  })
+}
+
+function getFileName(fileId) {
+  const file = fileList.value.find(f => f.id === fileId)
+  return file ? file.fileName : 'download'
+}
+
+function handleDelete(fileId) {
+  ElMessageBox.confirm('确定删除该文件吗?', '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(() => {
+    deleteFile(fileId).then(res => {
+      if (res.code === 200) {
+        ElMessage.success('删除成功')
+        loadFileList(selectedDirId.value)
+      } else {
+        ElMessage.error(res.msg || '删除失败')
+      }
+    })
+  }).catch(() => {})
+}
+
+function previewFile(file) {
+}
+
+function getFileIcon(ext) {
+  return Document
+}
+
+function formatFileSize(size) {
+  if (!size) return '0 B'
+  const units = ['B', 'KB', 'MB', 'GB']
+  let result = size
+  let unitIndex = 0
+  while (result >= 1024 && unitIndex < units.length - 1) {
+    result /= 1024
+    unitIndex++
+  }
+  return result.toFixed(2) + ' ' + units[unitIndex]
+}
+
+function formatDate(dateStr) {
+  if (!dateStr) return '-'
+  const date = new Date(dateStr)
+  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
+}
 </script>
 
 <style scoped>
-.page-container {
+.document-page {
   height: 100%;
+  display: flex;
+  flex-direction: column;
 }
 
 .page-header {
@@ -42,37 +300,187 @@
 }
 
 .page-content {
+  flex: 1;
+  display: flex;
+  gap: 20px;
+  overflow: hidden;
+}
+
+.sidebar {
+  width: 280px;
   background: #fff;
   border-radius: 12px;
-  padding: 20px;
-  min-height: 500px;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.sidebar-header {
+  padding: 15px;
+  border-bottom: 1px solid #e4e7ed;
+}
+
+.sidebar-title {
+  font-size: 16px;
+  font-weight: bold;
+  color: #303133;
+}
+
+.tree-container {
+  flex: 1;
+  overflow-y: auto;
+  padding: 10px;
+}
+
+.directory-tree {
+  font-size: 14px;
 }
 
-.document-placeholder {
+.tree-node {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+}
+
+.node-label {
+  flex: 1;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.file-count {
+  font-size: 12px;
+  color: #909399;
+  background: #f5f7fa;
+  padding: 2px 6px;
+  border-radius: 10px;
+}
+
+.main-content {
+  flex: 1;
+  background: #fff;
+  border-radius: 12px;
   display: flex;
   flex-direction: column;
+  overflow: hidden;
+}
+
+.content-header {
+  padding: 15px 20px;
+  border-bottom: 1px solid #e4e7ed;
+  display: flex;
+  justify-content: space-between;
   align-items: center;
-  justify-content: center;
-  height: 500px;
-  background: #f8fafc;
+}
+
+.current-dir {
+  flex: 1;
+}
+
+.action-buttons {
+  flex-shrink: 0;
+}
+
+.upload-btn {
+  margin-left: 10px;
+}
+
+.file-list-container {
+  flex: 1;
+  overflow-y: auto;
+  padding: 20px;
+}
+
+.file-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
+  gap: 20px;
+}
+
+.file-card {
+  border: 1px solid #e4e7ed;
   border-radius: 8px;
+  padding: 20px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  position: relative;
 }
 
-.document-icon {
-  width: 80px;
-  height: 80px;
-  color: #0066cc;
-  margin-bottom: 20px;
+.file-card:hover {
+  border-color: #1c97e7;
+  box-shadow: 0 2px 12px rgba(28, 151, 231, 0.15);
 }
 
-.document-placeholder p {
-  font-size: 18px;
-  color: #333;
-  margin: 0 0 10px 0;
+.file-icon {
+  margin-bottom: 12px;
+  color: #1c97e7;
 }
 
-.document-placeholder .hint {
+.file-info {
+  text-align: center;
+  width: 100%;
+}
+
+.file-name {
   font-size: 14px;
-  color: #999;
+  font-weight: 500;
+  color: #303133;
+  margin-bottom: 8px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.file-meta {
+  font-size: 12px;
+  color: #909399;
+  display: flex;
+  justify-content: center;
+  gap: 15px;
+}
+
+.file-actions {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  display: none;
+  gap: 5px;
+}
+
+.file-card:hover .file-actions {
+  display: flex;
+}
+
+.file-actions .el-button {
+  padding: 2px 8px;
+  font-size: 12px;
+}
+
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  height: 300px;
+  color: #909399;
+}
+
+.empty-icon {
+  font-size: 64px;
+  margin-bottom: 16px;
+  opacity: 0.5;
+}
+
+.empty-state p {
+  font-size: 16px;
+  margin: 0 0 8px 0;
+}
+
+.empty-state .hint {
+  font-size: 13px;
 }
 </style>

+ 0 - 69
gw-ui/src/views/front/Home.vue

@@ -74,75 +74,6 @@
         </div>
       </div>
     </div>
-
-    <div class="section">
-      <h2>实时数据概览</h2>
-      <div class="data-overview">
-        <div class="overview-card water-quality">
-          <h3>水质监测</h3>
-          <div class="overview-content">
-            <div class="indicator">
-              <span class="label">pH值</span>
-              <span class="value normal">7.2</span>
-            </div>
-            <div class="indicator">
-              <span class="label">溶解氧</span>
-              <span class="value normal">6.8 mg/L</span>
-            </div>
-            <div class="indicator">
-              <span class="label">高锰酸盐指数</span>
-              <span class="value normal">4.2 mg/L</span>
-            </div>
-            <div class="indicator">
-              <span class="label">氨氮</span>
-              <span class="value warning">0.55 mg/L</span>
-            </div>
-          </div>
-        </div>
-        <div class="overview-card water-level">
-          <h3>水情监测</h3>
-          <div class="overview-content">
-            <div class="indicator">
-              <span class="label">太湖水位</span>
-              <span class="value normal">3.12 m</span>
-            </div>
-            <div class="indicator">
-              <span class="label">洪泽湖水位</span>
-              <span class="value normal">12.35 m</span>
-            </div>
-            <div class="indicator">
-              <span class="label">长江流量</span>
-              <span class="value normal">23400 m³/s</span>
-            </div>
-            <div class="indicator">
-              <span class="label">入湖流量</span>
-              <span class="value normal">1250 m³/s</span>
-            </div>
-          </div>
-        </div>
-        <div class="overview-card ecology">
-          <h3>生态监测</h3>
-          <div class="overview-content">
-            <div class="indicator">
-              <span class="label">叶绿素a</span>
-              <span class="value warning">28 μg/L</span>
-            </div>
-            <div class="indicator">
-              <span class="label">蓝藻密度</span>
-              <span class="value danger">1.2×10⁶ cells/mL</span>
-            </div>
-            <div class="indicator">
-              <span class="label">透明度</span>
-              <span class="value normal">0.85 m</span>
-            </div>
-            <div class="indicator">
-              <span class="label">水色</span>
-              <span class="value normal">Ⅲ类</span>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
   </div>
 </template>
 

+ 3 - 3
gw-ui/src/views/gx/station/index.vue

@@ -47,10 +47,10 @@
 
     <el-table v-loading="loading" :data="stationList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
-      <el-table-column label="单位名称" prop="unitName" width="160" :show-overflow-tooltip="true" />
+      <el-table-column label="单位名称" prop="unitName" width="250" :show-overflow-tooltip="true" />
       <el-table-column label="业务域" prop="bizName" width="160" :show-overflow-tooltip="true" />
-      <el-table-column label="站码" prop="stcd" width="120" />
-      <el-table-column label="站名" prop="stnm" width="150" :show-overflow-tooltip="true" />
+<!--      <el-table-column label="站码" prop="stcd" width="120" />-->
+      <el-table-column label="站名" prop="stnm" width="200" :show-overflow-tooltip="true" />
       <el-table-column label="河流" prop="rvnm" width="120" />
       <el-table-column label="湖泊" prop="hnnm" width="100" />
       <el-table-column label="经度" prop="lgtd" width="100" align="center" />

+ 0 - 69
gw-ui/src/views/index.vue

@@ -74,75 +74,6 @@
         </div>
       </div>
     </div>
-
-    <div class="section">
-      <h2>实时数据概览</h2>
-      <div class="data-overview">
-        <div class="overview-card water-quality">
-          <h3>水质监测</h3>
-          <div class="overview-content">
-            <div class="indicator">
-              <span class="label">pH值</span>
-              <span class="value normal">7.2</span>
-            </div>
-            <div class="indicator">
-              <span class="label">溶解氧</span>
-              <span class="value normal">6.8 mg/L</span>
-            </div>
-            <div class="indicator">
-              <span class="label">高锰酸盐指数</span>
-              <span class="value normal">4.2 mg/L</span>
-            </div>
-            <div class="indicator">
-              <span class="label">氨氮</span>
-              <span class="value warning">0.55 mg/L</span>
-            </div>
-          </div>
-        </div>
-        <div class="overview-card water-level">
-          <h3>水情监测</h3>
-          <div class="overview-content">
-            <div class="indicator">
-              <span class="label">太湖水位</span>
-              <span class="value normal">3.12 m</span>
-            </div>
-            <div class="indicator">
-              <span class="label">洪泽湖水位</span>
-              <span class="value normal">12.35 m</span>
-            </div>
-            <div class="indicator">
-              <span class="label">长江流量</span>
-              <span class="value normal">23400 m³/s</span>
-            </div>
-            <div class="indicator">
-              <span class="label">入湖流量</span>
-              <span class="value normal">1250 m³/s</span>
-            </div>
-          </div>
-        </div>
-        <div class="overview-card ecology">
-          <h3>生态监测</h3>
-          <div class="overview-content">
-            <div class="indicator">
-              <span class="label">叶绿素a</span>
-              <span class="value warning">28 μg/L</span>
-            </div>
-            <div class="indicator">
-              <span class="label">蓝藻密度</span>
-              <span class="value danger">1.2×10⁶ cells/mL</span>
-            </div>
-            <div class="indicator">
-              <span class="label">透明度</span>
-              <span class="value normal">0.85 m</span>
-            </div>
-            <div class="indicator">
-              <span class="label">水色</span>
-              <span class="value normal">Ⅲ类</span>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
   </div>
 </template>
 

+ 5 - 0
pom.xml

@@ -133,6 +133,11 @@
                 <artifactId>kaptcha</artifactId>
                 <version>${kaptcha.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>1.18.24</version>
+            </dependency>
             <!-- 核心模块-->
             <dependency>
                 <groupId>com.goldenwater</groupId>