初始版本提交

This commit is contained in:
yaoyn
2024-02-05 09:15:37 +08:00
parent b52d4414be
commit 445292105f
1848 changed files with 236859 additions and 75 deletions

View File

@ -0,0 +1,17 @@
import { withInstall } from '/@/utils';
import organizationalTree from './src/OrganizationalTree.vue';
import userCard from './src/card/UserCard.vue';
import selectUser from './src/SelectUser.vue';
import selectPost from './src/SelectPost.vue';
import selectRole from './src/SelectRole.vue';
import selectMember from './src/SelectMember.vue';
import selectDepartment from './src/SelectDepartment.vue';
export const OrganizationalTree = withInstall(organizationalTree);
export const SelectUser = withInstall(selectUser);
export const SelectPost = withInstall(selectPost);
export const SelectRole = withInstall(selectRole);
export const UserCard = withInstall(userCard);
export const SelectMember = withInstall(selectMember);
export const SelectDepartment = withInstall(selectDepartment);

View File

@ -0,0 +1,70 @@
<template>
<div class="overflow-hidden">
<NodeHead class="header-title" :node-name="t('组织架构')" />
<BasicTree
title=""
search
expandOnSearch
:checkable="isCheckable"
:treeData="treeData"
:fieldNames="{ key: 'id', title: 'name' }"
@select="handleSelect"
@check="handleCheck"
>
<template #title="{ name, departmentType }">
<a-tag color="processing" v-if="departmentType === 1">公司</a-tag>
<a-tag color="warning" v-else-if="departmentType === 0">部门</a-tag>
{{ name }}
</template>
</BasicTree>
</div>
</template>
<script setup lang="ts">
import { getDepartmentTree } from '/@/api/system/department';
import { TreeItem } from '/@/components/Tree';
import { BasicTree } from '/@/components/Tree';
import { NodeHead } from '/@/components/ModalPanel/index';
import { onMounted, ref } from 'vue';
import { useI18n } from '/@/hooks/web/useI18n';
defineProps({
isCheckable: {
type: Boolean,
default: false,
},
});
const { t } = useI18n();
const emits = defineEmits(['select', 'check']);
const treeData = ref<TreeItem[]>([]);
async function getList() {
treeData.value = (await getDepartmentTree()) as unknown as TreeItem[];
}
function handleSelect(keys: string) {
emits('select', keys[0]);
}
function handleCheck(keys: string) {
emits('check', keys);
}
onMounted(() => {
getList();
});
</script>
<style scoped>
.header-title {
height: 40px;
font-size: 16px;
color: #333;
border-bottom: 1px solid #f0f0f0;
}
:deep(.ant-tree-treenode) {
margin-bottom: 10px;
}
:deep(.vben-tree-header) {
display: none;
}
</style>

View File

@ -0,0 +1,281 @@
<template>
<ModalPanel
:visible="props.visible"
:width="1200"
:isDeptSelect="data.isDeptSelect"
:title="t('添加人员')"
@submit="submit"
@close="close"
>
<template #header>
<a-row class="header-box">
<a-col :span="2" align="right"> <span class="required-dot">*</span>选择类型 </a-col>
<a-col :span="22">
<a-select v-model:value="data.selectType" @change="handleTypeChange" style="width: 100%">
<a-select-option :value="0">选择人员</a-select-option>
<a-select-option :value="1">按组织批量选择</a-select-option>
</a-select>
</a-col>
</a-row>
</template>
<template #left>
<OrganizationalTree
@select="handleSelect"
@check="handleCheck"
:isCheckable="data.isDeptSelect"
/>
</template>
<div v-if="!data.isDeptSelect">
<!-- 已选 -->
<Selected
v-if="visible"
type="user"
:list="data.selectedList"
@abolish="abolishChecked"
:disabledIds="props.disabledIds"
/>
<SearchBox @search="search" />
<div class="list-page-box" v-if="visible && data.list.length > 0">
<UserCard
:class="data.selectedIds.includes(item.id) ? 'picked' : 'not-picked'"
:disabled="props.disabledIds && props.disabledIds.includes(item.id) ? true : false"
v-for="(item, index) in data.list"
:key="index"
:item="item"
@click="checked(item)"
>
<template #check>
<a-checkbox size="small" :checked="data.selectedIds.includes(item.id)" />
</template>
</UserCard>
<div class="page-box">
<a-pagination
v-model:current="data.page.current"
:pageSize="data.page.pageSize"
:total="data.page.total"
show-less-items
/></div>
</div>
<EmptyBox v-if="data.list.length == 0" />
</div>
</ModalPanel>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, watch } from 'vue';
import OrganizationalTree from '/@/components/SelectOrganizational/src/OrganizationalTree.vue';
import UserCard from '/@/components/SelectOrganizational/src/card/UserCard.vue';
import Selected from '/@/components/SelectOrganizational/src/Selected.vue';
import { ModalPanel, EmptyBox, SearchBox } from '/@/components/ModalPanel/index';
import { getUserList, getUserMulti } from '/@/api/system/user';
import { UserInfo } from '/@/api/system/user/model';
import { cloneDeep } from 'lodash-es';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits(['change', 'changeNames', 'close']);
const props = defineProps({
selectedIds: {
type: Array as PropType<string[]>,
default: () => [],
},
disabledIds: {
type: Array as PropType<string[]>,
default: () => [],
},
multiple: Boolean,
visible: Boolean,
});
let data: {
multiSelect: boolean;
page: { current: number; total: number; pageSize: number };
list: Array<UserInfo>;
selectedList: Array<UserInfo>;
selectedIds: Array<string>;
searchConfig: {
keyword: string;
deptId: string;
};
selectType: number;
isDeptSelect: boolean;
departmentIds: Array<string>;
} = reactive({
multiSelect: false,
page: {
current: 1,
total: 0,
pageSize: 9,
},
selectedIds: [],
list: [],
selectedList: [],
searchConfig: {
keyword: '',
deptId: '',
},
selectType: 0,
isDeptSelect: false,
departmentIds: [],
});
const paramRef = computed(() => {
return {
limit: data.page.current,
size: data.page.pageSize,
departmentId: data.searchConfig.deptId,
keyword: data.searchConfig.keyword,
};
});
watch(
() => data.page.current,
() => {
getList();
},
);
onMounted(() => {
show();
});
async function show() {
data.selectedIds = [];
data.selectedList = [];
data.page.current = 1;
data.page.total = 0;
data.searchConfig.deptId = '';
if (props.selectedIds && Array.isArray(props.selectedIds)) {
data.selectedIds = cloneDeep(props.selectedIds);
}
await getList();
await getSelectedList();
}
function submit() {
if (data.isDeptSelect) {
emits('change', data.departmentIds, data.selectType);
} else {
emits('change', data.selectedIds, data.selectType);
changeSelectedNames();
}
close();
}
function close() {
data.list = [];
data.selectedIds = [];
data.selectedList = [];
emits('close', false);
}
function checked(item) {
if (
props.disabledIds &&
Array.isArray(props.disabledIds) &&
props.disabledIds.includes(item.id)
) {
return;
}
if (props.multiple && props.multiple == true) {
if (data.selectedIds.includes(item.id)) {
data.selectedIds.splice(
data.selectedIds.findIndex((itemId) => itemId === item.id),
1,
);
data.selectedList.splice(
data.selectedList.findIndex((ele) => ele.id === item.id),
1,
);
} else {
data.selectedIds.push(item.id);
data.selectedList.push(item);
}
} else {
if (data.selectedIds.includes(item.id)) {
data.selectedIds = [];
data.selectedList = [];
} else {
data.selectedIds = [item.id];
}
}
}
async function getList() {
data.list = [];
data.page.total = 0;
let res = await getUserList(paramRef.value);
if (res.total) {
data.page.total = res.total;
}
if (res.list.length > 0) {
res.list.forEach((ele) => {
let item = {
name: ele.name, //姓名
id: ele.id, //ID
code: ele.code, //code
gender: !isNaN(ele.gender) ? ele.gender : -1, //性别
};
data.list.push(item);
});
}
}
function search(keyword: string) {
data.page.current = 1;
data.searchConfig.keyword = keyword;
getList();
}
function handleSelect(deptId = '') {
if (data.isDeptSelect) return;
data.page.current = 1;
data.searchConfig.deptId = deptId;
getList();
}
function handleCheck(ids = []) {
data.departmentIds = ids;
}
async function getSelectedList() {
let users = await getUserMulti(data.selectedIds.join(','));
if (users.length > 0) {
users.forEach((ele) => {
data.selectedList.push({
name: ele.name, //姓名
id: ele.id, //ID
code: ele.code, //code
gender: !isNaN(ele.gender) ? ele.gender : -1, //性别
});
});
changeSelectedNames();
}
}
function changeSelectedNames() {
let userNames = data.selectedList
.map((ele) => {
return ele.name;
})
.join(',');
emits('changeNames', userNames);
}
function abolishChecked(id: string) {
data.selectedList = data.selectedList.filter((ele) => {
return ele.id != id;
});
data.selectedIds.splice(
data.selectedIds.findIndex((itemId) => itemId === id),
1,
);
}
function handleTypeChange(val) {
data.isDeptSelect = !!val;
}
</script>
<style lang="less" scoped>
.header-box {
margin: 10px 20px 10px 10px;
display: flex;
align-items: center;
font-size: 16px;
.required-dot {
color: #ff4d4f;
margin-right: 5px;
}
}
</style>

View File

