角色选择

This commit is contained in:
caixiaofeng 2023-11-25 15:26:30 +08:00
parent a78bc1ff2f
commit 2ff0a50620
10 changed files with 511 additions and 7 deletions

View File

@ -1,6 +1,10 @@
import user from "./user";
import role from "./role";
const mockModules = [...user];
const mockModules = {
user,
role
}
export default mockModules

68
mock/role.ts Normal file
View File

@ -0,0 +1,68 @@
import {MockMethod} from "vite-plugin-mock";
import {ResultData} from "../src/api";
const roleList = [
{
id: "1",
name: "项目经理"
},
{
id: "2",
name: "产品经理"
},
{
id: "3",
name: "高级开发工程师"
},
{
id: "4",
name: "中级开发工程师"
},
{
id: "5",
name: "项目总监"
},
{
id: "6",
name: "产品策划"
},
{
id: "7",
name: "客服"
},
{
id: "8",
name: "销售经理"
}
]
const role = [
{
url: "/api/role/info",
method: "get",
response: (req:any) => {
const id = req.query.id;
return {
code: 200,
success: true,
message: "操作成功",
data: roleList.find(item => item.id === id)
} as ResultData
}
},
{
url: "/api/role/list",
method: "post",
response: (req:any) => {
const roleIds = req.body.roleIds
return {
code: 200,
success: true,
message: "操作成功",
data: Array.isArray(roleIds) ? roleList.filter(item => roleIds.includes(item.id)) : roleList
} as ResultData
}
}
] as MockMethod[]
export default role

View File

@ -1,5 +1,5 @@
import {MockMethod} from "vite-plugin-mock";
import {ResultData, ResultPage} from "../src/api";
import {ResultData} from "../src/api";
const userList = [
{
@ -56,7 +56,7 @@ const user = [
{
url: "/api/user/info",
method: "get",
response: (req) => {
response: (req:any) => {
const username = req.query.username;
return {
code: 200,
@ -69,7 +69,7 @@ const user = [
{
url: "/api/user/list",
method: "post",
response: (req) => {
response: (req:any) => {
const userIds = req.body.userIds
return {
code: 200,

22
src/api/modules/role.ts Normal file
View File

@ -0,0 +1,22 @@
import http from '~/api/index'
export interface Role {
id: string,
name: string
}
/**
*
* @param id
*/
export const getById = (id: string) => {
return http.get<Role>(`/role/info`,{id:id})
}
/**
*
*/
export const getList = (roleIds?: string[]) => {
const params = roleIds ? {roleIds: roleIds} : {}
return http.post<Role[]>('/role/list', params)
}

View File

@ -21,7 +21,8 @@ export default defineComponent({
Select: defineAsyncComponent(() => import('element-plus/es').then(({ElSelect}) => ElSelect)),
Radio: defineAsyncComponent(() => import('element-plus/es').then(({ElRadio}) => ElRadio)),
Checkbox: defineAsyncComponent(() => import('element-plus/es').then(({ElCheckbox}) => ElCheckbox)),
UserSelection: defineAsyncComponent(() => import('~/components/UserSelection/index'))
UserSelection: defineAsyncComponent(() => import('~/components/UserSelection/index')),
RoleSelection: defineAsyncComponent(() => import('~/components/RoleSelection/index'))
},
setup(props, {emit}) {
/**

View File

@ -0,0 +1,12 @@
import roleTag from './src/RoleTag.vue'
import rolePicker from './src/RolePicker.vue'
import roleSelector from './src/RoleSelector.vue'
import {withInstall, withNoopInstall} from 'element-plus/es/utils/vue/install'
export const RoleTag = withNoopInstall(roleTag)
export const RolePicker = withNoopInstall(rolePicker)
export const RoleSelector = withInstall(roleSelector, {
roleTag,
rolePicker
})
export default RoleSelector

View File

@ -0,0 +1,229 @@
<script setup lang="ts">
import {useVModel} from '@vueuse/core'
import {TreeNodeData} from 'element-plus/es/components/tree/src/tree.type'
import {type ElTree} from 'element-plus'
import {reactive, ref, watch} from "vue";
import {getList} from "~/api/modules/role";
import {School, Check} from "@element-plus/icons-vue";
export type ModelValueType = string | string[] | null | undefined
export interface RoleDropdownProps {
modelValue: ModelValueType,
multiple?: boolean
}
const treeProps = {
label: 'name',
children: 'children',
isLeaf: 'leaf',
class: (role: TreeNodeData) => renderClass(role)
}
export interface Role {
id: string
name: string
}
const $props = withDefaults(defineProps<RoleDropdownProps>(), {
multiple: false
})
const $emits = defineEmits<{
(e: 'update:modelValue', modelValue: ModelValueType): void
}>()
const value = useVModel($props, 'modelValue', $emits)
const roleOptions = ref<Role[]>([])
const roleOrgOptions = ref<Role[]>([])
const orgTreeRef = ref<InstanceType<typeof ElTree>>()
const expandedKeys = ref<string[]>([])
const renderClass = (role: TreeNodeData): string | { [key: string]: boolean } => {
const val = roleOptions.value.find(e => e.id === role.id)
if (val) {
return 'is-active'
} else {
return ''
}
}
const onNodeClick = (data: Role) => {
if ($props.multiple) {
const index = roleOptions.value.findIndex(e => e.id === data.id)
if (index === -1) {
roleOptions.value.push(data)
roleOptions.value.sort((a, b) => a.id.localeCompare(b.id))
} else {
roleOptions.value.splice(index, 1)
}
} else {
const index = roleOptions.value.findIndex(e => e.id === data.id)
if (index === -1) {
roleOptions.value = [data]
} else {
roleOptions.value.splice(index, 1)
}
}
}
const dialogVisible = ref(false)
const queryForm = reactive({
name: null,
})
watch(() => queryForm.name, (val) => {
orgTreeRef.value?.filter(val)
})
const filterNode = (value: string, data: TreeNodeData): boolean => {
if (!value) return true
return data.name.includes(value)
}
const open = () => {
dialogVisible.value = true
}
const onOpen = () => {
getList().then(res => {
if (res.success) {
roleOrgOptions.value = res.data.map(e => {
return {
id: e.id,
name: e.name
} as Role
})
}
})
let roleIds: string[] = []
if (Array.isArray(value.value)) {
roleIds.push(...value.value)
} else if (value.value) {
roleIds.push(value.value)
}
if (roleIds.length > 0) {
getList(roleIds).then(res => {
if (res.success) {
roleOptions.value = res.data.map(role => {
return {
id: role.id,
name: role.name
} as Role
})
roleOptions.value.sort((a, b) => a.id.localeCompare(b.id))
}
})
} else {
roleOptions.value = []
}
}
const handelConfirm = () => {
if ($props.multiple) {
value.value = roleOptions.value.map(e => e.id)
} else {
if (roleOptions.value.length > 0) {
value.value = roleOptions.value[0].id
} else {
value.value = null
}
}
dialogVisible.value = false
}
defineExpose({
open
})
</script>
<template>
<el-dialog
v-model="dialogVisible"
@open="onOpen"
align-center
draggable
title="选择角色"
width="30%"
>
<el-card shadow="never" class="org-card">
<template #header>
<el-input
v-model="queryForm.name"
placeholder="输入关键字进行查询"
:style="{width: '100%'}"
suffix-icon="search"
clearable>
</el-input>
</template>
<el-scrollbar tag="div" class="org-tree">
<el-tree
ref="orgTreeRef"
node-key="id"
:data="roleOrgOptions"
:default-expanded-keys="expandedKeys"
:props="treeProps"
:filter-node-method="filterNode"
@node-click="onNodeClick">
<template #default="{data}">
<div class="flex flex-1 flex-items-center flex-justify-between">
<div class="flex-center">
<el-icon :size="16">
<School/>
</el-icon>
&nbsp;{{ data.name }}
</div>
<el-icon class="is-selected">
<Check/>
</el-icon>
</div>
</template>
</el-tree>
</el-scrollbar>
</el-card>
<template #footer>
<el-button @click="dialogVisible=false">取消</el-button>
<el-button type="primary" @click="handelConfirm">确认</el-button>
</template>
</el-dialog>
</template>
<style scoped lang="scss">
:deep {
.el-tree {
--el-tree-node-content-height: 40px;
.el-tree-node__content {
border-radius: 8px;
margin: 2px 0 2px 0;
}
.is-active {
color: var(--el-color-primary);
.is-selected {
display: block;
}
}
}
}
.el-card {
background-color: transparent;
:deep(.el-card__header) {
padding: 10px !important;
}
:deep(.el-card__body) {
padding: 0 !important;
}
}
.org-tree {
height: 270px;
padding: 5px;
}
.is-selected {
display: none;
padding-right: 15px;
}
</style>

View File

@ -0,0 +1,88 @@
<script setup lang="ts">
import {useVModel} from '@vueuse/core'
import RoleTag from './RoleTag.vue'
import RolePicker, {ModelValueType} from './RolePicker.vue'
import {useFormDisabled, useFormSize} from 'element-plus'
import {computed, CSSProperties, ref} from 'vue'
import {Avatar} from "@element-plus/icons-vue";
export interface RoleSelectorProps {
modelValue: ModelValueType,
placeholder?: string,
multiple?: boolean,
disabled?: boolean,
style?: CSSProperties
}
const $props = withDefaults(defineProps<RoleSelectorProps>(), {
multiple: false,
disabled: false,
placeholder: '请选择角色'
})
const $emits = defineEmits<{
(e: 'update:modelValue', modelValue: ModelValueType): void
}>()
const value = useVModel($props, 'modelValue', $emits)
const valueArr = computed<string []>(() => {
if (!value.value) return []
return Array.isArray(value.value) ? value.value : [value.value]
})
const RolePickerRef = ref<InstanceType<typeof RolePicker>>()
const formDisabled = useFormDisabled()
const formSize = useFormSize()
const disabled = computed<boolean>(() => {
return formDisabled.value || $props.disabled
})
const openRolePicker = () => {
RolePickerRef.value?.open()
}
const onClose = (username: string) => {
if (!value.value) return
if ($props.multiple && Array.isArray(value.value)) {
value.value.splice(value.value.indexOf(username), 1)
} else {
value.value = null
}
}
</script>
<template>
<role-picker ref="RolePickerRef" :multiple="multiple" v-model="value"/>
<div class="role-wrapper">
<el-button class="role-but-item" :size="formSize" :disabled="disabled" @click="openRolePicker" circle>
<el-icon>
<Avatar/>
</el-icon>
</el-button>
<RoleTag v-for="item in valueArr" :closable="!disabled" :key="item" :id="item" @close="onClose"/>
<el-text v-show="!value || value.length===0" class="placeholder">
{{ placeholder }}
</el-text>
</div>
</template>
<style scoped lang="scss">
.el-tag {
padding: 0 3px;
}
.role-wrapper {
display: flex;
flex-wrap: wrap;
align-items: center;
grid-gap: 7px;
gap: 7px;
.placeholder {
color: var(--el-text-color-placeholder);
}
.role-but-item {
border-style: dashed;
&:hover {
border-style: solid;
}
}
}
</style>

View File

@ -0,0 +1,72 @@
<script setup lang="ts">
import {getById} from '~/api/modules/role'
import {componentSizeMap, useFormSize} from 'element-plus'
import {computed, onMounted, reactive} from "vue";
export interface RoleTagProps {
id: string,
type?: 'success' | 'info' | 'warning' | 'danger'
closable?: boolean
}
const $props = withDefaults(defineProps<RoleTagProps>(), {
closable: false,
type: 'info'
})
const $emits = defineEmits<{
(e: 'close', id: string): void
}>()
export interface RoleInfo {
id?: string
name?: string
}
let roleInfo = reactive<RoleInfo>({
id: undefined,
name: undefined
})
onMounted(() => {
if (!$props.id) {
throw new Error('username is required')
}
getById($props.id).then(res => {
if (res.success) {
console.log(res);
roleInfo.id = res.data.id
roleInfo.name = res.data.name
}
})
})
const getBaseUrl = computed(() => {
const url = import.meta.env.VITE_API_URL
if (url.startsWith('http')) {
return url
} else {
return window.location.origin + url
}
})
const formSize = useFormSize()
const getComponentSize = computed(() => {
return componentSizeMap[formSize.value || 'default'] - 12
})
const onClose = () => {
$emits('close', $props.id)
}
</script>
<template>
<el-tag round :closable="$props.closable" :type="type" effect="light" @close="onClose">
<div class="flex-center" style="gap: 4px;grid-gap: 4px;">
<span>{{ roleInfo.name || id }}</span>
</div>
</el-tag>
</template>
<style scoped lang="scss">
:deep {
.el-tag__content:only-child {
margin-right: 4px;
}
}
</style>

View File

@ -6,6 +6,8 @@ import {computed, inject, Ref, ref, watchEffect} from "vue";
import {CircleCheck, CircleClose, Switch, Plus, Minus} from "@element-plus/icons-vue";
import {Field} from "~/components/Render/interface";
import {FormProperty} from "~/views/flowDesign/index";
import UserSelection from "~/components/UserSelection";
import RoleSelection from "~/components/RoleSelection";
const activeName = ref('properties')
@ -190,7 +192,7 @@ watchEffect(() => {
</el-select>
</el-form-item>
<el-form-item prop="roles" label="指定角色" v-if="node.assigneeType === 'role'">
待添加...
<role-selection multiple placeholder="请选择角色" v-model="node.roles"/>
</el-form-item>
<el-form-item prop="formUser" label="表单内人员" v-if="node.assigneeType === 'formUser'">
<el-select placeholder="选择表单内人员" v-model="node.formUser">
@ -202,7 +204,13 @@ watchEffect(() => {
</el-select>
</el-form-item>
<el-form-item prop="formRole" label="表单内角色" v-if="node.assigneeType === 'formRole'">
待添加...
<el-select placeholder="选择表单内角色" v-model="node.formUser">
<el-option
v-for="item in fields.filter(e=>e.name === 'RoleSelection')"
:key="item.id"
:label="item.title"
:value="item.id"/>
</el-select>
</el-form-item>
<el-form-item prop="method" label="多人审批方式">
<el-radio-group class="flex-column" v-model="node.multi">