feat: 团队成员
This commit is contained in:
parent
558213db40
commit
7187056e95
@ -1,5 +1,5 @@
|
|||||||
import { Result } from '@/request/Result'
|
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 { TeamMember, TeamMemberRequest } from '@/api/type/team'
|
||||||
// import type { Ref } from 'vue'
|
// import type { Ref } from 'vue'
|
||||||
|
|
||||||
@ -16,11 +16,20 @@ const getTeamMember: () => Promise<Result<TeamMember[]>> = () => {
|
|||||||
* 添加成员
|
* 添加成员
|
||||||
* @param 参数 { "username_or_email": "string" }
|
* @param 参数 { "username_or_email": "string" }
|
||||||
*/
|
*/
|
||||||
const postCreatTeamMember: (request: TeamMemberRequest) => Promise<Result<boolean>> = (request) => {
|
const postCreatTeamMember: (body: TeamMemberRequest) => Promise<Result<boolean>> = (body) => {
|
||||||
return post(`${prefix}`, request)
|
return post(`${prefix}`, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除成员
|
||||||
|
* @param 参数 member_id
|
||||||
|
*/
|
||||||
|
const delTeamMember: (member_id: String) => Promise<Result<boolean>> = (member_id) => {
|
||||||
|
return del(`${prefix}/${member_id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getTeamMember,
|
getTeamMember,
|
||||||
postCreatTeamMember
|
postCreatTeamMember,
|
||||||
|
delTeamMember
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import AppIcon from './icons/AppIcon.vue'
|
|||||||
import LoginLayout from './login-layout/index.vue'
|
import LoginLayout from './login-layout/index.vue'
|
||||||
import LoginContainer from './login-container/index.vue'
|
import LoginContainer from './login-container/index.vue'
|
||||||
import LayoutContent from './content-container/LayoutContent.vue'
|
import LayoutContent from './content-container/LayoutContent.vue'
|
||||||
|
import TagsInput from './tags-input/index.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
install(app: App) {
|
install(app: App) {
|
||||||
@ -10,5 +11,6 @@ export default {
|
|||||||
app.component(LoginLayout.name, LoginLayout)
|
app.component(LoginLayout.name, LoginLayout)
|
||||||
app.component(LoginContainer.name, LoginContainer)
|
app.component(LoginContainer.name, LoginContainer)
|
||||||
app.component(LayoutContent.name, LayoutContent)
|
app.component(LayoutContent.name, LayoutContent)
|
||||||
|
app.component(TagsInput.name, TagsInput)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
91
ui/src/components/tags-input/index.vue
Normal file
91
ui/src/components/tags-input/index.vue
Normal file
@ -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,
|
data?: unknown,
|
||||||
loading?: NProgress | Ref<boolean>
|
loading?: NProgress | Ref<boolean>
|
||||||
) => Promise<Result<any>> = (url, params, data, loading) => {
|
) => 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,
|
data?: unknown,
|
||||||
loading?: NProgress | Ref<boolean>
|
loading?: NProgress | Ref<boolean>
|
||||||
) => Promise<Result<any>> = (url, params, data, loading) => {
|
) => 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: (
|
export const exportExcel: (
|
||||||
|
|||||||
@ -14,6 +14,16 @@
|
|||||||
--el-form-inline-content-width: 100%;
|
--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 {
|
||||||
.el-drawer__header {
|
.el-drawer__header {
|
||||||
|
|||||||
102
ui/src/views/setting/component/CreateMemberDialog.vue
Normal file
102
ui/src/views/setting/component/CreateMemberDialog.vue
Normal file
@ -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">
|
<div class="team-member p-15 border-r">
|
||||||
<h3>团队成员</h3>
|
<h3>团队成员</h3>
|
||||||
<div class="align-right">
|
<div class="align-right">
|
||||||
<el-button type="primary" link
|
<el-button type="primary" link @click="addMember">
|
||||||
><AppIcon iconName="app-add-users" class="add-user-icon" />添加成员</el-button
|
<AppIcon iconName="app-add-users" class="add-user-icon" />添加成员
|
||||||
>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-10">
|
<div class="mt-10">
|
||||||
<el-input v-model="filterText" placeholder="请输入用户名搜索" suffix-icon="Search" />
|
<el-input v-model="filterText" placeholder="请输入用户名搜索" suffix-icon="Search" />
|
||||||
</div>
|
</div>
|
||||||
<div class="member-list mt-10">
|
<div class="member-list mt-10" v-loading="loading">
|
||||||
<el-scrollbar>
|
<el-scrollbar>
|
||||||
<ul>
|
<ul v-if="filterMember.length > 0">
|
||||||
<template v-for="(item, index) in memberList" :key="index">
|
<template v-for="(item, index) in filterMember" :key="index">
|
||||||
<li class="active border-b-light flex-between p-15">
|
<li
|
||||||
|
@click="clickMemberHandle(item.id)"
|
||||||
|
:class="currentUser === item.id ? 'active' : ''"
|
||||||
|
class="border-b-light flex-between p-15 cursor"
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<span>{{ item.username }}</span>
|
<span class="mr-10">{{ item.username }}</span>
|
||||||
<el-tag class="ml-10" effect="dark">所有者</el-tag>
|
<el-tag effect="dark" v-if="isManage(item.type)">所有者</el-tag>
|
||||||
|
<el-tag effect="dark" type="warning" v-else>用户</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<el-dropdown trigger="click">
|
<el-dropdown trigger="click" v-if="!isManage(item.type)">
|
||||||
<span class="cursor">
|
<span class="cursor">
|
||||||
<el-icon><MoreFilled /></el-icon>
|
<el-icon><MoreFilled /></el-icon>
|
||||||
</span>
|
</span>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item>移除</el-dropdown-item>
|
<el-dropdown-item @click.stop="deleteMember(item.id)"
|
||||||
|
>移除</el-dropdown-item
|
||||||
|
>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
|
<el-empty description="暂无数据" v-else />
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -70,6 +78,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<CreateMemberDialog ref="CreateMemberRef" @refresh="refresh" />
|
||||||
</LayoutContent>
|
</LayoutContent>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -77,11 +86,16 @@
|
|||||||
import { onMounted, ref, watch, nextTick } from 'vue'
|
import { onMounted, ref, watch, nextTick } from 'vue'
|
||||||
import TeamApi from '@/api/team'
|
import TeamApi from '@/api/team'
|
||||||
import type { TeamMember } from '@/api/type/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 loading = ref(false)
|
||||||
const memberList = ref<TeamMember[]>([])
|
const memberList = ref<TeamMember[]>([]) // 全部成员
|
||||||
|
const filterMember = ref<TeamMember[]>([]) // 搜索过滤后列表
|
||||||
|
const currentUser = ref<String>('')
|
||||||
const filterText = ref('')
|
const filterText = ref('')
|
||||||
|
|
||||||
const activeName = ref('dataset')
|
const activeName = ref('dataset')
|
||||||
const allChecked = ref(false)
|
const allChecked = ref(false)
|
||||||
const tableHeight = ref(0)
|
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() {
|
function getMember() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
TeamApi.getTeamMember().then((res) => {
|
TeamApi.getTeamMember()
|
||||||
memberList.value = res.data
|
.then((res) => {
|
||||||
loading.value = false
|
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(() => {
|
onMounted(() => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user