@ -0,0 +1,236 @@
<template>
<ModalPanel
:visible="props.visible"
:width="1200"
:title="t('添加人员')"
@submit="submit"
@close="close"
>
<template #left v-if="isShowTree">
<OrganizationalTree @select="handleSelect" />
</template>
<!-- 已选 -->
<Selected
v-if="visible"
type="user"
:list="data.selectedList"
@abolish="abolishChecked"
:disabledIds="props.disabledIds"
/>
<SearchBox @search="search" />
<div class="list-page-box" v-if="visible && data.list.length > 0">
<UserCard
:class="data.selectedIds.includes(item.id) ? 'picked' : 'not-picked'"
:disabled="props.disabledIds && props.disabledIds.includes(item.id) ? true : false"
v-for="(item, index) in data.list"
:key="index"
:item="item"
:isShowTree="isShowTree"
@click="checked(item)"
>
<template #check>
<a-checkbox size="small" :checked="data.selectedIds.includes(item.id)" />
</template>
</UserCard>
<div class="page-box">
<a-pagination
v-model:current="data.page.current"
:pageSize="data.page.pageSize"
:total="data.page.total"
show-less-items
/></div>
</div>
<EmptyBox v-if="data.list.length == 0" />
</ModalPanel>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, watch } from 'vue';
import OrganizationalTree from '/@/components/SelectOrganizational/src/OrganizationalTree.vue';
import UserCard from '/@/components/SelectOrganizational/src/card/UserCard.vue';
import Selected from '/@/components/SelectOrganizational/src/Selected.vue';
import { ModalPanel, EmptyBox, SearchBox } from '/@/components/ModalPanel/index';
import { getUserList, getUserMulti } from '/@/api/system/user';
import { UserInfo } from '/@/api/system/user/model';
import { cloneDeep } from 'lodash-es';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits(['change', 'changeNames', 'close']);
const props = defineProps({
selectedIds: {
type: Array as PropType<string[]>,
default: () => [],
},
disabledIds: {
type: Array as PropType<string[]>,
default: () => [],
},
multiple: Boolean,
visible: Boolean,
isShowTree: {
type: Boolean,
default: true,
},
});
let data: {
multiSelect: boolean;
page: { current: number; total: number; pageSize: number };
list: Array<UserInfo>;
selectedList: Array<UserInfo>;
selectedIds: Array<string>;
searchConfig: {
keyword: string;
deptId: string;
};
} = reactive({
multiSelect: false,
page: {
current: 1,
total: 0,
pageSize: 9,
},
selectedIds: [],
list: [],
selectedList: [],
searchConfig: {
keyword: '',
deptId: '',
},
});
const paramRef = computed(() => {
return {
limit: data.page.current,
size: data.page.pageSize,
departmentId: data.searchConfig.deptId,
keyword: data.searchConfig.keyword,
};
});
watch(
() => data.page.current,
() => {
getList();
},
);
onMounted(() => {
show();
});
async function show() {
data.selectedIds = [];
data.selectedList = [];
data.page.current = 1;
data.page.total = 0;
data.searchConfig.deptId = '';
if (props.selectedIds && Array.isArray(props.selectedIds)) {
data.selectedIds = cloneDeep(props.selectedIds);
}
await getList();
await getSelectedList();
}
function submit() {
emits('change', data.selectedIds);
changeSelectedNames();
close();
}
function close() {
data.list = [];
data.selectedIds = [];
data.selectedList = [];
emits('close', false);
}
function checked(item) {
if (
props.disabledIds &&
Array.isArray(props.disabledIds) &&
props.disabledIds.includes(item.id)
) {
return;
}
if (props.multiple && props.multiple == true) {
if (data.selectedIds.includes(item.id)) {
data.selectedIds.splice(
data.selectedIds.findIndex((itemId) => itemId === item.id),
1,
);
data.selectedList.splice(
data.selectedList.findIndex((ele) => ele.id === item.id),
1,
);
} else {
data.selectedIds.push(item.id);
data.selectedList.push(item);
}
} else {
if (data.selectedIds.includes(item.id)) {
data.selectedIds = [];
data.selectedList = [];
} else {
data.selectedIds = [item.id];
}
}
}
async function getList() {
data.list = [];
data.page.total = 0;
let res = await getUserList(paramRef.value);
if (res.total) {
data.page.total = res.total;
}
if (res.list.length > 0) {
res.list.forEach((ele) => {
let item = {
name: ele.name, //姓名
id: ele.id, //ID
code: ele.code, //code
gender: !isNaN(ele.gender) ? ele.gender : -1, //性别
};
data.list.push(item);
});
}
}
function search(keyword: string) {
data.page.current = 1;
data.searchConfig.keyword = keyword;
getList();
}
function handleSelect(deptId = '') {
data.page.current = 1;
data.searchConfig.deptId = deptId;
getList();
}
async function getSelectedList() {
let users = await getUserMulti(data.selectedIds.join(','));
if (users.length > 0) {
users.forEach((ele) => {
data.selectedList.push({
name: ele.name, //姓名
id: ele.id, //ID
code: ele.code, //code
gender: !isNaN(ele.gender) ? ele.gender : -1, //性别
});
});
changeSelectedNames();
}
}
function changeSelectedNames() {
let userNames = data.selectedList
.map((ele) => {
return ele.name;
})
.join(',');
emits('changeNames', userNames);
}
function abolishChecked(id: string) {
data.selectedList = data.selectedList.filter((ele) => {
return ele.id != id;
});
data.selectedIds.splice(
data.selectedIds.findIndex((itemId) => itemId === id),
1,
);
}
</script>

View File

