知识库数据隔离测试完成

This commit is contained in:
2025-11-08 13:42:33 +08:00
parent 38209a642d
commit 95919eb27b
13 changed files with 324 additions and 21 deletions

View File

@@ -102,6 +102,9 @@
<Logger name="org.xyzh.achievement.mapper" level="debug" additivity="false"> <Logger name="org.xyzh.achievement.mapper" level="debug" additivity="false">
<AppenderRef ref="Console"/> <AppenderRef ref="Console"/>
</Logger> </Logger>
<Logger name="org.xyzh.ai.mapper" level="debug" additivity="false">
<AppenderRef ref="Console"/>
</Logger>
<Logger name="org.xyzh.system.mapper" level="debug" additivity="false"> <Logger name="org.xyzh.system.mapper" level="debug" additivity="false">
<AppenderRef ref="Console"/> <AppenderRef ref="Console"/>
</Logger> </Logger>

View File

@@ -3,6 +3,7 @@ package org.xyzh.ai.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.dto.ai.TbAiKnowledge; import org.xyzh.common.dto.ai.TbAiKnowledge;
import org.xyzh.common.vo.UserDeptRoleVO; import org.xyzh.common.vo.UserDeptRoleVO;
@@ -48,7 +49,7 @@ public interface AiKnowledgeMapper extends BaseMapper<TbAiKnowledge> {
*/ */
List<TbAiKnowledge> selectKnowledgesPage( List<TbAiKnowledge> selectKnowledgesPage(
@Param("filter") TbAiKnowledge filter, @Param("filter") TbAiKnowledge filter,
@Param("pageParam") org.xyzh.common.core.page.PageParam pageParam, @Param("pageParam") PageParam pageParam,
@Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles @Param("userDeptRoles") List<UserDeptRoleVO> userDeptRoles
); );

View File

@@ -1,7 +1,5 @@
package org.xyzh.ai.service; package org.xyzh.ai.service;
import java.util.List;
/** /**
* @description AI知识库Redis管理服务接口 * @description AI知识库Redis管理服务接口
* @filename AiKnowledgeRedisService.java * @filename AiKnowledgeRedisService.java
@@ -21,5 +19,19 @@ public interface AiKnowledgeRedisService {
* 清除所有知识库配置 * 清除所有知识库配置
*/ */
void clearAllKnowledgeConfig(); void clearAllKnowledgeConfig();
/**
* 添加知识库到部门缓存
* @param deptId 部门ID
* @param difyDatasetId Dify知识库ID
*/
void addKnowledgeToDeptCache(String deptId, String difyDatasetId);
/**
* 从部门缓存移除知识库
* @param deptId 部门ID
* @param difyDatasetId Dify知识库ID
*/
void removeKnowledgeFromDeptCache(String deptId, String difyDatasetId);
} }

View File

