Commit d5f1810f authored by 罗超's avatar 罗超

完成成员管理

parent 369174a7
......@@ -193,6 +193,9 @@ page {
.primary-borders-thin{
border:1px solid #d14424 !important;
}
.borders-light{
border:1px solid #F1f2f4;
}
.transparent-borders{
border:1px solid transparent;
}
......@@ -616,7 +619,7 @@ page {
}
.ppt-button{
font-size: 14px !important;
font-weight: bold !important;
font-weight: bold;
font-family: 'pingfangr';
}
.primary-link-button{
......
......@@ -141,7 +141,8 @@ import {
Dashboard,
Tag,
SettingOne,
Pencil
Pencil,
ToBottom
} from '@icon-park/vue-next'
export interface Icons {
......@@ -287,7 +288,8 @@ export const icons: Icons = {
IconDashBoard:Dashboard,
IconTag:Tag,
IconSettingOne:SettingOne,
IconPencli:Pencil
IconPencli:Pencil,
IconToBottom:ToBottom
}
export default {
......
......@@ -33,5 +33,15 @@ class UserServices{
let msg = {id}
return Api.Post("travel_remove_member",msg)
}
static async SetTenantSAAsync(id:string):Promise<HttpResponse>{
let msg = {id}
return Api.Post("travel_set_sa",msg)
}
static async SetTenantInviteCodeAsync(ia:0|1):Promise<HttpResponse>{
let msg = {ia}
return Api.Post("travel_set_invite_code",msg)
}
}
export default UserServices;
\ No newline at end of file
......@@ -84,7 +84,7 @@ export const useMenuStore = defineStore('menu', {
getTeamMenu:(state)=>{
const userStore= useUserStore()
const userInfo = userStore.getUser
if(userInfo.it && userInfo.ia){
if(userInfo.it && (userInfo.ia || userInfo.ic)){
const finds = menus.filter(x=>x.owner=='A')
return finds.map(x=>x.menu)
}
......
......@@ -60,6 +60,8 @@ export const useUserStore = defineStore('user', {
d.ia = 0
d.it = true
d.expire = d.Expire
d.ic = d.IsGroupCreate
d.id = d.DisplayId
this.userInfo = d
if(!this.userInfo.photo || !this.userInfo.photo.includes('http://')|| !this.userInfo.photo.includes('https://')){
this.userInfo.photo = USER_DEFAULT_HEADER
......@@ -108,6 +110,10 @@ export const useUserStore = defineStore('user', {
}
} catch (error) {}
return { status:'ERROR' } as UserLoginResult
},
setOldSaPermission(ia:boolean){
this.userInfo.ia = ia
this.userInfo.ic = false
}
},
persist: {
......
......@@ -82,6 +82,44 @@ export const getDaysBetween = (date1:any, date2:any) => {
return Math.round(difference / ONE_DAY); // 两个日期之间的天数
}
export const dateFormat = (value: number|string|Date = Date.now(), format = 'YYYY-MM-DD HH:mm:ss'): string => {
if (typeof value === 'number' || typeof value === 'string') {
var date = new Date(value)
} else {
var date = value
}
let showTime = format
if (showTime.includes('SSS')) {
const S = String(date.getMilliseconds())
showTime = showTime.replace('SSS', S.padStart(3, '0'))
}
if (showTime.includes('YY')) {
const Y = String(date.getFullYear())
showTime = showTime.includes('YYYY') ? showTime.replace('YYYY', Y) : showTime.replace('YY', Y.slice(2, 4))
}
if (showTime.includes('M')) {
const M = String(date.getMonth() + 1)
showTime = showTime.includes('MM') ? showTime.replace('MM', M.padStart(2, '0')) : showTime.replace('M', M)
}
if (showTime.includes('D')) {
const D = String(date.getDate())
showTime = showTime.includes('DD') ? showTime.replace('DD', D.padStart(2, '0')) : showTime.replace('D', D)
}
if (showTime.includes('H')) {
const H = String(date.getHours())
showTime = showTime.includes('HH') ? showTime.replace('HH', H.padStart(2, '0')) : showTime.replace('H', H)
}
if (showTime.includes('m')) {
const m = String(date.getMinutes())
showTime = showTime.includes('mm') ? showTime.replace('mm', m.padStart(2, '0')) : showTime.replace('m', m)
}
if (showTime.includes('s')) {
const s = String(date.getSeconds())
showTime = showTime.includes('ss') ? showTime.replace('ss', s.padStart(2, '0')) : showTime.replace('s', s)
}
return showTime
}
export const formatDateTimeToRead = (dateStr:string,prefix:string='') =>{
let minute = 1000 * 60;
let hour = minute * 60;
......
......@@ -154,7 +154,7 @@
<el-icon><Plus /></el-icon>
<span>加入企业</span>
</el-menu-item>
<el-menu-item index="2" class="rounded" v-if="userInfo.it && userInfo.ia" @click="redicetTo('/a')">
<el-menu-item index="2" class="rounded" v-if="userInfo.it && (userInfo.ia || userInfo.ic)" @click="redicetTo('/a')">
<el-icon><Setting /></el-icon>
<span>管理团队/企业</span>
</el-menu-item>
......@@ -162,7 +162,7 @@
<IconLogout size="18" style="margin-right: 8px" />
<span>退出团队/企业</span>
</el-menu-item>
<el-menu-item index="4" class="rounded" v-if="userInfo.it && userInfo.ia">
<el-menu-item index="4" class="rounded" v-if="userInfo.it && (userInfo.ia || userInfo.ic)">
<IconShareOne size="18" style="margin-right: 8px" />
<span>邀请成员</span>
</el-menu-item>
......
......@@ -2,7 +2,20 @@
<div class="full-height full-width column" ref="memberListRef">
<div class="row items-center">
<div class="text-dark text-weight-bolder col">成员管理</div>
<el-button class="ppt-button" type="primary">邀请成员</el-button>
<el-popover v-if="used[0]<used[1]" :width="100" popper-style="border-radius:10px; padding: 5px;" >
<template #reference>
<el-button class="ppt-button text-normal" type="primary">邀请成员</el-button>
</template>
<el-menu class="no-border md-menu"> <!-- style="font-family: 'Microsoft JhengHei ui';" -->
<el-menu-item @click="inviteHandler(1)">邀请成员</el-menu-item>
<el-menu-item @click="inviteHandler(2)">批量导入成员</el-menu-item>
</el-menu>
</el-popover>
<template v-else>
<div class="text-small text-negative q-mr-md">* 当前已无可用账号</div>
<el-button class="ppt-button text-normal" type="primary">购买席位</el-button>
</template>
</div>
<div class="q-mt-lg row flex-between">
<el-select v-model="parameters.t" placeholder="用户角色" style="width:150px" size="default" @change="changeSearch">
......@@ -62,13 +75,15 @@
</el-table-column>
<el-table-column prop="" label="操作" width="150">
<template #default="scope">
<el-button v-if="scope.row.ic" link type="primary" class="ppt-button">转让</el-button>
<el-button v-else link type="primary" class="ppt-button" @click="removeMemeberHandler(scope.row)">移除</el-button>
<el-button v-if="scope.row.ic && userInfo.ic" link type="primary" class="ppt-button" @click="setSaExchangeHandler">转让</el-button>
<el-button v-else-if="!scope.row.ic" link type="primary" class="ppt-button" @click="removeMemeberHandler(scope.row)">移除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</div>
<!-- 移除成员 -->
<el-dialog v-model="removeVisible" title="移除成员" style="width:400px;">
<main>
<el-alert type="warning" show-icon :closable="false">
......@@ -84,6 +99,25 @@
<el-button :type="removeTokenStr!='移除并清空'?'info':'primary'" size="large" @click="removeMember" :disabled="removeTokenStr!='移除并清空' || loading" class="ppt-button text-grey-8">移除并清空</el-button>
</template>
</el-dialog>
<!-- 转让管理员 -->
<el-dialog v-model="exchangeVisible" title="转让管理员" style="width:400px;">
<main>
<el-alert type="warning" show-icon :closable="false">
<template #title>
<span class="text-normal text-dark">你正在转让超级管理员,转让后你将失去团队/企业管理的最高权限,请谨慎操作</span>
</template>
</el-alert>
<el-select v-model="exchangeMember" size="large" class="full-width q-mt-md" filterable remote reserve-keyword placeholder="请输入成员姓名查找" :remote-method="querySearchAsync" :loading="exchangeLoading" >
<el-option v-for="item in exchangeMemberList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</main>
<template #footer>
<el-button @click="setSaExchangeHandler(false)" size="large" class="ppt-button" :disabled="loading">我再想想</el-button>
<el-button :type="exchangeMember==''?'info':'primary'" size="large" @click="exchangeTenantSa" :disabled="exchangeMember=='' || loading" class="ppt-button text-grey-8">确认转让</el-button>
</template>
</el-dialog>
<invite-member v-if="showAddMember" :show-type="showAddMemberType" @close="()=>showAddMember=false" @imported="changeSearch"></invite-member>
</template>
<script lang="ts" setup>
......@@ -91,14 +125,20 @@ import { ApiResult } from "@/configs/axios";
import { USER_DEFAULT_HEADER } from "@/configs/customer";
import OrgService from "@/services/OrgService";
import UserServices from "@/services/UserService";
import { useUserStore } from "@/store";
import { ElLoading, ElMessage } from "element-plus";
import { storeToRefs } from "pinia";
import { ref } from "vue";
import { useRouter } from "vue-router";
import InviteMember from '@/views/components/Team/InviteMember.vue'
const userGroup = ref<{id:number,name:string}[]>([
{id:0,name:'全部角色'},
{id:1,name:'管理员'},
{id:2,name:'成员'},
])
const userStore = useUserStore()
const {userInfo} = storeToRefs(userStore)
const data = ref<any[]>([])
const loading = ref(false)
const pageCount = ref(0)
......@@ -109,6 +149,13 @@ const nickNameModel = ref('')
const removeVisible = ref(false)
const removeTokenStr = ref('')
const removeMemberModel = ref<any>()
const exchangeVisible = ref(false)
const exchangeMember = ref('')
const exchangeLoading = ref(false)
const exchangeMemberList = ref<any[]>([])
const router = useRouter()
const showAddMember=ref(false)
const showAddMemberType=ref(1)
const parameters = ref<any>({
t:0,
......@@ -125,12 +172,19 @@ const setNickNameHandler = (target:any) =>{
nickNameModel.value = ''
}
}
const setSaExchangeHandler = (status:boolean)=>{
if(status){
exchangeMember.value = ''
exchangeVisible.value = true
exchangeMemberList.value = []
}else{
exchangeVisible.value = false
}
}
const getMembers = async ()=>{
if(loading.value) return
loading.value=true
//data.value = []
//pageCount.value = 0
const response = await OrgService.GetMembersAsync(parameters.value)
if(response.data.resultCode == ApiResult.SUCCESS && response.data.data.pageData){
......@@ -139,6 +193,10 @@ const getMembers = async ()=>{
}
loading.value=false
}
const inviteHandler = (t:2|1)=>{
showAddMemberType.value=t
showAddMember.value=true
}
const removeMemeberHandler = (target:any)=>{
removeMemberModel.value=target
removeVisible.value=true
......@@ -167,6 +225,10 @@ const setMemberManagerStatus = async (target:any,value:0|1)=>{
if(response.data.resultCode == ApiResult.SUCCESS){
ElMessage.success({message:'更新成功'})
target.isa =value==1
if(userInfo.value.id==target.id && value!=1){
userInfo.value.ia = false
router.push({path:'/space'})
}
}else{
ElMessage.error({message:'更新失败'})
}
......@@ -181,6 +243,10 @@ const setMemberNickName = async (target:any)=>{
if(response.data.resultCode == ApiResult.SUCCESS){
ElMessage.success({message:'更新成功'})
target.name =nickNameModel.value
if(userInfo.value.id==target.id){
userInfo.value.nickname = nickNameModel.value
}
setNickNameHandler(null)
}else{
ElMessage.error({message:'更新失败'})
......@@ -210,6 +276,41 @@ const removeMember = async ()=>{
changeSearch()
getMemberUsedStatus()
}
const querySearchAsync = async (queryString: string, cb: (arg: any) => void) => {
const query = {
t:0,
k:queryString,
pageIndex:1,
pageSize:2000
}
const response = await OrgService.GetMembersAsync(query)
if(response.data.resultCode == ApiResult.SUCCESS && response.data.data.pageData){
exchangeMemberList.value = response.data.data.pageData
}
}
const exchangeTenantSa =async () => {
if(exchangeMember.value=='') return
setSaExchangeHandler(false)
const loadingInstanct = ElLoading.service({text:'正在转移超级管理员权限'})
const response = await UserServices.SetTenantSAAsync(exchangeMember.value)
if(response.data.resultCode == ApiResult.SUCCESS){
ElMessage.success({message:'权限转移成功'})
const isa = response.data.data
userStore.setOldSaPermission(isa)
loadingInstanct.close()
if(!isa) router.push({path:'/space'})
else changeSearch()
}else{
ElMessage.error({message:'移除失败'})
loadingInstanct.close()
}
}
getMembers()
getMemberUsedStatus()
</script>
......
<template>
<el-dialog v-model="show" title="邀请成员" style="width:744px;" @close="closed" class="bold-head q-pa-none">
<div class="row" style="height: 440px;">
<div class="q-pa-md full-height" style="width:164px; border-right: 1px solid #f1f2f4;">
<el-menu class="no-border md-menu" :default-active="currentMenu">
<el-menu-item index="1" @click="currentMenu = '1'">邀请成员</el-menu-item>
<el-menu-item index="2" @click="currentMenu = '2'">批量导入成员</el-menu-item>
</el-menu>
</div>
<div class="col" style="padding: 24px;" v-if="currentMenu == '1'">
<div class="text-dark">发送链接邀请好友加入企业,一起创作吧~</div>
<div class="q-mt-xl">
<el-popover trigger="click" ref="popoverRef" width="532px"
popper-style="border-radius:10px; padding: 5px;">
<template #reference>
<div class="row items-center borders-light group-share-buttom rounded" style="padding:3px">
<div class="col row items-center q-pl-lg cusor-pointer">
<span>加入团队/企业后身份为</span>
<span class="q-mx-md">{{ inviteIsAdmin == '0' ? '成员' : '管理员' }}</span>
<IconDown size="16px"></IconDown>
</div>
<el-button type="primary" class="rounded" @click="(event) => copyInviteUrlHandler(event)"
:loading="copyLoading">复制链接</el-button>
</div>
</template>
<el-menu class="no-border md-menu" :default-active="inviteIsAdmin">
<el-menu-item index="0" @click="setIsAdminHandler(0)">邀请成员</el-menu-item>
<el-menu-item index="1" @click="setIsAdminHandler(1)">邀请管理员</el-menu-item>
</el-menu>
</el-popover>
</div>
<div class="q-mt-md text-small text-info">链接有效期14天</div>
</div>
<div class="col column" style="padding: 24px;" v-else>
<div class="row items-center">
<div class="num-serial text-small q-mr-md">1</div>
<div class="col text-dark" style="font-size: 16px">下载模板</div>
</div>
<div class="row items-center">
<div style="border-left: 1px dashed #000; margin-left: 8px;margin-right: 9px; height:98px;"></div>
<div class="col q-ml-md">
<el-alert type="info" :closable="false">
<template #title>
<div class="row items-center">
<div class="col">请按参考模板填写内容;字段不符合规则,则不予以导入</div>
<div class="text-primary row items-center cusor-pointer q-ml-md">
<IconToBottom size="16" class="q-mr-sm"></IconToBottom>
<span>下载导入模板</span>
</div>
</div>
</template>
</el-alert>
</div>
</div>
<div class="row items-center">
<div class="num-serial text-small q-mr-md">2</div>
<div class="col text-dark" style="font-size: 16px">上传文件</div>
</div>
<div class="q-mt-lg">
<el-upload class="full-height full-width text-primary" drag action="" accept=".xls,.xlsx">
<IconUpload size="54" class=""></IconUpload>
<div class="el-upload__text">
<div style="font-size: 16px;" class="q-my-lg">成员名单仅支持 .xls .xlsx</div>
<div class="text-center">
<el-button class="ppt-button" type="primary" size="target">导入成员名单</el-button>
</div>
</div>
<template #tip></template>
</el-upload>
</div>
</div>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { ApiResult } from "@/configs/axios"
import UserServices from "@/services/UserService"
import { useUserStore } from "@/store"
import { copyText } from "@/utils/clipboard"
import { dateFormat } from "@/utils/common"
import { ElMessage } from "element-plus"
import { storeToRefs } from "pinia"
import { ref, unref } from "vue"
const props = defineProps({
showType: {
type: Number,
default: 1
},
})
const emit = defineEmits<{
(event: 'close'): void,
(event: 'imported'): void
}>()
const show = ref(true)
const currentMenu = ref(props.showType.toString())
const inviteIsAdmin = ref('0')
const popoverRef = ref()
const copyLoading = ref(false)
const useUser = useUserStore()
const { userInfo } = storeToRefs(useUser)
const closed = () => {
emit('close')
}
const copyInviteUrlHandler = async (e: MouseEvent) => {
e.stopPropagation()
if (copyLoading.value) return
copyLoading.value = true
const ia = inviteIsAdmin.value == '0' ? 0 : 1
const response = await UserServices.SetTenantInviteCodeAsync(ia)
if (response.data.resultCode == ApiResult.SUCCESS) {
let currentDate = new Date();
currentDate.setDate(currentDate.getDate() + 14);
const url = `http://www.viitto.com/j/${response.data.data} \n我正在使用Travel Design,点击链接立即加入我的团队。\n邀请人:${userInfo.value.nickname}\n团队/企业名称:${userInfo.value.company}\n有效期至:${dateFormat(currentDate, "YYYYMMDD")}`
copyText(url)
ElMessage.success({ message: '复制成功' })
} else {
ElMessage.error({ message: '当前无法生成邀请链接' })
}
copyLoading.value = false
return false
}
const setIsAdminHandler = (isa: number) => {
inviteIsAdmin.value = isa.toString()
popoverRef.value?.hide?.()
}
</script>
<style>
.bold-head .el-dialog__header {
font-weight: 800 !important;
}
.bold-head .el-dialog__body {
border-top: 1px solid #F1f2f4;
}
.q-pa-none .el-dialog__body {
padding: 0 !important;
}
.group-share-buttom:hover {
border-color: var(--el-color-primary);
}
.num-serial {
width: 18px;
height: 18px;
color: #000;
border: 1px solid #000;
border-radius: 18px;
line-height: 16px;
text-align: center;
background: #FFF;
}
</style>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment