пофикшен вебпарт

This commit is contained in:
gorbenko.pavel
2026-08-18 11:54:59 +05:00
parent 5b2c062e81
commit 2251212177
9 changed files with 271 additions and 29 deletions
@@ -1,4 +1,5 @@
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { SPHttpClient } from '@microsoft/sp-http';
/**
* ОТЛАДОЧНАЯ подмена «текущего пользователя»: портал ведёт себя так, будто им
@@ -22,10 +23,12 @@ import { WebPartContext } from '@microsoft/sp-webpart-base';
*/
let impersonatedEmail = '';
let impersonatedDisplayName = '';
/** Вызывается веб-частью на каждый render — свойство могли поменять в панели. */
export function setImpersonatedEmail(email: string | undefined): void {
export function setImpersonatedIdentity(email: string | undefined, displayName?: string): void {
impersonatedEmail = (email || '').trim();
impersonatedDisplayName = (displayName || '').trim();
}
/** Заданная подмена ('' — подмены нет). Для предупреждающей полосы в интерфейсе. */
@@ -33,7 +36,89 @@ export function getImpersonatedEmail(): string {
return impersonatedEmail;
}
/** ФИО подменённого сотрудника, если его удалось найти поиском по имени ('' — для e-mail, введённого напрямую). */
export function getImpersonatedDisplayName(): string {
return impersonatedDisplayName;
}
/** Email, под которым портал работает: подменённый, если задан, иначе — из токена. */
export function effectiveUserEmail(context: WebPartContext): string {
return impersonatedEmail || context.pageContext.user.email || '';
}
export interface IResolvedDebugUser {
email: string;
displayName: string;
}
interface IPeopleSearchHit {
displayName: string;
email: string;
}
/** Сырой ответ ClientPeoplePickerSearchUser — value внутри двойного JSON. */
interface IPeoplePickerEntity {
DisplayText?: string;
Description?: string;
EntityData?: { Email?: string };
}
async function searchPeopleByText(context: WebPartContext, queryText: string): Promise<IPeopleSearchHit[]> {
const url = `${context.pageContext.web.absoluteUrl}/_api/SP.UI.ApplicationPages.ClientPeoplePickerWebServiceInterface.clientPeoplePickerSearchUser`;
const body = JSON.stringify({
queryParams: {
AllowEmailAddresses: true,
AllowMultipleEntities: false,
MaximumEntitySuggestions: 5,
PrincipalSource: 15, // All
PrincipalType: 1, // User
QueryString: queryText
}
});
const response = await context.spHttpClient.post(url, SPHttpClient.configurations.v1, {
headers: {
Accept: 'application/json;odata=nometadata',
'Content-Type': 'application/json;odata=nometadata'
},
body
});
if (!response.ok) return [];
const json: { value?: string } = await response.json();
if (!json.value) return [];
let entries: IPeoplePickerEntity[];
try {
entries = JSON.parse(json.value);
} catch {
return [];
}
return entries
.map((e) => ({ displayName: e.DisplayText || '', email: e.EntityData?.Email || e.Description || '' }))
.filter((e) => !!e.email);
}
/**
* Ищет сотрудника по имени для отладочной подмены (см. property pane —
* PropertyPaneDebugUser/DebugUserField). Порядок «Фамилия Имя» или «Имя
* Фамилия» не важен: если прямой запрос ничего не находит, пробуем с
* переставленными первыми двумя словами.
*/
export async function resolveDebugUser(context: WebPartContext, nameQuery: string): Promise<IResolvedDebugUser | undefined> {
const text = nameQuery.trim();
if (!text) return undefined;
let hits = await searchPeopleByText(context, text);
if (hits.length === 0) {
const tokens = text.split(/\s+/).filter(Boolean);
if (tokens.length >= 2) {
const swapped = [tokens[1], tokens[0], ...tokens.slice(2)].join(' ');
hits = await searchPeopleByText(context, swapped);
}
}
return hits[0];
}