Merge remote-tracking branch 'origin/0.8.X' into 0.8.X

This commit is contained in:
2026-07-04 10:13:43 +08:00
29 changed files with 940 additions and 112 deletions

View File

@@ -0,0 +1,25 @@
-- =============================================================
-- 线上线下库对齐修复脚本
-- 用于 klp-oa线上和 klp-oa-test线下
-- =============================================================
SET NAMES utf8mb4;
-- ==================== 第一部分:补字典(两边都执行) ====================
-- 1.1 regrade_quality_type 字典类型
INSERT IGNORE INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark)
VALUES ('改判后质量状态', 'regrade_quality_type', '0', 'admin', NOW(), 'admin', NOW(), '');
-- 1.2 regrade_quality_type 字典数据
INSERT IGNORE INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
VALUES (1, '协议销售', 'protocol_sale', 'regrade_quality_type', '', '', 'Y', '0', 'admin', NOW()),
(2, '转分剪', 'to_slitting', 'regrade_quality_type', '', '', 'Y', '0', 'admin', NOW()),
(3, '降级', 'downgrade', 'regrade_quality_type', '', '', 'Y', '0', 'admin', NOW()),
(4, '返修', 'rework', 'regrade_quality_type', '', '', 'Y', '0', 'admin', NOW()),
(5, '报废', 'scrap', 'regrade_quality_type', '', '', 'Y', '0', 'admin', NOW());
-- 1.3 quality_review_status 补充缺失数据
INSERT IGNORE INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
VALUES (2, '待审批', '2', 'quality_review_status', '', 'warning', 'Y', '0', 'admin', NOW()),
(3, '已通过', '3', 'quality_review_status', '', 'success', 'Y', '0', 'admin', NOW()),
(4, '已驳回', '4', 'quality_review_status', '', 'danger', 'Y', '0', 'admin', NOW());

View File

@@ -0,0 +1,51 @@
-- =============================================================
-- 线上线下库对齐修复修复版v2
-- 适配 dict_code / dict_id 非自增的表结构
-- =============================================================
SET NAMES utf8mb4;
-- ==================== 1. 补字典类型 ====================
SET @max_dict_id = (SELECT COALESCE(MAX(dict_id), 0) FROM sys_dict_type);
INSERT IGNORE INTO sys_dict_type (dict_id, dict_name, dict_type, status, create_by, create_time)
VALUES (@max_dict_id + 1, '改判后质量状态', 'regrade_quality_type', '0', 'admin', NOW());
-- ==================== 2. 补字典数据 ====================
SET @max_code = (SELECT COALESCE(MAX(dict_code), 0) FROM sys_dict_data);
SET @code = @max_code + 1;
-- quality_review_status 缺失的3条
INSERT IGNORE INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
VALUES (@code, 2, '待审批', '2', 'quality_review_status', '', 'warning', 'Y', '0', 'admin', NOW());
SET @code = @code + 1;
INSERT IGNORE INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
VALUES (@code, 3, '已通过', '3', 'quality_review_status', '', 'success', 'Y', '0', 'admin', NOW());
SET @code = @code + 1;
INSERT IGNORE INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
VALUES (@code, 4, '已驳回', '4', 'quality_review_status', '', 'danger', 'Y', '0', 'admin', NOW());
SET @code = @code + 1;
-- regrade_quality_type 5条
INSERT IGNORE INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
VALUES (@code, 1, '协议销售', 'protocol_sale', 'regrade_quality_type', '', '', 'Y', '0', 'admin', NOW());
SET @code = @code + 1;
INSERT IGNORE INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
VALUES (@code, 2, '转分剪', 'to_slitting', 'regrade_quality_type', '', '', 'Y', '0', 'admin', NOW());
SET @code = @code + 1;
INSERT IGNORE INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
VALUES (@code, 3, '降级', 'downgrade', 'regrade_quality_type', '', '', 'Y', '0', 'admin', NOW());
SET @code = @code + 1;
INSERT IGNORE INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
VALUES (@code, 4, '返修', 'rework', 'regrade_quality_type', '', '', 'Y', '0', 'admin', NOW());
SET @code = @code + 1;
INSERT IGNORE INTO sys_dict_data (dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time)
VALUES (@code, 5, '报废', 'scrap', 'regrade_quality_type', '', '', 'Y', '0', 'admin', NOW());
-- ==================== 3. 验证 ====================
SELECT 'Dict fix OK' AS result;

69
docs/sql_align_dev.sql Normal file
View File

@@ -0,0 +1,69 @@
-- =============================================================
-- 线下库 klp-oa-test 菜单对齐修复
-- 将质量评审菜单从"质量管理"下移到"评审流程"目录下
-- 并补充次品钢卷、O级卷菜单
-- =============================================================
SET NAMES utf8mb4;
-- ==================== 1. 清理旧的质量评审菜单 ====================
DELETE FROM sys_role_menu WHERE menu_id IN (2100000000000000036,2100000000000000037,2100000000000000038,2100000000000000039,2100000000000000040,2100000000000000041,2100000000000000042,2100000000000000043);
DELETE FROM sys_menu WHERE menu_id IN (2100000000000000036,2100000000000000037,2100000000000000038,2100000000000000039,2100000000000000040,2100000000000000041,2100000000000000042,2100000000000000043);
-- 计算可用菜单ID
SET @max_id = (SELECT COALESCE(MAX(menu_id), 0) FROM sys_menu);
-- ==================== 2. 创建"评审流程"目录(挂在质量管理下) ====================
SET @parent_quality = 2068954238598967297; -- 质量管理
SET @id_review_dir = @max_id + 1;
INSERT IGNORE INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, is_frame, is_cache, menu_type, visible, status, icon, create_by, create_time, update_by, update_time)
VALUES (@id_review_dir, '评审流程', @parent_quality, 5, 'review', 1, 0, 'M', '0', '0', 'guide', 'admin', NOW(), 'admin', NOW());
-- ==================== 3. 在评审流程下创建子菜单 ====================
SET @parent = @id_review_dir;
SET @id = @max_id + 2;
-- 次品钢卷 (order=4)
INSERT IGNORE INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, icon, create_by, create_time)
VALUES (@id, '次品钢卷', @parent, 4, 'rubbish', 'wms/coil/views/scrap', 1, 0, 'C', '0', '0', 'checkbox', 'admin', NOW());
SET @id = @id + 1;
-- O级卷 (order=5)
INSERT IGNORE INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, icon, create_by, create_time)
VALUES (@id, 'O级卷', @parent, 5, 'jishu', 'wms/coil/views/jishu', 1, 0, 'C', '0', '0', 'checkbox', 'admin', NOW());
SET @id = @id + 1;
-- 质量评审 (order=6)
SET @id_review_menu = @id;
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
VALUES (@id_review_menu, '质量评审', @parent, 6, 'qualityReview', 'mes/qc/qualityReview/index', 1, 0, 'C', '0', '0', 'qc:qualityReview:list', 'guide', 'admin', NOW());
SET @id = @id + 1;
-- 质量评审待审批 (order=7, 隐藏)
SET @id_review_todo = @id;
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time)
VALUES (@id_review_todo, '质量评审待审批', @parent, 7, 'qualityReviewTodo', 'mes/qc/qualityReview/todo', 1, 0, 'C', '1', '0', 'qc:qualityReview:approve', 'checkbox', 'admin', NOW());
SET @id = @id + 1;
-- ==================== 4. 按钮权限(挂在质量评审菜单下) ====================
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, create_by, create_time)
VALUES (@id, '质量评审新增', @id_review_menu, 1, 'F', '0', '0', 'qc:qualityReview:add', 'admin', NOW()); SET @id = @id + 1;
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, create_by, create_time)
VALUES (@id, '质量评审修改', @id_review_menu, 2, 'F', '0', '0', 'qc:qualityReview:edit', 'admin', NOW()); SET @id = @id + 1;
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, create_by, create_time)
VALUES (@id, '质量评审删除', @id_review_menu, 3, 'F', '0', '0', 'qc:qualityReview:delete', 'admin', NOW()); SET @id = @id + 1;
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, create_by, create_time)
VALUES (@id, '质量评审提交送审', @id_review_menu, 4, 'F', '0', '0', 'qc:qualityReview:submit', 'admin', NOW()); SET @id = @id + 1;
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, create_by, create_time)
VALUES (@id, '质量评审审批', @id_review_menu, 5, 'F', '0', '0', 'qc:qualityReview:approve', 'admin', NOW()); SET @id = @id + 1;
INSERT INTO sys_menu (menu_id, menu_name, parent_id, order_num, menu_type, visible, status, perms, create_by, create_time)
VALUES (@id, '质量评审执行改判', @id_review_menu, 6, 'F', '0', '0', 'qc:qualityReview:execute', 'admin', NOW());
-- ==================== 5. 授权admin角色 ====================
INSERT IGNORE INTO sys_role_menu (role_id, menu_id) VALUES
(1, @parent_quality), (1, @id_review_dir),
(1, @id_review_menu), (1, @id_review_todo);
-- 按钮权限
INSERT IGNORE INTO sys_role_menu (role_id, menu_id) SELECT 1, menu_id FROM sys_menu WHERE parent_id = @id_review_menu AND menu_type = 'F';
-- ==================== 6. 验证 ====================
SELECT 'Dev align OK' AS result;

View File

