fix:联调权限页面模块 优化全局table组件
This commit is contained in:
1
components.d.ts
vendored
1
components.d.ts
vendored
@@ -13,6 +13,7 @@ declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AutoTooltip: typeof import('./src/components/autoTooltip/index.vue')['default']
|
||||
CardItem: typeof import('./src/components/cardItem/index.vue')['default']
|
||||
CollapseHeader: typeof import('./src/components/collapseHeader/index.vue')['default']
|
||||
Comment: typeof import('./src/components/comment/index.vue')['default']
|
||||
CommonFilter: typeof import('./src/components/commonFilter/index.vue')['default']
|
||||
DynamicSvgIcon: typeof import('./src/components/dynamicSvgIcon/index.vue')['default']
|
||||
|
||||
@@ -5,6 +5,11 @@ export const getRouteMenus = () => {
|
||||
return request.get('/auth/v1/backend/menu');
|
||||
};
|
||||
|
||||
// 获取当前用户信息
|
||||
export const getUserInfo = () => {
|
||||
return request.get('/auth/v1/my/info');
|
||||
};
|
||||
|
||||
// 登录接口
|
||||
export const login = (data: { username: string; password: string }) => {
|
||||
return request.post('/auth/oauth2/token', data,{
|
||||
@@ -13,3 +18,12 @@ export const login = (data: { username: string; password: string }) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**员工公用接口*/
|
||||
|
||||
|
||||
// 根据员工关键字查询员工
|
||||
export const getEmployeeList = (params: any) => {
|
||||
return request.get('/auth/v1/employee', params);
|
||||
};
|
||||
@@ -18,6 +18,12 @@ interface addDataProps{
|
||||
wxWork:wxWorkProps;
|
||||
}
|
||||
|
||||
interface defaultProps{
|
||||
keyword?:string;
|
||||
pageNo:number;
|
||||
pageSize:number;
|
||||
}
|
||||
|
||||
|
||||
// 查询企业
|
||||
export const getEnterprise = (params: paramsProps) =>{
|
||||
@@ -56,3 +62,8 @@ export const getEnterpriseDetail = () => {
|
||||
export const getEnterpriseOrgDetail = (departmentId:string) => {
|
||||
return request.get(`/auth/v1/backend/enterprise/department/${departmentId}`);
|
||||
}
|
||||
|
||||
// 获取企业信息职位
|
||||
export const getEnterprisePosition = (params:defaultProps) => {
|
||||
return request.get(`/auth/v1/backend/enterprise/position`,params);
|
||||
}
|
||||
@@ -26,24 +26,24 @@ 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}`);
|
||||
return request.put(`/auth/v1/backend/role/${id}/enable`);
|
||||
}
|
||||
|
||||
// 禁用角色
|
||||
export const disableRole = (id: string) => {
|
||||
return request.put(`/auth/v1/backend/role/disable/${id}`);
|
||||
return request.put(`/auth/v1/backend/role/${id}/disable`);
|
||||
}
|
||||
|
||||
// 复制权限
|
||||
export const copyRolePermission = (roleId: string) => {
|
||||
return request.post(`/auth/v1/backend/role/${roleId}/copy`);
|
||||
}
|
||||
|
||||
// 批量保存角色
|
||||
export const batchSaveRole = (roleId: string,data: number[]) => {
|
||||
return request.post(`auth/v1/backend/role/${roleId}/members`, data);
|
||||
}
|
||||
|
||||
/**------------------------角色权限相关---------------------------**/
|
||||
|
||||
60
src/components/collapseHeader/index.vue
Normal file
60
src/components/collapseHeader/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="mj-collapse-header">
|
||||
<div class="header-left">
|
||||
<div class="title">{{ title }}</div>
|
||||
<slot name="extra"></slot>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<slot name="headerRight"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 全局公用collapse表头组件 -->
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$primary-blue: #0052d9;
|
||||
$text-main: #1d2129;
|
||||
.mj-collapse-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding-right: 8px;
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 12px;
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: $text-main;
|
||||
|
||||
// 使用伪类实现蓝色装饰条
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 3px;
|
||||
height: 14px;
|
||||
background-color: $primary-blue;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -22,33 +22,78 @@
|
||||
|
||||
<div class="drawer-body-container">
|
||||
<div class="search-bar">
|
||||
<el-input
|
||||
v-model="searchQuery"
|
||||
placeholder="按姓名或部门搜索成员..."
|
||||
:prefix-icon="Search"
|
||||
clearable
|
||||
/>
|
||||
<el-button type="primary" plain @click="addNewMember"
|
||||
>添加成员</el-button
|
||||
<el-select
|
||||
v-model="memberSearchQuery"
|
||||
placeholder="按姓名搜索成员"
|
||||
multiple
|
||||
filterable
|
||||
remote
|
||||
:remote-method="remoteMethod"
|
||||
:loading="remoteLoading"
|
||||
collapse-tags
|
||||
:teleported="false"
|
||||
:fit-input-width="true"
|
||||
value-key="id"
|
||||
v-select-more="handleLoadMore"
|
||||
@change="changeMember"
|
||||
>
|
||||
</div>
|
||||
<div class="member-list" ref="listRef">
|
||||
<transition-group name="list-fade">
|
||||
<div
|
||||
v-for="item in filteredMembers"
|
||||
<el-option
|
||||
v-for="item in selectOptions"
|
||||
:key="item.id"
|
||||
class="member-item"
|
||||
@click="selectMember(item)"
|
||||
:value="item"
|
||||
:label="item.name"
|
||||
>
|
||||
<div class="select-item">
|
||||
<name-avatar
|
||||
:size="26"
|
||||
:name="item.name"
|
||||
:src="item.avatar"
|
||||
:bgColor="'var(--mj-avatar-bg)'"
|
||||
:avatarTextColor="'var(--mj-text-color)'"
|
||||
/>
|
||||
<el-tooltip :content="item.name">
|
||||
<div class="select-item-name mj-ellipsis-one-line">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-option>
|
||||
<el-option
|
||||
v-if="loadMore"
|
||||
disabled
|
||||
class="mj-select-dropdown-loading"
|
||||
value="loading"
|
||||
>
|
||||
<div class="status-container">
|
||||
<el-icon v-if="loadMore" class="is-loading" :size="20"
|
||||
><Loading
|
||||
/></el-icon>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
<el-scrollbar
|
||||
height="calc(100vh - 216px)"
|
||||
@end-reached="loadMoreList"
|
||||
ref="listRef"
|
||||
>
|
||||
<div class="member-list">
|
||||
<transition-group name="list-fade">
|
||||
<div v-for="item in displayMemberList" :key="item.id" class="member-item">
|
||||
<name-avatar
|
||||
:size="30"
|
||||
:name="item.name"
|
||||
:src="item.avatar"
|
||||
:bgColor="'var(--mj-avatar-bg)'"
|
||||
:avatarTextColor="'var(--mj-text-color)'"
|
||||
/>
|
||||
<div class="member-info">
|
||||
<div class="name">{{ item.name }}</div>
|
||||
<div class="dept">{{ item.dept }}</div>
|
||||
<div class="dept">
|
||||
{{
|
||||
(item.departments || []).map((dept) => dept.name).join(",")
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="member-action">
|
||||
<el-button link type="info" @click.stop="handleRemove(item.id)"
|
||||
@@ -58,6 +103,7 @@
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
@@ -67,8 +113,10 @@
|
||||
<span class="count">共 {{ memberList.length }} 名成员</span>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<el-button link @click="drawerVisible = false">取消</el-button>
|
||||
<el-button type="primary" class="btn-confirm">确认保存变更</el-button>
|
||||
<el-button link @click="cancelMember">取消</el-button>
|
||||
<el-button type="primary" class="btn-confirm" @click="saveMember"
|
||||
>确认保存变更</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -77,74 +125,102 @@
|
||||
<script setup lang="ts">
|
||||
import { Search } from "@element-plus/icons-vue";
|
||||
import NameAvatar from "@/components/NameAvatar/index.vue";
|
||||
import { getEmployeeList } from "@/api";
|
||||
import { useSelectLoadMore } from "@/hooks/useSelectLoadMore";
|
||||
import { useLocalManager } from "@/hooks/useLocalManager";
|
||||
|
||||
const {
|
||||
options,
|
||||
remoteLoading,
|
||||
loadMore,
|
||||
noMore,
|
||||
remoteMethod,
|
||||
handleLoadMore,
|
||||
} = useSelectLoadMore({
|
||||
fetchApi: (p) => getEmployeeList({ ...p, kind: 1 }),
|
||||
});
|
||||
defineOptions({ name: "MemberSelector" });
|
||||
|
||||
// emit数据
|
||||
const emit = defineEmits(["save-member"]);
|
||||
const listRef = ref(null);
|
||||
const drawerVisible = defineModel("visible", { type: Boolean });
|
||||
const searchQuery = ref("");
|
||||
const currentHoverId = ref("");
|
||||
// 模拟数据
|
||||
const memberList = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: "程彬",
|
||||
dept: "数字化管理部",
|
||||
type: "active-user",
|
||||
active: true,
|
||||
// 搜索部门的数据
|
||||
const memberSearchQuery = ref([]);
|
||||
|
||||
const memberList = defineModel<any[]>("dataList", { default: () => [] });
|
||||
|
||||
const {
|
||||
displayData: displayMemberList,
|
||||
loadMore: handleLoadMoreList,
|
||||
noMore: noMoreLocal
|
||||
} = useLocalManager(() => memberList.value, 20);
|
||||
|
||||
// 动态返回options数据
|
||||
const selectOptions = computed(() => {
|
||||
const remoteData = options.value || [];
|
||||
const selectedData = memberList.value || [];
|
||||
|
||||
// 创建一个 Map 用于去重,以 id 为 key
|
||||
const optionMap = new Map();
|
||||
|
||||
// 1. 先把远程搜索结果放进去
|
||||
remoteData.forEach(item => optionMap.set(item.id, item));
|
||||
|
||||
// 2. 再把已选择的数据放进去
|
||||
selectedData.forEach(item => {
|
||||
if (!optionMap.has(item.id)) {
|
||||
optionMap.set(item.id, item);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(optionMap.values());
|
||||
});
|
||||
// 监听数据变化
|
||||
watch(
|
||||
() => memberList.value,
|
||||
(newVal) => {
|
||||
memberSearchQuery.value = [...newVal];
|
||||
},
|
||||
{ id: 2, name: "王建国", dept: "集团财务部", type: "normal", active: false },
|
||||
{ id: 3, name: "新成员", dept: "待分配部门", type: "normal", active: false },
|
||||
{ id: 4, name: "新成员", dept: "待分配部门", type: "normal", active: false },
|
||||
{ id: 5, name: "新成员", dept: "待分配部门", type: "normal", active: false },
|
||||
{ id: 6, name: "新成员", dept: "待分配部门", type: "normal", active: false },
|
||||
{ id: 7, name: "新成员", dept: "待分配部门", type: "normal", active: false },
|
||||
{ id: 8, name: "新成员", dept: "待分配部门", type: "normal", active: false },
|
||||
{ id: 9, name: "新成员", dept: "待分配部门", type: "normal", active: false },
|
||||
{ id: 10, name: "新成员", dept: "待分配部门", type: "normal", active: false },
|
||||
]);
|
||||
|
||||
const addNewMember = () => {
|
||||
const newId = Date.now();
|
||||
const newMember = {
|
||||
id: newId,
|
||||
name: "新成员",
|
||||
dept: "待分配部门",
|
||||
type: "normal",
|
||||
active: true,
|
||||
};
|
||||
|
||||
memberList.value.push(newMember);
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
// 添加成员 (添加成员)
|
||||
const changeMember = (value) => {
|
||||
memberList.value = value;
|
||||
nextTick(() => {
|
||||
if (listRef.value) {
|
||||
listRef.value.scrollTo({
|
||||
top: listRef.value.scrollHeight,
|
||||
top: listRef.value?.wrapRef.scrollHeight,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 搜索过滤逻辑
|
||||
const filteredMembers = computed(() => {
|
||||
const query = searchQuery.value.trim().toLowerCase();
|
||||
if (!query) return memberList.value;
|
||||
return memberList.value.filter(
|
||||
(item) =>
|
||||
item.name.toLowerCase().includes(query) ||
|
||||
item.dept.toLowerCase().includes(query)
|
||||
);
|
||||
});
|
||||
|
||||
// 移除成员方法
|
||||
const handleRemove = (id) => {
|
||||
memberList.value = memberList.value.filter((m) => m.id !== id);
|
||||
const handleRemove = async (id) => {
|
||||
memberList.value = memberList.value.filter((item) => item.id !== id);
|
||||
};
|
||||
|
||||
// 切换选中状态(模拟点击高亮)
|
||||
const selectMember = (item) => {
|
||||
// 如果需要单选高亮,可以先重置所有 active
|
||||
// memberList.value.forEach(m => m.active = false)
|
||||
item.active = !item.active;
|
||||
// 前端加载更多 触底操作
|
||||
const loadMoreList = () => {
|
||||
handleLoadMoreList();
|
||||
};
|
||||
// 保存成员方法
|
||||
const saveMember = async () => {
|
||||
try {
|
||||
await emit("save-member");
|
||||
cancelMember();
|
||||
} catch (error) {
|
||||
console.log("add member error", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 取消成员方法
|
||||
const cancelMember = () => {
|
||||
drawerVisible.value = false;
|
||||
memberSearchQuery.value = [];
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@@ -153,20 +229,22 @@ const selectMember = (item) => {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0; // 关键:允许子元素溢出滚动
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
padding: 20px 24px;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
background-color: #f8fafc;
|
||||
:deep(.el-input) {
|
||||
--el-input-border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.member-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 0 14px 20px;
|
||||
box-sizing: border-box;
|
||||
.list-fade-enter-active,
|
||||
.list-fade-leave-active {
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
@@ -190,14 +268,16 @@ const selectMember = (item) => {
|
||||
align-items: center;
|
||||
padding: 10px 16px;
|
||||
margin-bottom: 4px;
|
||||
border-radius: 8px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
--mj-avatar-bg: #d1e9ff;
|
||||
--mj-text-color: #409eff;
|
||||
background-color: #f0f7ff;
|
||||
background-color: #f8fafc;
|
||||
border-color: #f5f5f5;
|
||||
.member-action {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -225,6 +305,22 @@ const selectMember = (item) => {
|
||||
}
|
||||
}
|
||||
|
||||
.select-item {
|
||||
--mj-avatar-bg: #f2f3f5;
|
||||
--mj-text-color: #abb0b8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
&:hover {
|
||||
--mj-avatar-bg: #d1e9ff;
|
||||
--mj-text-color: #409eff;
|
||||
}
|
||||
.select-item-name {
|
||||
width: calc(100% - 34px);
|
||||
}
|
||||
}
|
||||
|
||||
.el-drawer__footer {
|
||||
padding: 16px 24px;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ defineOptions({ name: "StageBreadcrumbs" });
|
||||
|
||||
const { title,styleClass } = defineProps<{
|
||||
title: string;
|
||||
styleClass: string;
|
||||
styleClass?: string;
|
||||
}>();
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
49
src/hooks/useLocalManager.ts
Normal file
49
src/hooks/useLocalManager.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { ref, shallowRef, computed, watch } from 'vue';
|
||||
|
||||
export function useLocalManager<T>(initialDataGetter: () => T[], pageSize = 20) {
|
||||
// 1. 全量数据池(使用 shallowRef 保证 1W 条数据操作不卡顿)
|
||||
const fullData = shallowRef<T[]>([]);
|
||||
|
||||
// 2. 内部展示条数
|
||||
const displayCount = ref(pageSize);
|
||||
|
||||
// 3. 监听初始数据的变化(处理后端异步返回)
|
||||
watch(initialDataGetter, (newVal) => {
|
||||
if (Array.isArray(newVal)) {
|
||||
fullData.value = [...newVal];
|
||||
displayCount.value = pageSize;
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 4. 切片数据(UI 渲染对象)
|
||||
const displayData = computed(() => {
|
||||
return fullData.value.slice(0, displayCount.value);
|
||||
});
|
||||
|
||||
const noMore = computed(() => displayCount.value >= fullData.value.length);
|
||||
|
||||
// 加载更多
|
||||
const loadMore = () => {
|
||||
if (noMore.value) return;
|
||||
displayCount.value += pageSize;
|
||||
};
|
||||
|
||||
// 移除
|
||||
const remove = (id: string | number, key = 'id') => {
|
||||
fullData.value = fullData.value.filter(item => (item as any)[key] !== id);
|
||||
};
|
||||
|
||||
// 新增
|
||||
const add = (item: T) => {
|
||||
fullData.value = [item, ...fullData.value];
|
||||
};
|
||||
|
||||
return {
|
||||
fullData,
|
||||
displayData,
|
||||
loadMore,
|
||||
noMore,
|
||||
remove,
|
||||
add
|
||||
};
|
||||
}
|
||||
77
src/hooks/useSelectLoadMore.ts
Normal file
77
src/hooks/useSelectLoadMore.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { reactive, toRefs } from "vue";
|
||||
import { debounce } from "lodash-es";
|
||||
|
||||
export function useSelectLoadMore(config: any) {
|
||||
const { fetchApi, pageSize = 10, delay = 300 } = config;
|
||||
|
||||
const state = reactive({
|
||||
options: [] as any[],
|
||||
remoteLoading: false,
|
||||
loadMore: false,
|
||||
noMore: false,
|
||||
pageNo: 1,
|
||||
query: "",
|
||||
});
|
||||
|
||||
const executeFetch = async (isRefresh: boolean) => {
|
||||
// 拦截逻辑
|
||||
if (!isRefresh && (state.loadMore || state.noMore)) return;
|
||||
if (isRefresh) {
|
||||
state.pageNo = 1;
|
||||
state.noMore = false;
|
||||
state.remoteLoading = true;
|
||||
} else {
|
||||
state.loadMore = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetchApi({
|
||||
pageNo: state.pageNo,
|
||||
pageSize: pageSize,
|
||||
keyword: state.query,
|
||||
});
|
||||
|
||||
const newList = res || [];
|
||||
|
||||
if (isRefresh) {
|
||||
state.options = newList;
|
||||
} else {
|
||||
// 滚动加载:追加数据,不置空,不触发 select 的 loading
|
||||
state.options.push(...newList);
|
||||
}
|
||||
|
||||
// 探测 noMore
|
||||
if (newList.length < pageSize) {
|
||||
state.noMore = true;
|
||||
} else {
|
||||
state.pageNo++;
|
||||
}
|
||||
} finally {
|
||||
// 请求结束,关闭所有状态
|
||||
state.remoteLoading = false;
|
||||
state.loadMore = false;
|
||||
}
|
||||
};
|
||||
|
||||
const remoteMethod = debounce(async (query: string) => {
|
||||
state.query = query;
|
||||
await executeFetch(true);
|
||||
}, delay);
|
||||
|
||||
const handleLoadMore = async () => {
|
||||
await executeFetch(false);
|
||||
};
|
||||
|
||||
const onVisibleChange = async (visible: boolean) => {
|
||||
if (visible && state.options.length === 0) {
|
||||
await executeFetch(true);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
remoteMethod,
|
||||
handleLoadMore,
|
||||
onVisibleChange,
|
||||
};
|
||||
}
|
||||
@@ -323,36 +323,6 @@ const handleFetchSearch = async (keyword, signal) => {
|
||||
selectedUsersCache.forEach(userList => {
|
||||
userList.forEach(u => selectedIds.add(u.id));
|
||||
});
|
||||
// FIXME:模拟接口返回的数据人员信息
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
const allEmployees = [
|
||||
{
|
||||
id:1,
|
||||
name:"张三",
|
||||
},
|
||||
{
|
||||
id:2,
|
||||
name:"李四",
|
||||
},
|
||||
{
|
||||
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,
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
</el-form>
|
||||
|
||||
<div class="form-footer">
|
||||
<span>© 2025 智视界保留所有权利</span>
|
||||
<span>© 2025 智服链保留所有权利</span>
|
||||
<div class="links">
|
||||
<el-link underline="never">隐私政策</el-link>
|
||||
<el-link underline="never">服务条款</el-link>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
top="10vh"
|
||||
top="6vh"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" label-position="top" :rules="rules">
|
||||
<div class="row-flex">
|
||||
@@ -16,44 +16,84 @@
|
||||
<el-input v-model="form.name" placeholder="请输入角色名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="角色编码" prop="code">
|
||||
<el-input v-model="form.code" placeholder="ROLE_CODE" />
|
||||
<el-input
|
||||
v-model="form.code"
|
||||
placeholder="ROLE_CODE"
|
||||
:disabled="detailId ? true : false"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<el-form-item label="角色类型">
|
||||
<el-form-item label="角色类型" prop="type">
|
||||
<div class="full-width-radio">
|
||||
<BaseSegmented v-model="form.type" :options="PermissionManage.roleTypeOptions" />
|
||||
<BaseSegmented
|
||||
v-model="form.type"
|
||||
:options="PermissionManage.roleTypeOptions"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="isOrgRelated">
|
||||
<div class="feature-card light-green">
|
||||
<div class="info">
|
||||
<div class="title">组织架构</div>
|
||||
<div class="desc">是否将该角色与系统组织架构体系进行深度关联</div>
|
||||
</div>
|
||||
<el-switch v-model="form.isOrgRelated" style="--el-switch-on-color: #13ce66; --el-switch-off-color: #CAD5E2" :active-value="1" :inactive-value="0" />
|
||||
<el-switch
|
||||
v-model="form.isOrgRelated"
|
||||
style="
|
||||
--el-switch-on-color: #13ce66;
|
||||
--el-switch-off-color: #cad5e2;
|
||||
"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="关联岗位">
|
||||
<el-form-item label="关联岗位" prop="positionIds">
|
||||
<el-select
|
||||
v-model="form.positionIds"
|
||||
placeholder="请选择关联岗位"
|
||||
multiple
|
||||
filterable
|
||||
remote
|
||||
:remote-method="remoteMethod"
|
||||
:loading="remoteLoading"
|
||||
collapse-tags
|
||||
:fit-input-width="true"
|
||||
:teleported="false"
|
||||
v-select-more="handleLoadMore"
|
||||
>
|
||||
<el-option :label="item.label" :value="item.value" v-for="(item,index) in positionList" :key="index"/>
|
||||
<el-option
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
v-for="(item, index) in options"
|
||||
:key="item.id"
|
||||
/>
|
||||
<el-option v-if="loadMore" disabled value="more" class="mj-select-dropdown-loading">
|
||||
<div class="status-container">
|
||||
<el-icon v-if="loadMore" class="is-loading" :size="20"
|
||||
><Loading
|
||||
/></el-icon>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="status">
|
||||
<div class="feature-card light-blue">
|
||||
<div class="info">
|
||||
<div class="title">启用状态</div>
|
||||
<div class="desc">控制该角色及其下属权限是否立即生效</div>
|
||||
</div>
|
||||
<el-switch v-model="form.status" :active-value="1" :inactive-value="0"/>
|
||||
<el-switch
|
||||
v-model="form.status"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注说明">
|
||||
<el-form-item label="备注说明" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
@@ -67,7 +107,9 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="cancelRoles" text>取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit(formRef)">确认创建</el-button>
|
||||
<el-button type="primary" @click="handleSubmit(formRef)"
|
||||
>确认创建</el-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -76,46 +118,61 @@
|
||||
<script lang="ts" setup>
|
||||
import BaseSegmented from "./baseSegmented.vue";
|
||||
import { PermissionManage } from "@/dict";
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { Loading } from "@element-plus/icons-vue";
|
||||
import type { FormInstance, FormRules } from "element-plus";
|
||||
import {
|
||||
addRole,
|
||||
getRoleDetail
|
||||
updateRole,
|
||||
getRoleDetail,
|
||||
} from "@/api/stage/permission/index.ts";
|
||||
import { getEnterprisePosition } from "@/api/stage/organization";
|
||||
import { useSelectLoadMore } from "@/hooks/useSelectLoadMore";
|
||||
defineOptions({ name: "addRoles" });
|
||||
const dialogVisible = defineModel("visible", { type: Boolean, default: false });
|
||||
const props = defineProps({
|
||||
detailId: {
|
||||
type: [String,Number],
|
||||
default: '',
|
||||
}
|
||||
type: [String, Number],
|
||||
default: "",
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
options,
|
||||
remoteLoading,
|
||||
loadMore,
|
||||
noMore,
|
||||
remoteMethod,
|
||||
handleLoadMore,
|
||||
} = useSelectLoadMore({
|
||||
fetchApi: (p) => getEnterprisePosition({ ...p }),
|
||||
});
|
||||
|
||||
const computedTitle = computed(() => {
|
||||
return props.detailId ? "编辑系统角色" : "新增系统角色";
|
||||
});
|
||||
const emit = defineEmits(["on-success"]);
|
||||
const positionList = ref([]);
|
||||
const positionList = ref([]); //岗位列表数据
|
||||
const formRef = ref<FormInstance>(null);
|
||||
const form = reactive<RuleForm>({
|
||||
name: "",
|
||||
code: "",
|
||||
type: "DEFAULT",
|
||||
isOrgRelated: 0, // 是否和组织架构深度关联
|
||||
post: "",
|
||||
positionIds: "",
|
||||
status: 1,
|
||||
remark: "",
|
||||
});
|
||||
|
||||
|
||||
const rules = reactive<FormInstance<RuleForm>>({
|
||||
name: [
|
||||
{ required: true, message: "请输入角色名称", trigger: "blur" }
|
||||
],
|
||||
code: [
|
||||
{ required: true, message: "请输入角色编码", trigger: "blur" }
|
||||
]
|
||||
})
|
||||
name: [{ required: true, message: "请输入角色名称", trigger: "blur" }],
|
||||
code: [{ required: true, message: "请输入角色编码", trigger: "blur" }],
|
||||
});
|
||||
|
||||
// 分页信息
|
||||
const pageInfo = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
const cancelRoles = () => {
|
||||
formRef.value && formRef.value.resetFields();
|
||||
@@ -123,39 +180,44 @@ const cancelRoles = () => {
|
||||
};
|
||||
|
||||
// 获取角色详情
|
||||
const getRoleShowDetail = async (val) =>{
|
||||
const getRoleShowDetail = async (val) => {
|
||||
try {
|
||||
const res = await getRoleDetail(props.detailId);
|
||||
Object.assign(form,res);
|
||||
Object.assign(form, res, { isOrgRelated: res.isOrgRelated ? 1 : 0 });
|
||||
} catch (error) {
|
||||
console.log('getRoleDetail error:',error);
|
||||
console.log("getRoleDetail error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// 监听详情
|
||||
watch(()=>dialogVisible.value,(val) => {
|
||||
if(val && props.detailId){
|
||||
watch(
|
||||
() => dialogVisible.value,
|
||||
(val) => {
|
||||
if (val && props.detailId) {
|
||||
getRoleShowDetail();
|
||||
}
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
const handleSubmit = (formEl:FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
// 新增编辑角色(仅系统类型可新增)
|
||||
const handleSubmit = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
const res = await addRole(form);
|
||||
const res = props.detailId
|
||||
? await updateRole(form)
|
||||
: await addRole(form);
|
||||
ElMessage.success("操作成功");
|
||||
cancelRoles();
|
||||
emit('on-success');
|
||||
emit("on-success");
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
} else {
|
||||
console.log('error submit!')
|
||||
console.log("error submit!");
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -200,7 +262,7 @@ const handleSubmit = (formEl:FormInstance | undefined) => {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
.full-width-radio{
|
||||
.full-width-radio {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -211,7 +273,7 @@ const handleSubmit = (formEl:FormInstance | undefined) => {
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 22px;
|
||||
flex: 1;
|
||||
|
||||
.info {
|
||||
.title {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
class="auto-expand-input"
|
||||
:prefix-icon="'Search'"
|
||||
v-model="searchVal"
|
||||
@keyup.enter="fetchTableData"
|
||||
@keyup.enter="onRefresh"
|
||||
></el-input>
|
||||
</div>
|
||||
<div class="mj-dict-actions-right">
|
||||
@@ -27,7 +27,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</stageBreadcrumbs>
|
||||
|
||||
<template v-if="[1, 2].includes(activeTab)">
|
||||
<!-- 表格 -->
|
||||
<CommonTable
|
||||
ref="tableRef"
|
||||
@@ -61,18 +61,29 @@
|
||||
link
|
||||
icon="UserFilled"
|
||||
type="primary"
|
||||
@click.stop="addMember"
|
||||
@click.stop="addMember(row)"
|
||||
>{{ row.memberCount }}</el-button
|
||||
>
|
||||
</template>
|
||||
</CommonTable>
|
||||
</template>
|
||||
<!-- 前线配置模块内容 -->
|
||||
<template v-else> 权限配置模块 </template>
|
||||
</div>
|
||||
|
||||
<!-- 成员管理 -->
|
||||
<member-selector v-model:visible="showMember" />
|
||||
<member-selector
|
||||
v-model:visible="showMember"
|
||||
v-model:dataList="memberList"
|
||||
@save-member="handleSaveMember"
|
||||
/>
|
||||
|
||||
<!-- 新增角色 -->
|
||||
<add-roles v-model:visible="showRoles" @on-success="onRefresh" :detail-id="detailId"/>
|
||||
<add-roles
|
||||
v-model:visible="showRoles"
|
||||
@on-success="onRefresh"
|
||||
:detail-id="detailId"
|
||||
/>
|
||||
|
||||
<!-- 权限抽屉 -->
|
||||
<permission-drawer v-model:visible="showPermission" />
|
||||
@@ -92,33 +103,38 @@ import {
|
||||
addRole,
|
||||
deleteRole,
|
||||
enableRole,
|
||||
disableRole
|
||||
disableRole,
|
||||
getRoleMemberList,
|
||||
copyRolePermission
|
||||
} from "@/api/stage/permission/index.ts";
|
||||
import { useLocalManager } from '@/hooks/useLocalManager';
|
||||
defineOptions({ name: "PermissionManagement" });
|
||||
const activeTab = ref(1);
|
||||
const tableRef = ref(null);
|
||||
const searchVal = ref("");
|
||||
const dataList = ref([]);
|
||||
const memberList = ref([]); // 获取成员角色列表
|
||||
const total = ref(0);
|
||||
const showMember = ref(false);
|
||||
const showRoles = ref(false);
|
||||
const detailId = ref('');
|
||||
const detailId = ref("");
|
||||
const showPermission = ref(false);
|
||||
const tabList = [
|
||||
{
|
||||
label: "角色与权限",
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
label: "用户管理",
|
||||
id: 2,
|
||||
},
|
||||
{
|
||||
label: "权限配置",
|
||||
id: 3,
|
||||
},
|
||||
];
|
||||
|
||||
const {
|
||||
fullData,
|
||||
displayData,
|
||||
loadMore,
|
||||
noMore,
|
||||
remove,
|
||||
add
|
||||
} = useLocalManager(()=>memberList.value);
|
||||
|
||||
const roleColumns = [
|
||||
{
|
||||
prop: "id",
|
||||
@@ -129,7 +145,12 @@ const roleColumns = [
|
||||
return `#${formatIndex(val.id)}`;
|
||||
},
|
||||
},
|
||||
{ prop: "name", label: "角色名称", align: "center",showOverflowTooltip:true },
|
||||
{
|
||||
prop: "name",
|
||||
label: "角色名称",
|
||||
align: "center",
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
prop: "memberCount",
|
||||
label: "成员数量",
|
||||
@@ -185,14 +206,14 @@ const roleColumns = [
|
||||
prop: "actions",
|
||||
label: "操作",
|
||||
align: "right",
|
||||
fixed:"right",
|
||||
fixed: "right",
|
||||
width: "200",
|
||||
actions: [
|
||||
{
|
||||
label: "权限",
|
||||
type: "primary",
|
||||
link: true,
|
||||
permission: ["edit"],
|
||||
permission: ["*"],
|
||||
onClick: (row) => {
|
||||
showPermission.value = true;
|
||||
},
|
||||
@@ -202,7 +223,15 @@ const roleColumns = [
|
||||
type: "default",
|
||||
link: true,
|
||||
permission: ["edit"],
|
||||
onClick: (row) => {},
|
||||
onClick: async (row) => {
|
||||
try {
|
||||
await copyRolePermission(row.id);
|
||||
ElMessage.success("复制成功");
|
||||
tableRef.value.refresh();
|
||||
} catch (error) {
|
||||
console.log('copy error',error);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "编辑",
|
||||
@@ -220,80 +249,27 @@ const roleColumns = [
|
||||
link: true,
|
||||
permission: ["delete"],
|
||||
disabledFn: (row) => {
|
||||
return (row.type !== 'SYSTEM')
|
||||
return row.type !== "SYSTEM";
|
||||
},
|
||||
onClick: async (row) => {
|
||||
ElMessageBox.confirm("确定要删除吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
})
|
||||
.then(() => {
|
||||
try {
|
||||
deleteRole(row.id).then(() => {
|
||||
ElMessage.success("删除成功");
|
||||
tableRef.value.refresh();
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('error',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;
|
||||
})
|
||||
.catch(() => {
|
||||
console.log("cancel");
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -303,14 +279,19 @@ const userColumns = [
|
||||
const tableColumns = computed(() => {
|
||||
const columns = {
|
||||
1: roleColumns,
|
||||
2: userColumns,
|
||||
}[activeTab.value];
|
||||
return columns;
|
||||
});
|
||||
|
||||
// 当前成员数量
|
||||
const addMember = () => {
|
||||
const addMember = (row) => {
|
||||
showMember.value = true;
|
||||
getMemberList({ id: row.id });
|
||||
};
|
||||
|
||||
// 保存成员数量
|
||||
const handleSaveMember = async () => {
|
||||
console.log("保存成员数量",memberList.value);
|
||||
};
|
||||
|
||||
// 刷新列表
|
||||
@@ -319,10 +300,10 @@ const onRefresh = () => {
|
||||
};
|
||||
|
||||
// 监听Tabs变化
|
||||
watch(activeTab,()=>{
|
||||
watch(activeTab, () => {
|
||||
dataList.value = [];
|
||||
tableRef.value && tableRef.value.reset();
|
||||
})
|
||||
});
|
||||
|
||||
// 权限
|
||||
const handleDictStatus = async (row) => {
|
||||
@@ -335,52 +316,46 @@ const handleDictStatus = async (row) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 请求接口获取
|
||||
const getTableData = async (params) => {
|
||||
console.log('每次请求的分页:',params);
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
return {
|
||||
records: [
|
||||
{
|
||||
id: 1,
|
||||
name: "测试角色名称",
|
||||
memberCount: 10,
|
||||
permissionCount: 22,
|
||||
status: 1,
|
||||
type: "SYSTEM",
|
||||
positionIds: "",
|
||||
updateTime: 1767878508824,
|
||||
updater: "更新人",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 正式请求
|
||||
const queryParams = {
|
||||
...(searchVal.value && { name:searchVal.value }),
|
||||
status:1,
|
||||
type:1,
|
||||
pageNo:1,
|
||||
pageSize:10
|
||||
}
|
||||
...(searchVal.value && { name: searchVal.value }),
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
};
|
||||
try {
|
||||
const res = await getRoleList(queryParams);
|
||||
return res;
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
// TODO: 后面会修改成全部获取获取成员角色列表 (分页获取)
|
||||
const getMemberList = async (params) => {
|
||||
// 获取成员角色列表
|
||||
const queryParams = {
|
||||
roleId: "",
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
};
|
||||
try {
|
||||
const res = await getRoleMemberList(params.id);
|
||||
memberList.value = res.records;
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取
|
||||
const checkRolesText = computed(() => {
|
||||
const btnText = {
|
||||
1: "新增角色",
|
||||
2: "新增用户",
|
||||
}[activeTab.value];
|
||||
|
||||
const placeholder = {
|
||||
1: "搜索角色名称...",
|
||||
2: "搜索用户姓名/账号...",
|
||||
}[activeTab.value];
|
||||
|
||||
return {
|
||||
@@ -389,9 +364,6 @@ const checkRolesText = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
// 请求数据信息
|
||||
const fetchTableData = () => {};
|
||||
|
||||
// 新增角色
|
||||
const addBtnClick = () => {
|
||||
if (activeTab.value === 1) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
type RouteRecordRaw,
|
||||
} from "vue-router";
|
||||
import { useUserStore } from "@/store";
|
||||
import { getRouteMenus } from "@/api";
|
||||
import { getUserInfo } from "@/api";
|
||||
import TokenManager from "@/utils/storage";
|
||||
|
||||
import Login from "@/pages/Login/index.vue";
|
||||
@@ -30,7 +30,6 @@ const asyncRoutes: RouteRecordRaw[] = [
|
||||
path: "/",
|
||||
name: "Layout",
|
||||
component: HomeView,
|
||||
// redirect: '/home',
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
@@ -165,7 +164,8 @@ const addDynamicRoutes = async () => {
|
||||
// 从后端获取路由菜单数据 (这边需要区分 后台的菜单 和用户的菜单)
|
||||
let allRoutes: any[] = [];
|
||||
if (userStore.isBackendUser) {
|
||||
// const backendResponse = await getRouteMenus();
|
||||
const backendResponses = await getUserInfo();
|
||||
console.log("获取当前的菜单数据信息:",backendResponses);
|
||||
const backendResponse = [];
|
||||
allRoutes = [
|
||||
{
|
||||
@@ -257,7 +257,6 @@ router.beforeEach(async (to, _from, next) => {
|
||||
try {
|
||||
// 加载动态路由
|
||||
await addDynamicRoutes();
|
||||
console.log("当前完整路由表:", router.getRoutes());
|
||||
const redirect = to.query.redirect as string;
|
||||
|
||||
if (to.path === "/" || to.path === "/login") {
|
||||
|
||||
@@ -18,7 +18,7 @@ body {
|
||||
--mj-border-color:#{$mj-border-color};
|
||||
--mj-padding-standard:#{$mj-padding-standard};
|
||||
--mj-popper-radius: 8px;
|
||||
--el-color-primary:#2b65f6;
|
||||
--el-color-primary: #2b65f6;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ body {
|
||||
// 字典状态全局样式
|
||||
.mj-status-dot {
|
||||
cursor: pointer;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
@@ -102,7 +103,8 @@ body {
|
||||
|
||||
.filter-item {
|
||||
margin-bottom: 20px;
|
||||
label{
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
@@ -112,7 +114,7 @@ body {
|
||||
}
|
||||
|
||||
.custom-select {
|
||||
--el-border-color:transparent;
|
||||
--el-border-color: transparent;
|
||||
width: 100%;
|
||||
background-color: #f5f7fa;
|
||||
box-shadow: none;
|
||||
@@ -128,6 +130,7 @@ body {
|
||||
font-weight: bold;
|
||||
border-radius: 6px;
|
||||
margin-top: 10px;
|
||||
|
||||
&:hover {
|
||||
background-color: #407eff;
|
||||
border-color: #407eff;
|
||||
@@ -148,27 +151,48 @@ body {
|
||||
|
||||
// 卡片的全局样式
|
||||
|
||||
.mj-card-container{
|
||||
.mj-card-container {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
// 默认:一行 5 列
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
|
||||
// 1400px 以下:觉得 5 个太挤,切到一行 4 列
|
||||
@media (max-width: 1400px) {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
// 1100px 以下 (iPad横屏区间):一行 3 列
|
||||
@media (max-width: 1100px) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
// 768px 以下 (iPad竖屏):一行 2 列
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
// 480px 以下 (手机端):一行 1 列
|
||||
@media (max-width: 480px) {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// select下拉搜索loading全局公用样式
|
||||
.mj-select-dropdown-loading {
|
||||
cursor: default;
|
||||
background-color: transparent;
|
||||
|
||||
.status-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #909399;
|
||||
font-size: 13px;
|
||||
height: 34px;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,16 @@
|
||||
import { useUserStore } from "@/store";
|
||||
|
||||
function permissionDirective(el: HTMLElement,binding:any) {
|
||||
import { throttle } from "lodash-es";
|
||||
function permissionDirective(el: HTMLElement, binding: any) {
|
||||
const appStore = useUserStore();
|
||||
const userPermissions = appStore.role;
|
||||
let requiredPermissions = binding.value;
|
||||
// 使用特殊值 '*' 表示跳过权限检查
|
||||
const isSkipCheck =
|
||||
requiredPermissions === "*" ||
|
||||
(Array.isArray(requiredPermissions) && requiredPermissions.includes("*"));
|
||||
if (isSkipCheck) {
|
||||
return;
|
||||
}
|
||||
if (typeof requiredPermissions === "string") {
|
||||
requiredPermissions = [requiredPermissions];
|
||||
}
|
||||
@@ -25,8 +32,48 @@ const permission = {
|
||||
},
|
||||
};
|
||||
|
||||
const selectMore = {
|
||||
mounted(el, binding) {
|
||||
const callback = binding.value;
|
||||
const onScroll = throttle(
|
||||
function () {
|
||||
const { scrollTop, scrollHeight, clientHeight } = this;
|
||||
const isBottom = scrollHeight - scrollTop <= clientHeight + 10;
|
||||
|
||||
if (isBottom) {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
200,
|
||||
{ trailing: true }
|
||||
);
|
||||
// 关键:轮询寻找滚动容器,因为 mounted 时下拉框可能还没渲染
|
||||
const interval = setInterval(() => {
|
||||
// 兼容 teleport 情况,如果 el 找不到,尝试寻找 popper
|
||||
const wrapper = el.querySelector(".el-scrollbar__wrap");
|
||||
|
||||
if (wrapper) {
|
||||
wrapper.addEventListener("scroll", onScroll);
|
||||
el._scrollWrapper = wrapper;
|
||||
el._onScroll = onScroll;
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 150);
|
||||
|
||||
setTimeout(() => clearInterval(interval), 10000);
|
||||
},
|
||||
|
||||
beforeUnmount(el) {
|
||||
if (el._scrollWrapper && el._onScroll) {
|
||||
el._scrollWrapper.removeEventListener("scroll", el._onScroll);
|
||||
el._onScroll.cancel?.();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const directives = {
|
||||
permission,
|
||||
selectMore,
|
||||
};
|
||||
|
||||
export default (app: any) => {
|
||||
|
||||
@@ -8,6 +8,6 @@ export function getImageUrl(url: string) {
|
||||
|
||||
|
||||
// 设置index前缀 如:001 010 100种 默认兼容3位数
|
||||
export const formatIndex = (index,padNum=3) => {
|
||||
export const formatIndex = (index,padNum?:number=3) => {
|
||||
return String(index + 1).padStart(padNum, '0');
|
||||
};
|
||||
Reference in New Issue
Block a user