@ -0,0 +1,201 @@
<template>
<div style="width: 100%">
<div @click="show"><slot></slot></div>
<ModalPanel
:visible="data.visible"
:width="1200"
:title="t('添加岗位')"
@submit="submit"
@close="close"
>
<template #left>
<OrganizationalTree @select="handleSelect" />
</template>
<!-- 已选 -->
<Selected type="post" :list="data.selectedList" @abolish="abolishChecked" />
<SearchBox @search="search" />
<div class="list-page-box" v-if="data.list.length > 0">
<PostCard
:class="data.selectedIds.includes(item.id) ? 'picked' : 'not-picked'"
v-for="(item, index) in data.list"
:key="index"
:item="item"
@click="checked(item)"
>
<template #check>
<a-checkbox size="small" :checked="data.selectedIds.includes(item.id)" />
</template>
</PostCard>
<div class="page-box">
<a-pagination
v-model:current="data.page.current"
:pageSize="data.page.pageSize"
:total="data.page.total"
show-less-items
@change="pageChange"
/></div>
</div>
<EmptyBox v-if="data.list.length == 0" />
</ModalPanel>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import OrganizationalTree from './OrganizationalTree.vue';
import PostCard from './card/PostCard.vue';
import Selected from './Selected.vue';
import { ModalPanel, EmptyBox, SearchBox } from '/@/components/ModalPanel/index';
import { getPostTree, getPostMulti } from '/@/api/system/post';
import { PostInfo } from '/@/api/system/post/model';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits(['change']);
const props = withDefaults(
defineProps<{
selectedIds: Array<string>;
multiple?: Boolean;
}>(),
{
selectedIds: () => {
return [];
},
disabledIds: () => {
return [];
},
},
);
let data: {
visible: boolean;
multiSelect: boolean;
page: { current: number; total: number; pageSize: number };
list: Array<PostInfo>;
selectedList: Array<PostInfo>;
selectedIds: Array<string>;
searchConfig: {
keyword: string;
deptId: string;
};
} = reactive({
visible: false,
multiSelect: false,
page: {
current: 1,
total: 0,
pageSize: 9,
},
selectedIds: [],
list: [],
selectedList: [],
searchConfig: {
keyword: '',
deptId: '',
},
});
async function show() {
data.page.current = 1;
data.page.total = 0;
data.searchConfig.deptId = '';
if (props.selectedIds && Array.isArray(props.selectedIds)) data.selectedIds = props.selectedIds;
await getList();
await getSelectedList();
data.visible = true;
}
function submit() {
emits('change', data.selectedIds);
close();
}
function close() {
data.list = [];
data.selectedIds = [];
data.selectedList = [];
data.visible = false;
}
function checked(item) {
if (props.multiple && props.multiple == true) {
if (data.selectedIds.includes(item.id)) {
data.selectedIds.splice(
data.selectedIds.findIndex((itemId) => itemId === item.id),
1,
);
data.selectedList.splice(
data.selectedList.findIndex((ele) => ele.id === item.id),
1,
);
} else {
data.selectedIds.push(item.id);
data.selectedList.push(item);
}
} else {
if (data.selectedIds.includes(item.id)) {
data.selectedIds = [];
data.selectedList = [];
} else {
data.selectedIds = [item.id];
}
}
}
async function getList() {
data.list = [];
data.page.total = 0;
let params = {
limit: data.page.current,
size: data.page.pageSize,
departmentId: data.searchConfig.deptId,
keyword: data.searchConfig.keyword,
};
let res = await getPostTree(params);
if (res.total) {
data.page.total = res.total;
}
if (res.list.length > 0) {
res.list.forEach((ele) => {
let item = {
name: ele.name,
id: ele.id,
code: ele.code,
};
data.list.push(item);
});
}
}
function search(keyword: string) {
data.page.current = 1;
data.searchConfig.keyword = keyword;
getList();
}
function handleSelect(deptId = '') {
data.page.current = 1;
data.searchConfig.deptId = deptId;
getList();
}
function pageChange(pagination) {
data.page.current = pagination.current;
}
async function getSelectedList() {
let list = await getPostMulti(data.selectedIds.join(','));
if (list.length > 0) {
list.forEach((ele) => {
data.selectedList.push({
name: ele.name,
id: ele.id,
code: ele.code,
});
});
}
}
function abolishChecked(id: string) {
data.selectedList = data.selectedList.filter((ele) => {
return ele.id != id;
});
data.selectedIds.splice(
data.selectedIds.findIndex((itemId) => itemId === id),
1,
);
}
</script>

View File