@@ -0,0 +1,50 @@
package com.klp.web.controller.system;
import java.util.List;
import lombok.RequiredArgsConstructor;
import javax.validation.constraints.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import com.klp.common.annotation.RepeatSubmit;
import com.klp.common.annotation.Log;
import com.klp.common.core.controller.BaseController;
import com.klp.common.core.domain.R;
import com.klp.common.core.validate.AddGroup;
import com.klp.common.enums.BusinessType;
import com.klp.system.domain.vo.SysFileCommentVo;
import com.klp.system.domain.bo.SysFileCommentBo;
import com.klp.system.service.ISysFileCommentService;
/**
* 文件评论
*
* @author klp
* @date 2026-07-04
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/file/comment")
public class SysFileCommentController extends BaseController {
private final ISysFileCommentService iSysFileCommentService;
/**
* 查询文件评论列表
*/
@GetMapping("/list/{fileId}")
public R<List<SysFileCommentVo>> list(@NotNull(message = "文件ID不能为空") @PathVariable Long fileId) {
return R.ok(iSysFileCommentService.queryListByFileId(fileId));
}
/**
* 新增文件评论
*/
@Log(title = "文件评论", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody SysFileCommentBo bo) {
return toAjax(iSysFileCommentService.insertByBo(bo));
}
}

View File

@@ -98,6 +98,15 @@ public class SysFileController extends BaseController {
return toAjax(iSysFileService.deleteWithValidByIds(Arrays.asList(fileIds), true));
}
/**
* 文件浏览次数 +1
*/
@PutMapping("/incrementView/{fileId}")
public R<Void> incrementView(@NotNull(message = "主键不能为空") @PathVariable Long fileId) {
iSysFileService.incrementViewCount(fileId);
return R.ok();
}
/**
* 查询与我相关的文件(私有文件且当前用户在可见用户列表中)
*/

View File

@@ -1,4 +1,4 @@
package com.klp.controller;
package com.klp.pt.controller;
import java.util.List;
import java.util.Arrays;
@@ -17,9 +17,9 @@ import com.klp.common.core.validate.AddGroup;
import com.klp.common.core.validate.EditGroup;
import com.klp.common.enums.BusinessType;
import com.klp.common.utils.poi.ExcelUtil;
import com.klp.domain.vo.PtProductToleranceVo;
import com.klp.domain.bo.PtProductToleranceBo;
import com.klp.service.IPtProductToleranceService;
import com.klp.pt.domain.vo.PtProductToleranceVo;
import com.klp.pt.domain.bo.PtProductToleranceBo;
import com.klp.pt.service.IPtProductToleranceService;
import com.klp.common.core.page.TableDataInfo;
/**

View File

@@ -1,4 +1,4 @@
package com.klp.domain;
package com.klp.pt.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.klp.common.core.domain.BaseEntity;

View File

@@ -1,4 +1,4 @@
package com.klp.domain.bo;
package com.klp.pt.domain.bo;
import com.klp.common.core.domain.BaseEntity;
import lombok.Data;

View File

@@ -1,4 +1,4 @@
package com.klp.domain.vo;
package com.klp.pt.domain.vo;
import java.math.BigDecimal;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;

View File

@@ -1,7 +1,7 @@
package com.klp.mapper;
package com.klp.pt.mapper;
import com.klp.domain.PtProductTolerance;
import com.klp.domain.vo.PtProductToleranceVo;
import com.klp.pt.domain.PtProductTolerance;
import com.klp.pt.domain.vo.PtProductToleranceVo;
import com.klp.common.core.mapper.BaseMapperPlus;
/**

View File

@@ -1,8 +1,8 @@
package com.klp.service;
package com.klp.pt.service;
import com.klp.domain.PtProductTolerance;
import com.klp.domain.vo.PtProductToleranceVo;
import com.klp.domain.bo.PtProductToleranceBo;
import com.klp.pt.domain.PtProductTolerance;
import com.klp.pt.domain.vo.PtProductToleranceVo;
import com.klp.pt.domain.bo.PtProductToleranceBo;
import com.klp.common.core.page.TableDataInfo;
import com.klp.common.core.domain.PageQuery;

View File

@@ -9,11 +9,11 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import com.klp.domain.bo.PtProductToleranceBo;
import com.klp.domain.vo.PtProductToleranceVo;
import com.klp.domain.PtProductTolerance;
import com.klp.mapper.PtProductToleranceMapper;
import com.klp.service.IPtProductToleranceService;
import com.klp.pt.domain.bo.PtProductToleranceBo;
import com.klp.pt.domain.vo.PtProductToleranceVo;
import com.klp.pt.domain.PtProductTolerance;
import com.klp.pt.mapper.PtProductToleranceMapper;
import com.klp.pt.service.IPtProductToleranceService;
import java.util.List;
import java.util.Map;

View File

@@ -2,9 +2,9 @@
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.klp.mapper.PtProductToleranceMapper">
<mapper namespace="com.klp.pt.mapper.PtProductToleranceMapper">
<resultMap type="com.klp.domain.PtProductTolerance" id="PtProductToleranceResult">
<resultMap type="com.klp.pt.domain.PtProductTolerance" id="PtProductToleranceResult">
<result property="toleranceId" column="tolerance_id"/>
<result property="standardId" column="standard_id"/>
<result property="toleranceType" column="tolerance_type"/>

View File

@@ -60,6 +60,10 @@ public class SysFile extends BaseEntity {
* 备注
*/
private String remark;
/**
* 浏览次数
*/
private Long viewCount;
/**
* 删除标识 0正常 2删除
*/

