fix:联调权限页面模块 优化全局table组件

This commit is contained in:
liangdong
2026-01-12 19:28:26 +08:00
parent 79e16909f0
commit a4bb81dc0a
17 changed files with 768 additions and 386 deletions

1
components.d.ts vendored
View File

@@ -13,6 +13,7 @@ declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
AutoTooltip: typeof import('./src/components/autoTooltip/index.vue')['default'] AutoTooltip: typeof import('./src/components/autoTooltip/index.vue')['default']
CardItem: typeof import('./src/components/cardItem/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'] Comment: typeof import('./src/components/comment/index.vue')['default']
CommonFilter: typeof import('./src/components/commonFilter/index.vue')['default'] CommonFilter: typeof import('./src/components/commonFilter/index.vue')['default']
DynamicSvgIcon: typeof import('./src/components/dynamicSvgIcon/index.vue')['default'] DynamicSvgIcon: typeof import('./src/components/dynamicSvgIcon/index.vue')['default']

View File

@@ -5,6 +5,11 @@ export const getRouteMenus = () => {
return request.get('/auth/v1/backend/menu'); 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 }) => { export const login = (data: { username: string; password: string }) => {
return request.post('/auth/oauth2/token', data,{ return request.post('/auth/oauth2/token', data,{
@@ -12,4 +17,13 @@ export const login = (data: { username: string; password: string }) => {
'Content-Type': 'application/x-www-form-urlencoded' 'Content-Type': 'application/x-www-form-urlencoded'
} }
}); });
};
/**员工公用接口*/
// 根据员工关键字查询员工
export const getEmployeeList = (params: any) => {
return request.get('/auth/v1/employee', params);
}; };

View File

@@ -18,6 +18,12 @@ interface addDataProps{
wxWork:wxWorkProps; wxWork:wxWorkProps;
} }
interface defaultProps{
keyword?:string;
pageNo:number;
pageSize:number;
}
// 查询企业 // 查询企业
export const getEnterprise = (params: paramsProps) =>{ export const getEnterprise = (params: paramsProps) =>{
@@ -55,4 +61,9 @@ export const getEnterpriseDetail = () => {
// 加载部门详情 // 加载部门详情
export const getEnterpriseOrgDetail = (departmentId:string) => { export const getEnterpriseOrgDetail = (departmentId:string) => {
return request.get(`/auth/v1/backend/enterprise/department/${departmentId}`); return request.get(`/auth/v1/backend/enterprise/department/${departmentId}`);
}
// 获取企业信息职位
export const getEnterprisePosition = (params:defaultProps) => {
return request.get(`/auth/v1/backend/enterprise/position`,params);
} }

View File

@@ -26,24 +26,24 @@ export const deleteRole = (id: string) => {
return request.delete(`/auth/v1/backend/role/${id}`); 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) => { 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) => { 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);
} }
/**------------------------角色权限相关---------------------------**/ /**------------------------角色权限相关---------------------------**/

View 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>

View File

@@ -22,33 +22,78 @@
<div class="drawer-body-container"> <div class="drawer-body-container">
<div class="search-bar"> <div class="search-bar">
<el-input <el-select
v-model="searchQuery" v-model="memberSearchQuery"
placeholder="按姓名或部门搜索成员..." placeholder="按姓名搜索成员"
:prefix-icon="Search" multiple
clearable filterable
/> remote
<el-button type="primary" plain @click="addNewMember" :remote-method="remoteMethod"
>添加成员</el-button :loading="remoteLoading"
collapse-tags
:teleported="false"
:fit-input-width="true"
value-key="id"
v-select-more="handleLoadMore"
@change="changeMember"
> >
<el-option
v-for="item in selectOptions"
:key="item.id"
: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> </div>
<div class="member-list" ref="listRef"> <el-scrollbar
height="calc(100vh - 216px)"
@end-reached="loadMoreList"
ref="listRef"
>
<div class="member-list">
<transition-group name="list-fade"> <transition-group name="list-fade">
<div <div v-for="item in displayMemberList" :key="item.id" class="member-item">
v-for="item in filteredMembers"
:key="item.id"
class="member-item"
@click="selectMember(item)"
>
<name-avatar <name-avatar
:size="30" :size="30"
:name="item.name" :name="item.name"
:src="item.avatar"
:bgColor="'var(--mj-avatar-bg)'" :bgColor="'var(--mj-avatar-bg)'"
:avatarTextColor="'var(--mj-text-color)'" :avatarTextColor="'var(--mj-text-color)'"
/> />
<div class="member-info"> <div class="member-info">
<div class="name">{{ item.name }}</div> <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>
<div class="member-action"> <div class="member-action">
<el-button link type="info" @click.stop="handleRemove(item.id)" <el-button link type="info" @click.stop="handleRemove(item.id)"
@@ -58,6 +103,7 @@
</div> </div>
</transition-group> </transition-group>
</div> </div>
</el-scrollbar>
</div> </div>
<template #footer> <template #footer>
@@ -67,8 +113,10 @@
<span class="count"> {{ memberList.length }} 名成员</span> <span class="count"> {{ memberList.length }} 名成员</span>
</div> </div>
<div class="actions"> <div class="actions">
<el-button link @click="drawerVisible = false">取消</el-button> <el-button link @click="cancelMember">取消</el-button>
<el-button type="primary" class="btn-confirm">确认保存变更</el-button> <el-button type="primary" class="btn-confirm" @click="saveMember"
>确认保存变更</el-button
>
</div> </div>
</div> </div>
</template> </template>
@@ -77,74 +125,102 @@
<script setup lang="ts"> <script setup lang="ts">
import { Search } from "@element-plus/icons-vue"; import { Search } from "@element-plus/icons-vue";
import NameAvatar from "@/components/NameAvatar/index.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" }); defineOptions({ name: "MemberSelector" });
// emit数据
const emit = defineEmits(["save-member"]);
const listRef = ref(null); const listRef = ref(null);
const drawerVisible = defineModel("visible", { type: Boolean }); const drawerVisible = defineModel("visible", { type: Boolean });
const searchQuery = ref(""); // 搜索部门的数据
const currentHoverId = ref(""); const memberSearchQuery = ref([]);
// 模拟数据
const memberList = ref([ const memberList = defineModel<any[]>("dataList", { default: () => [] });
{
id: 1, const {
name: "程彬", displayData: displayMemberList,
dept: "数字化管理部", loadMore: handleLoadMoreList,
type: "active-user", noMore: noMoreLocal
active: true, } = 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 }, { immediate: true, deep: true }
{ 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);
// 添加成员 (添加成员)
const changeMember = (value) => {
memberList.value = value;
nextTick(() => { nextTick(() => {
if (listRef.value) { if (listRef.value) {
listRef.value.scrollTo({ listRef.value.scrollTo({
top: listRef.value.scrollHeight, top: listRef.value?.wrapRef.scrollHeight,
behavior: "smooth", 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) => { const handleRemove = async (id) => {
memberList.value = memberList.value.filter((m) => m.id !== id); memberList.value = memberList.value.filter((item) => item.id !== id);
}; };
// 切换选中状态(模拟点击高亮) // 前端加载更多 触底操作
const selectMember = (item) => { const loadMoreList = () => {
// 如果需要单选高亮,可以先重置所有 active handleLoadMoreList();
// memberList.value.forEach(m => m.active = false) };
item.active = !item.active; // 保存成员方法
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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -153,20 +229,22 @@ const selectMember = (item) => {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; // 关键:允许子元素溢出滚动 min-height: 0;
} }
.search-bar { .search-bar {
padding: 20px 24px; padding: 20px 24px;
display: flex; display: flex;
gap: 12px; gap: 12px;
background-color: #f8fafc;
:deep(.el-input) {
--el-input-border-radius: 2px;
}
} }
.member-list { .member-list {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 0 14px 20px; padding: 0 14px 20px;
box-sizing: border-box;
.list-fade-enter-active, .list-fade-enter-active,
.list-fade-leave-active { .list-fade-leave-active {
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
@@ -190,14 +268,16 @@ const selectMember = (item) => {
align-items: center; align-items: center;
padding: 10px 16px; padding: 10px 16px;
margin-bottom: 4px; margin-bottom: 4px;
border-radius: 8px; border-radius: 2px;
border: 1px solid transparent;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
&:hover { &:hover {
--mj-avatar-bg: #d1e9ff; --mj-avatar-bg: #d1e9ff;
--mj-text-color: #409eff; --mj-text-color: #409eff;
background-color: #f0f7ff; background-color: #f8fafc;
border-color: #f5f5f5;
.member-action { .member-action {
opacity: 1; 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 { .el-drawer__footer {
padding: 16px 24px; padding: 16px 24px;

View File

@@ -14,7 +14,7 @@ defineOptions({ name: "StageBreadcrumbs" });
const { title,styleClass } = defineProps<{ const { title,styleClass } = defineProps<{
title: string; title: string;
styleClass: string; styleClass?: string;
}>(); }>();
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View 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
};
}

View 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,
};
}

View File

@@ -323,36 +323,6 @@ const handleFetchSearch = async (keyword, signal) => {
selectedUsersCache.forEach(userList => { selectedUsersCache.forEach(userList => {
userList.forEach(u => selectedIds.add(u.id)); 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 = { const queryParams = {
keyword, keyword,

View File

@@ -74,7 +74,7 @@
</el-form> </el-form>
<div class="form-footer"> <div class="form-footer">
<span>© 2025 视界保留所有权利</span> <span>© 2025 服链保留所有权利</span>
<div class="links"> <div class="links">
<el-link underline="never">隐私政策</el-link> <el-link underline="never">隐私政策</el-link>
<el-link underline="never">服务条款</el-link> <el-link underline="never">服务条款</el-link>

View File

@@ -8,7 +8,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"
top="10vh" top="6vh"
> >
<el-form ref="formRef" :model="form" label-position="top" :rules="rules"> <el-form ref="formRef" :model="form" label-position="top" :rules="rules">
<div class="row-flex"> <div class="row-flex">
@@ -16,44 +16,84 @@
<el-input v-model="form.name" placeholder="请输入角色名称" /> <el-input v-model="form.name" placeholder="请输入角色名称" />
</el-form-item> </el-form-item>
<el-form-item label="角色编码" prop="code"> <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> </el-form-item>
</div> </div>
<el-form-item label="角色类型"> <el-form-item label="角色类型" prop="type">
<div class="full-width-radio"> <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"
/>
</div> </div>
</el-form-item> </el-form-item>
<div class="feature-card light-green"> <el-form-item label="关联岗位" prop="positionIds">
<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" />
</div>
<el-form-item label="关联岗位">
<el-select <el-select
v-model="form.positionIds" v-model="form.positionIds"
placeholder="请选择关联岗位" placeholder="请选择关联岗位"
multiple multiple
filterable
remote
:remote-method="remoteMethod"
:loading="remoteLoading"
collapse-tags 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-select>
</el-form-item> </el-form-item>
<el-form-item prop="status">
<div class="feature-card light-blue"> <div class="feature-card light-blue">
<div class="info"> <div class="info">
<div class="title">启用状态</div> <div class="title">启用状态</div>
<div class="desc">控制该角色及其下属权限是否立即生效</div> <div class="desc">控制该角色及其下属权限是否立即生效</div>
</div>
<el-switch
v-model="form.status"
:active-value="1"
:inactive-value="0"
/>
</div> </div>
<el-switch v-model="form.status" :active-value="1" :inactive-value="0"/> </el-form-item>
</div>
<el-form-item label="备注说明"> <el-form-item label="备注说明" prop="remark">
<el-input <el-input
v-model="form.remark" v-model="form.remark"
type="textarea" type="textarea"
@@ -67,7 +107,9 @@
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="cancelRoles" text>取消</el-button> <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> </div>
</template> </template>
</el-dialog> </el-dialog>
@@ -76,46 +118,61 @@
<script lang="ts" setup> <script lang="ts" setup>
import BaseSegmented from "./baseSegmented.vue"; import BaseSegmented from "./baseSegmented.vue";
import { PermissionManage } from "@/dict"; 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 { import {
addRole, addRole,
getRoleDetail updateRole,
getRoleDetail,
} from "@/api/stage/permission/index.ts"; } from "@/api/stage/permission/index.ts";
import { getEnterprisePosition } from "@/api/stage/organization";
import { useSelectLoadMore } from "@/hooks/useSelectLoadMore";
defineOptions({ name: "addRoles" }); defineOptions({ name: "addRoles" });
const dialogVisible = defineModel("visible", { type: Boolean, default: false }); const dialogVisible = defineModel("visible", { type: Boolean, default: false });
const props = defineProps({ const props = defineProps({
detailId: { detailId: {
type: [String,Number], type: [String, Number],
default: '', default: "",
} },
});
const {
options,
remoteLoading,
loadMore,
noMore,
remoteMethod,
handleLoadMore,
} = useSelectLoadMore({
fetchApi: (p) => getEnterprisePosition({ ...p }),
}); });
const computedTitle = computed(() => { const computedTitle = computed(() => {
return props.detailId ? "编辑系统角色" : "新增系统角色"; return props.detailId ? "编辑系统角色" : "新增系统角色";
}); });
const emit = defineEmits(["on-success"]); const emit = defineEmits(["on-success"]);
const positionList = ref([]); const positionList = ref([]); //岗位列表数据
const formRef = ref<FormInstance>(null); const formRef = ref<FormInstance>(null);
const form = reactive<RuleForm>({ const form = reactive<RuleForm>({
name: "", name: "",
code: "", code: "",
type: "DEFAULT", type: "DEFAULT",
isOrgRelated: 0, // 是否和组织架构深度关联 isOrgRelated: 0, // 是否和组织架构深度关联
post: "", positionIds: "",
status: 1, status: 1,
remark: "", remark: "",
}); });
const rules = reactive<FormInstance<RuleForm>>({ const rules = reactive<FormInstance<RuleForm>>({
name: [ name: [{ required: true, message: "请输入角色名称", trigger: "blur" }],
{ required: true, message: "请输入角色名称", trigger: "blur" } code: [{ required: true, message: "请输入角色编码", trigger: "blur" }],
], });
code: [
{ required: true, message: "请输入角色编码", trigger: "blur" }
]
})
// 分页信息
const pageInfo = reactive({
pageNo: 1,
pageSize: 20,
});
const cancelRoles = () => { const cancelRoles = () => {
formRef.value && formRef.value.resetFields(); formRef.value && formRef.value.resetFields();
@@ -123,39 +180,44 @@ const cancelRoles = () => {
}; };
// 获取角色详情 // 获取角色详情
const getRoleShowDetail = async (val) =>{ const getRoleShowDetail = async (val) => {
try { try {
const res = await getRoleDetail(props.detailId); const res = await getRoleDetail(props.detailId);
Object.assign(form,res); Object.assign(form, res, { isOrgRelated: res.isOrgRelated ? 1 : 0 });
} catch (error) { } catch (error) {
console.log('getRoleDetail error:',error); console.log("getRoleDetail error:", error);
} }
} };
// 监听详情 // 监听详情
watch(()=>dialogVisible.value,(val) => { watch(
if(val && props.detailId){ () => dialogVisible.value,
getRoleShowDetail(); (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) => { formEl.validate(async (valid) => {
if (valid) { if (valid) {
try { try {
const res = await addRole(form); const res = props.detailId
? await updateRole(form)
: await addRole(form);
ElMessage.success("操作成功"); ElMessage.success("操作成功");
cancelRoles(); cancelRoles();
emit('on-success'); emit("on-success");
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
} else { } else {
console.log('error submit!') console.log("error submit!");
} }
}) });
}; };
</script> </script>
@@ -200,7 +262,7 @@ const handleSubmit = (formEl:FormInstance | undefined) => {
flex: 1; flex: 1;
} }
} }
.full-width-radio{ .full-width-radio {
width: 100%; width: 100%;
} }
@@ -211,7 +273,7 @@ const handleSubmit = (formEl:FormInstance | undefined) => {
align-items: center; align-items: center;
padding: 16px; padding: 16px;
border-radius: 4px; border-radius: 4px;
margin-bottom: 22px; flex: 1;
.info { .info {
.title { .title {

View File

@@ -12,7 +12,7 @@
class="auto-expand-input" class="auto-expand-input"
:prefix-icon="'Search'" :prefix-icon="'Search'"
v-model="searchVal" v-model="searchVal"
@keyup.enter="fetchTableData" @keyup.enter="onRefresh"
></el-input> ></el-input>
</div> </div>
<div class="mj-dict-actions-right"> <div class="mj-dict-actions-right">
@@ -27,52 +27,63 @@
</div> </div>
</template> </template>
</stageBreadcrumbs> </stageBreadcrumbs>
<template v-if="[1, 2].includes(activeTab)">
<!-- 表格 -->
<CommonTable
ref="tableRef"
:columns="tableColumns"
v-model:data="dataList"
v-model:total="total"
pagination
:request-api="getTableData"
>
<!-- 状态 -->
<template #status="{ row }">
<div
class="mj-status-dot"
:style="{
'--data-status-color': PermissionManage.roleDictColor[row.status],
}"
@click="handleDictStatus(row)"
>
{{ PermissionManage.roleDict[row.status] }}
</div>
</template>
<!-- 表格 --> <!-- 类型 -->
<CommonTable <template #type="{ row }">
ref="tableRef" <el-tag type="primary">{{ row.type }}</el-tag>
:columns="tableColumns" </template>
v-model:data="dataList"
v-model:total="total"
pagination
:request-api="getTableData"
>
<!-- 状态 -->
<template #status="{ row }">
<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 }"> <template #member="{ row }">
<el-tag type="primary">{{ row.type }}</el-tag> <el-button
</template> link
icon="UserFilled"
<!-- 成员数量 --> type="primary"
<template #member="{ row }"> @click.stop="addMember(row)"
<el-button >{{ row.memberCount }}</el-button
link >
icon="UserFilled" </template>
type="primary" </CommonTable>
@click.stop="addMember" </template>
>{{ row.memberCount }}</el-button <!-- 前线配置模块内容 -->
> <template v-else> 权限配置模块 </template>
</template>
</CommonTable>
</div> </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" /> <permission-drawer v-model:visible="showPermission" />
@@ -92,33 +103,38 @@ import {
addRole, addRole,
deleteRole, deleteRole,
enableRole, enableRole,
disableRole disableRole,
getRoleMemberList,
copyRolePermission
} from "@/api/stage/permission/index.ts"; } from "@/api/stage/permission/index.ts";
import { useLocalManager } from '@/hooks/useLocalManager';
defineOptions({ name: "PermissionManagement" }); defineOptions({ name: "PermissionManagement" });
const activeTab = ref(1); const activeTab = ref(1);
const tableRef = ref(null); const tableRef = ref(null);
const searchVal = ref(""); const searchVal = ref("");
const dataList = ref([]); const dataList = ref([]);
const memberList = 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 detailId = ref("");
const showPermission = ref(false); const showPermission = ref(false);
const tabList = [ const tabList = [
{ {
label: "角色与权限", label: "角色与权限",
id: 1, id: 1,
}, },
{
label: "用户管理",
id: 2,
},
{
label: "权限配置",
id: 3,
},
]; ];
const {
fullData,
displayData,
loadMore,
noMore,
remove,
add
} = useLocalManager(()=>memberList.value);
const roleColumns = [ const roleColumns = [
{ {
prop: "id", prop: "id",
@@ -129,7 +145,12 @@ const roleColumns = [
return `#${formatIndex(val.id)}`; return `#${formatIndex(val.id)}`;
}, },
}, },
{ prop: "name", label: "角色名称", align: "center",showOverflowTooltip:true }, {
prop: "name",
label: "角色名称",
align: "center",
showOverflowTooltip: true,
},
{ {
prop: "memberCount", prop: "memberCount",
label: "成员数量", label: "成员数量",
@@ -140,7 +161,7 @@ const roleColumns = [
prop: "permissionCount", prop: "permissionCount",
label: "权限数量", label: "权限数量",
align: "center", align: "center",
formatter: (val) => { formatter: (val) => {
return val.permissionCount ? `${val.permissionCount}` : "-"; return val.permissionCount ? `${val.permissionCount}` : "-";
}, },
}, },
@@ -185,14 +206,14 @@ const roleColumns = [
prop: "actions", prop: "actions",
label: "操作", label: "操作",
align: "right", align: "right",
fixed:"right", fixed: "right",
width: "200", width: "200",
actions: [ actions: [
{ {
label: "权限", label: "权限",
type: "primary", type: "primary",
link: true, link: true,
permission: ["edit"], permission: ["*"],
onClick: (row) => { onClick: (row) => {
showPermission.value = true; showPermission.value = true;
}, },
@@ -202,7 +223,15 @@ const roleColumns = [
type: "default", type: "default",
link: true, link: true,
permission: ["edit"], 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: "编辑", label: "编辑",
@@ -220,80 +249,27 @@ const roleColumns = [
link: true, link: true,
permission: ["delete"], permission: ["delete"],
disabledFn: (row) => { disabledFn: (row) => {
return (row.type !== 'SYSTEM') return row.type !== "SYSTEM";
}, },
onClick: async (row) => { onClick: async (row) => {
try { ElMessageBox.confirm("确定要删除吗?", "提示", {
deleteRole(row.id).then(() => { confirmButtonText: "确定",
ElMessage.success("删除成功"); cancelButtonText: "取消",
tableRef.value.refresh(); type: "warning",
})
.then(() => {
try {
deleteRole(row.id).then(() => {
ElMessage.success("删除成功");
tableRef.value.refresh();
});
} catch (error) {
console.log("error", error);
}
})
.catch(() => {
console.log("cancel");
}); });
} 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;
}, },
}, },
], ],
@@ -303,14 +279,19 @@ const userColumns = [
const tableColumns = computed(() => { const tableColumns = computed(() => {
const columns = { const columns = {
1: roleColumns, 1: roleColumns,
2: userColumns,
}[activeTab.value]; }[activeTab.value];
return columns; return columns;
}); });
// 当前成员数量 // 当前成员数量
const addMember = () => { const addMember = (row) => {
showMember.value = true; showMember.value = true;
getMemberList({ id: row.id });
};
// 保存成员数量
const handleSaveMember = async () => {
console.log("保存成员数量",memberList.value);
}; };
// 刷新列表 // 刷新列表
@@ -318,11 +299,11 @@ const onRefresh = () => {
tableRef.value && tableRef.value.reset(); tableRef.value && tableRef.value.reset();
}; };
// 监听Tabs变化 // 监听Tabs变化
watch(activeTab,()=>{ watch(activeTab, () => {
dataList.value = []; dataList.value = [];
tableRef.value && tableRef.value.reset(); tableRef.value && tableRef.value.reset();
}) });
// 权限 // 权限
const handleDictStatus = async (row) => { const handleDictStatus = async (row) => {
@@ -335,52 +316,46 @@ const handleDictStatus = async (row) => {
} }
}; };
// 请求接口获取
const getTableData = async (params) => { 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 = { const queryParams = {
...(searchVal.value && { name:searchVal.value }), ...(searchVal.value && { name: searchVal.value }),
status:1, pageNo: 1,
type:1, pageSize: 10,
pageNo:1, };
pageSize:10
}
try { try {
const res = await getRoleList(queryParams); const res = await getRoleList(queryParams);
return res; return res;
} catch (error) { } catch (error) {
console.log("error", 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 checkRolesText = computed(() => {
const btnText = { const btnText = {
1: "新增角色", 1: "新增角色",
2: "新增用户",
}[activeTab.value]; }[activeTab.value];
const placeholder = { const placeholder = {
1: "搜索角色名称...", 1: "搜索角色名称...",
2: "搜索用户姓名/账号...",
}[activeTab.value]; }[activeTab.value];
return { return {
@@ -389,9 +364,6 @@ const checkRolesText = computed(() => {
}; };
}); });
// 请求数据信息
const fetchTableData = () => {};
// 新增角色 // 新增角色
const addBtnClick = () => { const addBtnClick = () => {
if (activeTab.value === 1) { if (activeTab.value === 1) {

View File

@@ -4,7 +4,7 @@ import {
type RouteRecordRaw, type RouteRecordRaw,
} from "vue-router"; } from "vue-router";
import { useUserStore } from "@/store"; import { useUserStore } from "@/store";
import { getRouteMenus } from "@/api"; import { getUserInfo } from "@/api";
import TokenManager from "@/utils/storage"; import TokenManager from "@/utils/storage";
import Login from "@/pages/Login/index.vue"; import Login from "@/pages/Login/index.vue";
@@ -30,7 +30,6 @@ const asyncRoutes: RouteRecordRaw[] = [
path: "/", path: "/",
name: "Layout", name: "Layout",
component: HomeView, component: HomeView,
// redirect: '/home',
meta: { meta: {
requiresAuth: true, requiresAuth: true,
}, },
@@ -165,7 +164,8 @@ const addDynamicRoutes = async () => {
// 从后端获取路由菜单数据 (这边需要区分 后台的菜单 和用户的菜单) // 从后端获取路由菜单数据 (这边需要区分 后台的菜单 和用户的菜单)
let allRoutes: any[] = []; let allRoutes: any[] = [];
if (userStore.isBackendUser) { if (userStore.isBackendUser) {
// const backendResponse = await getRouteMenus(); const backendResponses = await getUserInfo();
console.log("获取当前的菜单数据信息:",backendResponses);
const backendResponse = []; const backendResponse = [];
allRoutes = [ allRoutes = [
{ {
@@ -257,7 +257,6 @@ router.beforeEach(async (to, _from, next) => {
try { try {
// 加载动态路由 // 加载动态路由
await addDynamicRoutes(); await addDynamicRoutes();
console.log("当前完整路由表:", router.getRoutes());
const redirect = to.query.redirect as string; const redirect = to.query.redirect as string;
if (to.path === "/" || to.path === "/login") { if (to.path === "/" || to.path === "/login") {

View File

@@ -3,72 +3,73 @@
html, html,
body { body {
height: 100%; height: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
#app { #app {
height: inherit; height: inherit;
} }
:root { :root {
--mj-menu-header-height:#{$mj-menu-header-height}; --mj-menu-header-height:#{$mj-menu-header-height};
--mj-border-color:#{$mj-border-color}; --mj-border-color:#{$mj-border-color};
--mj-padding-standard:#{$mj-padding-standard}; --mj-padding-standard:#{$mj-padding-standard};
--mj-popper-radius: 8px; --mj-popper-radius: 8px;
--el-color-primary:#2b65f6; --el-color-primary: #2b65f6;
} }
// popover 筛选框的全局样式 // popover 筛选框的全局样式
.filter-popper.el-popover.el-popper { .filter-popper.el-popover.el-popper {
--el-popover-padding: 0; --el-popover-padding: 0;
border-radius: var(--mj-popper-radius); border-radius: var(--mj-popper-radius);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
} }
// 全局重写element相关样式 // 全局重写element相关样式
.mj-input-form { .mj-input-form {
.el-input { .el-input {
--el-border-radius-base: 10px; --el-border-radius-base: 10px;
--el-border-color: #E2E8F0; --el-border-color: #E2E8F0;
} }
} }
// 搜索框动画 // 搜索框动画
.search-auto-expand-input { .search-auto-expand-input {
--default-width: 160px; --default-width: 160px;
--max-width: 224px; --max-width: 224px;
width: var(--default-width); width: var(--default-width);
transition: width 0.3s ease; transition: width 0.3s ease;
&:focus-within { &:focus-within {
width: var(--max-width); width: var(--max-width);
} }
.auto-expand-input { .auto-expand-input {
--el-input-border-radius: 4px; --el-input-border-radius: 4px;
width: 100%; width: 100%;
} }
} }
// 字典状态全局样式 // 字典状态全局样式
.mj-status-dot { .mj-status-dot {
cursor: pointer; cursor: pointer;
&::before {
content: ""; &::before {
display: inline-block; content: "";
width: 6px; display: inline-block;
height: 6px; width: 6px;
border-radius: 50%; height: 6px;
background-color: var(--data-status-color); border-radius: 50%;
margin-right: 8px; background-color: var(--data-status-color);
vertical-align: middle; margin-right: 8px;
} vertical-align: middle;
}
} }
@@ -102,7 +103,8 @@ body {
.filter-item { .filter-item {
margin-bottom: 20px; margin-bottom: 20px;
label{
label {
display: block; display: block;
font-size: 14px; font-size: 14px;
color: #909399; color: #909399;
@@ -112,7 +114,7 @@ body {
} }
.custom-select { .custom-select {
--el-border-color:transparent; --el-border-color: transparent;
width: 100%; width: 100%;
background-color: #f5f7fa; background-color: #f5f7fa;
box-shadow: none; box-shadow: none;
@@ -128,6 +130,7 @@ body {
font-weight: bold; font-weight: bold;
border-radius: 6px; border-radius: 6px;
margin-top: 10px; margin-top: 10px;
&:hover { &:hover {
background-color: #407eff; background-color: #407eff;
border-color: #407eff; border-color: #407eff;
@@ -148,27 +151,48 @@ body {
// 卡片的全局样式 // 卡片的全局样式
.mj-card-container{ .mj-card-container {
display: grid; display: grid;
gap: 16px; gap: 16px;
width: 100%; width: 100%;
// 默认:一行 5 列 // 默认:一行 5 列
grid-template-columns: repeat(5, 1fr); grid-template-columns: repeat(5, 1fr);
// 1400px 以下:觉得 5 个太挤,切到一行 4 列 // 1400px 以下:觉得 5 个太挤,切到一行 4 列
@media (max-width: 1400px) { @media (max-width: 1400px) {
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
} }
// 1100px 以下 (iPad横屏区间):一行 3 列 // 1100px 以下 (iPad横屏区间):一行 3 列
@media (max-width: 1100px) { @media (max-width: 1100px) {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
} }
// 768px 以下 (iPad竖屏):一行 2 列 // 768px 以下 (iPad竖屏):一行 2 列
@media (max-width: 768px) { @media (max-width: 768px) {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 12px; gap: 12px;
} }
// 480px 以下 (手机端):一行 1 列 // 480px 以下 (手机端):一行 1 列
@media (max-width: 480px) { @media (max-width: 480px) {
grid-template-columns: repeat(1, 1fr); 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;
}
} }

View File

@@ -1,9 +1,16 @@
import { useUserStore } from "@/store"; import { useUserStore } from "@/store";
import { throttle } from "lodash-es";
function permissionDirective(el: HTMLElement,binding:any) { function permissionDirective(el: HTMLElement, binding: any) {
const appStore = useUserStore(); const appStore = useUserStore();
const userPermissions = appStore.role; const userPermissions = appStore.role;
let requiredPermissions = binding.value; let requiredPermissions = binding.value;
// 使用特殊值 '*' 表示跳过权限检查
const isSkipCheck =
requiredPermissions === "*" ||
(Array.isArray(requiredPermissions) && requiredPermissions.includes("*"));
if (isSkipCheck) {
return;
}
if (typeof requiredPermissions === "string") { if (typeof requiredPermissions === "string") {
requiredPermissions = [requiredPermissions]; requiredPermissions = [requiredPermissions];
} }
@@ -25,12 +32,52 @@ 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 = { const directives = {
permission, permission,
selectMore,
}; };
export default (app: any) => { export default (app: any) => {
Object.keys(directives).forEach((key) => { Object.keys(directives).forEach((key) => {
app.directive(key, directives[key]); app.directive(key, directives[key]);
}); });
}; };

View File

@@ -8,6 +8,6 @@ export function getImageUrl(url: string) {
// 设置index前缀 如001 010 100种 默认兼容3位数 // 设置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'); return String(index + 1).padStart(padNum, '0');
}; };