@ -0,0 +1,165 @@
<template>
<div style="width: 100%">
<div @click="show"><slot></slot></div>
<ModalPanel
:visible="data.visible"
:width="1200"
:title="t('添加角色')"
@submit="submit"
@close="close"
>
<!-- 已选 -->
<Selected type="role" :list="data.selectedList" @abolish="abolishChecked" />
<SearchBox @search="search" />
<div class="list-page-box" v-if="data.list.length > 0">
<RoleCard
:class="data.selectedIds && data.selectedIds.includes(item.id) ? 'picked' : 'not-picked'"
v-for="(item, index) in data.list"
:key="index"
:item="item"
@click="checked(item)"
>
<template #check>
<a-checkbox
size="small"
:checked="data.selectedIds && data.selectedIds.includes(item.id)"
/>
</template>
</RoleCard>
</div>
<EmptyBox v-if="data.list.length == 0" />
</ModalPanel>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import RoleCard from './card/RoleCard.vue';
import Selected from './Selected.vue';
import { ModalPanel, EmptyBox, SearchBox } from '/@/components/ModalPanel/index';
import { getRoleList, getRoleMulti } from '/@/api/system/role';
import { RoleInfo } from '/@/api/system/role/model';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits(['change']);
const props = withDefaults(
defineProps<{
selectedIds: Array<string>;
multiple?: Boolean;
}>(),
{
selectedIds: () => {
return [];
},
disabledIds: () => {
return [];
},
},
);
let data: {
visible: boolean;
multiSelect: boolean;
list: Array<RoleInfo>;
selectedList: Array<RoleInfo>;
selectedIds: Array<string>;
searchConfig: {
keyword: string;
};
} = reactive({
visible: false,
multiSelect: false,
selectedIds: [],
list: [],
selectedList: [],
searchConfig: {
keyword: '',
},
});
async function show() {
if (props.selectedIds && Array.isArray(props.selectedIds)) data.selectedIds = props.selectedIds;
await getList();
await getSelectedList();
data.visible = true;
}
function submit() {
emits('change', data.selectedIds);
close();
}
function close() {
data.list = [];
data.selectedIds = [];
data.selectedList = [];
data.visible = false;
}
function checked(item) {
if (props.multiple && props.multiple == true) {
if (data.selectedIds.includes(item.id)) {
data.selectedIds.splice(
data.selectedIds.findIndex((itemId) => itemId === item.id),
1,
);
data.selectedList.splice(
data.selectedList.findIndex((ele) => ele.id === item.id),
1,
);
} else {
data.selectedIds.push(item.id);
data.selectedList.push(item);
}
} else {
if (data.selectedIds.includes(item.id)) {
data.selectedIds = [];
data.selectedList = [];
} else {
data.selectedIds = [item.id];
}
}
}
async function getList() {
data.list = [];
let params = {
keyword: data.searchConfig.keyword,
};
let list = await getRoleList(params);
if (list.length > 0) {
list.forEach((ele) => {
data.list.push({
name: ele.name,
id: ele.id,
code: ele.code,
count: ele.count, //角色人数
});
});
}
}
function search(keyword: string) {
data.searchConfig.keyword = keyword;
getList();
}
async function getSelectedList() {
let list = await getRoleMulti(data.selectedIds.join(','));
if (list.length > 0) {
list.forEach((ele) => {
data.selectedList.push({
name: ele.name,
id: ele.id,
code: ele.code,
count: ele.count, //角色人数
});
});
}
}
function abolishChecked(id: string) {
data.selectedList = data.selectedList.filter((ele) => {
return ele.id != id;
});
data.selectedIds.splice(
data.selectedIds.findIndex((itemId) => itemId === id),
1,
);
}
</script>

View File

@ -0,0 +1,269 @@
<template>
<div style="width: 100%" @click="show">
<slot></slot>
<ModalPanel
:visible="data.visible"
:width="1200"
:title="t('添加人员')"
@submit="submit"
@close="close"
>
<template #left>
<OrganizationalTree @select="handleSelect" />
</template>
<!-- 已选 -->
<Selected
v-if="data.visible"
type="user"
:list="data.selectedList"
@abolish="abolishChecked"
:disabledIds="props.disabledIds"
/>
<SearchBox @search="search" />
<div class="list-page-box" v-if="data.visible && data.list.length > 0">
<UserCard
:class="data.selectedIds.includes(item.id) ? 'picked' : 'not-picked'"
:disabled="props.disabledIds && props.disabledIds.includes(item.id) ? true : false"
v-for="(item, index) in data.list"
:key="index"
:item="item"
@click="checked(item)"
>
<template #check>
<a-checkbox size="small" :checked="data.selectedIds.includes(item.id)" />
</template>
</UserCard>
<div class="page-box">
<a-pagination
v-model:current="data.page.current"
:pageSize="data.page.pageSize"
:total="data.page.total"
show-less-items
/></div>
</div>
<EmptyBox v-if="data.list.length == 0" />
</ModalPanel>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, watch } from 'vue';
import OrganizationalTree from './OrganizationalTree.vue';
import UserCard from './card/UserCard.vue';
import Selected from './Selected.vue';
import { ModalPanel, EmptyBox, SearchBox } from '/@/components/ModalPanel/index';
import { getUserList, getUserMulti } from '/@/api/system/user';
import { UserInfo } from '/@/api/system/user/model';
import { cloneDeep } from 'lodash-es';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits(['change', 'changeNames', 'close']);
const props = withDefaults(
defineProps<{
selectedIds: Array<string> | string;
disabledIds?: Array<string>;
multiple?: Boolean;
visible?: Boolean;
}>(),
{
selectedIds: () => {
return [];
},
disabledIds: () => {
return [];
},
},
);
let data: {
visible: boolean;
multiSelect: boolean;
page: { current: number; total: number; pageSize: number };
list: Array<UserInfo>;
selectedList: Array<UserInfo>;
selectedIds: Array<string>;
searchConfig: {
keyword: string;
deptId: string;
};
} = reactive({
visible: false,
multiSelect: false,
page: {
current: 1,
total: 0,
pageSize: 9,
},
selectedIds: [],
list: [],
selectedList: [],
searchConfig: {
keyword: '',
deptId: '',
},
});
const paramRef = computed(() => {
return {
limit: data.page.current,
size: data.page.pageSize,
departmentId: data.searchConfig.deptId,
keyword: data.searchConfig.keyword,
};
});
watch(
() => data.page.current,
() => {
getList();
},
);
onMounted(() => {
if (props.selectedIds && Array.isArray(props.selectedIds)) {
data.selectedIds = cloneDeep(props.selectedIds);
getSelectedList();
}
});
async function show() {
data.selectedIds = [];
data.selectedList = [];
data.page.current = 1;
data.page.total = 0;
data.searchConfig.deptId = '';
if (props.selectedIds && Array.isArray(props.selectedIds)) {
data.selectedIds = cloneDeep(props.selectedIds);
}
if ((props.visible !== undefined && props.visible) || props.visible === undefined) {
await getList();
await getSelectedList();
data.visible = true;
} else {
data.visible = false;
}
}
function submit() {
emits('change', data.selectedIds, data.selectedList);
changeSelectedNames();
close();
}
function close() {
data.list = [];
data.selectedIds = [];
data.selectedList = [];
data.visible = false;
emits('close');
}
function checked(item) {
if (
props.disabledIds &&
Array.isArray(props.disabledIds) &&
props.disabledIds.includes(item.id)
) {
return;
}
if (props.multiple && props.multiple == true) {
if (data.selectedIds.includes(item.id)) {
data.selectedIds.splice(
data.selectedIds.findIndex((itemId) => itemId === item.id),
1,
);
data.selectedList.splice(
data.selectedList.findIndex((ele) => ele.id === item.id),
1,
);
} else {
data.selectedIds.push(item.id);
data.selectedList.push(item);
}
} else {
if (data.selectedIds.includes(item.id)) {
data.selectedIds = [];
data.selectedList = [];
} else {
data.selectedIds = [item.id];
data.selectedList = [item];
}
}
}
async function getList() {
data.list = [];
data.page.total = 0;
// let params = {
// limit: data.page.current,
// size: data.page.pageSize,
// departmentId: data.searchConfig.deptId,
// keyword: data.searchConfig.keyword,
// };
console.log('params', paramRef);
let res = await getUserList(paramRef.value);
if (res.total) {
data.page.total = res.total;
}
if (res.list.length > 0) {
res.list.forEach((ele) => {
let item = {
name: ele.name, //姓名
id: ele.id, //ID
code: ele.code, //code
gender: !isNaN(ele.gender) ? ele.gender : -1, //性别
mobile: ele.mobile, //联系电话
};
data.list.push(item);
});
}
}
function search(keyword: string) {
data.page.current = 1;
data.searchConfig.keyword = keyword;
console.log('search');
getList();
}
function handleSelect(deptId = '') {
data.page.current = 1;
data.searchConfig.deptId = deptId;
console.log('handleSelect');
getList();
}
// async function pageChange(pagination) {
// data.page.current = pagination.current;
// console.log('data.page', data.page, pagination);
// await getList();
// }
async function getSelectedList() {
let users = await getUserMulti(data.selectedIds.join(','));
if (users.length > 0) {
users.forEach((ele) => {
data.selectedList.push({
name: ele.name, //姓名
id: ele.id, //ID
code: ele.code, //code
gender: !isNaN(ele.gender) ? ele.gender : -1, //性别
mobile: ele.mobile, //联系电话
});
});
changeSelectedNames();
}
}
function changeSelectedNames() {
let userNames = data.selectedList
.map((ele) => {
return ele.name;
})
.join(',');
emits('changeNames', userNames);
}
function abolishChecked(id: string) {
data.selectedList = data.selectedList.filter((ele) => {
return ele.id != id;
});
data.selectedIds.splice(
data.selectedIds.findIndex((itemId) => itemId === id),
1,
);
}
</script>