View File

@@ -0,0 +1,40 @@
package com.klp.system.domain;
import com.baomidou.mybatisplus.annotation.*;
import com.klp.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 文件评论对象 sys_file_comment
*
* @author klp
* @date 2026-07-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_file_comment")
public class SysFileComment extends BaseEntity {
private static final long serialVersionUID=1L;
/**
* 主键ID
*/
@TableId(value = "comment_id")
private Long commentId;
/**
* 关联文件ID
*/
private Long fileId;
/**
* 评论内容
*/
private String content;
/**
* 所属部门
*/
private String dept;
}

View File

@@ -67,5 +67,9 @@ public class SysFileBo extends BaseEntity {
*/
private String remark;
/**
* 浏览次数
*/
private Long viewCount;
}

View File

@@ -0,0 +1,42 @@
package com.klp.system.domain.bo;
import com.klp.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.*;
/**
* 文件评论业务对象 sys_file_comment
*
* @author klp
* @date 2026-07-04
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SysFileCommentBo extends BaseEntity {
/**
* 主键ID
*/
private Long commentId;
/**
* 关联文件ID
*/
@NotNull(message = "文件ID不能为空")
private Long fileId;
/**
* 评论内容
*/
@NotBlank(message = "评论内容不能为空")
private String content;
/**
* 所属部门
*/
private String dept;
}

View File

@@ -0,0 +1,40 @@
package com.klp.system.domain.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.klp.common.core.domain.BaseEntity;
import lombok.Data;
/**
* 文件评论视图对象 sys_file_comment
*
* @author klp
* @date 2026-07-04
*/
@Data
@ExcelIgnoreUnannotated
public class SysFileCommentVo extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
private Long commentId;
/**
* 关联文件ID
*/
private Long fileId;
/**
* 评论内容
*/
private String content;
/**
* 所属部门
*/
private String dept;
}

View File

@@ -80,5 +80,10 @@ public class SysFileVo extends BaseEntity {
@ExcelProperty(value = "备注")
private String remark;
/**
* 浏览次数
*/
@ExcelProperty(value = "浏览次数")
private Long viewCount;
}

View File

@@ -0,0 +1,15 @@
package com.klp.system.mapper;
import com.klp.system.domain.SysFileComment;
import com.klp.system.domain.vo.SysFileCommentVo;
import com.klp.common.core.mapper.BaseMapperPlus;
/**
* 文件评论Mapper接口
*
* @author klp
* @date 2026-07-04
*/
public interface SysFileCommentMapper extends BaseMapperPlus<SysFileCommentMapper, SysFileComment, SysFileCommentVo> {
}

View File

@@ -0,0 +1,25 @@
package com.klp.system.service;
import com.klp.system.domain.vo.SysFileCommentVo;
import com.klp.system.domain.bo.SysFileCommentBo;
import java.util.List;
/**
* 文件评论Service接口
*
* @author klp
* @date 2026-07-04
*/
public interface ISysFileCommentService {
/**
* 查询文件评论列表
*/
List<SysFileCommentVo> queryListByFileId(Long fileId);
/**
* 新增文件评论
*/
Boolean insertByBo(SysFileCommentBo bo);
}

View File

@@ -51,4 +51,9 @@ public interface ISysFileService {
* 查询与我相关的文件(私有文件且当前用户在可见用户列表中)
*/
TableDataInfo<SysFileVo> queryPageListRelatedToMe(SysFileBo bo, PageQuery pageQuery);
/**
* 文件浏览次数 +1
*/
void incrementViewCount(Long fileId);
}

View File

@@ -0,0 +1,50 @@
package com.klp.system.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.klp.common.helper.LoginHelper;
import com.klp.system.domain.bo.SysFileCommentBo;
import com.klp.system.domain.vo.SysFileCommentVo;
import com.klp.system.domain.SysFileComment;
import com.klp.system.mapper.SysFileCommentMapper;
import com.klp.system.service.ISysFileCommentService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 文件评论Service业务层处理
*
* @author klp
* @date 2026-07-04
*/
@RequiredArgsConstructor
@Service
public class SysFileCommentServiceImpl implements ISysFileCommentService {
private final SysFileCommentMapper baseMapper;
/**
* 查询文件评论列表(按时间升序)
*/
@Override
public List<SysFileCommentVo> queryListByFileId(Long fileId) {
LambdaQueryWrapper<SysFileComment> lqw = Wrappers.lambdaQuery();
lqw.eq(SysFileComment::getFileId, fileId);
lqw.orderByAsc(SysFileComment::getCreateTime);
return baseMapper.selectVoList(lqw);
}
/**
* 新增文件评论
*/
@Override
public Boolean insertByBo(SysFileCommentBo bo) {
SysFileComment add = BeanUtil.toBean(bo, SysFileComment.class);
// 自动填充部门和评论人
add.setDept(LoginHelper.getLoginUser().getDeptName());
return baseMapper.insert(add) > 0;
}
}

View File

