新增组织管理内容

This commit is contained in:
LovableCat
2025-03-15 18:08:35 +08:00
parent 782b7537fe
commit a7b9c0ffad
8 changed files with 747 additions and 26 deletions

View File

@@ -0,0 +1,53 @@
import request from '@/utils/request';
// TODO 获取组织信息功能 没有相关接口,待对齐
export function getOrganizationInfoApi(params) {
return request({
url: ``,
method: 'get',
params,
});
}
export function createOrganizationApi(params) {
return request({
url: `/api/v1/user/group/create`,
method: 'post',
data: params,
});
}
// TODO 更新组织信息功能 没有相关接口,待对齐
export function updateOrganizationInfoApi(params) {
return request({
url: `/api/v1/user/group/create`,
method: 'post',
data: params,
});
}
export function getOrganizationMemberListApi(params) {
return request({
url: `/api/v1/user/group/member/list`,
method: 'get',
params,
});
}
export function inviteOrganizationMemberApi(params) {
return request({
url: `/api/v1/user/group/member/invite `,
method: 'post',
data: params,
});
}
export function deleteOrganizationMemberApi(params) {
return request({
url: `/api/v1/user/group/member/remove`,
method: 'delete',
data: params,
});
}

View File

@@ -0,0 +1,12 @@
export interface OrganizationInfoType {
organizationName: string;
organizationProfile: string;
}
export interface OrganizationMemberItemType {
id: string;
userName: string;
role: string;
email: string;
joinDate: string;
}

View File