View File

@ -0,0 +1,163 @@
<template>
<div ref="drawer">
<div class="flex justify-between mr-5">
<NodeHead :node-name="listTitle" />
<a-button @click="show">{{ title }}</a-button></div
>
<a-drawer
:getContainer="() => $refs.drawer"
placement="right"
:visible="data.visible"
:closable="false"
:mask="false"
>
<div class="selected-head title">
<NodeHead :node-name="title" />
<div class="close-icon" @click="close">+</div>
</div>
<div class="list-box" v-if="props.list && props.list.length > 0">
<component
:is="componentName"
v-for="(item, index) in props.list"
class="picked"
:key="index"
:item="item"
@click="abolish(item.id)"
:disabled="props.disabledIds && props.disabledIds.includes(item.id) ? true : false"
>
<template #check>
<a-checkbox size="small" :checked="true" />
</template>
</component>
</div>
<EmptyBox v-else />
</a-drawer>
</div>
</template>
<script setup lang="ts">
import { computed, reactive } from 'vue';
import UserCard from './card/UserCard.vue';
import RoleCard from './card/RoleCard.vue';
import PostCard from './card/PostCard.vue';
import { EmptyBox, NodeHead } from '/@/components/ModalPanel/index';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
const emits = defineEmits(['abolish']);
const props = withDefaults(
defineProps<{
type: String;
list: Array<{ id: string }>;
disabledIds?: Array<string>;
}>(),
{
type: () => {
return '';
},
list: () => {
return [];
},
},
);
let data = reactive({
visible: false,
});
const componentName = computed(() => {
if (props.type == 'user') {
return UserCard;
} else if (props.type == 'role') {
return RoleCard;
} else if (props.type == 'post') {
return PostCard;
} else {
return UserCard;
}
});
const title = computed(() => {
if (props.type == 'user') {
return t('已选用户');
} else if (props.type == 'role') {
return t('已选角色');
} else if (props.type == 'post') {
return t('已选岗位');
} else {
return '';
}
});
const listTitle = computed(() => {
if (props.type == 'user') {
return t('用户列表');
} else if (props.type == 'role') {
return t('角色列表');
} else if (props.type == 'post') {
return t('岗位列表');
} else {
return '';
}
});
async function show() {
data.visible = true;
}
function close() {
data.visible = false;
}
function abolish(id: string) {
emits('abolish', id);
}
</script>
<style scoped>
.title {
display: flex;
justify-content: space-between;
height: 40px;
font-size: 16px;
color: #333;
border-bottom: 1px solid #f0f0f0;
}
:deep(.ant-drawer-content-wrapper) {
width: 100% !important;
box-shadow: 0 2px 2px 2px rgb(0 0 0 / 4%);
}
:deep(.ant-drawer-open) {
position: absolute;
width: calc(100% - 244px) !important;
top: 50px;
left: 240px;
box-shadow: -5px 5px 4px 1px rgb(0 0 0 / 6%);
height: calc(100% - 110px);
z-index: 20;
}
.list-box {
display: flex;
flex-wrap: wrap;
overflow-y: auto;
padding: 10px 0;
}
.selected-head {
display: flex;
justify-content: space-between;
align-items: center;
}
.selected-btn {
position: absolute;
right: 30px;
}
.close-icon {
cursor: pointer;
font-size: 24px;
transform: rotate(45deg);
}
.picked {
border-width: 1px;
border-style: dotted;
}
</style>