@@ -5,6 +5,7 @@ import com.klp.common.core.page.TableDataInfo;
import com.klp.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.klp.common.helper.LoginHelper;
import com.klp.common.utils.StringUtils;
@@ -124,6 +125,16 @@ public class SysFileServiceImpl implements ISysFileService {
return TableDataInfo.build(result);
}
/**
* 文件浏览次数 +1
*/
@Override
public void incrementViewCount(Long fileId) {
baseMapper.update(null, Wrappers.<SysFile>lambdaUpdate()
.setSql("view_count = view_count + 1")
.eq(SysFile::getFileId, fileId));
}
/**
* 保存前的数据校验
*/

View File

@@ -0,0 +1,16 @@
<?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.klp.system.mapper.SysFileCommentMapper">
<resultMap type="com.klp.system.domain.SysFileComment" id="SysFileCommentResult">
<result property="commentId" column="comment_id"/>
<result property="fileId" column="file_id"/>
<result property="content" column="content"/>
<result property="dept" column="dept"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
</resultMap>
</mapper>

View File

@@ -15,6 +15,7 @@
<result property="fileType" column="file_type"/>
<result property="scopeType" column="scope_type"/>
<result property="remark" column="remark"/>
<result property="viewCount" column="view_count"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>

View File

@@ -52,6 +52,14 @@ export function exportFile(query) {
})
}
// 文件浏览次数 +1
export function incrementView(fileId) {
return request({
url: '/system/file/incrementView/' + fileId,
method: 'put'
})
}
// 查询与我相关的文件
export function listRelatedToMe(query) {
return request({

View File

@@ -0,0 +1,18 @@
import request from '@/utils/request'
// 查询文件评论列表
export function listFileComment(fileId) {
return request({
url: '/system/file/comment/list/' + fileId,
method: 'get'
})
}
// 新增文件评论
export function addFileComment(data) {
return request({
url: '/system/file/comment',
method: 'post',
data: data
})
}

View File

@@ -28,6 +28,10 @@
</el-col>
</el-row>
<!-- 左右分栏布局可拖拽调节宽度 -->
<div class="file-layout" ref="layoutContainer">
<!-- 左侧文件列表 -->
<div class="file-left" :style="{ width: leftPanelWidth }">
<!-- 搜索栏 -->
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
<el-form-item label="文件名称" prop="fileName">
@@ -135,61 +139,22 @@
</el-row>
<!-- 文件列表表格 -->
<KLPTable v-loading="loading" :data="fileList" @selection-change="handleSelectionChange">
<KLPTable v-loading="loading" :data="fileList" @selection-change="handleSelectionChange" highlight-current-row @row-click="handleRowClick">
<el-table-column type="selection" width="55" align="center" v-if="activeTab === 'my' || activeTab === 'all'" />
<el-table-column label="文件名称" align="center" prop="fileName" :show-overflow-tooltip="true">
<template slot-scope="scope">
<el-link type="primary" @click="handlePreview(scope.row)">{{ scope.row.fileName }}</el-link>
</template>
</el-table-column>
<el-table-column label="文件类型" align="center" prop="fileType">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_file_type" :value="scope.row.fileType"/>
</template>
</el-table-column>
<el-table-column label="文件大小" align="center" prop="fileSize">
<template slot-scope="scope">
{{ formatFileSize(scope.row.fileSize) }}
</template>
</el-table-column>
<el-table-column label="订单编号" align="center" prop="orderNo" :show-overflow-tooltip="true" />
<el-table-column label="所属部门" align="center" prop="dept" :show-overflow-tooltip="true" />
<el-table-column label="可见范围" align="center" prop="scopeType">
<template slot-scope="scope">
<el-tag :type="scope.row.scopeType === 1 ? 'success' : 'warning'" size="small">
{{ scope.row.scopeType === 1 ? '公开' : '私有' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="上传人" align="center" prop="createBy" />
<el-table-column label="上传时间" align="center" prop="createTime" width="160">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column label="操作" align="center" width="220" class-name="small-padding fixed-width">
<el-table-column label="浏览次数" align="center" width="90">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handlePreview(scope.row)"
>预览</el-button>
<el-button
v-if="canEdit(scope.row)"
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
>编辑</el-button>
<el-button
v-if="canEdit(scope.row)"
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
>删除</el-button>
<span>{{ scope.row.viewCount != null ? scope.row.viewCount : 0 }}</span>
</template>
</el-table-column>
</KLPTable>
@@ -201,6 +166,76 @@
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</div>
<!-- 拖拽分隔条 -->
<div class="resize-handle" @mousedown="startResize" :class="{ dragging: isDragging }"></div>
<!-- 右侧文件预览 -->
<div class="file-right">
<div class="preview-panel">
<div v-if="!selectedFile" class="preview-empty">
<el-empty description="请选择左侧文件进行预览" />
</div>
<template v-else>
<div class="preview-header">
<span class="preview-filename" :title="selectedFile.fileName">{{ selectedFile.fileName }}</span>
<div>
<el-button size="mini" type="text" @click="handlePreview(selectedFile)">预览</el-button>
<el-button size="mini" plain icon="el-icon-info" @click="handleShowInfo(selectedFile)">查看</el-button>
<el-button size="mini" type="primary" icon="el-icon-download" @click="downloadFile(selectedFile)">下载</el-button>
<el-button v-if="canEdit(selectedFile)" size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(selectedFile)">编辑</el-button>
<el-button v-if="canEdit(selectedFile)" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(selectedFile)">删除</el-button>
</div>
</div>
<div class="preview-meta">
<span>{{ formatFileSize(selectedFile.fileSize) }}</span>
<dict-tag :options="dict.type.sys_file_type" :value="selectedFile.fileType" />
<span>{{ selectedFile.createBy }}</span>
<span>{{ parseTime(selectedFile.createTime) }}</span>
</div>
<div class="preview-comment">
<div class="comment-bar" @click="commentExpanded = !commentExpanded">
<span><i class="el-icon-chat-dot-round"></i> 评论 ({{ comments.length }})</span>
<span class="comment-toggle">{{ commentExpanded ? '收起' : '展开' }} <i :class="commentExpanded ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i></span>
</div>
<div v-show="commentExpanded" class="comment-body">
<div v-if="commentLoading" class="comment-hint"><i class="el-icon-loading"></i> 加载中...</div>
<div v-else-if="comments.length === 0" class="comment-hint comment-empty">暂无评论</div>
<div v-else class="comment-list">
<div v-for="item in comments" :key="item.commentId" class="comment-item">
<div class="comment-meta">
<span class="comment-dept">{{ item.dept }}</span>
<span class="comment-user">{{ item.createBy }}</span>
<span class="comment-time">{{ parseTime(item.createTime) }}</span>
</div>
<div class="comment-text">{{ item.content }}</div>
</div>
</div>
<div class="comment-input-area">
<el-input
v-model="commentInput"
type="textarea"
:rows="2"
placeholder="输入评论..."
resize="none"
/>
<el-button type="primary" size="mini" @click="handleAddComment" :disabled="!commentInput.trim()" style="margin-top: 6px;">发送</el-button>
</div>
</div>
</div>
<div class="preview-content">
<ImagePreview v-if="fileTypeCategory === 'image'" :src="selectedFile.filePath" />
<PdfPreview v-else-if="fileTypeCategory === 'pdf'" :src="selectedFile.filePath" />
<DocxPreview v-else-if="fileTypeCategory === 'docx'" :src="selectedFile.filePath" />
<XlsxPreview v-else-if="fileTypeCategory === 'xlsx'" :src="selectedFile.filePath" />
<XlsPreview v-else-if="fileTypeCategory === 'xls'" :src="selectedFile.filePath" />
<div v-else class="preview-not-supported">
<el-empty description="暂不支持预览此文件类型" />
</div>
</div>
</template>
</div>
</div>
</div>
<!-- 上传/编辑对话框 -->
<el-dialog :title="dialogTitle" :visible.sync="open" width="650px" append-to-body @close="handleDialogClose">
@@ -265,45 +300,53 @@
</div>
</el-dialog>
<!-- 文件预览对话框 -->
<el-dialog :title="previewTitle" :visible.sync="previewVisible" width="80%" append-to-body>
<div v-if="previewFile" class="file-preview-container">
<el-descriptions :column="2" border>
<el-descriptions-item label="文件名称">{{ previewFile.fileName }}</el-descriptions-item>
<!-- 文件详情对话框 -->
<el-dialog :title="infoTitle" :visible.sync="infoVisible" width="600px" append-to-body>
<el-descriptions v-if="infoFile" :column="2" border>
<el-descriptions-item label="文件名称">{{ infoFile.fileName }}</el-descriptions-item>
<el-descriptions-item label="文件类型">
<dict-tag :options="dict.type.sys_file_type" :value="previewFile.fileType"/>
<dict-tag :options="dict.type.sys_file_type" :value="infoFile.fileType"/>
</el-descriptions-item>
<el-descriptions-item label="文件大小">{{ formatFileSize(previewFile.fileSize) }}</el-descriptions-item>
<el-descriptions-item label="文件后缀">{{ previewFile.suffix }}</el-descriptions-item>
<el-descriptions-item label="订单编号">{{ previewFile.orderNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="所属部门">{{ previewFile.dept || '-' }}</el-descriptions-item>
<el-descriptions-item label="文件大小">{{ formatFileSize(infoFile.fileSize) }}</el-descriptions-item>
<el-descriptions-item label="文件后缀">{{ infoFile.suffix || '-' }}</el-descriptions-item>
<el-descriptions-item label="浏览次数">{{ infoFile.viewCount != null ? infoFile.viewCount : 0 }} </el-descriptions-item>
<el-descriptions-item label="订单编号">{{ infoFile.orderNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="所属部门">{{ infoFile.dept || '-' }}</el-descriptions-item>
<el-descriptions-item label="可见范围">
<el-tag :type="previewFile.scopeType === 1 ? 'success' : 'warning'" size="small">
{{ previewFile.scopeType === 1 ? '公开' : '私有' }}
<el-tag :type="infoFile.scopeType === 1 ? 'success' : 'warning'" size="small">
{{ infoFile.scopeType === 1 ? '公开' : '私有' }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="上传人">{{ previewFile.createBy }}</el-descriptions-item>
<el-descriptions-item label="上传时间">{{ parseTime(previewFile.createTime) }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ previewFile.remark || '-' }}</el-descriptions-item>
<el-descriptions-item label="上传人">{{ infoFile.createBy }}</el-descriptions-item>
<el-descriptions-item label="上传时间">{{ parseTime(infoFile.createTime) }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{ infoFile.remark || '-' }}</el-descriptions-item>
</el-descriptions>
<div v-if="previewFile.filePath" style="margin-top: 16px;">
<el-button type="primary" size="small" @click="downloadFile(previewFile)">下载文件</el-button>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
import { listFile, getFile, addFile, updateFile, delFile, exportFile, listVisibleUser, addVisibleUser, delVisibleUser, listVisibleUserByFileId, listRelatedToMe } from '@/api/system/file'
import { listFile, getFile, addFile, updateFile, delFile, exportFile, listVisibleUser, addVisibleUser, delVisibleUser, listVisibleUserByFileId, listRelatedToMe, incrementView } from '@/api/system/file'
import { listFileComment, addFileComment } from '@/api/system/fileComment'
import { getToken } from '@/utils/auth'
import UserSelect from '@/components/KLPService/UserSelect/index'
import ImagePreview from '@/components/FilePreview/preview/image/index.vue'
import PdfPreview from '@/components/FilePreview/preview/pdf/index.vue'
import DocxPreview from '@/components/FilePreview/preview/docx/index.vue'
import XlsxPreview from '@/components/FilePreview/preview/xlsx/index.vue'
import XlsPreview from '@/components/FilePreview/preview/xls/index.vue'
export default {
name: 'SysFile',
dicts: ['sys_file_type'],
components: {
UserSelect
UserSelect,
ImagePreview,
PdfPreview,
DocxPreview,
XlsxPreview,
XlsPreview
},
data() {
return {
@@ -379,15 +422,52 @@ export default {
Authorization: 'Bearer ' + getToken()
},
uploadFileList: [],
// 预览
previewVisible: false,
previewTitle: '',
previewFile: null
// 选中的文件(右侧预览
selectedFile: null,
// 文件详情弹窗
infoVisible: false,
infoTitle: '',
infoFile: null,
// 评论
comments: [],
commentExpanded: false,
commentInput: '',
commentLoading: false,
// 拖拽调节宽度
leftPanelWidth: '40%',
isDragging: false,
startX: 0,
startLeftWidth: 60
}
},
computed: {
/** 根据文件后缀分类,决定右侧用哪个预览组件 */
fileTypeCategory() {
if (!this.selectedFile) return null
// 优先使用 suffix 字段,其次从 fileName 提取
const raw = this.selectedFile.suffix || this.selectedFile.fileName || ''
const ext = (raw.includes('.') ? raw.split('.').pop() : raw).toLowerCase()
if (['png', 'jpg', 'jpeg', 'bmp', 'webp'].includes(ext)) return 'image'
if (ext === 'pdf') return 'pdf'
if (ext === 'docx') return 'docx'
if (ext === 'xlsx') return 'xlsx'
if (ext === 'xls') return 'xls'
return 'other'
}
},
created() {
this.getList()
},
watch: {
selectedFile(val) {
if (val) {
this.loadComments()
} else {
this.comments = []
this.commentExpanded = false
}
}
},
methods: {
/** 切换tab */
handleTabClick(tab) {
@@ -492,6 +572,7 @@ export default {
/** 重置按钮 */
resetQuery() {
this.dateRange = []
this.selectedFile = null
this.resetForm('queryForm')
this.queryParams = {
pageNum: 1,
@@ -666,17 +747,74 @@ export default {
...this.queryParams
}, `file_${new Date().getTime()}.xlsx`)
},
/** 预览 */
/** 点击文件名查看预览 */
handlePreview(row) {
this.previewFile = row
this.previewTitle = '文件详情 - ' + row.fileName
this.previewVisible = true
this.selectedFile = row
incrementView(row.fileId)
},
/** 点击查看按钮弹出详情 */
handleShowInfo(row) {
this.infoFile = row
this.infoTitle = '文件详情 - ' + row.fileName
this.infoVisible = true
},
/** 点击行选中预览 */
handleRowClick(row) {
this.selectedFile = row
},
/** 下载文件 */
downloadFile(row) {
if (row.filePath) {
window.open(row.filePath, '_blank')
}
},
/** 加载评论列表 */
loadComments() {
this.commentLoading = true
listFileComment(this.selectedFile.fileId).then(res => {
this.comments = res.data || []
this.commentLoading = false
}).catch(() => {
this.commentLoading = false
})
},
/** 发送评论 */
handleAddComment() {
const content = this.commentInput.trim()
if (!content) return
addFileComment({ fileId: this.selectedFile.fileId, content }).then(() => {
this.commentInput = ''
this.loadComments()
})
},
/** 开始拖拽 */
startResize(e) {
this.isDragging = true
this.startX = e.clientX
this.startLeftWidth = parseFloat(this.leftPanelWidth)
document.addEventListener('mousemove', this.doResize)
document.addEventListener('mouseup', this.stopResize)
document.body.style.cursor = 'col-resize'
document.body.style.userSelect = 'none'
},
/** 拖拽中 */
doResize(e) {
if (!this.isDragging) return
const container = this.$refs.layoutContainer
const rect = container.getBoundingClientRect()
const deltaX = e.clientX - this.startX
let percent = this.startLeftWidth + (deltaX / rect.width) * 100
percent = Math.max(30, Math.min(80, percent))
this.leftPanelWidth = percent + '%'
},
/** 结束拖拽 */
stopResize() {
if (!this.isDragging) return
this.isDragging = false
document.removeEventListener('mousemove', this.doResize)
document.removeEventListener('mouseup', this.stopResize)
document.body.style.cursor = ''
document.body.style.userSelect = ''
}
}
}
@@ -684,16 +822,16 @@ export default {
<style scoped>
.stat-row {
margin-bottom: 16px;
margin-bottom: 8px;
}
.stat-card {
background: #fff;
border-radius: 8px;
padding: 20px;
border-radius: 6px;
padding: 10px 16px;
text-align: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border-left: 4px solid #409eff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
border-left: 3px solid #409eff;
}
.stat-card.public {
@@ -705,23 +843,225 @@ export default {
}
.stat-num {
font-size: 32px;
font-size: 22px;
font-weight: 700;
color: #303133;
line-height: 1.2;
}
.stat-label {
font-size: 14px;
font-size: 12px;
color: #909399;
margin-top: 8px;
}
.file-preview-container {
padding: 8px;
margin-top: 2px;
}
.upload-demo {
width: 100%;
}
/* 左右分栏布局 */
.file-layout {
display: flex;
align-items: flex-start;
gap: 0;
}
.file-left {
flex-shrink: 0;
}
.file-right {
flex: 1;
min-width: 300px;
}
/* 拖拽分隔条 */
.resize-handle {
width: 6px;
cursor: col-resize;
flex-shrink: 0;
align-self: stretch;
background: transparent;
position: relative;
transition: background-color 0.2s;
margin: 0 2px;
border-radius: 3px;
}
.resize-handle:hover,
.resize-handle.dragging {
background: #409eff;
}
.resize-handle::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 30px;
background: #dcdfe6;
border-radius: 1px;
transition: background-color 0.2s;
}
.resize-handle:hover::after,
.resize-handle.dragging::after {
background: #fff;
}
.preview-panel {
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
height: calc(100vh - 280px);
min-height: 450px;
display: flex;
flex-direction: column;
position: sticky;
top: 16px;
}
.preview-empty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.preview-header {
padding: 12px 16px;
border-bottom: 1px solid #ebeef5;
display: flex;
justify-content: space-between;
align-items: center;
}
.preview-filename {
font-size: 14px;
font-weight: 500;
color: #303133;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 12px;
}
.preview-meta {
padding: 8px 16px;
border-bottom: 1px solid #ebeef5;
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
font-size: 12px;
color: #909399;
}
.preview-comment {
border-bottom: 1px solid #ebeef5;
}
.comment-bar {
padding: 8px 16px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
font-size: 13px;
color: #606266;
user-select: none;
}
.comment-bar:hover {
background: #f5f7fa;
}
.comment-toggle {
color: #909399;
font-size: 12px;
}
.comment-body {
max-height: 260px;
overflow-y: auto;
}
.comment-hint {
padding: 16px;
text-align: center;
color: #909399;
font-size: 13px;
}
.comment-list {
padding: 4px 0;
}
.comment-item {
padding: 8px 16px;
border-bottom: 1px solid #f2f2f2;
}
.comment-item:last-child {
border-bottom: none;
}
.comment-meta {
display: flex;
gap: 8px;
align-items: center;
font-size: 12px;
margin-bottom: 4px;
}
.comment-dept {
color: #409eff;
font-weight: 500;
}
.comment-user {
color: #606266;
}
.comment-time {
color: #c0c4cc;
}
.comment-text {
font-size: 13px;
color: #303133;
line-height: 1.5;
word-break: break-all;
}
.comment-input-area {
padding: 8px 16px;
border-top: 1px solid #f2f2f2;
}
.preview-content {
flex: 1;
overflow: auto;
display: flex;
}
.preview-content > div {
width: 100%;
}
.preview-not-supported {
display: flex;
justify-content: center;
align-items: center;
height: 400px;
}
</style>
<!-- 表格选中行高亮 scoped穿透 el-table -->
<style>
.file-left .el-table .current-row > td {
background-color: #ecf5ff !important;
}
</style>