feat: 项目初始化,未病局

This commit is contained in:
fuenmao
2025-09-20 10:36:39 +08:00
commit 39f70340a0
421 changed files with 48180 additions and 0 deletions
@@ -0,0 +1,104 @@
<!--
* @Description: 复制
* @Author: huguodong
* @Date: 2023-12-15 15:45:25
!-->
<template>
<form-container v-model="visible" title="复制机构" form-size="600px">
<el-form ref="orgFormRef" :rules="rules" :model="copyProps" label-width="auto">
<s-form-item label="目标机构" prop="targetId">
<org-selector v-model:org-value="copyProps.targetId" :org-tree-api="bizOrgApi.tree" :show-all="false" />
</s-form-item>
<s-form-item label="包含子集" prop="containsChild" :tooltip="{ content: '将组织下的子机构也复制一份到目标组织' }">
<s-radio-group v-model="copyProps.containsChild" :options="yesOptions" button />
</s-form-item>
<s-form-item label="包含职位" prop="containsPosition" :tooltip="{ content: '将组织下的岗位也复制一份到目标组织' }">
<s-radio-group v-model="copyProps.containsPosition" :options="yesOptions" button />
</s-form-item>
</el-form>
<template #footer>
<el-button @click="onClose"> 取消 </el-button>
<el-button type="primary" @click="handleSubmit"> 确定 </el-button>
</template>
</form-container>
</template>
<script setup lang="ts">
import { bizOrgApi } from "@/api";
import { SysDictEnum } from "@/enums";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";
import { useDictStore } from "@/stores/modules";
const visible = ref(false); //是否显示表单
const dictStore = useDictStore(); //字典仓库
// 是否选项
const yesOptions = dictStore.getDictList(SysDictEnum.YES_NO);
// 表单参数
interface copyProps {
/** 目标组织id */
targetId: string | number;
/** 选择的组织id */
ids: string[] | number[];
/** 是否包含子集 */
containsChild: boolean;
/** 是否包含职位 */
containsPosition: boolean;
/** 成功后的回调 */
successful?: () => void;
}
// 表单参数
const copyProps = reactive<copyProps>({
targetId: "",
ids: [],
containsChild: true,
containsPosition: true
});
// 表单验证规则
const rules = reactive({
targetId: [required("请选择目标机构")],
containsChild: [required("请选择是否包含子集")]
});
/**
* 打开表单
* @param props 表单参数
*/
function onOpen(ids: string[] | number[], successful: () => void) {
visible.value = true; //显示表单
copyProps.ids = ids; //设置机构id
copyProps.successful = successful;
}
// 提交数据(新增/编辑)
const orgFormRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
orgFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
//提交表单
await bizOrgApi
.copy(copyProps)
.then(() => {
copyProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
});
}
/** 关闭表单*/
function onClose() {
visible.value = false;
}
// 暴露给父组件的方法
defineExpose({
onOpen
});
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,157 @@
<!--
* @Description: 表单
* @Author: huguodong
* @Date: 2023-12-15 15:45:28
!-->
<template>
<div>
<form-container v-model="visible" :title="`${orgProps.opt}机构`" form-size="600px">
<el-form
ref="orgFormRef"
:rules="rules"
:disabled="orgProps.disabled"
:model="orgProps.record"
:hide-required-asterisk="orgProps.disabled"
label-width="auto"
label-suffix=" :"
>
<s-form-item label="上级机构" prop="parentId">
<org-selector v-model:org-value="orgProps.record.parentId!" :org-tree-api="bizOrgApi.tree" :show-all="false" />
</s-form-item>
<s-form-item label="机构名称" prop="name">
<s-input v-model="orgProps.record.name"></s-input>
</s-form-item>
<s-form-item label="机构编码" prop="code">
<s-input v-model="orgProps.record.code" placeholder="请填写机构编码,不填则为随机值" clearable></s-input>
</s-form-item>
<s-form-item label="状态" prop="status">
<s-radio-group v-model="orgProps.record.status" :options="statusOptions" button />
</s-form-item>
<s-form-item label="指定主管" prop="sortCode">
<el-button link type="primary" @click="showSelector">选择</el-button>
<el-tag v-if="orgProps.record.directorId" class="ml-3px" type="warning" closable @close="removeDirector">{{
orgProps.record.directorInfo?.name
}}</el-tag>
</s-form-item>
<s-form-item label="排序" prop="sortCode">
<el-slider v-model="orgProps.record.sortCode" show-input :min="1" />
</s-form-item>
</el-form>
<template #footer>
<el-button @click="onClose"> 取消 </el-button>
<el-button v-show="!orgProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
</template>
</form-container>
<user-selector
ref="userSelectorRef"
biz
:org-tree-api="bizOrgApi.tree"
:position-tree-api="bizPositionApi.tree"
:role-tree-api="bizRoleApi.tree"
:user-selector-api="bizUserApi.selector"
@successful="handleChooseUser"
/>
</div>
</template>
<script setup lang="ts">
import { SysOrg, SysUser, bizOrgApi, bizRoleApi, bizPositionApi, bizUserApi } from "@/api";
import { FormOptEnum, SysDictEnum } from "@/enums";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";
import { useDictStore } from "@/stores/modules";
import { UserSelectorInstance } from "@/components/Selectors/UserSelector/interface";
const visible = ref(false); //是否显示表单
const dictStore = useDictStore(); //字典仓库
// 通用状态选项
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
// 表单参数
const orgProps = reactive<FormProps.Base<SysOrg.SysOrgInfo>>({
opt: FormOptEnum.ADD,
record: {},
disabled: false
});
// 表单验证规则
const rules = reactive({
name: [required("请输入机构名称")],
parentId: [required("请选择上级机构")],
category: [required("请选择机构类型")],
status: [required("请选择状态")]
});
/**
* 打开表单
* @param props 表单参数
*/
function onOpen(props: FormProps.Base<SysOrg.SysOrgInfo>) {
Object.assign(orgProps, props); //合并参数
if (props.opt == FormOptEnum.ADD) {
//如果是新增,设置默认值
orgProps.record.sortCode = 99;
orgProps.record.status = statusOptions[0].value;
}
visible.value = true; //显示表单
if (props.record.id) {
//如果传了id,就去请求api获取record
bizOrgApi.detail({ id: props.record.id }).then(res => {
orgProps.record = res.data;
});
}
}
// 提交数据(新增/编辑)
const orgFormRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
orgFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
//提交表单
await bizOrgApi
.submitForm(orgProps.record, orgProps.record.id != undefined)
.then(() => {
orgProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
});
}
/** 关闭表单*/
function onClose() {
visible.value = false;
}
const userSelectorRef = ref<UserSelectorInstance>(); //用户选择器引用
/** 显示用户选择器 */
function showSelector() {
//将orgProps.record.directorInfo转为 SysUser.SysUserInfo[]类型
const directorInfo = orgProps.record.directorInfo ? [orgProps.record.directorInfo] : [];
userSelectorRef.value?.showSelector(directorInfo);
}
/** 选择用户 */
function handleChooseUser(data: SysUser.SysUserInfo[]) {
// 选择用户后,将用户id赋值给orgProps.record.directorId
if (data.length > 0) {
orgProps.record.directorId = data[0].id;
orgProps.record.directorInfo = data[0];
}
}
/** 移除主管 */
function removeDirector() {
orgProps.record.directorId = null;
orgProps.record.directorInfo = null;
}
// 暴露给父组件的方法
defineExpose({
onOpen
});
</script>
<style lang="scss" scoped></style>
+162
View File
@@ -0,0 +1,162 @@
<!--
* @Description: 机构管理
* @Author: huguodong
* @Date: 2023-12-15 15:45:32
!-->
<template>
<div class="main-box">
<TreeFilter
ref="treeFilter"
label="name"
title="机构列表"
:show-all="false"
:request-api="bizOrgApi.tree"
:default-value="initParam.parentId"
@change="changeTreeFilter"
/>
<div class="table-box">
<ProTable ref="proTable" title="机构列表" :columns="columns" :request-api="bizOrgApi.page" :init-param="initParam">
<!-- 表格 header 按钮 -->
<template #tableHeader="scope">
<s-button v-auth="bizOrgButtonCode.add" suffix="机构" @click="onOpen(FormOptEnum.ADD)" />
<s-button v-auth="bizOrgButtonCode.copy" :disabled="!scope.isSelected" :icon="CopyDocument" @click="onOpenCopy(scope.selectedListIds)">
复制机构
</s-button>
<s-button
type="danger"
v-auth="bizOrgButtonCode.batchDelete"
plain
suffix="机构"
:opt="FormOptEnum.DELETE"
:disabled="!scope.isSelected"
@click="onDelete(scope.selectedListIds, '删除所选机构')"
/>
</template>
<!-- 状态 -->
<template #status="scope">
<el-tag v-if="scope.row.status === CommonStatusEnum.ENABLE" type="success">{{
dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status)
}}</el-tag>
<el-tag v-else type="danger">{{ dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status) }}</el-tag>
</template>
<!-- 操作 -->
<template #operation="scope">
<s-button v-auth="bizOrgButtonCode.edit" link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
<s-button
v-auth="bizOrgButtonCode.delete"
link
:opt="FormOptEnum.DELETE"
@click="onDelete([scope.row.id], `删除【${scope.row.name}】机构`)"
/>
</template>
</ProTable>
<!-- 新增/编辑表单 -->
<Form ref="formRef" />
<!-- 复制表单 -->
<Copy ref="copyRef" />
</div>
</div>
</template>
<script setup lang="ts" name="sysOrg">
import { bizOrgApi, SysOrg, bizOrgButtonCode } from "@/api";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { CopyDocument } from "@element-plus/icons-vue";
import { SysDictEnum, FormOptEnum, CommonStatusEnum } from "@/enums";
import { useHandleData } from "@/hooks";
import { useDictStore } from "@/stores/modules";
import TreeFilter from "@/components/TreeFilter/index.vue";
import Form from "./components/form.vue";
import Copy from "./components/copy.vue";
const dictStore = useDictStore(); //字典仓库
// 机构类型选项
const orgCategoryOptions = dictStore.getDictList(SysDictEnum.ORG_CATEGORY);
// 状态选项
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
const treeFilter = ref<InstanceType<typeof TreeFilter> | null>(null);
interface InitParam {
parentId: number | string;
}
// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
const initParam = reactive<InitParam>({ parentId: 0 });
// 获取 ProTable 元素
const proTable = ref<ProTableInstance>();
// 表格配置项
const columns: ColumnProps<SysOrg.SysOrgInfo>[] = [
{ type: "selection", fixed: "left", width: 50 },
{ prop: "name", label: "机构名称", search: { el: "input" } },
{ prop: "names", label: "机构全称" },
{
prop: "category",
label: "分类",
enum: orgCategoryOptions,
search: { el: "select" }
},
{ prop: "code", label: "编码", search: { el: "input" } },
{ prop: "status", label: "状态", enum: statusOptions, search: { el: "select" } },
{ prop: "sortCode", label: "排序" },
{ prop: "createTime", label: "创建时间" },
{ prop: "operation", label: "操作", width: 150, fixed: "right" }
];
/** 部门切换 */
function changeTreeFilter(val: number | string) {
proTable.value!.pageable.pageNum = 1;
if (val != "") {
// 如果传入的val不为空,则将val赋值给initParam.parentId
initParam.parentId = val;
} else {
// 否则将initParam.parentId赋值为0
initParam.parentId = 0;
}
}
// 表单引用
const formRef = ref<InstanceType<typeof Form> | null>(null);
/**
* 打开表单
* @param opt 操作类型
* @param record 记录
*/
function onOpen(opt: FormOptEnum, record: {} | SysOrg.SysOrgInfo = {}) {
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
}
// 表单引用
const copyRef = ref<InstanceType<typeof Copy> | null>(null);
/**
* 打开复制表单
* @param opt 操作类型
* @param record 记录
*/
function onOpenCopy(ids: string[] | number[]) {
copyRef.value?.onOpen(ids, RefreshTable);
}
/**
* 删除
* @param ids id数组
*/
async function onDelete(ids: string[], msg: string) {
// 二次确认 => 请求api => 刷新表格
await useHandleData(bizOrgApi.delete, { ids }, msg);
RefreshTable();
}
/**
* 刷新表格
*/
function RefreshTable() {
proTable.value?.refresh(); //刷新表格
treeFilter.value?.refresh(); //刷新树形筛选器
}
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,122 @@
<!--
* @Description: 表单
* @Author: huguodong
* @Date: 2023-12-15 15:45:37
!-->
<template>
<div>
<form-container v-model="visible" :title="`${positionProps.opt}岗位`" form-size="600px">
<el-form
ref="positionFormRef"
:rules="rules"
:disabled="positionProps.disabled"
:model="positionProps.record"
:hide-required-asterisk="positionProps.disabled"
label-width="auto"
label-suffix=" :"
>
<s-form-item label="所属组织" prop="orgId">
<org-selector v-model:org-value="positionProps.record.orgId!" :org-tree-api="bizOrgApi.tree" :show-all="false" />
</s-form-item>
<s-form-item label="岗位名称" prop="name">
<s-input v-model="positionProps.record.name"></s-input>
</s-form-item>
<s-form-item label="岗位分类" prop="category">
<s-radio-group v-model="positionProps.record.category" :options="posCategoryOptions" button />
</s-form-item>
<s-form-item label="状态" prop="status">
<s-radio-group v-model="positionProps.record.status" :options="statusOptions" button />
</s-form-item>
<s-form-item label="排序" prop="sortCode">
<el-slider v-model="positionProps.record.sortCode" show-input :min="1" />
</s-form-item>
</el-form>
<template #footer>
<el-button @click="onClose"> 取消 </el-button>
<el-button v-show="!positionProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
</template>
</form-container>
</div>
</template>
<script setup lang="ts">
import { bizOrgApi, SysPosition, bizPositionApi } from "@/api";
import { FormOptEnum, SysDictEnum } from "@/enums";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";
import { useDictStore } from "@/stores/modules";
const visible = ref(false); //是否显示表单
const dictStore = useDictStore(); //字典仓库
// 岗位类型选项
const posCategoryOptions = dictStore.getDictList(SysDictEnum.POSITION_CATEGORY);
// 通用状态选项
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
// 表单参数
const positionProps = reactive<FormProps.Base<SysPosition.SysPositionInfo>>({
opt: FormOptEnum.ADD,
record: {},
disabled: false
});
// 表单验证规则
const rules = reactive({
parentId: [required("请选择所属组织")],
name: [required("请输入岗位名称")],
orgId: [required("请选择所属岗位")],
category: [required("请选择岗位类型")],
status: [required("请选择状态")]
});
/**
* 打开表单
* @param props 表单参数
*/
function onOpen(props: FormProps.Base<SysPosition.SysPositionInfo>) {
Object.assign(positionProps, props); //合并参数
if (props.opt == FormOptEnum.ADD) {
//如果是新增,设置默认值
positionProps.record.sortCode = 99;
positionProps.record.category = posCategoryOptions[0].value;
positionProps.record.status = statusOptions[0].value;
}
visible.value = true; //显示表单
if (props.record.id) {
//如果传了id,就去请求api获取record
bizPositionApi.detail({ id: props.record.id }).then(res => {
positionProps.record = res.data;
});
}
}
// 提交数据(新增/编辑)
const positionFormRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
positionFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
//提交表单
await bizPositionApi
.submitForm(positionProps.record, positionProps.record.id != undefined)
.then(() => {
positionProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
});
}
/** 关闭表单*/
function onClose() {
visible.value = false;
}
// 暴露给父组件的方法
defineExpose({
onOpen
});
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,141 @@
<!--
* @Description: 岗位管理
* @Author: huguodong
* @Date: 2023-12-15 15:45:43
!-->
<template>
<div class="main-box">
<TreeFilter
ref="treeFilter"
label="name"
title="机构列表"
:show-all="false"
:request-api="bizOrgApi.tree"
:default-value="initParam.orgId"
@change="changeTreeFilter"
/>
<div class="table-box">
<ProTable ref="proTable" title="岗位列表" :columns="columns" :request-api="bizPositionApi.page" :init-param="initParam">
<!-- 表格 header 按钮 -->
<template #tableHeader="scope">
<s-button v-auth="bizPositionButtonCode.add" suffix="岗位" @click="onOpen(FormOptEnum.ADD)" />
<s-button
v-auth="bizPositionButtonCode.batchDelete"
type="danger"
plain
suffix="岗位"
:opt="FormOptEnum.DELETE"
:disabled="!scope.isSelected"
@click="onDelete(scope.selectedListIds, '删除所选岗位')"
/>
</template>
<!-- 状态 -->
<template #status="scope">
<el-tag v-if="scope.row.status === CommonStatusEnum.ENABLE" type="success">{{
dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status)
}}</el-tag>
<el-tag v-else type="danger">{{ dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status) }}</el-tag>
</template>
<!-- 操作 -->
<template #operation="scope">
<s-button v-auth="bizPositionButtonCode.edit" link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
<s-button
v-auth="bizPositionButtonCode.delete"
link
:opt="FormOptEnum.DELETE"
@click="onDelete([scope.row.id], `删除【${scope.row.name}】岗位`)"
/>
</template>
</ProTable>
<!-- 新增/编辑表单 -->
<Form ref="formRef" />
</div>
</div>
</template>
<script setup lang="ts" name="sysPosition">
import { bizOrgApi, bizPositionApi, SysPosition, bizPositionButtonCode } from "@/api";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { SysDictEnum, FormOptEnum, CommonStatusEnum } from "@/enums";
import { useHandleData } from "@/hooks/useHandleData";
import { useDictStore } from "@/stores/modules";
import TreeFilter from "@/components/TreeFilter/index.vue";
import Form from "./components/form.vue";
const dictStore = useDictStore(); //字典仓库
// 岗位类型选项
const posCategoryOptions = dictStore.getDictList(SysDictEnum.POSITION_CATEGORY);
// 状态选项
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
const treeFilter = ref<InstanceType<typeof TreeFilter> | null>(null);
interface InitParam {
orgId: number | string;
}
// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
const initParam = reactive<InitParam>({ orgId: 0 });
// 获取 ProTable 元素
const proTable = ref<ProTableInstance>();
// 表格配置项
const columns: ColumnProps<SysPosition.SysPositionInfo>[] = [
{ type: "selection", fixed: "left", width: 50 },
{ prop: "name", label: "岗位名称", search: { el: "input" } },
{
prop: "category",
label: "岗位分类",
enum: posCategoryOptions,
search: { el: "select" }
},
{ prop: "code", label: "岗位编码", search: { el: "input" } },
{ prop: "status", label: "状态", enum: statusOptions, search: { el: "select" } },
{ prop: "sortCode", label: "排序" },
{ prop: "createTime", label: "创建时间" },
{ prop: "operation", label: "操作", width: 150, fixed: "right" }
];
/** 部门切换 */
function changeTreeFilter(val: number | string) {
proTable.value!.pageable.pageNum = 1;
if (val != "") {
// 如果传入的val不为空,则将val赋值给initParam.parentId
initParam.orgId = val;
} else {
// 否则将initParam.parentId赋值为0
initParam.orgId = 0;
}
}
// 表单引用
const formRef = ref<InstanceType<typeof Form> | null>(null);
/**
* 打开表单
* @param opt 操作类型
* @param record 记录
*/
function onOpen(opt: FormOptEnum, record: {} | SysPosition.SysPositionInfo = {}) {
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
}
/**
* 删除
* @param ids id数组
*/
async function onDelete(ids: string[], msg: string) {
// 二次确认 => 请求api => 刷新表格
await useHandleData(bizPositionApi.delete, { ids }, msg);
RefreshTable();
}
/**
* 刷新表格
*/
function RefreshTable() {
proTable.value?.refresh(); //刷新表格
treeFilter.value?.refresh(); //刷新树形筛选器
}
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,118 @@
<!--
* @Description: 数据范围选择器
* @Author: huguodong
* @Date: 2023-12-15 15:43:27
!-->
<template>
<div>
<el-space>
<s-radio-group v-model="defaultLevel" :options="scopeOptions" value-key="level" label-key="title" @change="changeDataScope">
<template #radio="{ item }">
<el-badge
v-if="
dataScope.level === defineScope?.level && item.scopeCategory === SysRole.DataScopeEnum.SCOPE_ORG_DEFINE && item.scopeDefineOrgIdList
"
:value="dataScope.scopeDefineOrgIdList.length"
type="success"
>
<span class="mb-5">{{ item.title }}</span>
</el-badge>
<span v-else>{{ item.title }}</span>
</template>
</s-radio-group>
<div v-if="dataScope.level === defineScope?.level">
<el-button link type="primary" class="ml-15px" @click="handleDefineOrg">选择机构</el-button>
</div>
</el-space>
<el-dialog v-model="dialogVisible" append-to-body title="选择机构" width="400" destroy-on-close>
<TreeFilter
title="机构列表"
multiple
check-strictly
class="treeFilter"
label="name"
:request-api="bizOrgApi.tree"
:default-value="dataScope.scopeDefineOrgIdList"
@change="changeTreeFilter"
/>
<template #footer>
<el-button @click="dialogCancel">取消</el-button>
<el-button type="primary" @click="chooseDefineOrg"> 确定 </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { SysRole, bizOrgApi } from "@/api";
const dialogVisible = ref(false); //是否显示弹窗
// props
interface DataScopeProps {
modelValue: SysRole.DefaultDataScope | undefined;
}
const emit = defineEmits(["update:modelValue"]); //定义emit
const props = defineProps<DataScopeProps>(); //定义props
// 用户信息
const dataScope = computed({
get: () => props.modelValue ?? scopeOptions[0], // 判断是否有值,没有值则取第一个
set: val => emit("update:modelValue", val)
});
// 数据范围列表,去掉全部
const scopeOptions = SysRole.dataScopeOptions.filter(item => item.scopeCategory !== SysRole.DataScopeEnum.SCOPE_ALL);
// 默认数据范围
const defaultLevel = ref(dataScope.value.level);
// 自定义数据范围
const defineScope = SysRole.dataScopeOptions.find(item => item.scopeCategory === SysRole.DataScopeEnum.SCOPE_ORG_DEFINE);
// 自定义机构id列表
const defineOrgIdList = ref<number[] | string[]>(dataScope.value.scopeDefineOrgIdList);
/** 修改数据权限 */
function changeDataScope(value: any) {
let scope = SysRole.dataScopeOptions.find(item => item.level === value);
if (scope) {
dataScope.value = scope;
}
}
// /** 处理自定义 */
function handleDefineOrg() {
dialogVisible.value = true;
console.log("[ dataScope.value ] >", dataScope.value);
}
/** 机构树数据变化 */
function changeTreeFilter(val: any[]) {
defineOrgIdList.value = val;
}
/** 取消选择机构 */
function dialogCancel() {
// 关闭弹窗
dialogVisible.value = false;
}
/** 选择机构 */
function chooseDefineOrg() {
// 设置默认数据权限中的机构ID列表
// 关闭弹窗
dialogVisible.value = false;
dataScope.value.scopeDefineOrgIdList = defineOrgIdList.value;
console.log("[ dataScope.value ] >", dataScope.value);
}
</script>
<style lang="scss" scoped>
.treeFilter {
width: 100% !important;
}
:deep(.el-badge__content.is-fixed) {
transform: translateY(-30%) translateX(100%);
}
</style>
@@ -0,0 +1,118 @@
<!--
* @Description: 表单
* @Author: huguodong
* @Date: 2023-12-15 15:43:34
!-->
<template>
<form-container v-model="visible" :title="`${sysRoleProps.opt}角色`" form-size="800px" @close="onClose">
<el-form
ref="sysRoleFormRef"
:rules="rules"
:disabled="sysRoleProps.disabled"
:model="sysRoleProps.record"
:hide-required-asterisk="sysRoleProps.disabled"
label-width="auto"
label-suffix=" :"
>
<s-form-item label="角色名称" prop="name">
<s-input v-model="sysRoleProps.record.name"></s-input>
</s-form-item>
<s-form-item label="所属组织" prop="orgId">
<org-selector v-model:org-value="sysRoleProps.record.orgId!" :org-tree-api="bizOrgApi.tree" :show-all="false" />
</s-form-item>
<s-form-item label="默认数据范围" prop="code">
<DataScopeSelector v-model="sysRoleProps.record.defaultDataScope"></DataScopeSelector>
</s-form-item>
<s-form-item label="状态" prop="status">
<s-radio-group v-model="sysRoleProps.record.status" :options="statusOptions" button />
</s-form-item>
<s-form-item label="排序" prop="sortCode">
<el-slider v-model="sysRoleProps.record.sortCode" show-input :min="1" />
</s-form-item>
</el-form>
<template #footer>
<el-button @click="onClose"> 取消 </el-button>
<el-button v-show="!sysRoleProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
</template>
</form-container>
</template>
<script setup lang="ts">
import { SysRole, bizRoleApi, bizOrgApi } from "@/api";
import { FormOptEnum, SysDictEnum } from "@/enums";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";
import { useDictStore } from "@/stores/modules";
import DataScopeSelector from "./dataScopeSelector.vue";
const visible = ref(false); //是否显示表单
const dictStore = useDictStore(); //字典仓库
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS); // 通用状态选项
// 表单参数
const sysRoleProps = reactive<FormProps.Base<SysRole.SysRoleInfo>>({
opt: FormOptEnum.ADD,
record: {},
disabled: false
});
// 表单验证规则
const rules = reactive({
name: [required("请输入角色名称")],
orgId: [required("请选择所属组织")],
category: [required("请选择角色类型")],
status: [required("请选择状态")]
});
/**
* 打开表单
* @param props 表单参数
*/
function onOpen(props: FormProps.Base<SysRole.SysRoleInfo>) {
Object.assign(sysRoleProps, props); //合并参数
if (props.opt == FormOptEnum.ADD) {
//如果是新增,设置默认值
sysRoleProps.record.sortCode = 99;
sysRoleProps.record.status = statusOptions[0].value;
sysRoleProps.record.defaultDataScope = SysRole.dataScopeOptions[0];
}
visible.value = true; //显示表单
if (props.record.id) {
//如果传了id,就去请求api获取record
bizRoleApi.detail({ id: props.record.id }).then(res => {
sysRoleProps.record = res.data;
});
}
}
// 提交数据(新增/编辑)
const sysRoleFormRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
sysRoleFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
//提交表单
await bizRoleApi
.submitForm(sysRoleProps.record, sysRoleProps.record.id != undefined)
.then(() => {
sysRoleProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
});
}
/** 关闭表单*/
function onClose() {
visible.value = false;
}
// 暴露给父组件的方法
defineExpose({
onOpen
});
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,414 @@
<!--
* @Description: 授权资源表单
* @Author: huguodong
* @Date: 2023-12-15 15:43:49
!-->
<template>
<div>
<form-container v-model="visible" :title="`授权资源->${grantProps.name}`" form-size="1200px" @close="onClose">
<el-alert title="非超管角色不可被授权系统模块菜单资源" type="warning" show-icon style="margin-bottom: 10px" />
<ProTable
ref="proTable"
:columns="columns"
:tool-button="false"
:cell-style="{ padding: '0px' }"
:row-style="{ height: '0' }"
:data="grantProps.tableData"
:pagination="false"
:span-method="objectSpanMethod"
>
<!-- 表格 header 按钮 -->
<template #tableHeader>
<s-radio-group
v-model="grantProps.moduleId"
:options="grantProps.echoDatalist"
value-key="id"
label-key="title"
button
class="mb-2"
@change="moduleClick"
>
<template #radio="{ item }">
<el-space>
<svg-icon :icon="item.icon" class="h-4 w-4"></svg-icon>
{{ item.title }}
</el-space>
</template>
</s-radio-group>
</template>
<!-- 一级目录 -->
<template #parentName="scope">
<el-checkbox v-model="scope.row.parentCheck" @change="val => changeParent(scope.row, val)">
{{ scope.row.parentName }}
</el-checkbox>
</template>
<!-- 菜单 -->
<template #title="scope">
<el-checkbox v-model="scope.row.nameCheck" @change="val => changeSub(scope.row, val)">
{{ scope.row.title }}
</el-checkbox>
</template>
<!-- 按钮授权 -->
<template #button="scope">
<template v-if="scope.row.button.length > 0">
<template v-for="(item, index) in scope.row.button" :key="item.id">
<el-checkbox v-model="item.check" style="margin-right: 5px" @change="val => changeChildCheckBox(scope.row, val)">
{{ item.title }}
</el-checkbox>
<br v-if="(index + 1) % 5 === 0" />
</template>
</template>
</template>
</ProTable>
<template #footer>
<el-button @click="onClose"> 取消 </el-button>
<el-button type="primary" @click="handleSubmit"> 确定 </el-button>
</template>
</form-container>
<el-dialog v-model="dialogVisible" title="请选择数据范围" width="30%" destroy-on-close>
<DataScopeSelector v-model="grantProps.defaultDataScope"></DataScopeSelector>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="handleUserSubmit"> 确定 </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { SysRole, SysUser, bizRoleApi, bizUserApi } from "@/api";
import { ProTableInstance, ColumnProps } from "@/components/ProTable/interface";
import type { TableColumnCtx } from "element-plus";
import DataScopeSelector from "./dataScopeSelector.vue";
const visible = ref(false); //授权资源表单是否显示
const dialogVisible = ref(false); // 数据范围弹窗显示
/** 授权资源表单参数接口 */
interface GrantResourceFormProps {
/** id */
id: number | string;
/** 名称 */
name: string;
/** 模块id */
moduleId: number | string;
/** 资源树 */
resTree: SysRole.ResTreeSelector[];
/** 已拥有的资源 */
ownResource: SysRole.RoleOwnResource | null;
/** 一级菜单索引 */
firstShowMap: Record<string, number[]>;
/** 回显数据列表 */
echoDatalist: SysRole.ResTreeSelector[];
/** 表格数据 */
tableData: SysRole.RoleGrantResourceMenu[];
//默认数据范围,用户才有
defaultDataScope: SysRole.DefaultDataScope;
}
/** 授权资源表单参数 */
const grantProps = reactive<GrantResourceFormProps>({
id: 0,
name: "",
moduleId: 0,
resTree: [],
ownResource: null,
firstShowMap: {},
echoDatalist: [],
tableData: [],
defaultDataScope: SysRole.dataScopeOptions[0]
});
/** props */
const props = defineProps<{
/** 是否是用户 */
user?: boolean;
}>();
// 获取 ProTable DOM
const proTable = ref<ProTableInstance>();
// 表格配置项
const columns: ColumnProps<SysRole.ResTreeSelector>[] = [
// { type: "selection", width: 80 },
{ prop: "parentName", label: "一级目录", width: 150, align: "left" },
{ prop: "title", label: "菜单", width: 180, align: "left" },
{ prop: "button", label: "按钮授权", align: "left" }
];
/** 列合并 */
interface SpanMethodProps {
// 行
row: SysRole.RoleGrantResourceMenu;
// 列
column: TableColumnCtx<SysRole.RoleGrantResourceMenu>;
// 行索引
rowIndex: number;
// 列索引
columnIndex: number;
}
/** 列合并*/
const objectSpanMethod = ({ row, rowIndex, columnIndex }: SpanMethodProps) => {
// 如果列索引为0
if (columnIndex == 0) {
// 获取父名称
const parentName = row.parentName;
// 获取父名称的索引数组
const indexArr = grantProps.firstShowMap[parentName];
// 如果行索引等于索引数组的第一个
if (rowIndex === indexArr[0]) {
return { rowspan: indexArr.length, colspan: 1 };
} else {
return { rowspan: 0, colspan: 0 };
}
}
};
/**
* 打开表单
* @param props 表单参数
*/
async function onOpen(props: FormProps.Base<SysRole.SysRoleInfo | SysUser.SysUserInfo>) {
grantProps.id = props.record.id || 0;
grantProps.name = props.record.name || "";
await loadData();
visible.value = true;
}
/** 加载数据 */
async function loadData() {
if (grantProps.echoDatalist.length > 0) {
let data = grantProps.echoDatalist.find(f => f.id === grantProps.moduleId)!.menu;
grantProps.tableData = data;
} else {
// 获取角色资源树
await bizRoleApi.resourceTreeSelector().then(res => {
grantProps.resTree = res.data;
grantProps.moduleId = res.data[0].id;
});
await getResources(); // 获取资源
// 如果有已拥有的资源
if (grantProps.ownResource) {
// 回显模块数据
// 获取echoDatalist
grantProps.echoDatalist = echoModuleData(grantProps.resTree, grantProps.ownResource);
// 获取模块id
grantProps.moduleId = grantProps.resTree[0].id;
// 获取表格数据
grantProps.tableData = grantProps.echoDatalist[0].menu;
}
}
}
/** 获取资源 */
async function getResources() {
if (props.user) {
// 获取用户拥有资源
await bizUserApi.ownResource({ id: grantProps.id }).then(res => {
grantProps.ownResource = res.data;
});
} else {
// 获取角色拥有资源
await bizRoleApi.ownResource({ id: grantProps.id }).then(res => {
grantProps.ownResource = res.data;
});
}
}
/**
* 回显模块数据
* @param resTree 资源树
* @param ownResource 已拥有的资源
*/
function echoModuleData(resTree: SysRole.ResTreeSelector[], ownResource: SysRole.RoleOwnResource) {
// 通过应用循环
resTree.forEach(module => {
//如果有菜单
if (module.menu) {
module.menu.forEach(menu => {
const menuCheck = ref(0);
// 如果已授权资源信息列表有数据
if (ownResource.grantInfoList.length > 0) {
// 通过已授权资源信息列表循环
ownResource.grantInfoList.forEach(grantInfo => {
// 如果已授权资源信息列表中的菜单id等于当前菜单id
if (menu.id == grantInfo.menuId) {
menuCheck.value++;
// 处理按钮
if (grantInfo.buttonInfo.length > 0) {
// 通过已授权资源信息列表中的按钮循环
grantInfo.buttonInfo.forEach(button => {
menu.button.forEach(itemButton => {
// 如果已授权资源信息列表中的按钮id等于当前按钮id
if (button === itemButton.id) {
itemButton.check = true;
}
});
});
}
}
});
}
// 回显前面的2个
if (menuCheck.value > 0) {
menu.parentCheck = true;
menu.nameCheck = true;
}
});
// 缓存加入索引
module.menu.forEach((item: SysRole.RoleGrantResourceMenu, index) => {
// 下面就是用来知道不同的一级菜单里面有几个二级菜单,以及他们所在的索引
if (grantProps.firstShowMap[item.parentName]) {
grantProps.firstShowMap[item.parentName].push(index);
} else {
grantProps.firstShowMap[item.parentName] = [index];
}
});
}
});
return resTree;
}
/** 选择模块 */
function moduleClick() {
loadData();
}
/** 一级目录点击 */
function changeParent(row: SysRole.RoleGrantResourceMenu, val: any) {
row.parentCheck = val;
// 通过这个应用id,找到应用下的所有菜单
const moduleMenu = grantProps.echoDatalist.find(f => row.module === f.id);
const parentName = row.parentName;
// 获取同一级菜单的所有索引
const indexArr = grantProps.firstShowMap[parentName];
indexArr.forEach(indexItem => {
// 获取同一级菜单的所有行
const row = moduleMenu?.menu[indexItem];
if (row)
// 给这些菜单的索引去勾选
changeSub(row, val);
});
}
/** 二级菜单的点击 */
function changeSub(row: SysRole.RoleGrantResourceMenu, val: any) {
// 选中二级菜单
row.nameCheck = val;
row.button.forEach(item => {
// 选中按钮
item.check = val;
});
}
/** 权限按钮点击 */
function changeChildCheckBox(row: SysRole.RoleGrantResourceMenu, val: any) {
// 如果val为false,并且row的子菜单没有选中,则取消所有按钮的选中
if (!val && checkAllChildNotChecked(row)) {
// 这里注释掉勾选去掉所有按钮,联动去掉菜单
/*record.nameCheck = false
record.parentCheck = false*/
} else if (val) {
// 如果val为true,则将row的nameCheck和parentCheck属性设置为true
row.nameCheck = val;
row.parentCheck = val;
}
}
/** 检查所有子级是否都没有勾选 */
function checkAllChildNotChecked(record: SysRole.RoleGrantResourceMenu) {
// 获取记录的子节点
const child = record.button;
// 判断子节点是否被选中,如果有一个子节点未被选中,则返回false
return child.every(field => !field.check);
}
/** 提交之前转换数据 */
function convertData() {
const params: SysRole.GrantResourceReq = {
id: grantProps.id,
grantInfoList: []
};
// 遍历echoDatalist,获取每一项
grantProps.echoDatalist.forEach(data => {
// 如果data有menu属性
if (data.menu) {
// 遍历data.menu,获取每一项
data.menu.forEach(item => {
// 初始化grantInfo
const grantInfo: SysRole.RelationRoleResource = {
menuId: 0,
buttonInfo: []
};
// 如果被选中了
if (item.nameCheck) {
// 获取item的id
grantInfo.menuId = item.id;
// 遍历item.button,获取每一项
item.button.forEach(button => {
// 如果button被选中了
if (button.check) {
// 将button的id添加到grantInfo.buttonInfo中
grantInfo.buttonInfo.push(button.id as never);
}
});
// 将grantInfo添加到params.grantInfoList中
params.grantInfoList.push(grantInfo);
}
});
}
});
// 返回params
return params;
}
/** 提交 */
function handleSubmit() {
if (props.user) {
dialogVisible.value = true;
} else {
let params = convertData();
grantResource(params);
}
}
/** 用户授权资源提交 */
const handleUserSubmit = () => {
const param = convertData();
param.defaultDataScope = grantProps.defaultDataScope;
grantResource(param);
dialogVisible.value = false;
};
/** 授权资源 */
function grantResource(params: SysRole.GrantResourceReq) {
// 根据用户判断走哪个方法
if (props.user) {
bizUserApi.grantResource(params).then(() => {
onClose();
});
} else {
bizRoleApi.grantResource(params).then(() => {
onClose();
});
}
}
/** 关闭表单 */
function onClose() {
visible.value = false;
grantProps.echoDatalist = [];
grantProps.moduleId = 0;
grantProps.firstShowMap = {};
grantProps.tableData = [];
}
// 暴露给父组件的方法
defineExpose({
onOpen
});
</script>
<style lang="scss" scoped></style>
+222
View File
@@ -0,0 +1,222 @@
<!--
* @Description: 角色管理
* @Author: huguodong
* @Date: 2023-12-15 15:43:53
!-->
<template>
<div class="main-box">
<TreeFilter
ref="treeFilter"
label="name"
title="组织列表"
:show-all="false"
:request-api="bizOrgApi.tree"
:default-value="initParam.orgId"
@change="changeTreeFilter"
/>
<div class="table-box">
<ProTable ref="proTable" :columns="columns" :request-api="bizRoleApi.page" :init-param="initParam">
<!-- 表格 header 按钮 -->
<template #tableHeader="scope">
<s-button v-auth="bizRoleButtonCode.add" suffix="角色" @click="onOpen(FormOptEnum.ADD)" />
<s-button
v-auth="bizRoleButtonCode.batchDelete"
type="danger"
plain
suffix="角色"
:opt="FormOptEnum.DELETE"
:disabled="!scope.isSelected"
@click="onDelete(scope.selectedListIds, '删除所选角色')"
/>
</template>
<!-- 状态 -->
<template #status="scope">
<el-tag v-if="scope.row.status === CommonStatusEnum.ENABLE" type="success">{{
dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status)
}}</el-tag>
<el-tag v-else type="danger">{{ dictStore.dictTranslation(SysDictEnum.COMMON_STATUS, scope.row.status) }}</el-tag>
</template>
<!-- 操作 -->
<template #operation="scope">
<el-space>
<s-button v-auth="bizRoleButtonCode.edit" link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
<s-button
v-auth="bizRoleButtonCode.delete"
link
:opt="FormOptEnum.DELETE"
@click="onDelete([scope.row.id], `删除【${scope.row.name}】角色`)"
/>
<el-dropdown @command="handleCommand">
<el-link type="primary" :underline="false" :icon="ArrowDown"> 授权 </el-link>
<template #dropdown>
<el-dropdown-menu>
<div v-auth="bizRoleButtonCode.grantResource">
<el-dropdown-item :command="command(scope.row, cmdEnum.GrantResource)">{{ cmdEnum.GrantResource }}</el-dropdown-item>
</div>
<div v-auth="bizRoleButtonCode.grantUser">
<el-dropdown-item :command="command(scope.row, cmdEnum.GrantUser)">{{ cmdEnum.GrantUser }}</el-dropdown-item>
</div>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-space>
</template>
</ProTable>
<!-- 新增/编辑表单 -->
<Form ref="formRef" />
<!-- 授权资源组件 -->
<GrantResource ref="grantResourceRef" />
<!-- 用户选择器 -->
<user-selector
ref="userSelectorRef"
biz
:org-tree-api="bizOrgApi.tree"
:position-tree-api="bizPositionApi.tree"
:role-tree-api="bizRoleApi.tree"
:user-selector-api="bizUserApi.selector"
multiple
@successful="handleChooseUser"
/>
</div>
</div>
</template>
<script setup lang="ts" name="sysRole">
import { bizOrgApi, bizRoleApi, SysRole, SysUser, bizUserApi, bizPositionApi, bizRoleButtonCode } from "@/api";
import { ArrowDown } from "@element-plus/icons-vue";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { SysDictEnum, FormOptEnum, CommonStatusEnum } from "@/enums";
import { useHandleData } from "@/hooks/useHandleData";
import { useDictStore } from "@/stores/modules";
import TreeFilter from "@/components/TreeFilter/index.vue";
import Form from "./components/form.vue";
import GrantResource from "./components/grantResource.vue";
import { UserSelectorInstance } from "@/components/Selectors/UserSelector/interface";
const dictStore = useDictStore(); //字典仓库
// 状态选项
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
// 组织列表组件
const treeFilter = ref<InstanceType<typeof TreeFilter> | null>(null);
interface InitParam {
orgId: number | string;
}
// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
const initParam = reactive<InitParam>({ orgId: 0 });
// 获取 ProTable 元素
const proTable = ref<ProTableInstance>();
// 表格配置项
const columns: ColumnProps<SysRole.SysRoleInfo>[] = [
{ type: "selection", fixed: "left", width: 50 },
{ prop: "name", label: "角色名称", search: { el: "input" } },
{ prop: "code", label: "角色编码", search: { el: "input" } },
{ prop: "status", label: "状态", enum: statusOptions, search: { el: "select" } },
{ prop: "sortCode", label: "排序" },
{ prop: "createTime", label: "创建时间" },
{ prop: "operation", label: "操作", width: 230, fixed: "right" }
];
// 表单引用
const formRef = ref<InstanceType<typeof Form> | null>(null);
/**
* 打开表单
* @param opt 操作类型
* @param record 记录
*/
function onOpen(opt: FormOptEnum, record: {} | SysRole.SysRoleInfo = {}) {
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
}
/**
* 删除
* @param ids id数组
*/
async function onDelete(ids: string[], msg: string) {
// 二次确认 => 请求api => 刷新表格
await useHandleData(bizRoleApi.delete, { ids }, msg);
RefreshTable();
}
/**
* 刷新表格
*/
function RefreshTable() {
proTable.value?.refresh(); //刷新表格
treeFilter.value?.refresh(); //刷新树形筛选器
}
/** 部门切换 */
function changeTreeFilter(val: number | string) {
proTable.value!.pageable.pageNum = 1;
if (val != "") {
// 如果传入的val不为空,则将val赋值给initParam.parentId
initParam.orgId = val;
} else {
// 否则将initParam.parentId赋值为0
initParam.orgId = 0;
}
}
/** 更多下拉菜单命令枚举 */
enum cmdEnum {
GrantResource = "授权资源",
GrantUser = "授权用户"
}
/** 下拉菜单参数接口 */
interface Command {
row: SysRole.SysRoleInfo;
command: cmdEnum;
}
/**配置command的参数 */
function command(row: SysRole.SysRoleInfo, command: cmdEnum): Command {
return {
row: row,
command: command
};
}
const grantResourceRef = ref<InstanceType<typeof GrantResource> | null>(null); //授权资源组件引用
const userSelectorRef = ref<UserSelectorInstance>(); //用户选择器引用
const roleId = ref<number | string>(0); //角色id
/**
* 更多下拉菜单点击事件
* @param command
*/
function handleCommand(command: Command) {
switch (command.command) {
case cmdEnum.GrantUser:
roleId.value = command.row.id; //获取角色id
bizRoleApi.ownUser({ id: command.row.id }).then(res => {
userSelectorRef.value?.showSelector(res.data); //显示用户选择器
});
break;
case cmdEnum.GrantResource:
// 打开授权资源组件
grantResourceRef.value?.onOpen({
opt: FormOptEnum.EDIT,
record: command.row,
successful: RefreshTable
});
break;
}
}
/** 选择用户 */
function handleChooseUser(data: SysUser.SysUserInfo[]) {
//组装参数
const grantUser: SysRole.GrantUserReq = {
id: roleId.value,
grantInfoList: data.map(item => item.id) as number[] | string[]
};
bizRoleApi.grantUser(grantUser);
}
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,169 @@
<!--
* @Description: 基本信息
* @Author: huguodong
* @Date: 2023-12-15 15:45:50
!-->
<template>
<div>
<el-tab-pane label="基础信息" name="basic">
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="账号" prop="account">
<s-input v-model="userInfo.account"></s-input>
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="姓名" prop="name">
<s-input v-model="userInfo.name"></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="性别" prop="gender">
<s-radio-group v-model="userInfo.gender" :options="genderOptions" />
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="昵称" prop="nickname">
<s-input v-model="userInfo.nickname"></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="手机号" prop="phone">
<s-input v-model="userInfo.phone"></s-input>
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="邮箱" prop="email">
<s-input v-model="userInfo.email"></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="出生日期" prop="birthday">
<date-picker v-model="userInfo.birthday"></date-picker>
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="入职日期" prop="entryDate">
<date-picker v-model="userInfo.entryDate"></date-picker>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="员工编号" prop="empNo">
<s-input v-model="userInfo.empNo"></s-input>
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="职级" prop="positionLevel">
<s-input v-model="userInfo.positionLevel" clearable></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="部门和职位" prop="positionId">
<position-selector v-model="userInfo.orgAndPosIdList" @change="handleChange" :selector-api="bizPositionApi.selector" />
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="主管" prop="directorId">
<el-button link type="primary" @click="showSelector">选择</el-button>
<el-tag v-if="userInfo.directorId" class="ml-3px" type="warning" closable @close="removeDirector">{{
userInfo.directorInfo?.name
}}</el-tag>
</s-form-item>
</el-col>
</el-row>
</el-tab-pane>
<user-selector
ref="userSelectorRef"
biz
:org-tree-api="bizOrgApi.tree"
:position-tree-api="bizPositionApi.tree"
:role-tree-api="bizRoleApi.tree"
:user-selector-api="bizUserApi.selector"
@successful="handleChooseUser"
/>
</div>
</template>
<script setup lang="ts">
import { SysUser, bizOrgApi, bizPositionApi, bizRoleApi, bizUserApi } from "@/api";
import { useDictStore } from "@/stores/modules";
import { SysDictEnum } from "@/enums";
import { UserSelectorInstance } from "@/components/Selectors/UserSelector/interface";
const dictStore = useDictStore(); //字典仓库
// props
interface FormProps {
modelValue: Partial<SysUser.SysUserInfo>;
}
const emit = defineEmits(["update:modelValue"]); //定义emit
const props = defineProps<FormProps>(); //定义props
// 人员信息
const userInfo = computed({
get: () => props.modelValue,
set: val => emit("update:modelValue", val)
});
// 通用状态选项
const genderOptions = dictStore.getDictList(SysDictEnum.GENDER);
onMounted(() => {
// 初始化
userInfo.value.gender = userInfo.value.gender ? userInfo.value.gender : genderOptions[0].value;
});
/** 用于处理职位选择器改变值的情况 */
function handleChange(value: any) {
if (!value) {
return;
}
// 获取改变后的值中最后一位的值,即positionId
let positionId = value[value.length - 1];
// 获取改变后的值中最后两位的值,即orgId
let orgId = value[value.length - 2];
// 将orgId赋值给userInfo的value的orgId
userInfo.value.orgId = orgId;
// 将positionId赋值给userInfo的value的positionId
userInfo.value.positionId = positionId;
}
const userSelectorRef = ref<UserSelectorInstance>(); //人员选择器ref
/** 显示人员选择器 */
function showSelector() {
//将userInfo.value.directorInfo转为 SysUser.SysUserInfo[]类型
const directorInfo = userInfo.value.directorInfo ? [userInfo.value.directorInfo] : [];
userSelectorRef.value?.showSelector(directorInfo);
}
/** 选择人员 */
function handleChooseUser(data: SysUser.SysUserInfo[]) {
// 选择人员后,将人员id赋值给userInfo.value.directorId
if (data.length > 0) {
userInfo.value.directorId = data[0].id;
userInfo.value.directorInfo = data[0];
}
}
/** 移除主管 */
function removeDirector() {
userInfo.value.directorId = null;
userInfo.value.directorInfo = null;
}
</script>
<style lang="scss" scoped>
:deep(.el-input__wrapper) {
width: 100% !important;
}
:deep(.el-date-editor.el-input) {
width: 92% !important;
}
</style>
@@ -0,0 +1,145 @@
<!--
* @Description: 更多信息
* @Author: huguodong
* @Date: 2023-12-15 15:45:55
!-->
<template>
<el-tab-pane label="更多信息" name="more">
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="民族" prop="nation">
<s-select v-model="userInfo.nation" :options="nationOptions"></s-select>
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="籍贯" prop="nativePlace">
<s-input v-model="userInfo.nativePlace"></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="家庭住址" prop="homeAddress">
<s-input v-model="userInfo.homeAddress" type="textarea" :autosize="{ minRows: 2, maxRows: 4 }"></s-input>
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="通信地址" prop="mailingAddress">
<s-input v-model="userInfo.mailingAddress" type="textarea" :autosize="{ minRows: 2, maxRows: 4 }"></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="证件类型" prop="nation">
<s-select v-model="userInfo.idCardType" :options="cardTypeOptions"></s-select>
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="证件号码" prop="idCardNumber">
<s-input v-model="userInfo.idCardNumber"></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="文化程度" prop="cultureLevel">
<s-select v-model="userInfo.cultureLevel" :options="cultureLevelOptions"></s-select>
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="政治面貌" prop="politicalOutlook">
<s-input v-model="userInfo.politicalOutlook"></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="毕业学校" prop="college">
<s-input v-model="userInfo.college"></s-input>
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="学历" prop="education">
<s-input v-model="userInfo.education"></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="学制" prop="eduLength">
<s-input v-model="userInfo.eduLength"></s-input>
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="学位" prop="degree">
<s-input v-model="userInfo.degree"></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="家庭电话" prop="homeTel">
<s-input v-model="userInfo.homeTel"></s-input>
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="办公电话" prop="officeTel">
<s-input v-model="userInfo.officeTel"></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="紧急联系人" prop="emergencyContact">
<s-input v-model="userInfo.emergencyContact"></s-input>
</s-form-item>
</el-col>
<el-col :span="12">
<s-form-item label="紧急联系电话" prop="emergencyPhone">
<s-input v-model="userInfo.emergencyPhone"></s-input>
</s-form-item>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :span="12">
<s-form-item label="紧急联系人地址" prop="emergencyAddress">
<s-input v-model="userInfo.emergencyAddress"></s-input>
</s-form-item>
</el-col>
</el-row>
</el-tab-pane>
</template>
<script setup lang="ts">
import { SysUser } from "@/api";
import { useDictStore } from "@/stores/modules";
import { SysDictEnum } from "@/enums";
const dictStore = useDictStore(); //字典仓库
// props
interface FormProps {
modelValue: Partial<SysUser.SysUserInfo>;
}
const emit = defineEmits(["update:modelValue"]); //定义emit
const props = defineProps<FormProps>(); //定义props
// 人员信息
const userInfo = computed({
get: () => props.modelValue,
set: val => emit("update:modelValue", val)
});
// 通用状态选项
const nationOptions = dictStore.getDictList(SysDictEnum.NATION);
// 证件类型选项
const cardTypeOptions = dictStore.getDictList(SysDictEnum.IDCARD_TYPE);
//文化程度选项
const cultureLevelOptions = dictStore.getDictList(SysDictEnum.CULTURE_LEVEL);
onMounted(() => {
// 初始化默认值
// userInfo.value.nation = userInfo.value.nation ? userInfo.value.nation : nationOptions[0].value;
userInfo.value.idCardType = userInfo.value.idCardType ? userInfo.value.idCardType : cardTypeOptions[0].value;
});
</script>
<style lang="scss" scoped></style>
@@ -0,0 +1,111 @@
<!--
* @Description: 表单
* @Author: huguodong
* @Date: 2023-12-15 15:45:59
!-->
<template>
<div>
<form-container v-model="visible" :title="`${sysUserProps.opt}人员`" form-size="800px" @close="onClose">
<el-form
ref="sysUserFormRef"
:rules="rules"
:disabled="sysUserProps.disabled"
:model="sysUserProps.record"
:hide-required-asterisk="sysUserProps.disabled"
label-width="auto"
label-suffix=" :"
>
<el-tabs v-model="activeName">
<Basic v-model="sysUserProps.record"></Basic>
<More v-model="sysUserProps.record"></More>
</el-tabs>
</el-form>
<template #footer>
<el-button @click="onClose"> 取消 </el-button>
<el-button v-show="!sysUserProps.disabled" type="primary" @click="handleSubmit"> 确定 </el-button>
</template>
</form-container>
</div>
</template>
<script setup lang="ts" name="sysUserForm">
import { SysUser, bizUserApi } from "@/api";
import { FormOptEnum, SysDictEnum } from "@/enums";
import { required } from "@/utils/formRules";
import { FormInstance } from "element-plus";
import { useDictStore } from "@/stores/modules";
import Basic from "./form_basic.vue";
import More from "./form_more.vue";
const dictStore = useDictStore(); //字典仓库
const visible = ref(false); //是否显示表单
const activeName = ref("basic");
// 通用状态选项
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
// 表单参数
const sysUserProps = reactive<FormProps.Base<SysUser.SysUserInfo>>({
opt: FormOptEnum.ADD,
record: {},
disabled: false
});
// 表单验证规则
const rules = reactive({
account: [required("请输入账号")],
name: [required("请输入姓名")],
gender: [required("请选择性别")],
positionId: [required("请选择部门和职位")]
});
/**
* 打开表单
* @param props 表单参数
*/
function onOpen(props: FormProps.Base<SysUser.SysUserInfo>) {
Object.assign(sysUserProps, props); //合并参数
if (props.opt == FormOptEnum.ADD) {
//如果是新增,设置默认值
sysUserProps.record.sortCode = 99;
sysUserProps.record.status = statusOptions[0].value;
}
visible.value = true; //显示表单
if (props.record.id) {
//如果传了id,就去请求api获取record
bizUserApi.detail({ id: props.record.id }).then(res => {
sysUserProps.record = res.data;
});
}
}
// 提交数据(新增/编辑)
const sysUserFormRef = ref<FormInstance>();
/** 提交表单 */
async function handleSubmit() {
sysUserFormRef.value?.validate(async valid => {
if (!valid) return; //表单验证失败
//提交表单
await bizUserApi
.submitForm(sysUserProps.record, sysUserProps.record.id != undefined)
.then(() => {
sysUserProps.successful!(); //调用父组件的successful方法
})
.finally(() => {
onClose();
});
});
}
/** 关闭表单*/
function onClose() {
visible.value = false;
activeName.value = "basic";
}
// 暴露给父组件的方法
defineExpose({
onOpen
});
</script>
<style lang="scss" scoped></style>
+249
View File
@@ -0,0 +1,249 @@
<!--
* @Description: 人员管理
* @Author: huguodong
* @Date: 2023-12-15 15:46:03
!-->
<template>
<div class="main-box">
<TreeFilter
ref="treeFilter"
label="name"
title="机构列表"
:show-all="false"
:request-api="bizOrgApi.tree"
:default-value="initParam.orgId"
@change="changeTreeFilter"
/>
<div class="table-box">
<ProTable ref="proTable" title="人员列表" :columns="columns" :request-api="bizUserApi.page" :init-param="initParam">
<!-- 表格 header 按钮 -->
<template #tableHeader="scope">
<s-button v-auth="bizUserButtonCode.add" suffix="人员" @click="onOpen(FormOptEnum.ADD)" />
<s-button
v-auth="bizUserButtonCode.batchDelete"
type="danger"
plain
suffix="人员"
:opt="FormOptEnum.DELETE"
:disabled="!scope.isSelected"
@click="onDelete(scope.selectedListIds, '删除所选人员')"
/>
</template>
<!-- 状态 -->
<template #avatar="scope">
<el-avatar :src="scope.row.avatar" :size="30" />
<!-- <span>{{ scope.row.avatar }}}</span> -->
</template>
<!-- 状态 -->
<template #status="scope">
<el-switch :model-value="scope.row.status === CommonStatusEnum.ENABLE" :loading="switchLoading" @change="editStatus(scope.row)" />
</template>
<!-- 操作 -->
<template #operation="scope">
<el-space>
<s-button v-auth="bizUserButtonCode.edit" link :opt="FormOptEnum.EDIT" @click="onOpen(FormOptEnum.EDIT, scope.row)" />
<s-button
v-auth="bizUserButtonCode.delete"
link
:opt="FormOptEnum.DELETE"
@click="onDelete([scope.row.id], `删除【${scope.row.account}】人员`)"
/>
<el-dropdown @command="handleCommand">
<el-link type="primary" :underline="false" :icon="ArrowDown"> 更多 </el-link>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-auth="bizUserButtonCode.pwdReset" :command="command(scope.row, cmdEnum.ResetPassword)">{{
cmdEnum.ResetPassword
}}</el-dropdown-item>
<el-dropdown-item v-auth="bizUserButtonCode.grantRole" :command="command(scope.row, cmdEnum.GrantRole)">{{
cmdEnum.GrantRole
}}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-space>
</template>
</ProTable>
<!-- 新增/编辑表单 -->
<Form ref="formRef" />
<!-- 角色选择器组件 -->
<role-selector
ref="roleSelectorRef"
multiple
:org-tree-api="bizOrgApi.tree"
:role-selector-api="bizRoleApi.roleSelector"
@successful="handleChooseRole"
></role-selector>
<!-- 授权资源组件 -->
<GrantResource user ref="grantResourceRef" />
<!-- 授权权限组件 -->
<GrantPermission user ref="grantPermissionRef" />
</div>
</div>
</template>
<script setup lang="ts" name="SysUser">
import { bizOrgApi, bizUserApi, SysUser, SysRole, bizRoleApi, bizUserButtonCode } from "@/api";
import { ArrowDown } from "@element-plus/icons-vue";
import { ColumnProps, ProTableInstance } from "@/components/ProTable/interface";
import { SysDictEnum, FormOptEnum, CommonStatusEnum } from "@/enums";
import { useHandleData } from "@/hooks/useHandleData";
import { useDictStore } from "@/stores/modules";
import TreeFilter from "@/components/TreeFilter/index.vue";
import Form from "./components/form/index.vue";
import { ElMessage } from "element-plus";
import { RoleSelectorInstance } from "@/components/Selectors/RoleSelector/interface";
import GrantResource from "@/views/sys/limit/role/components/grantResource.vue";
import GrantPermission from "@/views/sys/limit/role/components/grantPermission.vue";
const dictStore = useDictStore(); //字典仓库
// 状态选项
const statusOptions = dictStore.getDictList(SysDictEnum.COMMON_STATUS);
const treeFilter = ref<InstanceType<typeof TreeFilter> | null>(null);
//状态开关loading
const switchLoading = ref(false);
interface InitParam {
orgId: number | string;
}
// 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据)
const initParam = reactive<InitParam>({ orgId: 0 });
// 获取 ProTable 元素
const proTable = ref<ProTableInstance>();
// 表格配置项
const columns: ColumnProps<SysUser.SysUserInfo>[] = [
{ type: "selection", fixed: "left", width: 50 },
{ prop: "searchKey", label: "姓名或账号", search: { el: "input" }, isShow: false },
{ prop: "avatar", label: "头像", width: 80 },
{ prop: "account", label: "账号" },
{ prop: "name", label: "姓名" },
{ prop: "gender", label: "性别", width: 80 },
{ prop: "phone", label: "手机号" },
{ prop: "orgName", label: "机构" },
{ prop: "positionName", label: "职位" },
{
prop: "status",
label: "状态",
width: 90,
enum: statusOptions,
search: { el: "select" }
},
{ prop: "createTime", label: "创建时间" },
{ prop: "operation", label: "操作", width: 230, fixed: "right" }
];
/** 部门切换 */
function changeTreeFilter(val: number | string) {
proTable.value!.pageable.pageNum = 1;
if (val != "") {
// 如果传入的val不为空,则将val赋值给initParam.parentId
initParam.orgId = val;
} else {
// 否则将initParam.parentId赋值为0
initParam.orgId = 0;
}
}
// 表单引用
const formRef = ref<InstanceType<typeof Form> | null>(null);
/**
* 打开表单
* @param opt 操作类型
* @param record 记录
*/
function onOpen(opt: FormOptEnum, record: {} | SysUser.SysUserInfo = {}) {
formRef.value?.onOpen({ opt: opt, record: record, successful: RefreshTable });
}
/**
* 删除
* @param ids id数组
*/
async function onDelete(ids: string[], msg: string) {
// 二次确认 => 请求api => 刷新表格
await useHandleData(bizUserApi.delete, { ids }, msg);
RefreshTable();
}
/**
* 刷新表格
*/
function RefreshTable() {
proTable.value?.refresh(); //刷新表格
treeFilter.value?.refresh(); //刷新树形筛选器
}
/** 更多下拉菜单命令枚举 */
enum cmdEnum {
ResetPassword = "重置密码",
GrantRole = "授权角色"
}
/** 下拉菜单参数接口 */
interface Command {
row: SysUser.SysUserInfo;
command: cmdEnum;
}
/**配置command的参数 */
function command(row: SysUser.SysUserInfo, command: cmdEnum): Command {
return {
row: row,
command: command
};
}
const roleSelectorRef = ref<RoleSelectorInstance>(); //角色选择器引用
const userId = ref<number | string>(0); //人员id
const grantResourceRef = ref<InstanceType<typeof GrantResource> | null>(null); //授权资源组件引用
const grantPermissionRef = ref<InstanceType<typeof GrantPermission> | null>(null); //授权权限组件引用
/**
* 更多下拉菜单点击事件
* @param command
*/
function handleCommand(command: Command) {
switch (command.command) {
case cmdEnum.ResetPassword:
useHandleData(bizUserApi.resetPassword, { id: command.row.id }, cmdEnum.ResetPassword);
break;
case cmdEnum.GrantRole:
userId.value = command.row.id; //获取人员id
bizUserApi.ownRole({ id: command.row.id }).then(res => {
roleSelectorRef.value?.showSelector(res.data); //显示人员选择器
});
break;
}
}
/** 选择角色 */
function handleChooseRole(data: SysRole.SysRoleInfo[]) {
//组装参数
const grantUser: SysUser.GrantRoleReq = {
id: userId.value,
roleIdList: data.map(item => item.id) as number[] | string[]
};
bizUserApi.grantRole(grantUser); //授权角色
}
/**
* 修改状态
* @param row 当前行数据
*/
function editStatus(row: SysUser.SysUserInfo) {
switchLoading.value = true;
const status = row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE; // 状态取反
bizUserApi
.updateStatus({ id: row.id }, status)
.then(() => {
ElMessage.success("修改成功");
row.status = status;
})
.finally(() => {
switchLoading.value = false;
});
}
</script>
<style lang="scss" scoped></style>