mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
feat: 团队成员
This commit is contained in:
parent
558213db40
commit
7187056e95
|
|
@ -1,5 +1,5 @@
|
|||
import { Result } from '@/request/Result'
|
||||
import { get, post } from '@/request/index'
|
||||
import { get, post, del } from '@/request/index'
|
||||
import type { TeamMember, TeamMemberRequest } from '@/api/type/team'
|
||||
// import type { Ref } from 'vue'
|
||||
|
||||
|
|
@ -16,11 +16,20 @@ const getTeamMember: () => Promise<Result<TeamMember[]>> = () => {
|
|||
* 添加成员
|
||||
* @param 参数 { "username_or_email": "string" }
|
||||
*/
|
||||
const postCreatTeamMember: (request: TeamMemberRequest) => Promise<Result<boolean>> = (request) => {
|
||||
return post(`${prefix}`, request)
|
||||
const postCreatTeamMember: (body: TeamMemberRequest) => Promise<Result<boolean>> = (body) => {
|
||||
return post(`${prefix}`, body)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除成员
|
||||
* @param 参数 member_id
|
||||
*/
|
||||
const delTeamMember: (member_id: String) => Promise<Result<boolean>> = (member_id) => {
|
||||
return del(`${prefix}/${member_id}`)
|
||||
}
|
||||
|
||||
export default {
|
||||
getTeamMember,
|
||||
postCreatTeamMember
|
||||
postCreatTeamMember,
|
||||
delTeamMember
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import AppIcon from './icons/AppIcon.vue'
|
|||
import LoginLayout from './login-layout/index.vue'
|
||||
import LoginContainer from './login-container/index.vue'
|
||||
import LayoutContent from './content-container/LayoutContent.vue'
|
||||
import TagsInput from './tags-input/index.vue'
|
||||
|
||||
export default {
|
||||
install(app: App) {
|
||||
|
|
@ -10,5 +11,6 @@ export default {
|
|||
app.component(LoginLayout.name, LoginLayout)
|
||||
app.component(LoginContainer.name, LoginContainer)
|
||||
app.component(LayoutContent.name, LayoutContent)
|
||||
app.component(TagsInput.name, TagsInput)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
<template>
|
||||
<!-- 外层div -->
|
||||
<div ref="InputTag" class="tags-input">
|
||||
<div class="tags-container" v-if="tagsList.length">
|
||||
<!-- 标签 -->
|
||||
<el-tag
|
||||
v-for="(item, index) in tagsList"
|
||||
:key="index"
|
||||
@close="removeTag(item)"
|
||||
closable
|
||||
class="mr-10"
|
||||
>{{ item }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<!-- 输入框 -->
|
||||
<el-input
|
||||
:validate-event="false"
|
||||
v-model="currentval"
|
||||
:placeholder="tagsList.length == 0 ? placeholder : ''"
|
||||
@keydown.enter="addTags"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
defineOptions({ name: 'TagsInput' })
|
||||
const props = defineProps({
|
||||
tags: {
|
||||
// 多个
|
||||
type: Array<String>,
|
||||
default: () => []
|
||||
},
|
||||
tag: {
|
||||
// 单个
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入'
|
||||
},
|
||||
limit: {
|
||||
// 最多生成标签数
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
reg: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['update:tags', 'update:tag'])
|
||||
const currentval = ref('')
|
||||
const tagsList = ref<String[]>([])
|
||||
|
||||
watch([tagsList, currentval], (val) => {
|
||||
if (val[0]?.length > 0) {
|
||||
emit('update:tags', val[0])
|
||||
} else if (val[1]) {
|
||||
emit('update:tag', val[1])
|
||||
}
|
||||
})
|
||||
|
||||
function addTags() {
|
||||
const val = currentval.value.trim()
|
||||
if (val) {
|
||||
tagsList.value.push(val)
|
||||
}
|
||||
currentval.value = ''
|
||||
}
|
||||
function removeTag(tag: String) {
|
||||
tagsList.value.splice(tagsList.value.indexOf(tag), 1)
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tags-input {
|
||||
width: 100%;
|
||||
min-height: 70px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: var(--el-border-radius-base);
|
||||
:deep(.el-input__wrapper) {
|
||||
background: none !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 0 !important;
|
||||
resize: none;
|
||||
}
|
||||
.tags-container {
|
||||
padding: 0 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -150,7 +150,7 @@ export const put: (
|
|||
data?: unknown,
|
||||
loading?: NProgress | Ref<boolean>
|
||||
) => Promise<Result<any>> = (url, params, data, loading) => {
|
||||
return promise(request({ url: url, method: 'put', data, params }), loading)
|
||||
return promise(request({ url: url, method: 'put', params, data }), loading)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -166,7 +166,7 @@ export const del: (
|
|||
data?: unknown,
|
||||
loading?: NProgress | Ref<boolean>
|
||||
) => Promise<Result<any>> = (url, params, data, loading) => {
|
||||
return promise(request({ url: url, method: 'delete', data, params }), loading)
|
||||
return promise(request({ url: url, method: 'delete', params, data }), loading)
|
||||
}
|
||||
|
||||
export const exportExcel: (
|
||||
|
|
|
|||
|
|
@ -14,6 +14,16 @@
|
|||
--el-form-inline-content-width: 100%;
|
||||
}
|
||||
|
||||
.el-dialog {
|
||||
.dialog-sub-title {
|
||||
color: var(--el-text-color-regular);
|
||||
margin: 5px 0;
|
||||
}
|
||||
.el-dialog__body {
|
||||
padding: 15px var(--el-dialog-padding-primary) 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 抽屉样式整体修改
|
||||
.el-drawer {
|
||||
.el-drawer__header {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,102 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:close-on-press-escape="false"
|
||||
:close-on-click-modal="false"
|
||||
:destroy-on-close="true"
|
||||
width="600"
|
||||
>
|
||||
<template #header="{ titleId, titleClass }">
|
||||
<h4 :id="titleId" :class="titleClass">添加成员</h4>
|
||||
<div class="dialog-sub-title">成员登录后可以访问到您授权的数据。</div>
|
||||
</template>
|
||||
|
||||
<el-form
|
||||
ref="addMemberFormRef"
|
||||
:model="memberForm"
|
||||
label-position="top"
|
||||
:rules="rules"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item label="用户名/邮箱" prop="users">
|
||||
<tags-input
|
||||
v-model:tags="memberForm.users"
|
||||
v-model:tag="memberForm.user"
|
||||
placeholder="请输入成员的用户名或邮箱,若需添加多个成员请使用回车分割。"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> 取消 </el-button>
|
||||
<el-button type="primary" @click="submitMember(addMemberFormRef)"> 添加 </el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import TeamApi from '@/api/team'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
const memberForm = ref({
|
||||
users: [],
|
||||
user: ''
|
||||
})
|
||||
|
||||
const addMemberFormRef = ref<FormInstance>()
|
||||
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
const validateUsers = (rule: any, value: any, callback: any) => {
|
||||
if (value?.length == 0 && !memberForm.value.user) {
|
||||
callback(new Error('请输入用户名/邮箱'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const rules = ref<FormRules>({
|
||||
users: [{ type: 'array', validator: validateUsers }]
|
||||
})
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
memberForm.value = {
|
||||
users: [],
|
||||
user: ''
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const submitMember = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
const obj: any = {
|
||||
username_or_email: memberForm.value.users?.length
|
||||
? memberForm.value.users.toString()
|
||||
: memberForm.value.user
|
||||
}
|
||||
TeamApi.postCreatTeamMember(obj).then(() => {
|
||||
MsgSuccess('提交成功')
|
||||
emit('refresh')
|
||||
dialogVisible.value = false
|
||||
})
|
||||
} else {
|
||||
console.log('error submit!')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open, close })
|
||||
</script>
|
||||
<style lang="scss" scope></style>
|
||||
|
|
@ -4,35 +4,43 @@
|
|||
<div class="team-member p-15 border-r">
|
||||
<h3>团队成员</h3>
|
||||
<div class="align-right">
|
||||
<el-button type="primary" link
|
||||
><AppIcon iconName="app-add-users" class="add-user-icon" />添加成员</el-button
|
||||
>
|
||||
<el-button type="primary" link @click="addMember">
|
||||
<AppIcon iconName="app-add-users" class="add-user-icon" />添加成员
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="mt-10">
|
||||
<el-input v-model="filterText" placeholder="请输入用户名搜索" suffix-icon="Search" />
|
||||
</div>
|
||||
<div class="member-list mt-10">
|
||||
<div class="member-list mt-10" v-loading="loading">
|
||||
<el-scrollbar>
|
||||
<ul>
|
||||
<template v-for="(item, index) in memberList" :key="index">
|
||||
<li class="active border-b-light flex-between p-15">
|
||||
<ul v-if="filterMember.length > 0">
|
||||
<template v-for="(item, index) in filterMember" :key="index">
|
||||
<li
|
||||
@click="clickMemberHandle(item.id)"
|
||||
:class="currentUser === item.id ? 'active' : ''"
|
||||
class="border-b-light flex-between p-15 cursor"
|
||||
>
|
||||
<div>
|
||||
<span>{{ item.username }}</span>
|
||||
<el-tag class="ml-10" effect="dark">所有者</el-tag>
|
||||
<span class="mr-10">{{ item.username }}</span>
|
||||
<el-tag effect="dark" v-if="isManage(item.type)">所有者</el-tag>
|
||||
<el-tag effect="dark" type="warning" v-else>用户</el-tag>
|
||||
</div>
|
||||
<el-dropdown trigger="click">
|
||||
<el-dropdown trigger="click" v-if="!isManage(item.type)">
|
||||
<span class="cursor">
|
||||
<el-icon><MoreFilled /></el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>移除</el-dropdown-item>
|
||||
<el-dropdown-item @click.stop="deleteMember(item.id)"
|
||||
>移除</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<el-empty description="暂无数据" v-else />
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -70,6 +78,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CreateMemberDialog ref="CreateMemberRef" @refresh="refresh" />
|
||||
</LayoutContent>
|
||||
</template>
|
||||
|
||||
|
|
@ -77,11 +86,16 @@
|
|||
import { onMounted, ref, watch, nextTick } from 'vue'
|
||||
import TeamApi from '@/api/team'
|
||||
import type { TeamMember } from '@/api/type/team'
|
||||
import CreateMemberDialog from './component/CreateMemberDialog.vue'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
|
||||
const CreateMemberRef = ref<InstanceType<typeof CreateMemberDialog>>()
|
||||
const loading = ref(false)
|
||||
const memberList = ref<TeamMember[]>([])
|
||||
|
||||
const memberList = ref<TeamMember[]>([]) // 全部成员
|
||||
const filterMember = ref<TeamMember[]>([]) // 搜索过滤后列表
|
||||
const currentUser = ref<String>('')
|
||||
const filterText = ref('')
|
||||
|
||||
const activeName = ref('dataset')
|
||||
const allChecked = ref(false)
|
||||
const tableHeight = ref(0)
|
||||
|
|
@ -125,12 +139,53 @@ const tableData = [
|
|||
}
|
||||
]
|
||||
|
||||
watch(filterText, (val) => {
|
||||
if (val) {
|
||||
filterMember.value = memberList.value.filter((v) => v.username.includes(val))
|
||||
} else {
|
||||
filterMember.value = memberList.value
|
||||
}
|
||||
})
|
||||
|
||||
function deleteMember(id: String) {
|
||||
loading.value = true
|
||||
TeamApi.delTeamMember(id)
|
||||
.then(() => {
|
||||
MsgSuccess('删除成功')
|
||||
getMember()
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function isManage(type: String) {
|
||||
return type === 'manage'
|
||||
}
|
||||
|
||||
function clickMemberHandle(id: String) {
|
||||
currentUser.value = id
|
||||
}
|
||||
function addMember() {
|
||||
CreateMemberRef.value?.open()
|
||||
}
|
||||
|
||||
function getMember() {
|
||||
loading.value = true
|
||||
TeamApi.getTeamMember().then((res) => {
|
||||
memberList.value = res.data
|
||||
loading.value = false
|
||||
})
|
||||
TeamApi.getTeamMember()
|
||||
.then((res) => {
|
||||
memberList.value = res.data
|
||||
filterMember.value = res.data
|
||||
currentUser.value = memberList.value[0].id
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
getMember()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue