fix:完善权限管理页面
This commit is contained in:
1
components.d.ts
vendored
1
components.d.ts
vendored
@@ -22,6 +22,7 @@ declare module 'vue' {
|
|||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
ElCard: typeof import('element-plus/es')['ElCard']
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||||
|
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
|
||||||
ElCol: typeof import('element-plus/es')['ElCol']
|
ElCol: typeof import('element-plus/es')['ElCol']
|
||||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||||
ElDatePick: typeof import('element-plus/es')['ElDatePick']
|
ElDatePick: typeof import('element-plus/es')['ElDatePick']
|
||||||
|
|||||||
@@ -2,10 +2,14 @@
|
|||||||
<el-drawer
|
<el-drawer
|
||||||
v-model="drawerVisible"
|
v-model="drawerVisible"
|
||||||
size="440px"
|
size="440px"
|
||||||
class="member-drawer"
|
class="standard-ui-back-drawer"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
destroy-on-close
|
||||||
:with-header="false"
|
:with-header="false"
|
||||||
|
modal-class="standard-overlay-dialog-flat"
|
||||||
>
|
>
|
||||||
<div class="custom-header">
|
<div class="customer-drawer-header">
|
||||||
<div class="title-row">
|
<div class="title-row">
|
||||||
<span class="decorator"></span>
|
<span class="decorator"></span>
|
||||||
<span class="title">成员管理</span>
|
<span class="title">成员管理</span>
|
||||||
@@ -28,7 +32,6 @@
|
|||||||
>添加成员</el-button
|
>添加成员</el-button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="member-list" ref="listRef">
|
<div class="member-list" ref="listRef">
|
||||||
<transition-group name="list-fade">
|
<transition-group name="list-fade">
|
||||||
<div
|
<div
|
||||||
@@ -58,7 +61,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="drawer-footer">
|
<div class="custom-flat-drawer-footer">
|
||||||
<div class="stats-info">
|
<div class="stats-info">
|
||||||
<span class="label">统计信息</span>
|
<span class="label">统计信息</span>
|
||||||
<span class="count">共 {{ memberList.length }} 名成员</span>
|
<span class="count">共 {{ memberList.length }} 名成员</span>
|
||||||
@@ -144,53 +147,8 @@ const selectMember = (item) => {
|
|||||||
item.active = !item.active;
|
item.active = !item.active;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss" scoped>
|
||||||
.member-drawer {
|
.standard-ui-back-drawer {
|
||||||
.el-drawer__body {
|
|
||||||
padding: 0 !important; // 必须覆盖默认内边距
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 自定义头部
|
|
||||||
.custom-header {
|
|
||||||
padding: 20px 24px 15px;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
|
|
||||||
.title-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
.decorator {
|
|
||||||
width: 4px;
|
|
||||||
height: 16px;
|
|
||||||
background: #409eff;
|
|
||||||
border-radius: 2px;
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: bold;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.close-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
color: #909399;
|
|
||||||
&:hover {
|
|
||||||
color: #409eff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.sub-title {
|
|
||||||
margin: 8px 0 0 12px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: #909399;
|
|
||||||
span {
|
|
||||||
color: #409eff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer-body-container {
|
.drawer-body-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -268,14 +226,9 @@ const selectMember = (item) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-drawer__footer {
|
.el-drawer__footer {
|
||||||
border-top: 1px solid #f0f0f0;
|
|
||||||
padding: 16px 24px;
|
padding: 16px 24px;
|
||||||
|
|
||||||
.drawer-footer {
|
.custom-flat-drawer-footer {
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.stats-info {
|
.stats-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -290,16 +243,6 @@ const selectMember = (item) => {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-confirm {
|
|
||||||
background-color: #1d2635;
|
|
||||||
border-color: #1d2635;
|
|
||||||
padding: 10px 24px;
|
|
||||||
&:hover {
|
|
||||||
background-color: #2b364a;
|
|
||||||
border-color: #2b364a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ const props = defineProps({
|
|||||||
avatarTextColor: { type: String, default: "" },
|
avatarTextColor: { type: String, default: "" },
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(1111,props.bgColor)
|
|
||||||
|
|
||||||
const displayText = computed(() => {
|
const displayText = computed(() => {
|
||||||
return props.name ? props.name.charAt(0) : "";
|
return props.name ? props.name.charAt(0) : "";
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -109,9 +109,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<div class="pro-table-footer">
|
<div class="pro-table-footer" v-if="pagination">
|
||||||
<slot name="footer">
|
<slot name="footer">
|
||||||
<div class="mj-footer-content" v-if="pagination">
|
<div class="mj-footer-content">
|
||||||
<div class="footer-left">
|
<div class="footer-left">
|
||||||
<span class="total-text">共 {{ total || data.length }} 个条目</span>
|
<span class="total-text">共 {{ total || data.length }} 个条目</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,6 +134,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from "vue";
|
import { ref, computed } from "vue";
|
||||||
|
import { debounce } from 'lodash-es';
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
columns: { type: Array, required: true },
|
columns: { type: Array, required: true },
|
||||||
data: { type: Array, required: true },
|
data: { type: Array, required: true },
|
||||||
@@ -164,20 +165,21 @@ const tableContainerRef = ref(null);
|
|||||||
const containerHeight = ref(0);
|
const containerHeight = ref(0);
|
||||||
const minHeight = 400; // 最小高度值
|
const minHeight = 400; // 最小高度值
|
||||||
// 监听窗口大小变化
|
// 监听窗口大小变化
|
||||||
const handleResize = () => {
|
const handleResize = debounce(() => {
|
||||||
updateContainerHeight();
|
updateContainerHeight();
|
||||||
};
|
},100);
|
||||||
|
|
||||||
// 动态计算表格容器高度
|
// 动态计算表格容器高度
|
||||||
const updateContainerHeight = async () => {
|
const updateContainerHeight = async () => {
|
||||||
await nextTick();
|
await nextTick();
|
||||||
if (tableContainerRef.value) {
|
if (tableContainerRef.value) {
|
||||||
// 获取容器相对于视口的位置
|
// 获取容器相对于视口的位置
|
||||||
const rect = tableContainerRef.value.getBoundingClientRect();
|
const element = tableContainerRef.value;
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
const containerTop = rect.top;
|
const containerTop = rect.top;
|
||||||
|
|
||||||
// 计算从容器位置到浏览器底部的可用高度
|
// 计算从容器位置到浏览器底部的可用高度
|
||||||
const availableHeight = window.innerHeight - containerTop;
|
const availableHeight = window.innerHeight - containerTop -20;
|
||||||
containerHeight.value = Math.max(availableHeight, minHeight);
|
containerHeight.value = Math.max(availableHeight, minHeight);
|
||||||
} else {
|
} else {
|
||||||
// 如果元素尚未渲染,使用默认偏移值
|
// 如果元素尚未渲染,使用默认偏移值
|
||||||
@@ -186,8 +188,9 @@ const updateContainerHeight = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const tableHeight = computed(() => {
|
const tableHeight = computed(() => {
|
||||||
// 为页码预留空间,大约60px
|
// 为页码预留空间
|
||||||
return (containerHeight.value - 60) + 'px';
|
const paginationHeight = props.pagination ? 38 : 0;
|
||||||
|
return (containerHeight.value - paginationHeight) + 'px';
|
||||||
});
|
});
|
||||||
|
|
||||||
// 标记是否是首次挂载
|
// 标记是否是首次挂载
|
||||||
@@ -316,11 +319,15 @@ const getButtonProps = (button) => {
|
|||||||
background-color: #fbfcfd;
|
background-color: #fbfcfd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
:deep(.el-table__inner-wrapper:before){
|
||||||
|
--el-table-border-color:transparent;
|
||||||
|
}
|
||||||
|
|
||||||
/* 底部容器样式:对应图片中的布局 */
|
/* 底部容器样式:对应图片中的布局 */
|
||||||
.pro-table-footer {
|
.pro-table-footer {
|
||||||
padding: 3px 24px;
|
padding: 3px 24px;
|
||||||
background-color: #fcfdfe;
|
background-color: #fcfdfe;
|
||||||
|
border-top: 1px solid #E2E8F0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mj-footer-content {
|
.mj-footer-content {
|
||||||
|
|||||||
@@ -524,7 +524,8 @@ const updateUIAfterSend = (type, params, response) => {
|
|||||||
const newComment = {
|
const newComment = {
|
||||||
id: response,
|
id: response,
|
||||||
employee: {
|
employee: {
|
||||||
...currentUser.value,
|
userId:currentUser.value.id,
|
||||||
|
username:currentUser.value.name,
|
||||||
},
|
},
|
||||||
replyId: params.replyUserId,
|
replyId: params.replyUserId,
|
||||||
replyUser: {
|
replyUser: {
|
||||||
|
|||||||
@@ -229,8 +229,8 @@ const getTableData = async (params) => {
|
|||||||
try {
|
try {
|
||||||
const response = await getDictValues({
|
const response = await getDictValues({
|
||||||
...params,
|
...params,
|
||||||
keyword: searchVal.value,
|
...(searchVal.value && { keyword: searchVal.value }),
|
||||||
...filterForm,
|
...(filterForm.status && { status: filterForm.status })
|
||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
title="新增系统角色"
|
title="新增系统角色"
|
||||||
width="500px"
|
width="500px"
|
||||||
class="custom-role-dialog"
|
class="standard-ui-flat-dialog"
|
||||||
|
modal-class="standard-overlay-dialog-flat"
|
||||||
destroy-on-close
|
destroy-on-close
|
||||||
>
|
>
|
||||||
<el-form :model="form" label-position="top" class="role-form">
|
<el-form :model="form" label-position="top" class="role-form">
|
||||||
@@ -17,11 +18,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-form-item label="角色类型">
|
<el-form-item label="角色类型">
|
||||||
<el-radio-group v-model="form.type" class="full-width-radio">
|
<div class="full-width-radio">
|
||||||
<el-radio-button label="实例角色" />
|
<BaseSegmented v-model="roleType" :options="roleOptions" />
|
||||||
<el-radio-button label="默认角色" />
|
</div>
|
||||||
<el-radio-button label="系统角色" />
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<div class="feature-card light-green">
|
<div class="feature-card light-green">
|
||||||
@@ -55,6 +54,7 @@
|
|||||||
v-model="form.remark"
|
v-model="form.remark"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:rows="3"
|
:rows="3"
|
||||||
|
resize="none"
|
||||||
placeholder="请输入备注说明..."
|
placeholder="请输入备注说明..."
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button @click="dialogVisible = false">取消</el-button>
|
<el-button @click="dialogVisible = false" text>取消</el-button>
|
||||||
<el-button type="primary" @click="handleSubmit">确认创建</el-button>
|
<el-button type="primary" @click="handleSubmit">确认创建</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -70,9 +70,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import BaseSegmented from "./baseSegmented.vue";
|
||||||
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 roleOptions = [
|
||||||
|
{ label: '实例角色', value: 'instance' },
|
||||||
|
{ label: '默认角色', value: 'default' },
|
||||||
|
{ label: '系统角色', value: 'system' }
|
||||||
|
];
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
name: "",
|
name: "",
|
||||||
code: "",
|
code: "",
|
||||||
@@ -82,25 +89,20 @@ const form = reactive({
|
|||||||
status: true,
|
status: true,
|
||||||
remark: "",
|
remark: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
dialogVisible.value = false;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
/* 样式穿透修改 Element Plus 默认外观 */
|
.standard-ui-flat-dialog {
|
||||||
.custom-role-dialog {
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
.el-dialog__header {
|
.el-dialog__header {
|
||||||
margin-right: 0;
|
|
||||||
padding: 20px 24px;
|
|
||||||
border-bottom: 1px solid #f0f2f5;
|
|
||||||
|
|
||||||
.el-dialog__title {
|
.el-dialog__title {
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 12px;
|
padding-left: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
// 标题前面的蓝色小方块
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -109,14 +111,14 @@ const form = reactive({
|
|||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
width: 4px;
|
width: 4px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
background-color: #1661ff;
|
background-color: var(--blue-block-color);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-dialog__body {
|
.el-dialog__body {
|
||||||
padding: 24px;
|
padding: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表单标签样式
|
// 表单标签样式
|
||||||
@@ -134,31 +136,8 @@ const form = reactive({
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.full-width-radio{
|
||||||
// 选项卡风格的单选框
|
|
||||||
.full-width-radio {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #f2f3f5;
|
|
||||||
padding: 4px;
|
|
||||||
border-radius: 4px;
|
|
||||||
|
|
||||||
.el-radio-button {
|
|
||||||
flex: 1;
|
|
||||||
.el-radio-button__inner {
|
|
||||||
width: 100%;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
box-shadow: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: #4e5969;
|
|
||||||
}
|
|
||||||
&.is-active .el-radio-button__inner {
|
|
||||||
background: #fff;
|
|
||||||
color: #1661ff;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绿色和蓝色的特色功能卡片
|
// 绿色和蓝色的特色功能卡片
|
||||||
@@ -192,17 +171,5 @@ const form = reactive({
|
|||||||
border: 1px solid #e8f0f9;
|
border: 1px solid #e8f0f9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-dialog__footer {
|
|
||||||
padding: 16px 24px 24px;
|
|
||||||
text-align: right;
|
|
||||||
|
|
||||||
.el-button {
|
|
||||||
padding: 10px 24px;
|
|
||||||
&--primary {
|
|
||||||
background-color: #1661ff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
165
src/pages/stage/permission/baseSegmentMenu.vue
Normal file
165
src/pages/stage/permission/baseSegmentMenu.vue
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
<template>
|
||||||
|
<div class="permission-scroll-area">
|
||||||
|
<div v-for="group in permissions" :key="group.id" class="permission-group">
|
||||||
|
<div class="group-header">
|
||||||
|
<el-checkbox
|
||||||
|
v-model="group.allSelected"
|
||||||
|
:indeterminate="group.isIndeterminate"
|
||||||
|
@change="(val) => handleGroupCheckAll(group, val)"
|
||||||
|
>
|
||||||
|
<span class="group-title">{{ group.name }}</span>
|
||||||
|
<span class="group-count"
|
||||||
|
>({{ getCheckedCount(group) }}/{{ group.children.length }})</span
|
||||||
|
>
|
||||||
|
</el-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group-body">
|
||||||
|
<div v-for="row in group.children" :key="row.id" class="permission-row">
|
||||||
|
<div class="row-label">
|
||||||
|
<el-checkbox
|
||||||
|
v-model="row.checked"
|
||||||
|
@change="() => handleRowChange(group, row)"
|
||||||
|
>
|
||||||
|
{{ row.name }}
|
||||||
|
</el-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row-actions">
|
||||||
|
<el-checkbox-group
|
||||||
|
v-model="row.actions"
|
||||||
|
:disabled="!row.checked"
|
||||||
|
@change="() => handleActionChange(group, row)"
|
||||||
|
>
|
||||||
|
<el-checkbox value="add">新增</el-checkbox>
|
||||||
|
<el-checkbox value="delete" class="is-danger">删除</el-checkbox>
|
||||||
|
<el-checkbox value="import">导入</el-checkbox>
|
||||||
|
<el-checkbox value="export">导出</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, onMounted } from "vue";
|
||||||
|
defineOptions({name: "baseSegmentedMenu"});
|
||||||
|
|
||||||
|
// 模拟数据结构
|
||||||
|
const permissions = reactive([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "项目管理",
|
||||||
|
allSelected: false,
|
||||||
|
isIndeterminate: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
name: "需求管理",
|
||||||
|
checked: true,
|
||||||
|
actions: ["add", "delete", "import", "export"],
|
||||||
|
},
|
||||||
|
{ id: 12, name: "项目管理", checked: false, actions: [] },
|
||||||
|
{ id: 13, name: "任务管理", checked: false, actions: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "招聘管理",
|
||||||
|
allSelected: false,
|
||||||
|
isIndeterminate: false,
|
||||||
|
children: [
|
||||||
|
{ id: 21, name: "简历管理", checked: false, actions: [] },
|
||||||
|
{ id: 22, name: "推送管理", checked: false, actions: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "招聘管理",
|
||||||
|
allSelected: false,
|
||||||
|
isIndeterminate: false,
|
||||||
|
children: [
|
||||||
|
{ id: 21, name: "简历管理", checked: false, actions: [] },
|
||||||
|
{ id: 22, name: "推送管理", checked: false, actions: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "招聘管理",
|
||||||
|
allSelected: false,
|
||||||
|
isIndeterminate: false,
|
||||||
|
children: [
|
||||||
|
{ id: 21, name: "简历管理", checked: false, actions: [] },
|
||||||
|
{ id: 22, name: "推送管理", checked: false, actions: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "招聘管理",
|
||||||
|
allSelected: false,
|
||||||
|
isIndeterminate: false,
|
||||||
|
children: [
|
||||||
|
{ id: 21, name: "简历管理", checked: false, actions: [] },
|
||||||
|
{ id: 22, name: "推送管理", checked: false, actions: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "招聘管理",
|
||||||
|
allSelected: false,
|
||||||
|
isIndeterminate: false,
|
||||||
|
children: [
|
||||||
|
{ id: 21, name: "简历管理", checked: false, actions: [] },
|
||||||
|
{ id: 22, name: "推送管理", checked: false, actions: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "招聘管理",
|
||||||
|
allSelected: false,
|
||||||
|
isIndeterminate: false,
|
||||||
|
children: [
|
||||||
|
{ id: 21, name: "简历管理", checked: false, actions: [] },
|
||||||
|
{ id: 22, name: "推送管理", checked: false, actions: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 获取已选中的子项数量
|
||||||
|
const getCheckedCount = (group) =>
|
||||||
|
group.children.filter((item) => item.checked).length;
|
||||||
|
|
||||||
|
// 处理一级全选
|
||||||
|
const handleGroupCheckAll = (group, val) => {
|
||||||
|
group.children.forEach((row) => {
|
||||||
|
row.checked = val;
|
||||||
|
row.actions = val ? ["add", "delete", "import", "export"] : [];
|
||||||
|
});
|
||||||
|
group.isIndeterminate = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理二级勾选
|
||||||
|
const handleRowChange = (group, row) => {
|
||||||
|
if (!row.checked) row.actions = [];
|
||||||
|
updateGroupStatus(group);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理三级按钮勾选
|
||||||
|
const handleActionChange = (group, row) => {
|
||||||
|
if (row.actions.length > 0) row.checked = true;
|
||||||
|
updateGroupStatus(group);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新父级的半选/全选状态
|
||||||
|
const updateGroupStatus = (group) => {
|
||||||
|
const checkedCount = getCheckedCount(group);
|
||||||
|
group.allSelected = checkedCount === group.children.length;
|
||||||
|
group.isIndeterminate =
|
||||||
|
checkedCount > 0 && checkedCount < group.children.length;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@use './baseSegmentedPermission.scss' as *;
|
||||||
|
|
||||||
|
</style>
|
||||||
102
src/pages/stage/permission/baseSegmented.vue
Normal file
102
src/pages/stage/permission/baseSegmented.vue
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<template>
|
||||||
|
<div class="base-segmented-flat">
|
||||||
|
<div v-for="item in options" :key="item.value" class="segmented-item">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
:id="uid + item.value"
|
||||||
|
:name="uid"
|
||||||
|
:value="item.value"
|
||||||
|
:checked="modelValue === item.value"
|
||||||
|
@change="handleChange(item.value)"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
:for="uid + item.value"
|
||||||
|
:class="{ 'is-active': modelValue === item.value }"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { inject } from "vue";
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { useFormItem } from "element-plus";
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: [String, Number],
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:modelValue", "change"]);
|
||||||
|
|
||||||
|
// 生成唯一ID,防止页面存在多个组件时 name 冲突
|
||||||
|
const uid = `seg-${Math.random().toString(36).substring(2, 8)}`;
|
||||||
|
const { formItem } = useFormItem();
|
||||||
|
|
||||||
|
const handleChange = (val) => {
|
||||||
|
// 2. 更新父组件的值
|
||||||
|
emit("update:modelValue", val);
|
||||||
|
emit("change", val);
|
||||||
|
|
||||||
|
// 3. 关键:通知 el-form-item 进行校验
|
||||||
|
if (formItem) {
|
||||||
|
formItem.validate("change").catch(() => {});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$bg-color: #f2f3f5;
|
||||||
|
$active-bg: #ffffff;
|
||||||
|
$primary-color: #1661ff;
|
||||||
|
$text-secondary: #86909c;
|
||||||
|
|
||||||
|
.base-segmented-flat {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
background-color: $bg-color;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.segmented-item {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
flex: 1;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: $text-secondary;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
border-radius: 4px;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: darken($text-secondary, 15%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中状态样式
|
||||||
|
&.is-active {
|
||||||
|
background-color: $active-bg;
|
||||||
|
color: $primary-color;
|
||||||
|
font-weight: 500;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
106
src/pages/stage/permission/baseSegmentedPermission.scss
Normal file
106
src/pages/stage/permission/baseSegmentedPermission.scss
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
.permission-container {
|
||||||
|
--primary-blue: #1661ff;
|
||||||
|
--danger-red: #f53f3f;
|
||||||
|
--text-main: #1d2129;
|
||||||
|
--text-grey: #86909c;
|
||||||
|
--border-color: #f2f3f5;
|
||||||
|
--common-padding:24px;
|
||||||
|
|
||||||
|
.custom-permission-tabs {
|
||||||
|
--el-tabs-header-height:50px;
|
||||||
|
--el-border-color-light:transparent;
|
||||||
|
:deep(.el-tabs__header){
|
||||||
|
padding: 0 var(--common-padding);
|
||||||
|
background-color: #FBFCFD;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
:deep(.el-tabs__content){
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
:deep(.el-tabs__item) {
|
||||||
|
font-size: 14px;
|
||||||
|
&.is-active {
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.permission-scroll-area{
|
||||||
|
padding: 0 var(--common-padding);
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: calc(100vh - 190px); //设置固定的高度
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-group {
|
||||||
|
padding: 20px 0;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
|
||||||
|
.group-header {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.group-title {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-main);
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-count {
|
||||||
|
color: var(--text-grey);
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 图二中:半选状态的蓝色方块样式 */
|
||||||
|
:deep(.el-checkbox__input.is-indeterminate .el-checkbox__inner) {
|
||||||
|
background-color: var(--primary-blue);
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
&::before {
|
||||||
|
height: 3px;
|
||||||
|
top: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-body {
|
||||||
|
padding-left: 28px;
|
||||||
|
|
||||||
|
.permission-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.row-label {
|
||||||
|
width: 180px;
|
||||||
|
:deep(.el-checkbox__label) {
|
||||||
|
color: var(--text-grey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-actions {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
:deep(.el-checkbox) {
|
||||||
|
margin-right: 32px;
|
||||||
|
|
||||||
|
/* 默认选中文字也保持蓝色 */
|
||||||
|
&.is-checked .el-checkbox__label {
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 特殊处理:删除 按钮 */
|
||||||
|
&.is-danger.is-checked .el-checkbox__label {
|
||||||
|
color: var(--danger-red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 禁用时的文字颜色微调 */
|
||||||
|
:deep(.el-checkbox.is-disabled .el-checkbox__label) {
|
||||||
|
color: #c0c4cc;
|
||||||
|
}
|
||||||
|
}
|
||||||
322
src/pages/stage/permission/fieldPermissionManager.vue
Normal file
322
src/pages/stage/permission/fieldPermissionManager.vue
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
<template>
|
||||||
|
<div class="field-permission-manager">
|
||||||
|
<div class="module-grid">
|
||||||
|
<div
|
||||||
|
v-for="mod in modules"
|
||||||
|
:key="mod.id"
|
||||||
|
class="module-card"
|
||||||
|
:class="{ 'is-active': modelValue === mod.id }"
|
||||||
|
@click="handleModuleChange(mod.id)"
|
||||||
|
>
|
||||||
|
{{ mod.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field-group-list">
|
||||||
|
<div
|
||||||
|
v-for="(group, index) in fieldGroups"
|
||||||
|
:key="group.groupId"
|
||||||
|
class="group-container"
|
||||||
|
:class="{ 'is-collapsed': group.collapsed }"
|
||||||
|
>
|
||||||
|
<div class="group-header" :class="{ 'is-collapsed': group.collapsed }">
|
||||||
|
<div class="header-left">
|
||||||
|
<el-icon><List /></el-icon>
|
||||||
|
<span class="group-title">{{ group.groupName }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="header-right">
|
||||||
|
<span class="quick-set-label">快速设置:</span>
|
||||||
|
<div class="quick-actions">
|
||||||
|
<span
|
||||||
|
v-for="opt in quickOptions"
|
||||||
|
:key="opt.value"
|
||||||
|
class="quick-btn"
|
||||||
|
:class="{ 'is-disabled': isProcessing }"
|
||||||
|
@click.stop="batchSetPermissionChunked(group, opt.value)"
|
||||||
|
>
|
||||||
|
{{ opt.label }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<el-icon class="collapse-icon" @click.stop="toggleGroup(index)"
|
||||||
|
><ArrowDown
|
||||||
|
/></el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field-rows-collapse">
|
||||||
|
<div class="field-rows-inner">
|
||||||
|
<div
|
||||||
|
v-for="field in group.fields"
|
||||||
|
:key="field.id"
|
||||||
|
class="field-item"
|
||||||
|
>
|
||||||
|
<div class="field-info">
|
||||||
|
<i class="tree-line-icon"></i>
|
||||||
|
<span class="field-label">{{ field.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="field-ctrl">
|
||||||
|
<el-select v-model="field.permission">
|
||||||
|
<el-option label="可查看" value="read" />
|
||||||
|
<el-option label="可编辑" value="edit" />
|
||||||
|
<el-option label="不可查看" value="none" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from "vue";
|
||||||
|
import { List, Document, ArrowDown } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// 模块列表
|
||||||
|
modules: { type: Array, default: () => [] },
|
||||||
|
// 初始选中的模块ID
|
||||||
|
modelValue: [String, Number],
|
||||||
|
fieldGroups: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:modelValue", "update:fieldGroups", "change"]);
|
||||||
|
const isProcessing = ref(false);
|
||||||
|
const processingGroupId = ref(null);
|
||||||
|
|
||||||
|
// 快速设置选项
|
||||||
|
const quickOptions = [
|
||||||
|
{ label: "可查看", value: "read" },
|
||||||
|
{ label: "可编辑", value: "edit" },
|
||||||
|
{ label: "不可查看", value: "none" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const batchSetPermissionChunked = (group, type) => {
|
||||||
|
if (isProcessing.value) return;
|
||||||
|
|
||||||
|
const fields = group.fields;
|
||||||
|
const total = fields.length;
|
||||||
|
const chunkSize = 40;
|
||||||
|
let currentIndex = 0;
|
||||||
|
|
||||||
|
isProcessing.value = true;
|
||||||
|
processingGroupId.value = group.groupId;
|
||||||
|
|
||||||
|
const runChunk = () => {
|
||||||
|
const nextLimit = Math.min(currentIndex + chunkSize, total);
|
||||||
|
for (let i = currentIndex; i < nextLimit; i++) {
|
||||||
|
fields[i].permission = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIndex = nextLimit;
|
||||||
|
|
||||||
|
if (currentIndex < total) {
|
||||||
|
// 关键:将控制权交还给浏览器渲染线程,下一帧继续
|
||||||
|
requestAnimationFrame(runChunk);
|
||||||
|
} else {
|
||||||
|
isProcessing.value = false;
|
||||||
|
processingGroupId.value = null;
|
||||||
|
emit("update:fieldGroups", [...props.fieldGroups]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
runChunk();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换折叠逻辑
|
||||||
|
const toggleGroup = (index) => {
|
||||||
|
console.log("");
|
||||||
|
props.fieldGroups[index].collapsed = !props.fieldGroups[index].collapsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModuleChange = (id) => {
|
||||||
|
emit("update:modelValue", id);
|
||||||
|
emit("change", id);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 批量设置权限逻辑
|
||||||
|
const batchSetPermission = (group, type) => {
|
||||||
|
group.fields.forEach((field) => {
|
||||||
|
field.permission = type;
|
||||||
|
});
|
||||||
|
emit("update:fieldGroups", [...props.fieldGroups]);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.field-permission-manager {
|
||||||
|
--primary-blue: #1661ff;
|
||||||
|
--bg-light: #f2f3f5;
|
||||||
|
--border-color: #e5e6eb;
|
||||||
|
--transition-speed: 0.3s;
|
||||||
|
|
||||||
|
overflow-y: auto;
|
||||||
|
height: calc(100vh - 190px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
.module-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding: 24px 24px 0;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
.module-card {
|
||||||
|
width: calc((100% - 48px) / 5);
|
||||||
|
min-width: 100px;
|
||||||
|
height: 40px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #4e5969;
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
&.is-active {
|
||||||
|
background: var(--primary-blue);
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.field-group-list {
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 字段卡片样式
|
||||||
|
.group-container {
|
||||||
|
border: 1px solid var(--bg-light);
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.group-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 48px;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1px solid var(--bg-light);
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
.el-icon {
|
||||||
|
color: #86909c;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.quick-set-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #86909c;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-right: 16px;
|
||||||
|
.quick-btn {
|
||||||
|
padding: 2px 10px;
|
||||||
|
background: #f7f8fa;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background: #eff4ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.collapse-icon {
|
||||||
|
color: #c9cdd4;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.field-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 16px 10px 32px;
|
||||||
|
border-bottom: 1px solid #fafafa;
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.tree-line-icon {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-left: 1px solid var(--border-color);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
.field-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #4e5969;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-ctrl {
|
||||||
|
flex: 0 0 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-rows-collapse {
|
||||||
|
max-height: 6000px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height var(--transition-speed)
|
||||||
|
cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
|
opacity var(--transition-speed);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 折叠后的状态
|
||||||
|
&.is-collapsed {
|
||||||
|
.group-header {
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-rows-collapse {
|
||||||
|
max-height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transition: max-height var(--transition-speed) cubic-bezier(0, 1, 0, 1),
|
||||||
|
opacity var(--transition-speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-icon {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapse-icon {
|
||||||
|
transition: transform var(--transition-speed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -16,7 +16,13 @@
|
|||||||
></el-input>
|
></el-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="mj-dict-actions-right">
|
<div class="mj-dict-actions-right">
|
||||||
<el-button :icon="'Plus'" type="primary" plain @click="addRolesClick">{{ checkRolesText.btnText }}</el-button>
|
<el-button
|
||||||
|
:icon="'Plus'"
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
@click="addBtnClick"
|
||||||
|
>{{ checkRolesText.btnText }}</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -31,32 +37,42 @@
|
|||||||
pagination
|
pagination
|
||||||
:request-api="getTableData"
|
:request-api="getTableData"
|
||||||
>
|
>
|
||||||
|
|
||||||
<!-- 状态 -->
|
<!-- 状态 -->
|
||||||
<template #status="{row}">
|
<template #status="{ row }">
|
||||||
<el-tag>{{ row.status }}</el-tag>
|
<el-tag>{{ row.status }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- 成员数量 -->
|
||||||
|
<template #member="{ row }">
|
||||||
|
<el-tag @click.stop="addMember">{{ row.member }}</el-tag>
|
||||||
|
</template>
|
||||||
</CommonTable>
|
</CommonTable>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 成员管理 -->
|
<!-- 成员管理 -->
|
||||||
<!-- <member-selector v-model:visible="showMember" /> -->
|
<member-selector v-model:visible="showMember" />
|
||||||
|
|
||||||
<!-- 新增角色 -->
|
<!-- 新增角色 -->
|
||||||
<add-roles v-model:visible="showMember" />
|
<add-roles v-model:visible="showRoles" />
|
||||||
|
|
||||||
|
<!-- 权限抽屉 -->
|
||||||
|
<permission-drawer v-model:visible="showPermission" />
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import CommonTable from "@/components/proTable/index.vue";
|
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";
|
||||||
defineOptions({ name: "PermissionManagement" });
|
defineOptions({ name: "PermissionManagement" });
|
||||||
const activeTab = ref(1);
|
const activeTab = ref(1);
|
||||||
const dictTableRef = ref(null);
|
const dictTableRef = 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(true);
|
const showMember = ref(false);
|
||||||
|
const showRoles = ref(false);
|
||||||
|
const showPermission = ref(false);
|
||||||
const tabList = [
|
const tabList = [
|
||||||
{
|
{
|
||||||
label: "角色与权限",
|
label: "角色与权限",
|
||||||
@@ -76,9 +92,10 @@ const columns = [
|
|||||||
{ prop: "id", label: "编号", width: "80", align: "center", slot: "number" },
|
{ prop: "id", label: "编号", width: "80", align: "center", slot: "number" },
|
||||||
{ prop: "name", label: "字典名称", align: "center", slot: "name" },
|
{ prop: "name", label: "字典名称", align: "center", slot: "name" },
|
||||||
{
|
{
|
||||||
prop: "number",
|
prop: "member",
|
||||||
label: "成员数量",
|
label: "成员数量",
|
||||||
align: "center",
|
align: "center",
|
||||||
|
slot: "member",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: "key",
|
prop: "key",
|
||||||
@@ -130,75 +147,85 @@ const columns = [
|
|||||||
type: "primary",
|
type: "primary",
|
||||||
link: true,
|
link: true,
|
||||||
permission: ["edit"],
|
permission: ["edit"],
|
||||||
onClick: (row) => handleEdit(row),
|
onClick: (row) => {
|
||||||
|
showPermission.value = true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "复制",
|
label: "复制",
|
||||||
type: "default",
|
type: "default",
|
||||||
link: true,
|
link: true,
|
||||||
permission: ["edit"],
|
permission: ["edit"],
|
||||||
onClick: (row) => handleEdit(row),
|
onClick: (row) => {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "编辑",
|
label: "编辑",
|
||||||
type: "default",
|
type: "default",
|
||||||
link: true,
|
link: true,
|
||||||
permission: ["config"],
|
permission: ["config"],
|
||||||
onClick: (row) => handlefieldsConfig(row),
|
onClick: (row) => {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "删除",
|
label: "删除",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
link: true,
|
link: true,
|
||||||
permission: ["delete"],
|
permission: ["delete"],
|
||||||
onClick: (row) => handleDelete(row),
|
onClick: (row) => {},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const getTableData = async (params) => {
|
// 当前成员数量
|
||||||
await new Promise(resolve => setTimeout(resolve, 500))
|
const addMember = () => {
|
||||||
return {
|
showMember.value = true;
|
||||||
records:[
|
|
||||||
{
|
|
||||||
id:1,
|
|
||||||
name:'11111',
|
|
||||||
number:'2222',
|
|
||||||
status:1,
|
|
||||||
key:'',
|
|
||||||
roleType:1,
|
|
||||||
remark:'',
|
|
||||||
createTime:1767878508824,
|
|
||||||
updateByName:'111'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getTableData = async (params) => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
return {
|
||||||
|
records: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "11111",
|
||||||
|
member: "2222",
|
||||||
|
status: 1,
|
||||||
|
key: "",
|
||||||
|
roleType: 1,
|
||||||
|
remark: "",
|
||||||
|
createTime: 1767878508824,
|
||||||
|
updateByName: "111",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const checkRolesText = computed(() => {
|
const checkRolesText = computed(() => {
|
||||||
const btnText = {
|
const btnText = {
|
||||||
1:'新增角色',
|
1: "新增角色",
|
||||||
2:'新增用户'
|
2: "新增用户",
|
||||||
}[activeTab.value]
|
}[activeTab.value];
|
||||||
|
|
||||||
const placeholder = {
|
const placeholder = {
|
||||||
1:'搜索角色名称...',
|
1: "搜索角色名称...",
|
||||||
2:'搜索用户姓名/账号...'
|
2: "搜索用户姓名/账号...",
|
||||||
}[activeTab.value]
|
}[activeTab.value];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
btnText,
|
btnText,
|
||||||
placeholder
|
placeholder,
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// 请求数据信息
|
// 请求数据信息
|
||||||
const fetchTableData = () => {};
|
const fetchTableData = () => {};
|
||||||
|
|
||||||
// 新增角色
|
// 新增角色
|
||||||
const addRolesClick = () => {};
|
const addBtnClick = () => {
|
||||||
|
if (activeTab.value === 1) {
|
||||||
|
showRoles.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.mj-permission-management {
|
.mj-permission-management {
|
||||||
|
|||||||
103
src/pages/stage/permission/permissionDrawer.vue
Normal file
103
src/pages/stage/permission/permissionDrawer.vue
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer
|
||||||
|
v-model="drawerVisible"
|
||||||
|
size="40%"
|
||||||
|
title="测试评论"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
destroy-on-close
|
||||||
|
:with-header="false"
|
||||||
|
modal-class="standard-overlay-dialog-flat"
|
||||||
|
class="standard-ui-back-drawer"
|
||||||
|
>
|
||||||
|
<div class="permission-drawer-container">
|
||||||
|
<!-- header -->
|
||||||
|
<div class="customer-drawer-header">
|
||||||
|
<div class="title-row">
|
||||||
|
<span class="title">权限配置</span>
|
||||||
|
<el-icon class="close-icon" @click="drawerVisible = false"
|
||||||
|
><Close
|
||||||
|
/></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="sub-title">
|
||||||
|
正在为:<span class="sub-roles">[超级管理员] </span>分配系统访问权限
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 内容 -->
|
||||||
|
|
||||||
|
<div class="permission-drawer-content">
|
||||||
|
<div class="permission-container">
|
||||||
|
<el-tabs v-model="activeTab" class="custom-permission-tabs">
|
||||||
|
<el-tab-pane label="菜单权限" name="menu">
|
||||||
|
<base-segment-menu></base-segment-menu>
|
||||||
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="字段权限" name="field">
|
||||||
|
<fieldPermissionManager
|
||||||
|
v-model="currentModule"
|
||||||
|
:modules="moduleList"
|
||||||
|
v-model:field-groups="fieldsList"
|
||||||
|
@change="onModuleTabChange"
|
||||||
|
/>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 底部footer -->
|
||||||
|
<template #footer>
|
||||||
|
<div class="custom-flat-drawer-footer">
|
||||||
|
<div class="stats-info"></div>
|
||||||
|
<div class="actions">
|
||||||
|
<el-button link @click="drawerVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" class="btn-confirm">确认应用</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Close } from "@element-plus/icons-vue";
|
||||||
|
import baseSegmentMenu from "./baseSegmentMenu.vue";
|
||||||
|
import fieldPermissionManager from "./fieldPermissionManager.vue";
|
||||||
|
defineOptions({ name: "PermissionDrawer" });
|
||||||
|
|
||||||
|
const drawerVisible = defineModel("visible", { type: Boolean, default: false });
|
||||||
|
const activeTab = ref("menu");
|
||||||
|
const currentModule = ref(4); // 默认选中“商机详情”
|
||||||
|
const fieldsList = ref([
|
||||||
|
{
|
||||||
|
groupId: "g1",
|
||||||
|
groupName: "文本",
|
||||||
|
fields: [
|
||||||
|
{ id: "f1", name: "商机名称", permission: "read", collapsed: false },
|
||||||
|
{ id: "f2", name: "商机需求描述", permission: "read", collapsed: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
groupId: "g2",
|
||||||
|
groupName: "选择",
|
||||||
|
fields: [
|
||||||
|
{ id: "f3", name: "合作类型", permission: "read", collapsed: false },
|
||||||
|
{ id: "f4", name: "需求品类", permission: "read", collapsed: false },
|
||||||
|
{ id: "f5", name: "线索/商机渠道", permission: "read", collapsed: false },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const moduleList = [
|
||||||
|
{ id: 1, name: "线索详情" },
|
||||||
|
{ id: 2, name: "客户详情" },
|
||||||
|
{ id: 3, name: "工作室详情" },
|
||||||
|
{ id: 4, name: "商机详情" },
|
||||||
|
{ id: 5, name: "合同详情" },
|
||||||
|
{ id: 6, name: "项目详情" },
|
||||||
|
// ...以此类推
|
||||||
|
];
|
||||||
|
|
||||||
|
const onModuleTabChange = (id) => {
|
||||||
|
console.log("切换到模块:", id);
|
||||||
|
// 这里可以调用接口更新 currentFields 的数据
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@use "./baseSegmentedPermission.scss" as *;
|
||||||
|
</style>
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
|
|
||||||
|
|
||||||
// 当前样式表修改element 全局的样式
|
// 当前样式表修改element 全局的样式
|
||||||
|
|
||||||
// 标砖抽屉样式
|
// 标砖抽屉样式
|
||||||
.standard-ui-drawer{
|
.standard-ui-drawer {
|
||||||
.el-drawer__header{
|
.el-drawer__header {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
padding-bottom: var(--el-drawer-padding-primary);
|
padding-bottom: var(--el-drawer-padding-primary);
|
||||||
&::after{
|
|
||||||
content:'';
|
&::after {
|
||||||
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: var(--el-drawer-padding-primary);
|
left: var(--el-drawer-padding-primary);
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -20,51 +19,162 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.standard-ui-back-drawer{
|
.standard-ui-back-drawer {
|
||||||
@extend .standard-ui-drawer;
|
@extend .standard-ui-drawer;
|
||||||
.el-drawer__header{
|
|
||||||
|
.el-drawer__header {
|
||||||
background-color: #FBFCFD;
|
background-color: #FBFCFD;
|
||||||
&::after{
|
|
||||||
|
&::after {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.el-drawer__body{
|
|
||||||
|
.customer-drawer-header {
|
||||||
|
padding: 20px 24px 15px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 4px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: #409eff;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 20px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #909399;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-title {
|
||||||
|
margin: 2px 0 0 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #909399;
|
||||||
|
.sub-roles{
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-drawer__body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
.el-drawer__footer{
|
|
||||||
|
.el-drawer__footer {
|
||||||
background-color: #FBFCFD;
|
background-color: #FBFCFD;
|
||||||
border-top: 1px solid #E5E7EB;
|
border-top: 1px solid #E5E7EB;
|
||||||
|
|
||||||
|
.custom-flat-drawer-footer {
|
||||||
|
--black-bg-hover-color:#2b364a;
|
||||||
|
--black-bg-color:#1d2635;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
.btn-confirm {
|
||||||
|
background-color: var(--black-bg-color);
|
||||||
|
border-color: var(--black-bg-color);
|
||||||
|
padding: 10px 24px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--black-bg-hover-color);
|
||||||
|
border-color: var(--black-bg-hover-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 标注弹窗样式
|
// 标注弹窗样式
|
||||||
.standard-ui-dialog{
|
.standard-ui-dialog {
|
||||||
&.el-dialog{
|
&.el-dialog {
|
||||||
--el-dialog-inset-padding-primary:16px;
|
--el-dialog-inset-padding-primary: 16px;
|
||||||
--el-dialog-padding-primary:0;
|
--el-dialog-padding-primary: 0;
|
||||||
--el-dialog-border-radius:16px;
|
--el-dialog-border-radius: 16px;
|
||||||
--el-dialog-bg-header-footer:#FBFCFD;
|
--el-dialog-bg-header-footer: #FBFCFD;
|
||||||
--el-dialog-border-header-footer-color:#E5E7EB;
|
--el-dialog-border-header-footer-color: #E5E7EB;
|
||||||
padding: var(--el-dialog-padding-primary);
|
padding: var(--el-dialog-padding-primary);
|
||||||
}
|
}
|
||||||
.el-dialog__header{
|
|
||||||
|
.el-dialog__header {
|
||||||
border-bottom: 1px solid var(--el-dialog-border-header-footer-color);
|
border-bottom: 1px solid var(--el-dialog-border-header-footer-color);
|
||||||
background-color:var(--el-dialog-bg-header-footer);
|
background-color: var(--el-dialog-bg-header-footer);
|
||||||
padding: var(--el-dialog-inset-padding-primary);
|
padding: var(--el-dialog-inset-padding-primary);
|
||||||
border-radius: var(--el-dialog-border-radius) var(--el-dialog-border-radius) 0 0;
|
border-radius: var(--el-dialog-border-radius) var(--el-dialog-border-radius) 0 0;
|
||||||
}
|
}
|
||||||
.el-dialog__headerbtn{
|
|
||||||
|
.el-dialog__headerbtn {
|
||||||
height: 60px;
|
height: 60px;
|
||||||
}
|
}
|
||||||
.el-dialog__body{
|
|
||||||
|
.el-dialog__body {
|
||||||
padding: var(--el-dialog-inset-padding-primary);
|
padding: var(--el-dialog-inset-padding-primary);
|
||||||
}
|
}
|
||||||
.el-dialog__footer{
|
|
||||||
|
.el-dialog__footer {
|
||||||
padding: var(--el-dialog-inset-padding-primary);
|
padding: var(--el-dialog-inset-padding-primary);
|
||||||
background-color: var(--el-dialog-bg-header-footer);
|
background-color: var(--el-dialog-bg-header-footer);
|
||||||
border-top: 1px solid var(--el-dialog-border-header-footer-color);
|
border-top: 1px solid var(--el-dialog-border-header-footer-color);
|
||||||
border-radius: 0 0 var(--el-dialog-border-radius) var(--el-dialog-border-radius);
|
border-radius: 0 0 var(--el-dialog-border-radius) var(--el-dialog-border-radius);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 扁平化dialog样式
|
||||||
|
.standard-ui-flat-dialog {
|
||||||
|
&.el-dialog {
|
||||||
|
--el-dialog-inset-padding-primary: 16px;
|
||||||
|
--el-dialog-padding-primary: 0;
|
||||||
|
--el-dialog-border-radius: 4px;
|
||||||
|
--el-dialog-bg-header-footer: #fcfdfe;
|
||||||
|
--el-dialog-border-header-footer-color: #f0f2f5;
|
||||||
|
--blue-block-color: #1661ff;
|
||||||
|
padding: var(--el-dialog-padding-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__header {
|
||||||
|
padding: var(--el-dialog-inset-padding-primary);
|
||||||
|
border-bottom: 1px solid var(--el-dialog-border-header-footer-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__body {
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__footer {
|
||||||
|
text-align: right;
|
||||||
|
background-color: var(--el-dialog-bg-header-footer);
|
||||||
|
border-top: 1px solid var(--el-dialog-border-header-footer-color);
|
||||||
|
border-radius: 0 0 var(--el-dialog-border-radius) var(--el-dialog-border-radius);
|
||||||
|
padding: var(--el-dialog-inset-padding-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 扁平化遮罩层样式 (drawer-dialog 的遮罩层样式)
|
||||||
|
.standard-overlay-dialog-flat {
|
||||||
|
--el-overlay-color-lighter: rgba(0, 0, 0, .12);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user