View File

@ -0,0 +1,123 @@
<template>
<div class="list-item">
<div class="item-box">
<div class="item-left"><img :src="PostImg" /></div>
<div class="item-right">
<div class="item-title">{{ t('岗位名称') }}</div>
<div class="item-form-name">{{ props.item?.name }}</div>
</div>
<div class="fixed-checked">
<slot name="check"></slot>
</div>
<div class="fixed-icon">
<IconFontSymbol icon="post" fill-color="#fdf5ef" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import PostImg from '/@/assets/workflow/post.png';
import IconFontSymbol from '/@/components/IconFontSymbol/Index.vue';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
let props = defineProps({
item: Object,
});
</script>
<style lang="less" scoped>
@custom-color: #ff9935;
@bg-color: #fffbf7;
.list-item {
width: 30%;
height: 100px;
background: @bg-color;
border-color: transparent;
border-radius: 8px;
margin-left: 20px;
margin-bottom: 20px;
overflow: hidden;
&:hover {
border: 1px dotted @custom-color;
}
.item-box {
display: flex;
margin: 14px;
position: relative;
.item-left {
width: 30%;
margin-right: 14px;
img {
width: 100%;
height: 100%;
}
}
.item-right {
.item-title {
font-size: 12px;
font-weight: bold;
color: #999999;
margin: 10px 0 4px 0;
opacity: 0.8;
}
.item-form-name {
color: #303133;
font-size: 14px;
font-weight: bold;
opacity: 0.8;
}
}
.fixed-checked {
position: absolute;
bottom: -4px;
z-index: 1;
right: -6px;
}
.fixed-icon {
position: absolute;
right: -24px;
font-size: 70px;
transform: rotate(48deg);
top: -24px;
}
}
}
:deep(.ant-checkbox-inner) {
border-color: @custom-color;
}
:deep(.ant-checkbox-checked .ant-checkbox-inner) {
background-color: @custom-color;
border-color: @custom-color;
}
:deep(.ant-checkbox-checked::after),
:deep(.ant-checkbox-wrapper:hover .ant-checkbox-inner, .ant-checkbox:hover),
:deep(.ant-checkbox-inner),
:deep(.ant-checkbox:hover),
:deep(.ant-checkbox-input:focus + .ant-checkbox-inner) {
border-color: @custom-color;
}
.picked {
border-width: 1px;
border-style: dotted;
border-color: @custom-color;
}
.not-picked {
border-width: 1px;
border-style: dotted;
}
</style>

View File

@ -0,0 +1,131 @@
<template>
<div class="list-item">
<div class="item-box">
<div class="item-left"><img :src="RoleImg" /></div>
<div class="item-right item-role-box">
<div class="item-title">{{ t('角色名称') }}</div>
<div class="item-form-name">{{ props.item?.name }}</div>
</div>
<div class="item-right">
<div class="item-title">{{ t('角色人数') }}</div>
<div class="item-form-name"> {{ props.item?.count ? props.item.count : 0 }}</div>
</div>
<div class="fixed-checked">
<slot name="check"></slot>
</div>
<div class="fixed-icon">
<IconFontSymbol icon="role" fill-color="#e0f8f2" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import RoleImg from '/@/assets/workflow/role.png';
import IconFontSymbol from '/@/components/IconFontSymbol/Index.vue';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
let props = defineProps({
item: Object,
});
</script>
<style lang="less" scoped>
@custom-color: #0cc4a6;
@bg-color: #effbf9;
.list-item {
width: 30%;
height: 120px;
background: @bg-color;
border-color: transparent;
border-radius: 8px;
margin-left: 20px;
margin-bottom: 20px;
overflow: hidden;
&:hover {
border: 1px dotted @custom-color;
}
.item-box {
display: flex;
margin: 14px;
position: relative;
.item-left {
width: 30%;
margin-right: 14px;
img {
width: 100%;
height: 100%;
}
}
.item-right {
.item-title {
font-size: 12px;
font-weight: bold;
color: #999999;
margin: 10px 0 4px 0;
opacity: 0.8;
}
.item-form-name {
color: #303133;
font-size: 14px;
font-weight: bold;
opacity: 0.8;
}
}
.fixed-checked {
position: absolute;
bottom: -4px;
z-index: 1;
right: -6px;
}
.fixed-icon {
position: absolute;
right: -24px;
font-size: 70px;
transform: rotate(48deg);
top: -24px;
}
}
}
.item-role-box {
width: 30%;
}
:deep(.ant-checkbox-inner) {
border-color: @custom-color;
}
:deep(.ant-checkbox-checked .ant-checkbox-inner) {
background-color: @custom-color;
border-color: @custom-color;
}
:deep(.ant-checkbox-checked::after),
:deep(.ant-checkbox-wrapper:hover .ant-checkbox-inner, .ant-checkbox:hover),
:deep(.ant-checkbox-inner),
:deep(.ant-checkbox:hover),
:deep(.ant-checkbox-input:focus + .ant-checkbox-inner) {
border-color: @custom-color;
}
.picked {
border-width: 1px;
border-style: dotted;
border-color: @custom-color;
}
.not-picked {
border-width: 1px;
border-style: dotted;
}
</style>

