diff --git a/src/bk-user/bkuser/apis/web/data_source_organization/serializers.py b/src/bk-user/bkuser/apis/web/data_source_organization/serializers.py index bfa9bf323..bb0de684b 100644 --- a/src/bk-user/bkuser/apis/web/data_source_organization/serializers.py +++ b/src/bk-user/bkuser/apis/web/data_source_organization/serializers.py @@ -81,9 +81,10 @@ def validate_department_ids(self, department_ids): def validate_leader_ids(self, leader_ids): diff_leader_ids = set(leader_ids) - set( - DataSourceUser.objects.filter(id__in=leader_ids, data_source=self.context["data_source"]).values_list( - "id", flat=True - ) + DataSourceUser.objects.filter( + id__in=leader_ids, + data_source=self.context["data_source"], + ).values_list("id", flat=True) ) if diff_leader_ids: raise serializers.ValidationError(_("传递了错误的上级信息: {}").format(diff_leader_ids)) @@ -154,7 +155,7 @@ class UserUpdateInputSLZ(serializers.Serializer): email = serializers.CharField(help_text="邮箱") phone_country_code = serializers.CharField(help_text="手机国际区号") phone = serializers.CharField(help_text="手机号") - logo = serializers.CharField(help_text="用户 Logo", allow_blank=True) + logo = serializers.CharField(help_text="用户 Logo", allow_blank=True, required=False, default="") department_ids = serializers.ListField(help_text="部门ID列表", child=serializers.IntegerField()) leader_ids = serializers.ListField(help_text="上级ID列表", child=serializers.IntegerField()) @@ -175,9 +176,10 @@ def validate_department_ids(self, department_ids): def validate_leader_ids(self, leader_ids): diff_leader_ids = set(leader_ids) - set( - DataSourceUser.objects.filter(id__in=leader_ids, data_source=self.context["data_source"]).values_list( - "id", flat=True - ) + DataSourceUser.objects.filter( + id__in=leader_ids, + data_source=self.context["data_source"], + ).values_list("id", flat=True) ) if diff_leader_ids: raise serializers.ValidationError(_("传递了错误的上级信息: {}").format(diff_leader_ids)) diff --git a/src/bk-user/bkuser/apis/web/data_source_organization/urls.py b/src/bk-user/bkuser/apis/web/data_source_organization/urls.py index 9db730e2e..bec3f1c94 100644 --- a/src/bk-user/bkuser/apis/web/data_source_organization/urls.py +++ b/src/bk-user/bkuser/apis/web/data_source_organization/urls.py @@ -16,8 +16,8 @@ # 数据源用户 path("/users/", views.DataSourceUserListCreateApi.as_view(), name="data_source_user.list_create"), # 数据源用户 Leader - path("/leaders/", views.DataSourceLeadersListApi.as_view(), name="data_source_leaders.list"), + path("/leaders/", views.DataSourceLeadersListApi.as_view(), name="data_source_leader.list"), # 数据源部门 - path("/departments/", views.DataSourceDepartmentsListApi.as_view(), name="data_source_departments.list"), + path("/departments/", views.DataSourceDepartmentsListApi.as_view(), name="data_source_department.list"), path("users//", views.DataSourceUserRetrieveUpdateApi.as_view(), name="data_source_user.retrieve_update"), ] diff --git a/src/bk-user/bkuser/apis/web/tenant/serializers.py b/src/bk-user/bkuser/apis/web/tenant/serializers.py index 98231846c..bc84047cb 100644 --- a/src/bk-user/bkuser/apis/web/tenant/serializers.py +++ b/src/bk-user/bkuser/apis/web/tenant/serializers.py @@ -100,7 +100,9 @@ def get_data_sources(self, obj: Tenant) -> List[Dict]: class TenantUpdateInputSLZ(serializers.Serializer): name = serializers.CharField(help_text="租户名称") - logo = serializers.CharField(help_text="租户 Logo", required=False, default=settings.DEFAULT_TENANT_LOGO) + logo = serializers.CharField( + help_text="租户 Logo", required=False, allow_blank=True, default=settings.DEFAULT_TENANT_LOGO + ) manager_ids = serializers.ListField(child=serializers.CharField(), help_text="租户用户 ID 列表", allow_empty=False) feature_flags = TenantFeatureFlagSLZ(help_text="租户特性集") diff --git a/src/bk-user/bkuser/biz/tenant.py b/src/bk-user/bkuser/biz/tenant.py index b7ba4e0e0..26fdf78b7 100644 --- a/src/bk-user/bkuser/biz/tenant.py +++ b/src/bk-user/bkuser/biz/tenant.py @@ -12,6 +12,7 @@ from typing import Dict, List, Optional from django.db import transaction +from django.utils.timezone import now from pydantic import BaseModel from bkuser.apps.data_source.models import ( @@ -308,7 +309,7 @@ def update_with_managers(tenant_id: str, tenant_info: TenantEditableBaseInfo, ma with transaction.atomic(): # 更新基本信息 - Tenant.objects.filter(id=tenant_id).update(**tenant_info.model_dump()) + Tenant.objects.filter(id=tenant_id).update(updated_at=now(), **tenant_info.model_dump()) if should_deleted_manager_ids: TenantManager.objects.filter( diff --git a/src/pages/src/css/index.css b/src/pages/src/css/index.css index 693136fe0..528fc7795 100644 --- a/src/pages/src/css/index.css +++ b/src/pages/src/css/index.css @@ -52,6 +52,7 @@ .span-logo { display: inline-block; width: 16px; + margin-right: 8px; font-size: 12px; font-weight: 700; line-height: 16px; @@ -60,5 +61,4 @@ background-color: #C4C6CC; border-radius: 4px; flex-shrink: 0; - margin-right: 8px; } diff --git a/src/pages/src/hooks/use-validate.ts b/src/pages/src/hooks/use-validate.ts index 0cc5af549..728c9cbcb 100644 --- a/src/pages/src/hooks/use-validate.ts +++ b/src/pages/src/hooks/use-validate.ts @@ -18,7 +18,7 @@ export default () => { }; const userName = { - validator: (value: string) => /^([a-zA-Z])([a-zA-Z0-9._-]){0,31}$/.test(value), + validator: (value: string) => /^([a-zA-Z0-9])([a-zA-Z0-9._-]){0,31}$/.test(value), message: '由1-32位字母、数字、下划线(_)、点(.)、减号(-)字符组成,以字母或数字开头', trigger: 'blur', }; diff --git a/src/pages/src/hooks/useMenuInfo.ts b/src/pages/src/hooks/useMenuInfo.ts index 7f182f4a5..1a47df0ee 100644 --- a/src/pages/src/hooks/useMenuInfo.ts +++ b/src/pages/src/hooks/useMenuInfo.ts @@ -1,9 +1,10 @@ -import { computed } from 'vue'; +import { computed, inject } from 'vue'; import { RouteLocationNormalizedLoaded, RouteRecordRaw, useRoute, useRouter } from 'vue-router'; export const useMenuInfo = () => { const route = useRoute(); const router = useRouter(); + const editLeaveBefore = inject('editLeaveBefore'); // 获取 menu 相关配置 const { children } = route.matched[0]; @@ -29,6 +30,18 @@ export const useMenuInfo = () => { router.push({ name: key }); }; + + router.beforeEach(async (to, from, next) => { + let enableLeave = true; + if (window.changeInput) { + enableLeave = await editLeaveBefore(); + } + if (!enableLeave) { + return Promise.resolve(enableLeave); + } + next(); + }); + return { activeKey, openedKeys, diff --git a/src/pages/src/http/dataSourceFiles.ts b/src/pages/src/http/dataSourceFiles.ts index a9c42dd71..9f81ef569 100644 --- a/src/pages/src/http/dataSourceFiles.ts +++ b/src/pages/src/http/dataSourceFiles.ts @@ -1,20 +1,36 @@ import http from './fetch'; import type { DataSourceUsersResult, - NewDataSourceUsersParams, + NewDataSourceUserParams, + PutDataSourceUserParams, } from './types/dataSourceFiles'; /** * 数据源用户信息列表 */ -export const getDataSourceUsers = (id: string): Promise => http.get(`/api/v1/web/data-sources/${id}/users/`); +export const getDataSourceUsers = (id: string, username: string): Promise => http.get(`/api/v1/web/data-sources/${id}/users/?username=${username}`); /** * 新建数据源用户 */ -export const newDataSourceUsers = (params: NewDataSourceUsersParams) => http.post(`/api/v1/web/data-sources/${params.id}/users/`); +export const newDataSourceUser = (params: NewDataSourceUserParams) => http.post(`/api/v1/web/data-sources/${params.id}/users/`, params); /** * 数据源创建用户-下拉部门列表 */ -export const getDataSourceDepartments = (id: string) => http.get(`/api/v1/web/data-sources/${id}/departments/`); +export const getDataSourceDepartments = (id: string, name: string) => http.get(`/api/v1/web/data-sources/${id}/departments/?name=${name}`); + +/** + * 数据源创建用户-下拉上级列表 + */ +export const getDataSourceLeaders = (id: string, keyword: string) => http.get(`/api/v1/web/data-sources/${id}/leaders/?keyword=${keyword}`); + +/** + * 数据源用户详情 + */ +export const getDataSourceUserDetails = (id: string) => http.get(`/api/v1/web/data-sources/user/${id}/`); + +/** + * 更新数据源用户 + */ +export const putDataSourceUserDetails = (params: PutDataSourceUserParams) => http.put(`/api/v1/web/data-sources/user/${params.id}/`, params); diff --git a/src/pages/src/http/organizationFiles.ts b/src/pages/src/http/organizationFiles.ts index 021f39c6e..6dbc8e9a3 100644 --- a/src/pages/src/http/organizationFiles.ts +++ b/src/pages/src/http/organizationFiles.ts @@ -1,6 +1,7 @@ import http from './fetch'; import type { DepartmentsListParams, + TenantListParams, UpdateTenantParams, } from './types/organizationFiles'; @@ -39,3 +40,11 @@ export const getTenantDepartmentsList = (params: DepartmentsListParams) => { const { id, keyword, page, pageSize, recursive } = params; return http.get(`/api/v1/web/tenant-organization/departments/${id}/users/?keyword=${keyword}&page=${page}&page_size=${pageSize}&recursive=${recursive}`); }; + +/** + * 租户下用户列表 + */ +export const getTenantUsersList = (params: TenantListParams) => { + const { id, keyword, page, pageSize } = params; + return http.get(`/api/v1/web/tenant-organization/tenants/${id}/users/?keyword=${keyword}&page=${page}&page_size=${pageSize}`); +}; diff --git a/src/pages/src/http/types/dataSourceFiles.ts b/src/pages/src/http/types/dataSourceFiles.ts index bf2c28935..1690affac 100644 --- a/src/pages/src/http/types/dataSourceFiles.ts +++ b/src/pages/src/http/types/dataSourceFiles.ts @@ -16,7 +16,7 @@ export interface DataSourceUsersResult { /** * 新建数据源用户参数 */ -export interface NewDataSourceUsersParams { +export interface NewDataSourceUserParams { id: string, username: string, full_name: string, @@ -27,3 +27,17 @@ export interface NewDataSourceUsersParams { department_ids?: [], leader_ids?: [], } + +/** + * 更新数据源用户参数 + */ +export interface PutDataSourceUserParams { + id: string, + full_name: string, + email: string, + phone_country_code: string, + phone: string, + logo?: string, + department_ids?: [], + leader_ids?: [], +} diff --git a/src/pages/src/http/types/organizationFiles.ts b/src/pages/src/http/types/organizationFiles.ts index 97d03f61b..9a23b2aab 100644 --- a/src/pages/src/http/types/organizationFiles.ts +++ b/src/pages/src/http/types/organizationFiles.ts @@ -20,3 +20,13 @@ export interface DepartmentsListParams { pageSize: number, recursive: boolean, } + +/** + * 租户下用户列表参数 + */ +export interface TenantListParams { + id: string, + keyword: string, + page: number, + pageSize: number, +} diff --git a/src/pages/src/router/index.ts b/src/pages/src/router/index.ts index b3509e7ce..7addebe55 100644 --- a/src/pages/src/router/index.ts +++ b/src/pages/src/router/index.ts @@ -41,8 +41,8 @@ export default createRouter({ ], }, { - path: '/datasource', - name: 'datasource', + path: '/data-source', + name: 'dataSource', redirect: { name: 'local', }, @@ -55,8 +55,9 @@ export default createRouter({ path: '', name: '', meta: { - routeParentName: 'datasource', + routeParentName: 'data-source', navName: '数据源管理', + activeMenu: 'local', }, component: () => import('@/views/data-source/LocalCompany.vue'), children: [ @@ -64,8 +65,9 @@ export default createRouter({ path: 'local', name: 'local', meta: { - routeParentName: 'datasource', + routeParentName: 'dataSource', navName: '数据源管理', + activeMenu: 'local', }, component: () => import('@/views/data-source/LocalDataSource.vue'), }, @@ -73,19 +75,21 @@ export default createRouter({ path: 'other', name: 'other', meta: { - routeParentName: 'datasource', + routeParentName: 'dataSource', navName: '数据源管理', + activeMenu: 'local', }, component: () => import('@/views/data-source/OtherDataSource.vue'), }, ], }, { - path: 'local-details/:name/:type', + path: 'local-details/:name/:type/:id', name: 'dataConfDetails', meta: { - routeParentName: 'datasource', + routeParentName: 'dataSource', navName: '数据源详情', + activeMenu: 'local', }, component: () => import('@/views/data-source/local-details/index.vue'), }, @@ -93,8 +97,9 @@ export default createRouter({ path: 'new-local/:type', name: 'newLocal', meta: { - routeParentName: 'datasource', + routeParentName: 'dataSource', navName: '新建数据源', + activeMenu: 'local', }, component: () => import('@/views/data-source/new-data/NewLocalData.vue'), }, diff --git a/src/pages/src/views/Header.vue b/src/pages/src/views/Header.vue index 0f65b75ac..cad968171 100644 --- a/src/pages/src/views/Header.vue +++ b/src/pages/src/views/Header.vue @@ -109,7 +109,7 @@ const headerNav = reactive([ }, { name: '数据源管理', - path: 'datasource', + path: 'dataSource', }, { name: '租户管理', diff --git a/src/pages/src/views/data-source/LocalDataSource.vue b/src/pages/src/views/data-source/LocalDataSource.vue index 05388ac91..2b02a37f4 100644 --- a/src/pages/src/views/data-source/LocalDataSource.vue +++ b/src/pages/src/views/data-source/LocalDataSource.vue @@ -104,6 +104,7 @@ const dropdownList = ref([ const tableData = [ { + id: 2, name: '联通子公司正式员工', type: 'local', status: 'normal', @@ -111,6 +112,7 @@ const tableData = [ modified_at: '2022-04-30 22:35:49', }, { + id: 7, name: '企业内部', type: 'local', status: 'disabled', @@ -125,6 +127,7 @@ function handleClick(item) { params: { name: item.name, type: item.type, + id: item.id, }, }); } @@ -178,6 +181,7 @@ function newDataSource(item) { } .account-status-icon { + display: inline-block; width: 16px; height: 16px; margin-right: 5px; diff --git a/src/pages/src/views/data-source/local-details/EditUser.vue b/src/pages/src/views/data-source/local-details/EditUser.vue index 6b9aa19ee..f125254cd 100644 --- a/src/pages/src/views/data-source/local-details/EditUser.vue +++ b/src/pages/src/views/data-source/local-details/EditUser.vue @@ -16,6 +16,8 @@ - +
@@ -56,24 +59,30 @@ + multiple + :input-search="false" + :remote-method="searchDepartments" + @change="handleChange"> + filterable + multiple + :input-search="false" + :remote-method="searchLeaders" + @change="handleChange"> + v-for="item in state.leaders" + :key="Number(item.id)" + :value="Number(item.id)" + :label="item.username" /> @@ -121,7 +130,7 @@ 提交 - + 取消 @@ -129,9 +138,10 @@ diff --git a/src/pages/src/views/data-source/local-details/UserInfo.vue b/src/pages/src/views/data-source/local-details/UserInfo.vue index b15b49207..b47ccee19 100644 --- a/src/pages/src/views/data-source/local-details/UserInfo.vue +++ b/src/pages/src/views/data-source/local-details/UserInfo.vue @@ -1,5 +1,5 @@ - + diff --git a/src/pages/src/views/organization/details/UserInfo.vue b/src/pages/src/views/organization/details/UserInfo.vue index 45ae8c9fc..6b00bcf1a 100644 --- a/src/pages/src/views/organization/details/UserInfo.vue +++ b/src/pages/src/views/organization/details/UserInfo.vue @@ -1,7 +1,10 @@ @@ -93,13 +95,17 @@ const props = defineProps({ .details-content-value { display: flex; width: calc(100% - 100px); - overflow: hidden; font-size: 14px; color: #313238; - text-overflow: ellipsis; - white-space: nowrap; - vertical-align: top; flex-wrap: wrap; + + span { + display: inline-block; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } } } diff --git a/src/pages/src/views/organization/index.vue b/src/pages/src/views/organization/index.vue index 074ff9d3c..683834266 100644 --- a/src/pages/src/views/organization/index.vue +++ b/src/pages/src/views/organization/index.vue @@ -11,6 +11,7 @@ :node-content-action="['selected', 'expand', 'click', 'collapse']" :selected="selectedId" @node-click="changeNode" + @node-expand="changeNode" >