@@ -122,13 +122,9 @@ public class AiKnowledgeRedisServiceImpl implements AiKnowledgeRedisService, Com
} }
// 解析部门路径,例如:/1/3/5/ -> [1, 3, 5] // 解析部门路径,例如:/1/3/5/ -> [1, 3, 5]
String[] deptIds = deptPath.split("/"); Set<String> deptIdList = new HashSet<>(Arrays.asList(deptPath.split("/")));
List<String> deptIdList = new ArrayList<>(); deptIdList.add("root_department");
for (String deptId : deptIds) {
if (deptId != null && !deptId.isEmpty()) {
deptIdList.add(deptId);
}
}
if (deptIdList.isEmpty()) { if (deptIdList.isEmpty()) {
log.warn("解析部门路径失败: {}", deptPath); log.warn("解析部门路径失败: {}", deptPath);
@@ -146,6 +142,7 @@ public class AiKnowledgeRedisServiceImpl implements AiKnowledgeRedisService, Com
} }
} }
List<String> resultList = new ArrayList<>(knowledgeIdSet); List<String> resultList = new ArrayList<>(knowledgeIdSet);
log.info("根据部门路径 {} 获取到 {} 个知识库(已去重)", deptPath, resultList.size()); log.info("根据部门路径 {} 获取到 {} 个知识库(已去重)", deptPath, resultList.size());
return resultList; return resultList;
@@ -166,5 +163,81 @@ public class AiKnowledgeRedisServiceImpl implements AiKnowledgeRedisService, Com
log.error("清除所有知识库配置失败", e); log.error("清除所有知识库配置失败", e);
} }
} }
@Override
@SuppressWarnings("unchecked")
public void addKnowledgeToDeptCache(String deptId, String difyDatasetId) {
try {
if (deptId == null || deptId.isEmpty()) {
log.warn("部门ID为空无法添加知识库到缓存");
return;
}
if (difyDatasetId == null || difyDatasetId.isEmpty()) {
log.warn("Dify知识库ID为空无法添加到缓存");
return;
}
String key = REDIS_KEY_DEPT_KNOWLEDGE + deptId;
// 获取现有的知识库列表
List<String> knowledgeIds = (List<String>) redisTemplate.opsForValue().get(key);
if (knowledgeIds == null) {
knowledgeIds = new ArrayList<>();
}
// 避免重复添加
if (!knowledgeIds.contains(difyDatasetId)) {
knowledgeIds.add(difyDatasetId);
// 更新到Redis
redisTemplate.opsForValue().set(key, knowledgeIds, REDIS_EXPIRE_DAYS, TimeUnit.DAYS);
log.info("已添加知识库到部门缓存: deptId={}, difyDatasetId={}", deptId, difyDatasetId);
} else {
log.debug("知识库已存在于部门缓存中: deptId={}, difyDatasetId={}", deptId, difyDatasetId);
}
} catch (Exception e) {
log.error("添加知识库到部门缓存失败: deptId={}, difyDatasetId={}", deptId, difyDatasetId, e);
}
}
@Override
@SuppressWarnings("unchecked")
public void removeKnowledgeFromDeptCache(String deptId, String difyDatasetId) {
try {
if (deptId == null || deptId.isEmpty()) {
log.warn("部门ID为空无法从缓存移除知识库");
return;
}
if (difyDatasetId == null || difyDatasetId.isEmpty()) {
log.warn("Dify知识库ID为空无法从缓存移除");
return;
}
String key = REDIS_KEY_DEPT_KNOWLEDGE + deptId;
// 获取现有的知识库列表
List<String> knowledgeIds = (List<String>) redisTemplate.opsForValue().get(key);
if (knowledgeIds == null || knowledgeIds.isEmpty()) {
log.debug("部门缓存为空,无需移除: deptId={}", deptId);
return;
}
// 移除知识库
if (knowledgeIds.remove(difyDatasetId)) {
// 更新到Redis
if (knowledgeIds.isEmpty()) {
// 如果列表为空删除整个key
redisTemplate.delete(key);
log.info("部门缓存已空已删除key: deptId={}", deptId);
} else {
redisTemplate.opsForValue().set(key, knowledgeIds, REDIS_EXPIRE_DAYS, TimeUnit.DAYS);
}
log.info("已从部门缓存移除知识库: deptId={}, difyDatasetId={}", deptId, difyDatasetId);
} else {
log.debug("知识库不存在于部门缓存中: deptId={}, difyDatasetId={}", deptId, difyDatasetId);
}
} catch (Exception e) {
log.error("从部门缓存移除知识库失败: deptId={}, difyDatasetId={}", deptId, difyDatasetId, e);
}
}
} }

View File

@@ -62,6 +62,9 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
@Autowired @Autowired
private ResourcePermissionService resourcePermissionService; private ResourcePermissionService resourcePermissionService;
@Autowired
private org.xyzh.ai.service.AiKnowledgeRedisService knowledgeRedisService;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public ResultDomain<TbAiKnowledge> createKnowledge( public ResultDomain<TbAiKnowledge> createKnowledge(
@@ -238,6 +241,15 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
// 权限创建失败不影响知识库创建,记录日志即可 // 权限创建失败不影响知识库创建,记录日志即可
} }
// 6. 更新Redis缓存
try {
knowledgeRedisService.addKnowledgeToDeptCache(deptId, difyDatasetId);
log.info("已添加知识库到Redis缓存: deptId={}, difyDatasetId={}", deptId, difyDatasetId);
} catch (Exception e) {
log.error("更新Redis缓存失败", e);
// 缓存更新失败不影响知识库创建,记录日志即可
}
log.info("知识库创建成功: {} - {}", knowledge.getID(), knowledge.getTitle()); log.info("知识库创建成功: {} - {}", knowledge.getID(), knowledge.getTitle());
resultDomain.success("知识库创建成功", knowledge); resultDomain.success("知识库创建成功", knowledge);
return resultDomain; return resultDomain;
@@ -484,6 +496,17 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
int rows = knowledgeMapper.deleteKnowledge(deleteEntity); int rows = knowledgeMapper.deleteKnowledge(deleteEntity);
if (rows > 0) { if (rows > 0) {
// 6. 更新Redis缓存
try {
if (StringUtils.hasText(existing.getCreatorDept()) && StringUtils.hasText(existing.getDifyDatasetId())) {
knowledgeRedisService.removeKnowledgeFromDeptCache(existing.getCreatorDept(), existing.getDifyDatasetId());
log.info("已从Redis缓存移除知识库: deptId={}, difyDatasetId={}", existing.getCreatorDept(), existing.getDifyDatasetId());
}
} catch (Exception e) {
log.error("更新Redis缓存失败", e);
// 缓存更新失败不影响知识库删除,记录日志即可
}
log.info("知识库删除成功: {} - {}", knowledgeId, existing.getTitle()); log.info("知识库删除成功: {} - {}", knowledgeId, existing.getTitle());
resultDomain.success("知识库删除成功", true); resultDomain.success("知识库删除成功", true);
return resultDomain; return resultDomain;
@@ -564,8 +587,8 @@ public class AiKnowledgeServiceImpl implements AiKnowledgeService {
PageParam resultPageParam = new PageParam(pageParam.getPageNumber(), pageParam.getPageSize()); PageParam resultPageParam = new PageParam(pageParam.getPageNumber(), pageParam.getPageSize());
resultPageParam.setTotalElements(total); resultPageParam.setTotalElements(total);
resultPageParam.setTotalPages((int) Math.ceil((double) total / pageParam.getPageSize())); resultPageParam.setTotalPages((int) Math.ceil((double) total / pageParam.getPageSize()));
PageDomain<TbAiKnowledge> pageDomain = new PageDomain<>(resultPageParam, knowledges);
resultDomain.success("查询成功", knowledges); resultDomain.success("查询成功", pageDomain);
return resultDomain; return resultDomain;
} catch (Exception e) { } catch (Exception e) {

View File

@@ -35,6 +35,11 @@
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/> <result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
<result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/> <result column="delete_time" property="deleteTime" jdbcType="TIMESTAMP"/>
<result column="deleted" property="deleted" jdbcType="BOOLEAN"/> <result column="deleted" property="deleted" jdbcType="BOOLEAN"/>
<!-- 权限字段 -->
<result column="permission_type" property="permissionType" jdbcType="VARCHAR"/>
<result column="can_read" property="canRead" jdbcType="BOOLEAN"/>
<result column="can_write" property="canWrite" jdbcType="BOOLEAN"/>
<result column="can_delete" property="canDelete" jdbcType="BOOLEAN"/>
</resultMap> </resultMap>
<!-- 基础字段 --> <!-- 基础字段 -->
@@ -106,10 +111,27 @@
<!-- selectAiKnowledges带权限过滤 --> <!-- selectAiKnowledges带权限过滤 -->
<select id="selectAiKnowledges" resultMap="BaseResultMap"> <select id="selectAiKnowledges" resultMap="BaseResultMap">
SELECT DISTINCT k.* SELECT
k.*,
CASE
WHEN MAX(CASE WHEN rp.dept_id IS NULL AND rp.role_id IS NULL THEN 1 ELSE 0 END) = 1 THEN 'all'
WHEN MAX(CASE WHEN rp.dept_id IS NOT NULL AND rp.role_id IS NULL THEN 1 ELSE 0 END) = 1 THEN 'dept'
WHEN MAX(CASE WHEN rp.dept_id IS NULL AND rp.role_id IS NOT NULL THEN 1 ELSE 0 END) = 1 THEN 'role'
ELSE 'custom'
END as permission_type,
MAX(rp.can_read) as can_read,
MAX(rp.can_write) as can_write,
MAX(rp.can_execute) as can_delete
FROM tb_ai_knowledge k FROM tb_ai_knowledge k
<include refid="Permission_Filter"/> <include refid="Permission_Filter"/>
<include refid="Filter_Clause"/> <include refid="Filter_Clause"/>
GROUP BY k.id, k.title, k.avatar, k.description, k.content, k.source_type, k.source_id,
k.file_name, k.file_path, k.category, k.tags, k.dify_dataset_id,
k.dify_indexing_technique, k.embedding_model, k.embedding_model_provider,
k.rerank_model, k.rerank_model_provider, k.reranking_enable,
k.retrieval_top_k, k.retrieval_score_threshold, k.vector_id,
k.document_count, k.total_chunks, k.status, k.creator, k.creator_dept,
k.updater, k.create_time, k.update_time, k.delete_time, k.deleted
ORDER BY k.create_time DESC ORDER BY k.create_time DESC
</select> </select>
@@ -255,10 +277,27 @@
<!-- selectKnowledgesPage分页查询知识库带权限过滤 --> <!-- selectKnowledgesPage分页查询知识库带权限过滤 -->
<select id="selectKnowledgesPage" resultMap="BaseResultMap"> <select id="selectKnowledgesPage" resultMap="BaseResultMap">
SELECT DISTINCT k.* SELECT
k.*,
CASE
WHEN MAX(CASE WHEN rp.dept_id IS NULL AND rp.role_id IS NULL THEN 1 ELSE 0 END) = 1 THEN 'all'
WHEN MAX(CASE WHEN rp.dept_id IS NOT NULL AND rp.role_id IS NULL THEN 1 ELSE 0 END) = 1 THEN 'dept'
WHEN MAX(CASE WHEN rp.dept_id IS NULL AND rp.role_id IS NOT NULL THEN 1 ELSE 0 END) = 1 THEN 'role'
ELSE 'custom'
END as permission_type,
MAX(rp.can_read) as can_read,
MAX(rp.can_write) as can_write,
MAX(rp.can_execute) as can_delete
FROM tb_ai_knowledge k FROM tb_ai_knowledge k
<include refid="Permission_Filter"/> <include refid="Permission_Filter"/>
<include refid="Filter_Clause"/> <include refid="Filter_Clause"/>
GROUP BY k.id, k.title, k.avatar, k.description, k.content, k.source_type, k.source_id,
k.file_name, k.file_path, k.category, k.tags, k.dify_dataset_id,
k.dify_indexing_technique, k.embedding_model, k.embedding_model_provider,
k.rerank_model, k.rerank_model_provider, k.reranking_enable,
k.retrieval_top_k, k.retrieval_score_threshold, k.vector_id,
k.document_count, k.total_chunks, k.status, k.creator, k.creator_dept,
k.updater, k.create_time, k.update_time, k.delete_time, k.deleted
ORDER BY k.create_time DESC ORDER BY k.create_time DESC
LIMIT #{pageParam.offset}, #{pageParam.pageSize} LIMIT #{pageParam.offset}, #{pageParam.pageSize}
</select> </select>

View File

@@ -143,6 +143,28 @@ public class TbAiKnowledge extends BaseDTO {
*/ */
private String updater; private String updater;
// ========== 权限相关字段(不映射到数据库) ==========
/**
* @description 权限类型all/dept/role/custom
*/
private transient String permissionType;
/**
* @description 是否可读
*/
private transient Boolean canRead;
/**
* @description 是否可写
*/
private transient Boolean canWrite;
/**
* @description 是否可删除
*/
private transient Boolean canDelete;
public String getTitle() { public String getTitle() {
return title; return title;
} }
@@ -351,6 +373,38 @@ public class TbAiKnowledge extends BaseDTO {
this.retrievalScoreThreshold = retrievalScoreThreshold; this.retrievalScoreThreshold = retrievalScoreThreshold;
} }
public String getPermissionType() {
return permissionType;
}
public void setPermissionType(String permissionType) {
this.permissionType = permissionType;
}
public Boolean getCanRead() {
return canRead;
}
public void setCanRead(Boolean canRead) {
this.canRead = canRead;
}
public Boolean getCanWrite() {
return canWrite;
}
public void setCanWrite(Boolean canWrite) {
this.canWrite = canWrite;
}
public Boolean getCanDelete() {
return canDelete;
}
public void setCanDelete(Boolean canDelete) {
this.canDelete = canDelete;
}
@Override @Override
public String toString() { public String toString() {
return "TbAiKnowledge{" + return "TbAiKnowledge{" +

View File

@@ -74,7 +74,8 @@
d.description AS dept_description, d.description AS dept_description,
dr.role_id, dr.role_id,
r.name AS role_name, r.name AS role_name,
r.description AS role_description r.description AS role_description,
d.parent_id
FROM tb_sys_dept_role dr FROM tb_sys_dept_role dr
LEFT JOIN tb_sys_dept d ON dr.dept_id = d.dept_id AND d.deleted = 0 LEFT JOIN tb_sys_dept d ON dr.dept_id = d.dept_id AND d.deleted = 0
LEFT JOIN tb_sys_role r ON dr.role_id = r.role_id AND r.deleted = 0 LEFT JOIN tb_sys_role r ON dr.role_id = r.role_id AND r.deleted = 0

View File

@@ -6,6 +6,11 @@
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="/schoolNewsWeb/favicon.ico"> <link rel="icon" href="/schoolNewsWeb/favicon.ico">
<title>校园新闻管理系统</title> <title>校园新闻管理系统</title>
<style>
body{
height: 100vh;
}
</style>
</head> </head>
<body> <body>
<noscript> <noscript>

View File

@@ -99,6 +99,14 @@ export interface AiKnowledge extends BaseDTO {
creatorDept?: string; creatorDept?: string;
/** 更新者 */ /** 更新者 */
updater?: string; updater?: string;
/** 是否可读 */
canRead?: boolean;
/** 是否可写(修改知识库和文档) */
canWrite?: boolean;
/** 是否可删除(删除知识库和文档) */
canDelete?: boolean;
/** 权限类型all/department/role等 */
permissionType?: string;
} }
/** /**

View File

@@ -15,6 +15,8 @@
type="success" type="success"
@click="showAddSegmentDialog = true" @click="showAddSegmentDialog = true"
size="default" size="default"
:disabled="!props.canWrite"
:title="props.canWrite ? '添加分段' : '无添加权限'"
> >
添加分段 添加分段
</el-button> </el-button>
@@ -46,8 +48,10 @@
:model-value="segment.enabled" :model-value="segment.enabled"
:active-text="segment.enabled ? '已启用' : '已禁用'" :active-text="segment.enabled ? '已启用' : '已禁用'"
:loading="segment._switching" :loading="segment._switching"
:disabled="!props.canWrite"
@change="handleToggleEnabled(segment, $event)" @change="handleToggleEnabled(segment, $event)"
style="--el-switch-on-color: #67C23A; margin-right: 12px;" style="--el-switch-on-color: #67C23A; margin-right: 12px;"
:title="props.canWrite ? '' : '无修改权限'"
/> />
<el-tag <el-tag
:type="getStatusType(segment.status)" :type="getStatusType(segment.status)"
@@ -62,6 +66,8 @@
type="primary" type="primary"
size="small" size="small"
@click="startEdit(segment)" @click="startEdit(segment)"
:disabled="!props.canWrite"
:title="props.canWrite ? '编辑分段' : '无编辑权限'"
> >
编辑 编辑
</el-button> </el-button>
@@ -72,6 +78,8 @@
size="small" size="small"
@click="handleDeleteSegment(segment)" @click="handleDeleteSegment(segment)"
:loading="segment._deleting" :loading="segment._deleting"
:disabled="!props.canDelete"
:title="props.canDelete ? '删除分段' : '无删除权限'"
> >
删除 删除
</el-button> </el-button>
@@ -211,6 +219,10 @@ interface Props {
datasetId: string; datasetId: string;
/** Dify文档ID */ /** Dify文档ID */
documentId: string; documentId: string;
/** 是否可写(修改分段) */
canWrite?: boolean;
/** 是否可删除(删除分段) */
canDelete?: boolean;
} }
const props = defineProps<Props>(); const props = defineProps<Props>();

View File

@@ -41,13 +41,30 @@
<!-- 操作按钮 --> <!-- 操作按钮 -->
<div class="knowledge-actions" @click.stop> <div class="knowledge-actions" @click.stop>
<el-button size="small" @click="handleEdit">编辑</el-button> <el-button
<el-button size="small" type="danger" @click="handleDelete">删除</el-button> size="small"
@click="handleEdit"
:disabled="!hasWritePermission"
:title="hasWritePermission ? '编辑知识库' : '无编辑权限'"
>
编辑
</el-button>
<el-button
size="small"
type="danger"
@click="handleDelete"
:disabled="!hasDeletePermission"
:title="hasDeletePermission ? '删除知识库' : '无删除权限'"
>
删除
</el-button>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue';
import { useStore } from 'vuex';
import type { AiKnowledge } from '@/types/ai'; import type { AiKnowledge } from '@/types/ai';
import { FILE_DOWNLOAD_URL } from '@/config'; import { FILE_DOWNLOAD_URL } from '@/config';
@@ -62,6 +79,19 @@ interface Props {
const props = defineProps<Props>(); const props = defineProps<Props>();
const emit = defineEmits(['click', 'edit', 'delete', 'sync']); const emit = defineEmits(['click', 'edit', 'delete', 'sync']);
const store = useStore();
const currentUserId = computed(() => store.getters['auth/user']?.id);
// 判断是否有写权限:是创建者 或 有canWrite权限
const hasWritePermission = computed(() => {
return props.knowledge.creator === currentUserId.value || props.knowledge.canWrite;
});
// 判断是否有删除权限:是创建者 或 有canDelete权限
const hasDeletePermission = computed(() => {
return props.knowledge.creator === currentUserId.value || props.knowledge.canDelete;
});
function handleClick() { function handleClick() {
emit('click', props.knowledge); emit('click', props.knowledge);
} }

View File

@@ -7,11 +7,20 @@
返回列表 返回列表
</el-button> </el-button>
<div class="header-actions"> <div class="header-actions">
<el-button @click="handleEdit"> <el-button
@click="handleEdit"
:disabled="!hasWritePermission"
:title="hasWritePermission ? '编辑知识库' : '无编辑权限'"
>
<el-icon><Edit /></el-icon> <el-icon><Edit /></el-icon>
编辑 编辑
</el-button> </el-button>
<el-button type="primary" @click="handleUpload"> <el-button
type="primary"
@click="handleUpload"
:disabled="!hasWritePermission"
:title="hasWritePermission ? '上传文档' : '无上传权限'"
>
<el-icon><Upload /></el-icon> <el-icon><Upload /></el-icon>
上传文档 上传文档
</el-button> </el-button>
@@ -35,6 +44,15 @@
<el-tag :type="knowledge.status === 1 ? 'success' : 'info'" size="small"> <el-tag :type="knowledge.status === 1 ? 'success' : 'info'" size="small">
{{ knowledge.status === 1 ? '已启用' : '已禁用' }} {{ knowledge.status === 1 ? '已启用' : '已禁用' }}
</el-tag> </el-tag>
<el-tag v-if="knowledge.canRead" type="success" size="small" effect="plain">
可读
</el-tag>
<el-tag v-if="knowledge.canWrite" type="warning" size="small" effect="plain">
可写
</el-tag>
<el-tag v-if="knowledge.canDelete" type="danger" size="small" effect="plain">
可删除
</el-tag>
<span class="meta-item"> <span class="meta-item">
<el-icon><Clock /></el-icon> <el-icon><Clock /></el-icon>
{{ formatDate(knowledge.createTime) }} {{ formatDate(knowledge.createTime) }}
@@ -149,8 +167,10 @@
:model-value="row.enabled" :model-value="row.enabled"
:active-text="row.enabled ? '已启用' : '已禁用'" :active-text="row.enabled ? '已启用' : '已禁用'"
:loading="row._switching" :loading="row._switching"
:disabled="!hasWritePermission"
@change="handleToggleEnabled(row, $event)" @change="handleToggleEnabled(row, $event)"
style="--el-switch-on-color: #67C23A; margin-right: 12px;" style="--el-switch-on-color: #67C23A; margin-right: 12px;"
:title="hasWritePermission ? '' : '无修改权限'"
/> />
<el-button link type="primary" @click="handleViewSegments(row)"> <el-button link type="primary" @click="handleViewSegments(row)">
查看分段 查看分段
@@ -158,7 +178,13 @@
<el-button link type="primary" @click="handleDownload(row)"> <el-button link type="primary" @click="handleDownload(row)">
下载 下载
</el-button> </el-button>
<el-button link type="danger" @click="handleDeleteDocument(row)"> <el-button
link
type="danger"
@click="handleDeleteDocument(row)"
:disabled="!hasDeletePermission"
:title="hasDeletePermission ? '删除文档' : '无删除权限'"
>
删除 删除
</el-button> </el-button>
</template> </template>
@@ -207,6 +233,8 @@
:model-value="segmentDialogVisible" :model-value="segmentDialogVisible"
:dataset-id="props.knowledge.difyDatasetId" :dataset-id="props.knowledge.difyDatasetId"
:document-id="selectedDocument.difyDocumentId" :document-id="selectedDocument.difyDocumentId"
:can-write="hasWritePermission"
:can-delete="hasDeletePermission"
@update:model-value="segmentDialogVisible = $event" @update:model-value="segmentDialogVisible = $event"
/> />
</div> </div>
@@ -214,6 +242,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch } from 'vue'; import { ref, computed, watch } from 'vue';
import { useStore } from 'vuex';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import type { UploadUserFile, UploadInstance } from 'element-plus'; import type { UploadUserFile, UploadInstance } from 'element-plus';
import { import {
@@ -247,6 +276,19 @@ const emit = defineEmits<{
edit: [knowledge: AiKnowledge]; edit: [knowledge: AiKnowledge];
}>(); }>();
const store = useStore();
const currentUserId = computed(() => store.getters['auth/user']?.id);
// 判断是否有写权限:是创建者 或 有canWrite权限
const hasWritePermission = computed(() => {
return props.knowledge?.creator === currentUserId.value || props.knowledge?.canWrite;
});
// 判断是否有删除权限:是创建者 或 有canDelete权限
const hasDeletePermission = computed(() => {
return props.knowledge?.creator === currentUserId.value || props.knowledge?.canDelete;
});
// 数据状态 // 数据状态
const documents = ref<AiUploadFile[]>([]); const documents = ref<AiUploadFile[]>([]);
const loading = ref(false); const loading = ref(false);