fix:评论组件调试 权限模块前端页面开发

This commit is contained in:
liangdong
2026-01-09 19:23:04 +08:00
parent fc84d925d6
commit 0a54a7affb
17 changed files with 586 additions and 139 deletions

View File

@@ -34,3 +34,8 @@ export const addReplyComment = (data: addCommentProps) => {
export const deleteComment = (id: string) => { export const deleteComment = (id: string) => {
return request.delete(`/communicate/v1/comment/del/${id}`); return request.delete(`/communicate/v1/comment/del/${id}`);
} }
// 根据员工关键字查询员工信息
export const getEmployeeByKeyword = (params: Record<string, any>) => {
return request.get(`/auth/v1/employee`,params);
}

View File

@@ -0,0 +1,64 @@
import request from '@/request';
// 查询角色列表
export const getRoleList = (params: any) => {
return request.get('/auth/v1/backend/role', params);
}
// 获取角色详情
export const getRoleDetail = (id: string) => {
return request.get(`/auth/v1/backend/role/${id}`);
}
// 更新角色
export const updateRole = (data: any) => {
return request.put('/auth/v1/backend/role', data);
}
// 新增角色
export const addRole = (data: any) => {
return request.post('/auth/v1/backend/role', data);
}
// 删除角色
export const deleteRole = (id: string) => {
return request.delete(`/auth/v1/backend/role/${id}`);
}
// 删除用户角色
export const deleteUserRole = (userId:string,roleId: string) => {
return request.delete(`/auth/v1/backend/role/user/${userId}/role/${roleId}`);
}
// 添加用户角色
export const addUserRole = (userId:string,roleId: string) => {
return request.post(`/auth/v1/backend/role/user/${userId}/role/${roleId}`);
};
// 启用角色
export const enableRole = (id: string) => {
return request.put(`/auth/v1/backend/role/enable/${id}`);
}
// 禁用角色
export const disableRole = (id: string) => {
return request.put(`/auth/v1/backend/role/disable/${id}`);
}
/**------------------------角色权限相关---------------------------**/
// 保存角色权限
export const saveRolePermission = (data: any) => {
return request.post(`/auth/v1/backend/role/permission/save`, data);
}
// 查询角色权限
export const getRolePermission = (roleId: string) => {
return request.get(`/auth/v1/backend/role/${roleId}/permission`);
}
// 获取角色成员列表
export const getRoleMemberList = (roleId: string) => {
return request.get(`/auth/v1/backend/role/${roleId}/members`);
}

View File

@@ -8,7 +8,7 @@
header-row-class-name="header-row-name" header-row-class-name="header-row-name"
:height="tableHeight" :height="tableHeight"
> >
<template v-for="(col, index) in columns" :key="col.prop"> <template v-for="(col, index) in columns" :key="index">
<el-table-column <el-table-column
v-if="!col.slot && col.prop !== 'actions'" v-if="!col.slot && col.prop !== 'actions'"
v-bind="col" v-bind="col"
@@ -47,6 +47,7 @@
> >
<el-button <el-button
v-bind="getButtonProps(btn)" v-bind="getButtonProps(btn)"
:disabled="getButtonDisabled(btn, scope.row)"
@click="handleButtonClick(btn, scope.row)" @click="handleButtonClick(btn, scope.row)"
> >
{{ {{
@@ -58,15 +59,9 @@
</span> </span>
</template> </template>
<!-- 如果按钮超过maxButtons显示下拉菜单 --> <!-- 如果按钮超过maxButtons显示下拉菜单 (如果是移入移除展示按钮 不建议开启下拉菜单)-->
<el-dropdown <el-dropdown
v-if=" v-if="showDropdown && col.actions.length > (col.maxButtons || MAX_BUTTON_LENGTH) && hasDropdownPermission(col.actions.slice(col.maxButtons || MAX_BUTTON_LENGTH))"
col.actions.length >
(col.maxButtons || MAX_BUTTON_LENGTH) &&
hasDropdownPermission(
col.actions.slice(col.maxButtons || MAX_BUTTON_LENGTH)
)
"
class="dropdown-menu-table" class="dropdown-menu-table"
trigger="hover" trigger="hover"
> >
@@ -87,6 +82,7 @@
<el-dropdown-item> <el-dropdown-item>
<el-button <el-button
v-bind="getButtonProps(btn)" v-bind="getButtonProps(btn)"
:disabled="getButtonDisabled(btn, scope.row)"
@click="handleButtonClick(btn, scope.row)" @click="handleButtonClick(btn, scope.row)"
> >
{{ {{
@@ -145,8 +141,15 @@ const props = defineProps({
immediate: { type: Boolean, default: true }, immediate: { type: Boolean, default: true },
// 是否在激活时刷新数据 // 是否在激活时刷新数据
refreshOnActivated: { type: Boolean, default: true }, refreshOnActivated: { type: Boolean, default: true },
// 是否展示下拉菜单 (如果按钮是鼠标移入移出 不建议开启下拉菜单 会导致弹层的问题)
showDropdown: { type: Boolean, default: false },
}); });
const DEFAULT_PAGINATION = {
layout: "prev, pager, next",
background: false,
}
const emit = defineEmits([ const emit = defineEmits([
"current-change", "current-change",
"size-change", "size-change",
@@ -217,11 +220,12 @@ const params = computed(() => {
}); });
// 请求方法 // 请求方法
const refresh = async () => { const refresh = async (isReset:boolean=false) => {
if (!props.requestApi) return; if (!props.requestApi) return;
innerLoading.value = true; innerLoading.value = true;
try { try {
const res = await props.requestApi(params.value); const requestParams = isReset ? {...params.value,pageNo:1} : params.value;
const res = await props.requestApi(requestParams);
emit("update:data", res?.records || []); emit("update:data", res?.records || []);
emit("update:total", res?.total || 0); emit("update:total", res?.total || 0);
} catch (error) { } catch (error) {
@@ -242,6 +246,14 @@ const handleSizeChange = (val) => {
if (props.requestApi) refresh(); if (props.requestApi) refresh();
}; };
// 按钮支持 disabledFn函数
const getButtonDisabled = (button, row) => {
if (button.disabledFn && typeof button.disabledFn === 'function') {
return button.disabledFn(row);
}
return button.disabled || false;
};
onMounted(async () => { onMounted(async () => {
await updateContainerHeight(); await updateContainerHeight();
window.addEventListener('resize', handleResize); window.addEventListener('resize', handleResize);
@@ -267,14 +279,23 @@ onUnmounted(() => {
}); });
// 暴露 refresh 方法给外部使用 // 暴露 refresh 方法给外部使用
defineExpose({ refresh }); defineExpose({ refresh,reset:()=>refresh(true) });
// 分页配置 // 分页配置
const paginationConfig = computed(() => ({ const paginationConfig = computed(() =>{
layout: "prev, pager, next", if (typeof props.pagination === 'boolean') {
background: false, return DEFAULT_PAGINATION;
...props.pagination, }
}));
if (typeof props.pagination === 'object' && props.pagination !== null) {
return {
...DEFAULT_PAGINATION,
...props.pagination
};
}
return DEFAULT_PAGINATION;
});
// action按钮组的数据 // action按钮组的数据
const handleButtonClick = (button, row) => { const handleButtonClick = (button, row) => {
@@ -292,6 +313,9 @@ const shouldHideButton = (button, row) => {
const getVisibleButtonCount = (col) => { const getVisibleButtonCount = (col) => {
const { actions, maxButtons } = col; const { actions, maxButtons } = col;
const totalButtons = maxButtons || MAX_BUTTON_LENGTH; const totalButtons = maxButtons || MAX_BUTTON_LENGTH;
if(!props.showDropdown){
return actions.length;
}
return totalButtons === actions.length ? totalButtons : Math.min(totalButtons, actions.length)-1; return totalButtons === actions.length ? totalButtons : Math.min(totalButtons, actions.length)-1;
}; };
@@ -301,7 +325,7 @@ const hasDropdownPermission = (dropdownActions) => {
); );
}; };
const getButtonProps = (button) => { const getButtonProps = (button) => {
const { label, onClick, show, permission, ...buttonProps } = button; const { label, onClick, show, permission,disabledFn, ...buttonProps } = button;
return buttonProps; return buttonProps;
}; };
</script> </script>
@@ -348,7 +372,7 @@ const getButtonProps = (button) => {
pointer-events: none; pointer-events: none;
transition: opacity 0.25s ease-in, transform 0.25s ease-in; transition: opacity 0.25s ease-in, transform 0.25s ease-in;
} }
:deep(.el-table__row:hover) .action-group { :deep(.el-table__row:hover) .action-group,:deep(.row-dropdown-active) .action-group{
opacity: 1; opacity: 1;
transform: translateX(0); transform: translateX(0);
pointer-events: auto; pointer-events: auto;

View File

@@ -0,0 +1,37 @@
// 后台 - 权限管理模块-权限内容信息
// 权限状态映射
const roleDict = {
1: '启用',
0: '禁用'
}
// 权限状态颜色
const roleDictColor = {
1:'#66E5BE',
0:'#90A1B9'
}
// 创建角色类型字典
const roleTypeOptions = [
{ label: '实例角色', value: 'INSTANCE' },
{ label: '默认角色', value: 'DEFAULT' },
{ label: '系统角色', value: 'SYSTEM' }
]
// 设置权限转换为目标格式
const statusOptions = Object.keys(roleDict).map((key) => {
return {
label: roleDict[key],
value: Number(key)
}
})
export default {
roleDict,
roleDictColor,
statusOptions,
roleTypeOptions
}

View File

@@ -1,5 +1,14 @@
import DictManage from './dictManage'; const modules = import.meta.glob('./*.{vue,ts,js}', { eager: true });
const components: Record<string, any> = {};
const Dict = { DictManage } Object.entries(modules).forEach(([path, module]: [string, any]) => {
export { DictManage } // 过滤掉 index 文件本身
export default Dict; if (path.includes('index')) return;
const name = path.replace(/^\.\/(.*)\.\w+$/, '$1');
components[name] = module.default || module;
});
// 2. 统一导出
export const {
DictManage,
PermissionManage
} = components;
export default components;

View File

@@ -231,6 +231,7 @@ $color-white: #fff;
} }
.observer-anchor{ .observer-anchor{
height: 60px;
text-align: center; text-align: center;
font-size: 12px; font-size: 12px;
color: #808080; color: #808080;

View File

@@ -268,6 +268,7 @@ import {
getComment, getComment,
addReplyComment, addReplyComment,
deleteComment, deleteComment,
getEmployeeByKeyword
} from "@/api/modules/Comment"; } from "@/api/modules/Comment";
const { formatTime } = useRelativeTime(); const { formatTime } = useRelativeTime();
const userStore = useUserStore(); const userStore = useUserStore();
@@ -275,7 +276,7 @@ const { t } = useI18n();
// 当前用户信息 // 当前用户信息
const currentUser = computed(() => { const currentUser = computed(() => {
return { return {
name: userStore.userInfo.username, name: userStore.userInfo.nickname,
avatar: userStore.userInfo.avatar, avatar: userStore.userInfo.avatar,
id: 1, id: 1,
}; };
@@ -299,6 +300,8 @@ const {
// 评论业务逻辑 // 评论业务逻辑
const activeReply = reactive({ const activeReply = reactive({
id:"",
rootId:"",
replyUserId: "", replyUserId: "",
parentId: null, parentId: null,
targetName: "", targetName: "",
@@ -312,23 +315,61 @@ const commentData = ref([]);
// 滚动加载 // 滚动加载
const infinityLoading = ref(false); const infinityLoading = ref(false);
const loadMoreAnchor = ref(null); const loadMoreAnchor = ref(null);
const isProcessing = ref(false);
const noMore = ref(false); const noMore = ref(false);
// FIXME:请求用户列表的接口函数 // 请求用户列表的接口函数
const handleFetchSearch = async (keyword, signal) => { const handleFetchSearch = async (keyword, signal) => {
console.log("获取参数信息", keyword, signal); const selectedIds = new Set();
selectedUsersCache.forEach(userList => {
userList.forEach(u => selectedIds.add(u.id));
});
// FIXME:模拟接口返回的数据人员信息
await new Promise((resolve) => setTimeout(resolve, 300)); await new Promise((resolve) => setTimeout(resolve, 300));
return [ const allEmployees = [
{ id: 1, name: "李星倩" }, {
{ id: 2, name: "冯娜" }, id:1,
{ id: 4, name: "张三1" }, name:"张三",
{ id: 5, name: "张三2" }, },
{ id: 6, name: "张三3" }, {
{ id: 7, name: "张三4" }, id:2,
{ id: 8, name: "张三5" }, name:"李四",
{ id: 9, name: "张三6" }, },
{ id: 10, name: "张三7" }, {
{ id: 11, name: "张三8" }, id:3,
]; name:"王五",
},
{
id:4,
name:"赵六",
},
{
id:5,
name:"冯七 ",
},
]
return allEmployees.filter(user => {
const isMatch = user.name.toLowerCase().includes(keyword.toLowerCase());
const notSelected = !selectedIds.has(user.id);
return isMatch && notSelected;
});
// 正式请求
const queryParams = {
keyword,
kind:0,
pageNo: 1,
pageSize: 20,
};
try {
const allEmployees = await getEmployeeByKeyword(queryParams);
return allEmployees.filter(user => {
const isMatch = user.name.toLowerCase().includes(keyword.toLowerCase());
const notSelected = !selectedIds.has(user.id);
return isMatch && notSelected;
});
} catch (error) {
console.log('fetch user error:',error);
}
}; };
// 获取当前的的评论信息 // 获取当前的的评论信息
@@ -373,7 +414,7 @@ const onUserSelect = (user: any) => {
// 回复 // 回复
const openReply = (target, group) => { const openReply = (target, group) => {
// 1. 设置回复的目标关系 // 1. 设置回复的目标关系
activeReply.groupId = target.rootId; // 根评论ID activeReply.rootId = target.rootId; // 根评论ID
activeReply.replyUserId = target.employee.userId; //回复-人的id activeReply.replyUserId = target.employee.userId; //回复-人的id
activeReply.parentId = target.parentId; // 直接父级ID activeReply.parentId = target.parentId; // 直接父级ID
activeReply.targetName = target.employee.username; //回复人员的username activeReply.targetName = target.employee.username; //回复人员的username
@@ -393,7 +434,6 @@ const deleteReply = async (target, group) => {
try { try {
// 删除成功 // 删除成功
await deleteComment(target.id); await deleteComment(target.id);
ElMessage.success("删除成功");
const index = group.children.findIndex((item) => item.id === target.id); const index = group.children.findIndex((item) => item.id === target.id);
if (index !== -1) { if (index !== -1) {
group.children.splice(index, 1); group.children.splice(index, 1);
@@ -414,12 +454,11 @@ const deleteReply = async (target, group) => {
const deleteMainComment = async (target) => { const deleteMainComment = async (target) => {
try { try {
await deleteComment(target.id); await deleteComment(target.id);
ElMessage.success("删除成功");
// 前端动态操作 // 前端动态操作
const index = commentData.value.findIndex((item) => item.id === target.id); const index = commentData.value.findIndex((item) => item.id === target.id);
if (index !== -1) { if (index !== -1) {
commentData.value.splice(index, 1); commentData.value.splice(index, 1);
if (activeReply.groupId === target.id) { if (activeReply.rootId === target.id) {
cancelReply(); cancelReply();
} }
} }
@@ -433,11 +472,14 @@ const onSelectEmoji = (emoji) => {
mainInput.value += emoji; mainInput.value += emoji;
}; };
// 清除回复信息的操作 // 清除回复信息的人员数据
const resetReplyStatus = () => { const resetReplyStatus = () => {
activeReply.parentId = null; activeReply.parentId = null;
activeReply.targetName = ""; activeReply.targetName = "";
activeReply.groupId = null; activeReply.rootId = "";
activeReply.replyUserId = "";
activeReply.id = "";
}; };
// 取消评论 // 取消评论
@@ -451,7 +493,8 @@ const cancelReply = () => {
*/ */
const handleSendComment = async () => { const handleSendComment = async () => {
let rawText = mainInput.value; let rawText = mainInput.value;
if (!rawText.trim()) return ElMessage.warning("内容不能为空"); if (!rawText.trim() || isProcessing.value) return;
isProcessing.value = true;
let finalReplyId = null; let finalReplyId = null;
const expectedPrefix = `@${activeReply.targetName} `; const expectedPrefix = `@${activeReply.targetName} `;
@@ -484,7 +527,7 @@ const handleSendComment = async () => {
`匹配到用户: ${currentUser?.name}, ID: ${currentUser?.id}, 位置: ${match.index}` `匹配到用户: ${currentUser?.name}, ID: ${currentUser?.id}, 位置: ${match.index}`
); );
mentionList.push({ mentionList.push({
id: currentUser.id, userId: currentUser.id,
name: currentUser.name, name: currentUser.name,
start: match.index, start: match.index,
end: match.index + match[0].length, end: match.index + match[0].length,
@@ -495,12 +538,12 @@ const handleSendComment = async () => {
// 组装接口请求的参数 // 组装接口请求的参数
const params = { const params = {
content: rawText, content: rawText,
mentions: Object.keys(mentionList).length ? mentionList : null, mentions: mentionList.length ? mentionList : null,
...props.queryParams, ...props.queryParams,
}; };
// 回复数据复制 // 回复数据复制
if(type === "reply"){ if(type === "reply"){
params.rootId = activeReply.groupId ? activeReply.groupId : activeReply.id; params.rootId = activeReply.rootId ? activeReply.rootId : activeReply.id;
params.parentId = activeReply.parentId; params.parentId = activeReply.parentId;
params.replyUserId = activeReply.replyUserId; params.replyUserId = activeReply.replyUserId;
} }
@@ -513,7 +556,7 @@ const handleSendComment = async () => {
// 清空输入框 // 清空输入框
cancelReply(); cancelReply();
clearSelection(); clearSelection();
ElMessage.success(type === "reply" ? "回复成功" : "评论成功"); // ElMessage.success(type === "reply" ? "回复成功" : "评论成功"); //屏蔽当前的提示
} catch (error) { } catch (error) {
console.log("error", error); console.log("error", error);
} }
@@ -532,9 +575,9 @@ const updateUIAfterSend = (type, params, response) => {
userId: activeReply.replyUserId, userId: activeReply.replyUserId,
username: activeReply.targetName, username: activeReply.targetName,
}, },
rootId: params.groupId, rootId: params.rootId,
content: params.content, content: params.content,
mentions: params.mentionList, mentions: params.mentions,
createTime: new Date().valueOf(), createTime: new Date().valueOf(),
children: [], children: [],
}; };
@@ -544,33 +587,47 @@ const updateUIAfterSend = (type, params, response) => {
commentData.value.unshift(newComment); commentData.value.unshift(newComment);
} else { } else {
//回复某人的数据渲染 //回复某人的数据渲染
const targetGroup = commentData.value.find( const targetGroup = commentData.value.find((i) => i.id === params.rootId);
(i) => i.id === params.rootId
); //获取返回的数据信息
if (targetGroup) { if (targetGroup) {
targetGroup.childrenCount = (targetGroup.childrenCount || 0) + 1;
if (targetGroup.showAllReplies) {
if (!targetGroup.children) targetGroup.children = []; if (!targetGroup.children) targetGroup.children = [];
targetGroup.children.unshift(newComment); targetGroup.children.unshift(newComment);
targetGroup.childrenCount = targetGroup.children.length; }else{
handleExpand(targetGroup);
}
} }
} }
}; };
/* 二级评论加载 可能会触发一级加载的问题 */
// 是否有任何二级回复正在加载
const isSubTaskRunning = computed(() => {
return commentData.value.some(item => item.loading);
});
// 综合加载状态:主列表加载中 OR 子任务运行中 OR 发送中
const isBusy = computed(() => {
return infinityLoading.value || isSubTaskRunning.value || isProcessing.value;
});
// 滚动加载数据 // 滚动加载数据
const setupObserver = () => { const setupObserver = () => {
const observer = new IntersectionObserver( const observer = new IntersectionObserver(
(entries) => { (entries) => {
// 如果探测器进入视口,且当前没在加载,且还有更多数据 // 如果探测器进入视口,且当前没在加载,且还有更多数据
const target = entries[0];
if ( if (
entries[0].isIntersecting && target.isIntersecting &&
!infinityLoading.value && !isBusy.value &&
!noMore.value !noMore.value
) { ) {
loadMainComments(); loadMainComments();
} }
}, },
{ {
threshold: 0.1, threshold: 0.1
} }
); );
@@ -580,6 +637,7 @@ const setupObserver = () => {
}; };
const nextPage = ref(1); const nextPage = ref(1);
// TODO:能会存在数据重复的问题
const loadMainComments = async () => { const loadMainComments = async () => {
infinityLoading.value = true; infinityLoading.value = true;
try { try {
@@ -629,13 +687,15 @@ const loadMoreReplies = async (item) => {
try { try {
const res = await getCommentData({ const res = await getCommentData({
rootId: item.id, rootId: item.id,
pageNum: nextPage, pageNo: nextPage,
pageSize: PAGE_SIZE_MORE, pageSize: PAGE_SIZE_MORE,
}); });
const newRecords = res?.records || [];
// 将新数据追加到列表末尾 // 将新数据追加到列表末尾
item.children = [...item.children, ...res]; if(newRecords.length > 0){
item.children = [...item.children, ...newRecords];
item.currentPage = nextPage; item.currentPage = nextPage;
}
} finally { } finally {
item.loading = false; item.loading = false;
} }
@@ -644,7 +704,7 @@ const loadMoreReplies = async (item) => {
const collapseReplies = (item) => { const collapseReplies = (item) => {
item.showAllReplies = false; item.showAllReplies = false;
item.children = []; //清空数据 item.children = []; //清空数据
item.currentPage = 0; //重置页码 item.currentPage = 1; //重置页码
}; };
onMounted(() => { onMounted(() => {

View File

@@ -81,6 +81,12 @@ export function useUserSearch(
* 暴露给外部调用的入口 * 暴露给外部调用的入口
*/ */
const search = (keyword: string) => { const search = (keyword: string) => {
// 如果输入框里没这个 @名字 了,从 selectedUsersCache 删掉它
for (const name of selectedUsersCache.keys()) {
if (!mainInput.value.includes(`@${name}`)) {
selectedUsersCache.delete(name);
}
}
searching.value = true; searching.value = true;
debouncedSearch(keyword); debouncedSearch(keyword);
}; };

View File

@@ -26,6 +26,5 @@ export const parseMention = (text: string, atUsers: atUserProps[]) => {
result = prefix + highlight + suffix; result = prefix + highlight + suffix;
}); });
return result.replace(/\n/g, '<br>'); return result.replace(/\n/g, '<br>');
}; };

View File

@@ -9,6 +9,7 @@
destroy-on-close destroy-on-close
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="false" :close-on-press-escape="false"
modal-class="standard-overlay-dialog-flat"
> >
<el-form <el-form
ref="ruleFormRef" ref="ruleFormRef"

View File

@@ -9,6 +9,7 @@
destroy-on-close destroy-on-close
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="false" :close-on-press-escape="false"
modal-class="standard-overlay-dialog-flat"
> >
<el-form <el-form
ref="ruleFormRef" ref="ruleFormRef"

View File

@@ -239,7 +239,7 @@ const getTableData = async (params) => {
}; };
// 搜索查询条件信息 // 搜索查询条件信息
const fetchTableData = () => { const fetchTableData = () => {
dictTableRef.value && dictTableRef.value.refresh(); onConfirmSuccess();
}; };
const clearSelectItem = () => { const clearSelectItem = () => {
Object.assign(selectItem,{id:null,name:'',key:'',status:1,remark:''}) Object.assign(selectItem,{id:null,name:'',key:'',status:1,remark:''})
@@ -268,7 +268,7 @@ const handleDictStatus = async (row) => {
// 刷新Table数据信息 // 刷新Table数据信息
const onConfirmSuccess = () => { const onConfirmSuccess = () => {
dictTableRef.value && dictTableRef.value.refresh(); dictTableRef.value && dictTableRef.value.reset();
}; };
// TODO:字段配置 // TODO:字段配置
const handlefieldsConfig = (ite) => { const handlefieldsConfig = (ite) => {
@@ -281,7 +281,7 @@ const handleDelete = async (item) => {
.then(async () => { .then(async () => {
try { try {
await deleteDictValue(item.id); await deleteDictValue(item.id);
dictTableRef.value && dictTableRef.value.refresh(); onConfirmSuccess();
} catch (error) { } catch (error) {
console.log("fetch error", error); console.log("fetch error", error);
} }
@@ -301,19 +301,5 @@ const handleDelete = async (item) => {
align-items: center; align-items: center;
gap: 14px; gap: 14px;
} }
.mj-status-dot {
cursor: pointer;
&::before {
content: "";
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: var(--data-status-color);
margin-right: 8px;
vertical-align: middle;
}
}
} }
</style> </style>

View File

@@ -1,25 +1,28 @@
<template> <template>
<el-dialog <el-dialog
v-model="dialogVisible" v-model="dialogVisible"
title="新增系统角色" :title="computedTitle"
width="500px" width="500px"
class="standard-ui-flat-dialog" class="standard-ui-flat-dialog"
modal-class="standard-overlay-dialog-flat" modal-class="standard-overlay-dialog-flat"
destroy-on-close destroy-on-close
:close-on-click-modal="false"
:close-on-press-escape="false"
top="10vh"
> >
<el-form :model="form" label-position="top" class="role-form"> <el-form ref="formRef" :model="form" label-position="top" :rules="rules">
<div class="row-flex"> <div class="row-flex">
<el-form-item label="角色名称" required> <el-form-item label="角色名称" prop="name">
<el-input v-model="form.name" placeholder="请输入角色名称" /> <el-input v-model="form.name" placeholder="请输入角色名称" />
</el-form-item> </el-form-item>
<el-form-item label="角色编码" required> <el-form-item label="角色编码" prop="code">
<el-input v-model="form.code" placeholder="ROLE_CODE" /> <el-input v-model="form.code" placeholder="ROLE_CODE" />
</el-form-item> </el-form-item>
</div> </div>
<el-form-item label="角色类型"> <el-form-item label="角色类型">
<div class="full-width-radio"> <div class="full-width-radio">
<BaseSegmented v-model="roleType" :options="roleOptions" /> <BaseSegmented v-model="form.type" :options="PermissionManage.roleTypeOptions" />
</div> </div>
</el-form-item> </el-form-item>
@@ -28,16 +31,17 @@
<div class="title">组织架构</div> <div class="title">组织架构</div>
<div class="desc">是否将该角色与系统组织架构体系进行深度关联</div> <div class="desc">是否将该角色与系统组织架构体系进行深度关联</div>
</div> </div>
<el-switch v-model="form.isOrg" /> <el-switch v-model="form.isOrgRelated" style="--el-switch-on-color: #13ce66; --el-switch-off-color: #CAD5E2" :active-value="1" :inactive-value="0" />
</div> </div>
<el-form-item label="关联岗位"> <el-form-item label="关联岗位">
<el-select <el-select
v-model="form.post" v-model="form.positionIds"
placeholder="请选择岗位" placeholder="请选择关联岗位"
style="width: 100%" multiple
collapse-tags
> >
<el-option label="技术总监" value="1" /> <el-option :label="item.label" :value="item.value" v-for="(item,index) in positionList" :key="index"/>
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -46,7 +50,7 @@
<div class="title">启用状态</div> <div class="title">启用状态</div>
<div class="desc">控制该角色及其下属权限是否立即生效</div> <div class="desc">控制该角色及其下属权限是否立即生效</div>
</div> </div>
<el-switch v-model="form.status" /> <el-switch v-model="form.status" :active-value="1" :inactive-value="0"/>
</div> </div>
<el-form-item label="备注说明"> <el-form-item label="备注说明">
@@ -62,8 +66,8 @@
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="dialogVisible = false" text>取消</el-button> <el-button @click="cancelRoles" text>取消</el-button>
<el-button type="primary" @click="handleSubmit">确认创建</el-button> <el-button type="primary" @click="handleSubmit(formRef)">确认创建</el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>
@@ -71,28 +75,88 @@
<script lang="ts" setup> <script lang="ts" setup>
import BaseSegmented from "./baseSegmented.vue"; import BaseSegmented from "./baseSegmented.vue";
import { PermissionManage } from "@/dict";
import type { FormInstance, FormRules } from 'element-plus';
import {
addRole,
getRoleDetail
} from "@/api/stage/permission/index.ts";
defineOptions({ name: "addRoles" }); defineOptions({ name: "addRoles" });
const dialogVisible = defineModel("visible", { type: Boolean, default: false }); const dialogVisible = defineModel("visible", { type: Boolean, default: false });
const roleType = ref('system'); // 初始值 const props = defineProps({
detailId: {
type: [String,Number],
default: '',
}
});
const roleOptions = [ const computedTitle = computed(() => {
{ label: '实例角色', value: 'instance' }, return props.detailId ? "编辑系统角色" : "新增系统角色";
{ label: '默认角色', value: 'default' }, });
{ label: '系统角色', value: 'system' } const emit = defineEmits(["on-success"]);
]; const positionList = ref([]);
const form = reactive({ const formRef = ref<FormInstance>(null);
const form = reactive<RuleForm>({
name: "", name: "",
code: "", code: "",
type: "实例角色", type: "DEFAULT",
isOrg: false, isOrgRelated: 0, // 是否和组织架构深度关联
post: "", post: "",
status: true, status: 1,
remark: "", remark: "",
}); });
const handleSubmit = () => {
const rules = reactive<FormInstance<RuleForm>>({
name: [
{ required: true, message: "请输入角色名称", trigger: "blur" }
],
code: [
{ required: true, message: "请输入角色编码", trigger: "blur" }
]
})
const cancelRoles = () => {
formRef.value && formRef.value.resetFields();
dialogVisible.value = false; dialogVisible.value = false;
}; };
// 获取角色详情
const getRoleShowDetail = async (val) =>{
try {
const res = await getRoleDetail(props.detailId);
Object.assign(form,res);
} catch (error) {
console.log('getRoleDetail error:',error);
}
}
// 监听详情
watch(()=>dialogVisible.value,(val) => {
if(val){
getRoleShowDetail();
}
})
const handleSubmit = (formEl:FormInstance | undefined) => {
if (!formEl) return
formEl.validate(async (valid) => {
if (valid) {
try {
const res = await addRole(form);
ElMessage.success("操作成功");
cancelRoles();
emit('on-success');
} catch (error) {
console.log(error);
}
} else {
console.log('error submit!')
}
})
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -50,6 +50,7 @@ const handleChange = (val) => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@use "sass:color";
$bg-color: #f2f3f5; $bg-color: #f2f3f5;
$active-bg: #ffffff; $active-bg: #ffffff;
$primary-color: #1661ff; $primary-color: #1661ff;
@@ -86,7 +87,7 @@ $text-secondary: #86909c;
user-select: none; user-select: none;
&:hover { &:hover {
color: darken($text-secondary, 15%); color: color.adjust($text-secondary, $lightness: 15%);
} }
// 选中状态样式 // 选中状态样式

View File

@@ -30,8 +30,8 @@
<!-- 表格 --> <!-- 表格 -->
<CommonTable <CommonTable
ref="dictTableRef" ref="tableRef"
:columns="columns" :columns="tableColumns"
v-model:data="dataList" v-model:data="dataList"
v-model:total="total" v-model:total="total"
pagination pagination
@@ -39,12 +39,31 @@
> >
<!-- 状态 --> <!-- 状态 -->
<template #status="{ row }"> <template #status="{ row }">
<el-tag>{{ row.status }}</el-tag> <div
class="mj-status-dot"
:style="{
'--data-status-color': PermissionManage.roleDictColor[row.status],
}"
@click="handleDictStatus(row)"
>
{{ PermissionManage.roleDict[row.status] }}
</div>
</template>
<!-- 类型 -->
<template #type="{ row }">
<el-tag type="primary">{{ row.type }}</el-tag>
</template> </template>
<!-- 成员数量 --> <!-- 成员数量 -->
<template #member="{ row }"> <template #member="{ row }">
<el-tag @click.stop="addMember">{{ row.member }}</el-tag> <el-button
link
icon="UserFilled"
type="primary"
@click.stop="addMember"
>{{ row.memberCount }}</el-button
>
</template> </template>
</CommonTable> </CommonTable>
</div> </div>
@@ -53,7 +72,7 @@
<member-selector v-model:visible="showMember" /> <member-selector v-model:visible="showMember" />
<!-- 新增角色 --> <!-- 新增角色 -->
<add-roles v-model:visible="showRoles" /> <add-roles v-model:visible="showRoles" @on-success="onRefresh" :detail-id="detailId"/>
<!-- 权限抽屉 --> <!-- 权限抽屉 -->
<permission-drawer v-model:visible="showPermission" /> <permission-drawer v-model:visible="showPermission" />
@@ -64,14 +83,26 @@ import CommonTable from "@/components/proTable/index.vue";
import memberSelector from "@/components/memberSelector/index.vue"; import memberSelector from "@/components/memberSelector/index.vue";
import addRoles from "./addRoles.vue"; import addRoles from "./addRoles.vue";
import permissionDrawer from "./permissionDrawer.vue"; import permissionDrawer from "./permissionDrawer.vue";
import { PermissionManage } from "@/dict";
import { formatIndex } from "@/utils/utils";
import {
getRoleList,
getRoleDetail,
updateRole,
addRole,
deleteRole,
enableRole,
disableRole
} from "@/api/stage/permission/index.ts";
defineOptions({ name: "PermissionManagement" }); defineOptions({ name: "PermissionManagement" });
const activeTab = ref(1); const activeTab = ref(1);
const dictTableRef = ref(null); const tableRef = ref(null);
const searchVal = ref(""); const searchVal = ref("");
const dataList = ref([]); const dataList = ref([]);
const total = ref(0); const total = ref(0);
const showMember = ref(false); const showMember = ref(false);
const showRoles = ref(false); const showRoles = ref(false);
const detailId = ref('');
const showPermission = ref(false); const showPermission = ref(false);
const tabList = [ const tabList = [
{ {
@@ -88,19 +119,30 @@ const tabList = [
}, },
]; ];
const columns = [ const roleColumns = [
{ prop: "id", label: "编号", width: "80", align: "center", slot: "number" },
{ prop: "name", label: "字典名称", align: "center", slot: "name" },
{ {
prop: "member", prop: "id",
label: "编号",
width: "80",
align: "center",
formatter: (val) => {
return `#${formatIndex(val.id)}`;
},
},
{ prop: "name", label: "角色名称", align: "center",showOverflowTooltip:true },
{
prop: "memberCount",
label: "成员数量", label: "成员数量",
align: "center", align: "center",
slot: "member", slot: "member",
}, },
{ {
prop: "key", prop: "permissionCount",
label: "权限数量", label: "权限数量",
align: "center", align: "center",
formatter: (val) => {
return val.permissionCount ? `${val.permissionCount}` : "-";
},
}, },
{ {
prop: "status", prop: "status",
@@ -109,30 +151,33 @@ const columns = [
slot: "status", slot: "status",
}, },
{ {
prop: "roleType", prop: "type",
label: "角色类型", label: "角色类型",
align: "center", align: "center",
showOverflowTooltip: true, slot: "type",
}, },
{ {
prop: "remark", prop: "positionIds",
label: "关联岗位", label: "关联岗位",
align: "center", align: "center",
showOverflowTooltip: true, showOverflowTooltip: true,
formatter: (val) => {
return val.positionIds ? val.positionIds.join(",") : "-";
},
}, },
{ {
prop: "createTime", prop: "updateTime",
label: "更新时间", label: "更新时间",
align: "center", align: "center",
showOverflowTooltip: true, showOverflowTooltip: true,
formatter: (val) => { formatter: (val) => {
return val.createTime return val.updateTime
? dayjs(val.createTime).format("YYYY-MM-DD HH:mm") ? dayjs(val.updateTime).format("YYYY-MM-DD HH:mm")
: "-"; : "-";
}, },
}, },
{ {
prop: "updateByName", prop: "updater",
label: "更新人", label: "更新人",
align: "center", align: "center",
}, },
@@ -140,7 +185,8 @@ const columns = [
prop: "actions", prop: "actions",
label: "操作", label: "操作",
align: "right", align: "right",
width: "300", fixed:"right",
width: "200",
actions: [ actions: [
{ {
label: "权限", label: "权限",
@@ -163,41 +209,167 @@ const columns = [
type: "default", type: "default",
link: true, link: true,
permission: ["config"], permission: ["config"],
onClick: (row) => {}, onClick: (row) => {
showRoles.value = true;
detailId.value = row.id;
},
}, },
{ {
label: "删除", label: "删除",
type: "danger", type: "danger",
link: true, link: true,
permission: ["delete"], permission: ["delete"],
onClick: (row) => {}, disabledFn: (row) => {
return (row.type !== 'SYSTEM')
},
onClick: async (row) => {
try {
deleteRole(row.id).then(() => {
ElMessage.success("删除成功");
tableRef.value.refresh();
});
} catch (error) {
console.log('error',error);
}
},
}, },
], ],
}, },
]; ];
const userColumns = [
{
prop: "name",
label: "姓名",
align: "center",
},
{
prop: "code",
label: "账号",
align: "center",
},
{
prop: "departIds",
label: "所属部门",
align: "center",
},
{
prop: "roleIds",
label: "所属角色",
align: "center",
},
{
prop: "status",
label: "状态",
align: "center",
},
{
prop: "actions",
label: "操作",
align: "right",
fixed:"right",
actions: [
{
label: "编辑",
type: "primary",
link: true,
permission: ["edit"],
onClick: (row) => {
showPermission.value = true;
},
},
{
label: "重置密码",
type: "primary",
link: true,
permission: ["edit"],
onClick: (row) => {
showPermission.value = true;
},
},
{
label: "移除",
type: "danger",
link: true,
permission: ["edit"],
onClick: (row) => {
showPermission.value = true;
},
},
],
},
];
const tableColumns = computed(() => {
const columns = {
1: roleColumns,
2: userColumns,
}[activeTab.value];
return columns;
});
// 当前成员数量 // 当前成员数量
const addMember = () => { const addMember = () => {
showMember.value = true; showMember.value = true;
}; };
// 刷新列表
const onRefresh = () => {
tableRef.value && tableRef.value.reset();
};
// 监听Tabs变化
watch(activeTab,()=>{
dataList.value = [];
tableRef.value && tableRef.value.reset();
})
// 权限
const handleDictStatus = async (row) => {
try {
row.status === 1 ? await disableRole(row.id) : await enableRole(row.id);
ElMessage.success("操作成功");
onRefresh();
} catch (error) {
console.log("error", error);
}
};
const getTableData = async (params) => { const getTableData = async (params) => {
console.log('每次请求的分页:',params);
await new Promise((resolve) => setTimeout(resolve, 500)); await new Promise((resolve) => setTimeout(resolve, 500));
return { return {
records: [ records: [
{ {
id: 1, id: 1,
name: "11111", name: "测试角色名称",
member: "2222", memberCount: 10,
permissionCount: 22,
status: 1, status: 1,
key: "", type: "SYSTEM",
roleType: 1, positionIds: "",
remark: "", updateTime: 1767878508824,
createTime: 1767878508824, updater: "更新人",
updateByName: "111",
}, },
], ],
}; };
// 正式请求
const queryParams = {
...(searchVal.value && { name:searchVal.value }),
status:1,
type:1,
pageNo:1,
pageSize:10
}
try {
const res = await getRoleList(queryParams);
return res;
} catch (error) {
console.log("error", error);
}
}; };
const checkRolesText = computed(() => { const checkRolesText = computed(() => {

View File

@@ -85,7 +85,6 @@ const transformRoutes = (
routes: any[], routes: any[],
parentCode: string = "" parentCode: string = ""
): RouteRecordRaw[] => { ): RouteRecordRaw[] => {
console.log("transformRoutes", routes);
return routes.flatMap((route) => { return routes.flatMap((route) => {
const fullCode = parentCode ? `${parentCode}/${route.code}` : route.code; const fullCode = parentCode ? `${parentCode}/${route.code}` : route.code;
@@ -143,7 +142,8 @@ const addDynamicRoutes = async () => {
// 从后端获取路由菜单数据 (这边需要区分 后台的菜单 和用户的菜单) // 从后端获取路由菜单数据 (这边需要区分 后台的菜单 和用户的菜单)
let allRoutes:any[] = []; let allRoutes:any[] = [];
if (userStore.isBackendUser) { if (userStore.isBackendUser) {
const backendResponse = await getRouteMenus(); // const backendResponse = await getRouteMenus();
const backendResponse = [];
allRoutes = [ allRoutes = [
{ {
code: "stage", code: "stage",

View File

@@ -157,6 +157,23 @@
.el-dialog__header { .el-dialog__header {
padding: var(--el-dialog-inset-padding-primary); padding: var(--el-dialog-inset-padding-primary);
border-bottom: 1px solid var(--el-dialog-border-header-footer-color); border-bottom: 1px solid var(--el-dialog-border-header-footer-color);
.el-dialog__title{
position: relative;
padding-left: 16px;
font-size: 14px;
&::before{
content: '';
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background-color: var(--blue-block-color);
border-radius: 2px;
left: 0;
}
}
} }
.el-dialog__body { .el-dialog__body {