搜索结果列表页面开发
This commit is contained in:
430
src/views/Org/Member/index.vue
Normal file
430
src/views/Org/Member/index.vue
Normal file
@@ -0,0 +1,430 @@
|
||||
<template>
|
||||
<div class="box-border">
|
||||
<Card style="padding:0;">
|
||||
<div class="org-member">
|
||||
<div class="tabs flex items-center justify-between">
|
||||
<div class="tabs-nav flex items-center">
|
||||
<div v-for="(item) in tabList" :key="item.id" :id="item.id" class="tabs-nav-option"
|
||||
:class="{ active: tableTab === item.id }" @click="updateTable(item)"><span>{{ item.name }}</span></div>
|
||||
</div>
|
||||
<div class="right-search pr-3">
|
||||
<div v-if="tableTab === 'member'" class="flex items-center">
|
||||
<d-input v-model="username" placeholder="搜索组织成员" clearable>
|
||||
<template #prefix>
|
||||
<Icon name="gt-search" />
|
||||
</template>
|
||||
</d-input>
|
||||
<d-button class="ml-4 flex-shrink-0" variant="solid" color="primary" @click="router.push({ name: 'orgSettingMember' })"
|
||||
v-if="isAdmin">组织成员管理</d-button>
|
||||
</div>
|
||||
<div v-if="tableTab === 'follow'" class="flex items-center">
|
||||
<d-input v-model="keyword" placeholder="搜索关注用户" clearable @keydown="e=>e.key==='Enter'&&searchFollows()">
|
||||
<template #prefix>
|
||||
<Icon name="gt-search" @click="searchFollows"/>
|
||||
</template>
|
||||
</d-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tableTab === 'member'">
|
||||
<div class="member">
|
||||
<DataPanel :loading="loading" :empty="empty" skeleton :card="false" class="my-[12px]">
|
||||
<MemberItems class="" :data="userList" showTags isManager :isAdmin="isAdmin" @statusChange="handleChange"
|
||||
@removeMember="handleRemove" @exitProject="handleExit" />
|
||||
</DataPanel>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tableTab === 'follow'">
|
||||
<div class="overflow-hidden">
|
||||
<DataPanel :loading="followLoading" :empty="followList.length===0" skeleton :card="false" class="my-[12px]">
|
||||
<div v-for="item in followList" :key="item.id" class="flex items-center py-4 px-5">
|
||||
<GAvatar
|
||||
:src="item.avatar"
|
||||
:name="item.nickname"
|
||||
:width="48"
|
||||
:height="48"
|
||||
:is_round="true"
|
||||
></GAvatar>
|
||||
<div class="flex flex-col ml-5 w-[200px]">
|
||||
<GText class="text-base">{{ xssPurify(item.nickname) }}</GText>
|
||||
<div class="text-CG600 text-sm ellipsis mt-2">@{{ item.username }}</div>
|
||||
</div>
|
||||
<div class="text-CG600 flex-grow flex justify-center">
|
||||
<span>{{formatTime(item.create_time, 'YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</div>
|
||||
<div class="w-[100px] flex justify-center">
|
||||
<template v-if="localUser.username!==item.username">
|
||||
<d-button v-if="item.is_followed" size="sm" class="follow-btn border boder-G400" @click="toggleFollow(item)">取消关注</d-button>
|
||||
<d-button v-else size="sm" class="follow-btn border boder-G400" @click="toggleFollow(item)">关注</d-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</DataPanel>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div v-if="tableTab === 'member'">
|
||||
<d-pagination class="px-[20px] py-[20px] flex justify-center" auto-hide size="md" :page-size-options="[10, 20, 50]" :show-page-selector="false"
|
||||
:can-view-total="true" :can-change-page-size="true" :max-items="5" :total="pager.total"
|
||||
v-model:pageSize="pager.per_page" v-model:pageIndex="pager.page" @page-index-change="getUsers"
|
||||
@page-size-change="handleSizeChange" />
|
||||
</div>
|
||||
<div v-if="tableTab === 'follow'">
|
||||
<d-pagination class="px-[20px] py-[20px] flex justify-center" auto-hide size="md" :page-size-options="[10, 20, 50]" :show-page-selector="false"
|
||||
:can-view-total="true" :can-change-page-size="true" :max-items="5" :total="followPager.total"
|
||||
v-model:pageSize="followPager.per_page" v-model:pageIndex="followPager.page" @page-index-change="getFollowers()"
|
||||
@page-size-change="handleFollowSizeChange" />
|
||||
</div>
|
||||
<GModal v-model="visible" ref="deleteTip" showWarnIcon @confirm="handleConfirm" :title="warningInfo.title">
|
||||
<span class="inline-block text-G900 text-sm font-normal leading-[20px] break-all">{{ warningInfo.content }}</span>
|
||||
</GModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, toRaw, computed, watch, nextTick, onBeforeUnmount } from 'vue';
|
||||
import { Message } from 'vue-devui/message';
|
||||
import MemberItems from '@/components/MemberItems/index.vue';
|
||||
import type { MemberItemData } from '@/components/MemberItems/types';
|
||||
import { getOrgMembers, removeGroupUser, exitGroup, setGroupUserLevel } from '@/api/org';
|
||||
import { getGroupFollows } from '@/api/org/devIndex';
|
||||
import { useAccountStore } from '@/stores/user';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { getOrgInfo } from '@/views/Org/hooks/orgInfo';
|
||||
import { orgInfoStore } from '@/stores/Org';
|
||||
import setting from '@/setting';
|
||||
import { xssPurify } from '@/utils';
|
||||
import { GModal } from '@/components/Setting/index';
|
||||
import { useTimeFormat } from '@/utils/hooks/useTimeFormat';
|
||||
import { reqCatchV2 } from '@/utils/catch';
|
||||
import { followUser, unfollowUser } from '@/api/user';
|
||||
import { useLoginCheck } from '@/utils/hooks/useLoginCheck';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { namespace, getUserCreateOrg } = getOrgInfo();
|
||||
const { visitor, developer, admin } = setting.role;
|
||||
const loading = ref(true);
|
||||
const { isAdmin, communityInfo, access_level } = storeToRefs(orgInfoStore());
|
||||
const { formatTime } = useTimeFormat();
|
||||
const { loginCheck } = useLoginCheck();
|
||||
const empty = computed(() => {
|
||||
return !loading.value && !userList.value?.length;
|
||||
});
|
||||
const hasPermission = computed(() => access_level.value >= visitor);
|
||||
const { accountInfo: localUser } = useAccountStore();
|
||||
interface RepoGroupUser {
|
||||
access_level: number | string
|
||||
email: string
|
||||
id: number | string
|
||||
is_current_source_member: boolean
|
||||
name: string
|
||||
name_cn?: string
|
||||
username: string
|
||||
type: string
|
||||
web_url: string
|
||||
[x: string]: any
|
||||
}
|
||||
|
||||
// 邀请成员
|
||||
const visible = ref(false);
|
||||
const username = ref('');
|
||||
const pager = reactive({
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
total: 0
|
||||
});
|
||||
const followPager = reactive({
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
total: 0
|
||||
});
|
||||
const warningInfo = reactive<{ title: string, content: string, name: string, type: 'remove' | 'exit' | '' }>({
|
||||
title: '',
|
||||
content: '',
|
||||
name: '',
|
||||
type: ''
|
||||
});
|
||||
const tableTab = ref(hasPermission.value ? 'member' : 'follow');
|
||||
const tabList = hasPermission.value ? [
|
||||
{
|
||||
id: 'member',
|
||||
name: '组织成员'
|
||||
}, {
|
||||
id: 'follow',
|
||||
name: '社区粉丝'
|
||||
}
|
||||
] : [{
|
||||
id: 'follow',
|
||||
name: '社区粉丝'
|
||||
}];
|
||||
const updateTable = (item: any) => {
|
||||
tableTab.value = item.id;
|
||||
};
|
||||
// 判断是否从组织主页banner跳转
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const isOrgToFans = params.get('isOrgToFans');
|
||||
if (isOrgToFans === 'isOrgToFans') {
|
||||
const select = tabList.find(v => v.id === 'follow');
|
||||
select && updateTable(select);
|
||||
}
|
||||
watch(() => route.query, (val) => {
|
||||
if (val?.isOrgToFans === 'isOrgToFans') {
|
||||
const select = tabList.find(v => v.id === 'follow');
|
||||
select && updateTable(select);
|
||||
}
|
||||
}, { immediate: true, deep: true });
|
||||
const keyword = ref('');
|
||||
const followLoading = ref(false);
|
||||
const followList = ref([]);
|
||||
const userList = ref<MemberItemData[]>([]);
|
||||
const getUserInfo = (source: RepoGroupUser, content: RepoGroupUser[]) => {
|
||||
const userInfo = JSON.parse(localStorage.getItem('userInfo') as string);
|
||||
const adminList = content?.filter(item => item.access_level.toString() === '50') || [];
|
||||
return {
|
||||
imageSrc: source.avatar || source.avatar_url,
|
||||
nickname: source.nickname || source.username || '-',
|
||||
userName: source.username,
|
||||
isSelf: Number(userInfo.arts_id) === Number(source.id),
|
||||
isManager: source.access_level.toString() >= admin,
|
||||
isDeveloper: source.access_level.toString() >= developer && source.access_level.toString() < admin,
|
||||
isVisitor: source.access_level.toString() >= visitor && source.access_level.toString() < developer,
|
||||
allowedExit: source.access_level.toString() !== '50' || adminList.length > 1,
|
||||
isApplying: false,
|
||||
status: source.access_level.toString(),
|
||||
lastStatus: source.access_level.toString(),
|
||||
path: `/${source.username}`,
|
||||
type: source.type,
|
||||
join_way: source.join_way,
|
||||
disabledOption: false
|
||||
};
|
||||
};
|
||||
|
||||
const getUsers = async() => {
|
||||
const { page, per_page } = pager;
|
||||
const res = await getOrgMembers({ group_id: namespace.value, page, per_page, query: username.value.trim() });
|
||||
if (res.status === 200) {
|
||||
userList.value = (res.data.content as RepoGroupUser[]).map(item => {
|
||||
return getUserInfo(item, res.data.content);
|
||||
});
|
||||
|
||||
pager.total = res?.data?.total;
|
||||
pager.page = res?.data?.page_num;
|
||||
pager.per_page = res?.data?.page_size;
|
||||
}
|
||||
loading.value = false;
|
||||
};
|
||||
const getFollowers = async() => { // 获取组织的粉丝列表
|
||||
const { page, per_page } = followPager;
|
||||
followLoading.value = true;
|
||||
const res = await getGroupFollows({ groupId: namespace.value, nsId: communityInfo.value?.ns_id, keyword: keyword.value, pageNum: page, pageSize: per_page });
|
||||
followLoading.value = false;
|
||||
if (res.data) {
|
||||
const { content, total } = res.data.data;
|
||||
followList.value = (content || []).map(({ nick_name, ...other }) => ({ ...other, nickname: nick_name }));
|
||||
// if (code === 200) {
|
||||
// followList.value = (data.content || []).map(({ gitCodeUser, createdAt, ...other }) => ({
|
||||
// ...other,
|
||||
// avatar: gitCodeUser.avatar,
|
||||
// nickname: gitCodeUser.nickname,
|
||||
// username: gitCodeUser.username,
|
||||
// create_time: createdAt,
|
||||
// is_followed: gitCodeUser.isFollowedForGitCodeUser
|
||||
// }));
|
||||
// followPager.total = data.total;
|
||||
// }
|
||||
followPager.total = total;
|
||||
}
|
||||
};
|
||||
const searchFollows = () => {
|
||||
followPager.page = 1;
|
||||
followPager.total = 0;
|
||||
getFollowers();
|
||||
};
|
||||
const toggleFollow = async(item:any, key = 'is_followed') => { // 关注用户
|
||||
if (!loginCheck()) return;
|
||||
const params = {
|
||||
username: localUser.username || '',
|
||||
followType: 0,
|
||||
unfollowUsername: item.username,
|
||||
followedUsername: item.username
|
||||
};
|
||||
const followRes = await reqCatchV2(() => item[key] ? unfollowUser(params) : followUser(params));
|
||||
if (followRes.data) {
|
||||
Message({
|
||||
type: 'success',
|
||||
message: `${item[key] ? '取消关注' : '关注'}成功`
|
||||
});
|
||||
await getFollowers();
|
||||
}
|
||||
};
|
||||
const handleRemove = async(config: any) => {
|
||||
const { userName } = config.data;
|
||||
visible.value = true;
|
||||
warningInfo.type = 'remove';
|
||||
warningInfo.name = userName;
|
||||
warningInfo.title = `你正在移除成员 ${userName}`;
|
||||
warningInfo.content = '移除成员后无法恢复, 确认移除么?';
|
||||
};
|
||||
|
||||
const handleConfirm = async() => {
|
||||
const { type, name } = warningInfo;
|
||||
const config = {
|
||||
group_id: namespace.value,
|
||||
username: name
|
||||
};
|
||||
const reqFn = type === 'remove'
|
||||
? removeGroupUser
|
||||
: exitGroup;
|
||||
const res = await reqFn(config);
|
||||
if (!res.error) {
|
||||
visible.value = false;
|
||||
Message.success('操作成功');
|
||||
if (type === 'exit') {
|
||||
router.replace('/');
|
||||
} else {
|
||||
for (const key in warningInfo) {
|
||||
warningInfo[key as keyof typeof warningInfo] = '';
|
||||
}
|
||||
getUsers();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = async(data: any) => {
|
||||
const { status, userName, nickname } = toRaw(data.data);
|
||||
const cData = await getUserCreateOrg(userName);
|
||||
if (status === '50' && !cData) {
|
||||
data.data.status = data.data.lastStatus;
|
||||
return Message.warning(`${nickname}已经是 5 个组织的管理员,无法再添加为管理员。`);
|
||||
}
|
||||
const res = await setGroupUserLevel({ group_id: namespace.value, username: userName, access_level: status });
|
||||
if (!res.error) {
|
||||
Message.success('操作成功');
|
||||
getUsers();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = async() => {
|
||||
pager.total = 0;
|
||||
pager.page = 1;
|
||||
pager.per_page = 10;
|
||||
nextTick(() => {
|
||||
getUsers();
|
||||
});
|
||||
};
|
||||
|
||||
const setSearch = () => {
|
||||
const timer = setTimeout(() => {
|
||||
handleSearch();
|
||||
}, 200);
|
||||
const cancel = () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
return {
|
||||
cancel
|
||||
};
|
||||
};
|
||||
|
||||
const stop = watch(() => username.value.trim(), (_new, _old, onCleanup) => {
|
||||
const { cancel } = setSearch();
|
||||
onCleanup(cancel);
|
||||
});
|
||||
const handleExit = () => {
|
||||
visible.value = true;
|
||||
warningInfo.type = 'exit';
|
||||
warningInfo.title = `退出组织?`;
|
||||
warningInfo.content = `你确定你想退出吗?你将无法访问所有存储库和团队。`;
|
||||
};
|
||||
|
||||
const handleSizeChange = () => {
|
||||
pager.page = 1;
|
||||
nextTick(() => {
|
||||
getUsers();
|
||||
});
|
||||
};
|
||||
const handleFollowSizeChange = () => {
|
||||
followPager.page = 1;
|
||||
getFollowers();
|
||||
};
|
||||
onMounted(() => {
|
||||
if (hasPermission.value) getUsers();
|
||||
getFollowers();
|
||||
});
|
||||
// watch(() => communityInfo.value, (val) => {
|
||||
// if (val.ns_id) getFollowers();
|
||||
// }, { immediate: true });
|
||||
onBeforeUnmount(() => {
|
||||
stop();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.org-member{
|
||||
.tabs{
|
||||
height: 60px;
|
||||
border-bottom: 1px solid var(--color-G300);
|
||||
.tabs-nav{
|
||||
.tabs-nav-option{
|
||||
line-height: 58px;
|
||||
color: var(--color-CG600);
|
||||
cursor: pointer;
|
||||
padding: 0 4px;
|
||||
margin: 0 12px;
|
||||
&::after{
|
||||
content: "";
|
||||
display: block;
|
||||
margin: auto;
|
||||
height: 2px;
|
||||
width: 0;
|
||||
background: transparent;
|
||||
//transition: width var(--devui-animation-duration-slow, .3s) var(--devui-animation-ease-in-out, cubic-bezier(.5, .05, .5, .95)), background-color var(--devui-animation-duration-slow, .3s) var(--devui-animation-ease-in-out, cubic-bezier(.5, .05, .5, .95));
|
||||
}
|
||||
&.active{color:var(--color-G900);}
|
||||
&.active::after{
|
||||
content: "";
|
||||
display: block;
|
||||
margin: auto;
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
background: var(--color-G900);
|
||||
//transition: width var(--devui-animation-duration-slow, .3s) var(--devui-animation-ease-in-out, cubic-bezier(.5, .05, .5, .95)), background-color var(--devui-animation-duration-slow, .3s) var(--devui-animation-ease-in-out, cubic-bezier(.5, .05, .5, .95));
|
||||
}
|
||||
&:hover::after{
|
||||
content: "";
|
||||
display: block;
|
||||
margin: auto;
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
background: var(--color-G900);
|
||||
//transition: width var(--devui-animation-duration-slow, .3s) var(--devui-animation-ease-in-out, cubic-bezier(.5, .05, .5, .95)),background-color var(--devui-animation-duration-slow, .3s) var(--devui-animation-ease-in-out, cubic-bezier(.5, .05, .5, .95));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.repo-setting-member-modal {
|
||||
width: 370px;
|
||||
box-sizing: border-box;
|
||||
padding: 0 0 20px;
|
||||
|
||||
.devui-modal__body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal-slot-header {
|
||||
@apply box-border py-[18px] mx-[22px] text-[var(--color-G900)] text-[16px] border-b-[1px] border-solid border-[var(--color-G200)] font-bold;
|
||||
}
|
||||
|
||||
.devui-flexible-overlay {
|
||||
left: 30px !important;
|
||||
right: 30px !important;
|
||||
max-height: 320px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user