@@ -118,12 +118,6 @@ div.devui-tooltip {
}
}
}
// modal
.devui-modal {
&__header {
height: 40px !important;
}
}
// textarea 文字计数颜色
.devui-textarea__show-count {
color: var(--color-lighter) !important;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -0,0 +1,26 @@
<svg width="128.000000" height="128.000000" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<desc>
Created with Pixso.
</desc>
<defs/>
<rect id="插图/暂无数据-浅色" width="128.000000" height="128.000000" fill="#E4E4E4" fill-opacity="0"/>
<path id="矩形备份" d="M22.75 41.78L109.88 41.78C110.69 41.78 111.34 42.43 111.34 43.23L111.34 108.48C111.34 111.06 109.24 113.15 106.66 113.15L26.9 113.15C24.61 113.15 22.75 111.3 22.75 109L22.75 41.78Z" fill="#F3F3F3" fill-opacity="1.000000" fill-rule="evenodd"/>
<path id="矩形备份" d="M109.88 41.78C110.69 41.78 111.34 42.43 111.34 43.23L111.34 108.48C111.34 111.06 109.24 113.15 106.66 113.15L26.9 113.15C24.61 113.15 22.75 111.3 22.75 109L22.75 41.78L109.88 41.78ZM109.88 42.42C110.33 42.42 110.7 42.78 110.7 43.23L110.7 108.48C110.7 110.71 108.89 112.51 106.66 112.51L26.9 112.51C24.96 112.51 23.39 110.94 23.39 109L23.39 42.42L109.88 42.42Z" fill="#818181" fill-opacity="0" fill-rule="evenodd"/>
<path id="矩形" d="M17.6 41.74L87.45 41.74L87.45 108.89C87.45 111.18 85.6 113.04 83.3 113.04L20.3 113.04C18.01 113.04 16.15 111.18 16.15 108.89L16.15 43.19C16.15 42.39 16.8 41.74 17.6 41.74Z" fill="#FFFFFF" fill-opacity="1.000000" fill-rule="evenodd"/>
<path id="矩形" d="M87.45 41.74L87.45 108.89C87.45 111.18 85.6 113.04 83.3 113.04L20.3 113.04C18.01 113.04 16.15 111.18 16.15 108.89L16.15 43.19C16.15 42.39 16.8 41.74 17.6 41.74L87.45 41.74ZM86.81 42.38L86.81 108.89C86.81 110.83 85.24 112.4 83.3 112.4L20.3 112.4C18.36 112.4 16.79 110.83 16.79 108.89L16.79 43.19C16.79 42.74 17.15 42.38 17.6 42.38L86.81 42.38Z" fill="#818181" fill-opacity="0" fill-rule="evenodd"/>
<path id="矩形" d="M87.45 41.74L16.84 41.74C16.13 41.74 15.46 42.1 15.08 42.7L4.01 60.24C3.57 60.94 4.08 61.83 4.9 61.83L71.03 61.23C72.33 61.22 73.56 60.6 74.33 59.54L87.45 41.74Z" fill="#FFFFFF" fill-opacity="1.000000" fill-rule="evenodd"/>
<path id="矩形" d="" fill="#818181" fill-opacity="0" fill-rule="evenodd"/>
<path id="矩形备份 4" d="M87.12 41.78L111.2 41.78C111.89 41.78 112.54 42.12 112.93 42.7L124.91 60.55C125.37 61.24 124.88 62.17 124.05 62.17L101.98 62.17C100.54 62.17 99.2 61.42 98.44 60.19L87.12 41.78Z" fill="#FFFFFF" fill-opacity="1.000000" fill-rule="evenodd"/>
<path id="矩形备份 4" d="" fill="#818181" fill-opacity="0" fill-rule="evenodd"/>
<path id="路径" d="M111.34 62.17L111.34 107.35C111.34 110.56 108.77 113.15 105.6 113.15L80.75 113.15L80.75 111.87L105.6 111.87C108.07 111.87 110.06 109.85 110.06 107.35L110.06 62.17L111.34 62.17Z" fill="#191919" fill-opacity="1.000000" fill-rule="evenodd"/>
<path id="路径" d="M87.76 41.14L87.76 107.37C87.76 110.56 85.21 113.15 82.07 113.15L21.43 113.15C18.29 113.15 15.74 110.56 15.74 107.37L15.79 61.85L17.05 61.85L17 107.37C17 109.85 18.99 111.87 21.43 111.87L82.07 111.87C84.51 111.87 86.49 109.85 86.49 107.37L86.49 42.42L18.14 42.42L18.14 41.14L87.76 41.14Z" fill="#191919" fill-opacity="1.000000" fill-rule="evenodd"/>
<path id="矩形" d="M17.4102 41.1436C16.5332 41.1436 15.7188 41.582 15.252 42.3037L3.66797 60.2617C3.29492 60.8408 3.47656 61.6035 4.07227 61.9648C4.27539 62.0879 4.50781 62.1533 4.74805 62.1533L70.9844 62.1533C72.7109 62.1533 74.3223 61.3037 75.2598 59.8955L87.7617 41.1436L17.4102 41.1436ZM85.4219 42.3799L17.4102 42.3799C16.9727 42.3799 16.5645 42.5986 16.332 42.96L4.74805 60.918L70.9844 60.918C72.2793 60.918 73.4883 60.2803 74.1914 59.2246L85.4219 42.3799Z" clip-rule="evenodd" fill="#191919" fill-opacity="1.000000" fill-rule="evenodd"/>
<path id="矩形备份 4" d="M111.24 41.1436C112.121 41.1436 112.941 41.5713 113.418 42.2803L125.5 60.25C125.887 60.8242 125.713 61.5889 125.111 61.958C124.902 62.0859 124.658 62.1533 124.412 62.1533L102.389 62.167C100.555 62.167 98.8574 61.2393 97.9277 59.7295L86.4863 41.1562L111.24 41.1436ZM88.9785 42.3584L111.297 42.3584C111.736 42.3584 112.146 42.5723 112.385 42.9258L124.469 60.8965L102.619 60.8965C101.244 60.8965 99.9707 60.2012 99.2734 59.0684L88.9785 42.3584Z" clip-rule="evenodd" fill="#191919" fill-opacity="1.000000" fill-rule="evenodd"/>
<path id="路径备份" d="M65.81 15.63L65.81 24.54C65.81 24.9 65.52 25.18 65.17 25.18C64.82 25.18 64.53 24.9 64.53 24.54L64.53 15.63C64.53 15.28 64.82 15 65.17 15C65.52 15 65.81 15.28 65.81 15.63Z" fill="#191919" fill-opacity="1.000000" fill-rule="evenodd"/>
<path id="路径备份" d="M89.07 20.27L84.62 27.99C84.44 28.3 84.05 28.4 83.75 28.22C83.44 28.05 83.34 27.66 83.51 27.35L87.97 19.64C88.15 19.33 88.53 19.23 88.84 19.4C89.14 19.58 89.25 19.97 89.07 20.27Z" fill="#191919" fill-opacity="1.000000" fill-rule="evenodd"/>
<path id="路径备份" d="M42.37 19.64L46.83 27.35C47 27.66 46.9 28.05 46.59 28.22C46.29 28.4 45.9 28.3 45.73 27.99L41.27 20.27C41.09 19.97 41.2 19.58 41.5 19.4C41.81 19.23 42.2 19.33 42.37 19.64Z" fill="#191919" fill-opacity="1.000000" fill-rule="evenodd"/>
<rect id="矩形" x="24.667969" y="86.392578" rx="1.281281" width="24.830080" height="6.366683" fill="#7693F5" fill-opacity="1.000000"/>
<rect id="矩形" x="24.988281" y="86.712891" rx="1.281281" width="24.189440" height="5.726042" stroke="#000000" stroke-opacity="0" stroke-width="0.640641"/>
<rect id="矩形备份 2" x="24.667969" y="96.588867" rx="0.640000" width="18.622562" height="1.911917" fill="#191919" fill-opacity="1.000000"/>
<rect id="矩形备份 3" x="24.667969" y="102.325195" rx="0.640000" width="9.311282" height="1.911917" fill="#191919" fill-opacity="1.000000"/>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -4,9 +4,8 @@
<img
v-if="!hideImg"
class="block mb-[20px]"
width="300"
height="240"
src="/src/assets/imgs/jyh/nodata.png"
:width="imgWidth"
:src="src"
alt="NoData"
/>
</slot>
@@ -17,16 +16,36 @@
</template>
<script setup lang="ts">
import { computed } from "vue";
const props = defineProps({
hideImg: {
type: Boolean,
default: false,
},
small: {
type: Boolean,
default: true,
},
imgSrc: {
type: String,
default: '',
},
width: {
type: String,
default: '',
},
});
const src = computed(() => {
if (props.imgSrc) {
return props.imgSrc;
}
return props.small ? '/src/assets/imgs/jyh/nodata_small.svg' : '/src/assets/imgs/jyh/nodata.svg';
});
const imgWidth = computed(() => {
if (props.width) {
return props.width;
}
return props.small ? '128px' : '300px';
});
</script>
<style scoped lang="scss">
.bg-white {
background-color: #fff;
}
</style>

View File

@@ -1,13 +1,117 @@
<template>
<div>
<div v-if="hasOrganization"></div>
<div v-if="hasOrganization">
<d-card class="mb-[16px] bg-white" shadow="never">
<div class="text-[14px] w-full flex items-center justify-between">
<div class="font-bold mb-[16px]">基础信息</div>
<div>
<d-icon
v-if="!isEdit"
class="font-normal"
name="edit"
operable
@click="editIconClick"
>
<template #suffix>
<span>编辑</span>
</template>
</d-icon>
</div>
</div>
<d-form
ref="baseInfoFormRef"
:data="createFormData"
message-type="none"
>
<d-form-item
field="organizationName"
label="组织名称"
>
<d-input v-if="isEdit" v-model="createFormData.organizationName" />
<div v-else>{{ organizationInfo.organizationName }}</div>
</d-form-item>
<d-form-item field="organizationProfile" label="组织简介">
<d-textarea
v-if="isEdit"
v-model="createFormData.organizationProfile"
show-count
maxlength="200"
/>
<div v-else>{{ organizationInfo.organizationProfile }}</div>
</d-form-item>
</d-form>
<div v-if="isEdit">
<d-button
variant="solid"
class="mr-[8px]"
@click="editOrganizationConfirm"
>完成</d-button>
<d-button @click="editOrganizationCancel">取消</d-button>
</div>
</d-card>
<d-card class="mb-[16px] bg-white" shadow="never">
<div class="font-bold text-[14px] mb-[12px]">成员管理</div>
<div class="flex mb-[12px]">
<d-button
icon="add"
variant="solid"
class="mr-[8px]"
@click="openInviteMemberModal"
>邀请成员</d-button>
<d-search
icon-position="left"
class="w-[296px]"
placeholder="请输入搜索内容"
/>
</div>
<d-table
v-if="memberTableData.length"
:data="memberTableData"
class="w-full mb-[20px]"
fix-header
header-bg
max-height="500px"
>
<d-column field="userName" header="成员名" />
<d-column field="role" header="角色">
<template #default="{ row }">
{{ memberRoleMap[row.role] ?? '' }}
</template>
</d-column>
<d-column field="email" header="邮箱" />
<d-column field="joinDate" header="加入时间" />
<d-column header="操作">
<template #default="{row}">
<d-button
variant="text"
color="primary"
@click="openDeleteModal(row)"
>删除</d-button>
</template>
</d-column>
</d-table>
<NoData v-else />
<d-pagination
class="flex justify-end"
:total="pager.total"
v-model:pageSize="pager.pageSize"
v-model:pageIndex="pager.pageIndex"
:can-view-total="true"
:can-change-page-size="true"
:max-items="5"
/>
</d-card>
</div>
<div v-else>
<NoData>
<NoData :small="false">
<div class="font-bold mb-[20px]">很抱歉当前无任何组织赶快去</div>
<d-button
size="lg"
variant="solid"
@click="createOrganizationBtnClick"
@click="openCreateOrganizationModal"
>新建组织</d-button>
</NoData>
</div>
@@ -46,29 +150,168 @@
class="mr-[8px]"
@click="confirmCreateOrganizationBtnClick"
>确定</d-button>
<d-button @click="createOrganizationModalClose">取消</d-button>
<d-button @click="closeOrganizationModal">取消</d-button>
</div>
</d-modal>
<d-modal
v-model="inviteMemberModalCtrl"
title="邀请成员"
class="w-[600px]"
>
<d-form
ref="inviteMemberFormRef"
layout="vertical"
:data="inviteMemberFormData"
message-type="none"
>
<d-form-item
field="userName"
label="成员"
required
>
<d-input v-model="inviteMemberFormData.userName" placeholder="请输入" />
</d-form-item>
<d-form-item field="role" label="角色" required>
<d-select v-model="inviteMemberFormData.role">
<d-option
v-for="item of inviteMemberSelectOptions"
:key="item.value"
:value="item.value"
:name="item.name"
/>
</d-select>
</d-form-item>
</d-form>
<div class="flex justify-center">
<d-button
variant="solid"
class="mr-[8px]"
@click="inviteMemberConfirmBtnClick"
>确定</d-button>
<d-button @click="closeInviteMemberModal">取消</d-button>
</div>
</d-modal>
<d-modal
v-model="deleteMemberModalCtrl"
title="删除成员"
class="w-[600px]"
>
<div>
即将删除成员{{ deleteRow.userName }}请确认是否继续
</div>
<div class="flex justify-center">
<d-button
variant="solid"
class="mr-[8px]"
@click="deleteMember"
>确定</d-button>
<d-button @click="closeDeleteMemberModal">取消</d-button>
</div>
</d-modal>
</template>
<script setup lang="ts">
import {reactive, ref} from "vue";
import {reactive, ref, shallowRef} from "vue";
import NoData from "@/components/NoData/NoData.vue";
import {
createOrganizationApi, deleteOrganizationMemberApi,
getOrganizationInfoApi, getOrganizationMemberListApi, inviteOrganizationMemberApi,
updateOrganizationInfoApi
} from "@/api/organizationManage/organizationManageApi";
import {
type OrganizationInfoType,
type OrganizationMemberItemType,
} from "@/api/organizationManage/organizationManageType";
import table from "@/components/AIRepair/Table.vue";
const searchOrganizationLoading = ref(false);
const hasOrganization = ref(false);
const organizationInfo = shallowRef<OrganizationInfoType>({
organizationName: '',
organizationProfile: '',
});
const createOrganizationModalCtrl = ref(false);
const createFormData = reactive({
organizationName: "",
organizationProfile: "", // 组织简介
organizationName: '',
organizationProfile: '', // 组织简介
});
const createFormRef = ref();
const createOrganizationLoading = ref(false);
const isEdit = ref(false);
const editLoading = ref(false);
const memberListSearchLoading = ref(false);
const memberListSearchParam = ref('');
const memberTableData = shallowRef<OrganizationMemberItemType[]>([]);
const pager = reactive({
pageSize: 10,
pageIndex: 1,
total: 0,
});
const deleteRow = shallowRef<OrganizationMemberItemType>(null);
const deleteMemberModalCtrl = ref(false);
const deleteMemberLoading = ref(false);
const inviteMemberFormRef = ref();
const inviteMemberModalCtrl = ref(false);
const inviteMemberLoading = ref(false);
const inviteMemberFormData = reactive({
userName: '',
role: '',
});
const inviteMemberSelectOptions = [
{ name: '创建者', value: 'creator' },
{ name: '成员', value: 'member' },
];
const memberRoleMap = {
creator: '创建者',
member: '成员',
};
const mockTableData = [
{ userName: '张三', role: 'member', email: '111@gmail.com', joinDate: '2090-02-30', id: '1' },
{ userName: '李四', role: 'creator', email: '111@gmail.com', joinDate: '2090-02-30', id: '2' },
{ userName: '王五', role: 'member', email: '111@gmail.com', joinDate: '2090-02-30', id: '3' },
{ userName: '赵六', role: 'member', email: '111@gmail.com', joinDate: '2090-02-30', id: '4' },
{ userName: '秦七', role: 'member', email: '111@gmail.com', joinDate: '2090-02-30', id: '5' },
];
// -------------------- 获取组织信息 --------------------
async function getOrganizationInfo() {
const params = {
organizationName: createFormData.organizationName,
organizationProfile: createFormData.organizationProfile,
};
searchOrganizationLoading.value = true;
try {
const res = await getOrganizationInfoApi(params);
} catch (e) {
}
searchOrganizationLoading.value = false;
getMemberList();
// TODO 待接口定义好后删除下面两行固定赋值语句
hasOrganization.value = true;
organizationInfo.value = { organizationName: createFormData.organizationName, organizationProfile: createFormData.organizationProfile };
}
// -------------------- 获取成员信息 --------------------
async function getOrganizationMember(organizationId: string) {
const params = {};
}
// -------------------- 创建组织 --------------------
function confirmCreateOrganizationBtnClick() {
createFormRef.value.validate(isValid => {
createFormRef.value.validate((isValid: boolean) => {
if (isValid) {
createOrganization();
createOrganization();
}
});
}
@@ -78,21 +321,146 @@ async function createOrganization() {
organizationName: createFormData.organizationName,
organizationProfile: createFormData.organizationProfile,
};
createOrganizationLoading.value = true;
try {
const res = await createOrganizationApi(params);
} catch (e) {
}
closeOrganizationModal();
// TODO 待接口定义好后删除下面调用逻辑
getOrganizationInfo();
createOrganizationLoading.value = false;
}
function createOrganizationBtnClick(): void {
function openCreateOrganizationModal() {
createOrganizationModalCtrl.value = true;
}
function createOrganizationModalClose(): void {
function closeOrganizationModal() {
createOrganizationModalCtrl.value = false;
}
// -------------------- 编辑组织信息 --------------------
function editIconClick() {
isEdit.value = true;
}
async function editOrganizationConfirm() {
editLoading.value = true;
const params = {
organizationName: createFormData.organizationName,
organizationProfile: createFormData.organizationProfile,
};
try {
const res = await updateOrganizationInfoApi(params);
} catch (e) {
}
editLoading.value = false;
isEdit.value = false;
// TODO 待接口定义好后删除下面两行固定赋值语句
hasOrganization.value = true;
organizationInfo.value = { organizationName: createFormData.organizationName, organizationProfile: createFormData.organizationProfile };
}
function editOrganizationCancel() {
isEdit.value = false;
createFormData.organizationName = organizationInfo.value.organizationName;
createFormData.organizationProfile = organizationInfo.value.organizationProfile;
}
// -------------------- 邀请、查询成员 --------------------
async function getMemberList() {
const params = {
organizationName: createFormData.organizationName,
pageIndex: pager.pageIndex,
pageSize: pager.pageSize,
searchParam: memberListSearchParam.value,
};
memberListSearchLoading.value = true;
try {
const res = await getOrganizationMemberListApi(params);
} catch (e) {
}
memberTableData.value = mockTableData;
pager.total = 6;
memberListSearchLoading.value = false;
}
function openInviteMemberModal() {
inviteMemberModalCtrl.value = true;
}
function openDeleteModal(row: any) {
deleteRow.value = row;
deleteMemberModalCtrl.value = true;
}
function inviteMemberConfirmBtnClick() {
inviteMemberFormRef.value.validate((isValid: boolean) => {
if (isValid) {
console.log('aaaaaaaa')
inviteMember();
}
});
}
async function inviteMember() {
const params = {
userName: inviteMemberFormData.userName,
role: inviteMemberFormData.role,
};
inviteMemberLoading.value = true;
try {
const res = await inviteOrganizationMemberApi(params);
} catch (e) {
}
inviteMemberLoading.value = false;
closeInviteMemberModal();
// TODO 待接口定义好后删除下面调用逻辑
mockTableData.unshift({
userName: inviteMemberFormData.userName,
role: inviteMemberFormData.role,
joinDate: new Date().toDateString(),
id: String(pager.total++),
email: `${inviteMemberFormData.userName}@gmail.com`,
});
memberTableData.value = [...mockTableData];
}
function closeInviteMemberModal() {
inviteMemberModalCtrl.value = false;
}
async function deleteMember() {
const params = {
id: deleteRow.value.id,
};
deleteMemberLoading.value = true;
try {
const res = await deleteOrganizationMemberApi(params);
} catch (e) {
}
deleteMemberLoading.value = false;
closeDeleteMemberModal();
// TODO 待接口定义好后删除下面调用逻辑
const index = mockTableData.findIndex(item => item.id === deleteRow.value.id);
mockTableData.splice(index, 1);
memberTableData.value = [...mockTableData];
pager.total--;
}
function closeDeleteMemberModal() {
deleteMemberModalCtrl.value = false;
}
</script>
<style scoped lang="scss">
:deep(.devui-table__row td) {
padding: 10px 16px;
}
</style>