View File

@ -0,0 +1,193 @@
<template>
<div class="list-item">
<div class="item-box">
<div class="item-left"><img :src="genderImg" /></div>
<div class="z-10">
<div class="item-right flex items-center">
<div class="item-title">{{ t('编码') }}</div>
<a-tooltip v-if="item?.code && item.code.length > 12" :title="item.code">
<div class="item-form-name"> {{ `${item.code.slice(0, 12)}...` }}</div>
</a-tooltip>
<div class="item-form-name" v-else> {{ item?.code || '-' }}</div>
</div>
<div class="item-right flex items-center">
<div class="item-title">{{ t('姓名') }}</div>
<a-tooltip v-if="item?.name && item.name.length > 8" :title="item.name">
<div class="item-form-name"> {{ `${item.name.slice(0, 8)}...` }}</div>
</a-tooltip>
<div class="item-form-name" v-else> {{ item?.name }}</div>
</div>
</div>
<!-- <div v-if="props.disabled">
<div class="fixed-checked"> 禁用 </div>
</div> -->
<div class="fixed-checked" v-if="hasCheckSlot">
<slot name="check"></slot>
</div>
<div class="fixed-icon">
<IconFontSymbol icon="user" :fillColor="genderFillColor" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, useSlots } from 'vue';
import defaultImg from '/@/assets/workflow/default.png';
import FemaleImg from '/@/assets/workflow/female.png';
import MaleImg from '/@/assets/workflow/male.png';
import IconFontSymbol from '/@/components/IconFontSymbol/Index.vue';
import { GenderEnum } from '/@/enums/userEnum';
import { useI18n } from '/@/hooks/web/useI18n';
const { t } = useI18n();
let props = defineProps({
item: Object,
disabled: Boolean,
isShowTree: {
type: Boolean,
default: true,
},
});
const hasCheckSlot = computed(() => {
return !!useSlots().check;
});
const genderImg = computed(() => {
switch (props.item?.gender) {
case GenderEnum.FEMALE:
return FemaleImg;
case GenderEnum.MALE:
return MaleImg;
default:
return defaultImg;
}
});
const genderFillColor = computed(() => {
switch (props.item?.gender) {
case GenderEnum.FEMALE:
return '#ffedf5';
case GenderEnum.MALE:
return '#e9f0fe';
default:
return '#f1ecfe';
}
});
let fontcolor = computed(() => {
switch (props.item?.gender) {
case GenderEnum.FEMALE:
return '#ffd1d7';
case GenderEnum.MALE:
return '#3c7eff';
default:
return '#b389ff';
}
});
let bgcolor = computed(() => {
switch (props.item?.gender) {
case GenderEnum.FEMALE:
return '#fef6fa';
case GenderEnum.MALE:
return '#f3f8ff';
default:
return '#f5f1fd';
}
});
const itemleftwidth = computed(() => {
return props.isShowTree ? '30%' : '25%';
});
</script>
<style lang="less" scoped>
.list-item {
width: 30%;
background: v-bind(bgcolor);
border-color: transparent;
border-radius: 8px;
margin-left: 20px;
margin-bottom: 20px;
overflow: hidden;
&:hover {
border: 1px dotted v-bind(fontcolor);
}
.item-box {
display: flex;
margin: 14px;
position: relative;
.item-left {
width: v-bind(itemleftwidth);
margin-right: 14px;
img {
width: 100%;
height: 100%;
}
}
.item-right {
.item-title {
font-size: 12px;
font-weight: bold;
color: #999;
margin: 10px 10px 4px 0;
opacity: 0.8;
}
.item-form-name {
color: #303133;
font-size: 14px;
font-weight: bold;
margin: 8px 0 4px;
opacity: 0.8;
}
}
.fixed-checked {
position: absolute;
bottom: -4px;
z-index: 1;
right: -6px;
}
.fixed-icon {
position: absolute;
right: -24px;
font-size: 70px;
transform: rotate(48deg);
top: -24px;
z-index: 0;
}
}
}
:deep(.ant-checkbox-inner) {
border-color: v-bind(fontcolor);
}
:deep(.ant-checkbox-checked .ant-checkbox-inner) {
background-color: v-bind(fontcolor);
border-color: v-bind(fontcolor);
}
:deep(.ant-checkbox-checked::after),
:deep(.ant-checkbox-wrapper:hover .ant-checkbox-inner, .ant-checkbox:hover),
:deep(.ant-checkbox-inner),
:deep(.ant-checkbox:hover),
:deep(.ant-checkbox-input:focus + .ant-checkbox-inner) {
border-color: v-bind(fontcolor);
}
.picked {
border-width: 1px;
border-style: dotted;
border-color: v-bind(fontcolor);
}
.not-picked {
border-width: 1px;
border-style: dotted;
}
</style>