build(frontend): Element Plus 2.3.1 -> 2.14.5, sass 1.58 -> 1.103, icons 1.x -> 2.3.2

Element Plus с 2.8.5 требует sass >= 1.79, поэтому обновление идёт классом:
element-plus + sass + @element-plus/icons-vue, отдельным коммитом от Vue и
typechecker — UI-регрессию так проще локализовать.

Обновление внесло единственный новый класс ошибок типов — тот самый
DefaultRow/PeerVo, который аудит ожидал увидеть сразу: до 2.14 слоты таблицы
типизировались слабее и ошибки не давали. 10 ошибок в семи колонках списка
пиров.

el-table обобщён по типу строки, но el-table-column — отдельный компонент, и
тип из :data родительской таблицы в его слот не попадает: scope всегда
{ row: DefaultRow }. Аннотировать слот нельзя — DefaultRow не сужается до PeerVo
контравариантно. Поэтому переход нужен, и вопрос только в том, где он стоит:
здесь он ровно один (peerRow), назван и объяснён, вместо семи `as any` в
шаблоне, каждый из которых глушил бы и остальное выражение.

PeerVo переведён с interface на псевдоним типа: неявную индексную сигнатуру
TypeScript даёт литеральным типам, но не интерфейсам, поэтому раньше PeerVo и
DefaultRow не были совместимы ни в одну сторону и переход требовал `as unknown
as` — утверждения, которое компилятор не проверяет вообще.

typecheck: 0, eslint: 0, build: OK.
This commit is contained in:
2026-08-30 07:09:38 +05:00
parent 02ea33520d
commit d2dea2debf
7 changed files with 362 additions and 127 deletions
@@ -50,8 +50,8 @@ import type {
type DeepRequired<T> = T extends (infer U)[]
? DeepRequired<U>[]
: T extends object
? { [K in keyof T]-?: DeepRequired<NonNullable<T[K]>> }
: T;
? { [K in keyof T]-?: DeepRequired<NonNullable<T[K]>> }
: T;
/** Конфиг Hysteria в том виде, в котором его показывает панель. */
export type Hysteria2ServerConfigView = DeepRequired<Hysteria2ServerConfig>;
@@ -218,9 +218,7 @@ export const defaultHysteria2ServerConfigView: Hysteria2ServerConfigView = {
};
function isPlainObject(value: unknown): value is Record<string, unknown> {
return (
typeof value === "object" && value !== null && !Array.isArray(value)
);
return typeof value === "object" && value !== null && !Array.isArray(value);
}
/**
-1
View File
@@ -48,4 +48,3 @@ export function exportHysteria2ConfigApi(): AxiosPromise {
responseType: "blob",
});
}
+23 -2
View File
@@ -24,7 +24,28 @@ export interface PeerSaveDto {
remark: string;
}
export interface PeerVo extends IdDto {
/**
* Строка списка пиров.
*
* Объявлена через `type`, а не `interface`, и это не стилистика.
*
* `el-table-column` типизирует scope своего default-слота как
* `{ row: DefaultRow, ... }`, где `DefaultRow = Record<PropertyKey, any>`:
* колонка — отдельный компонент, и тип строки из `:data` родительской таблицы
* в неё не попадает. Значит, где-то на пути от слота к обработчику нужен ровно
* один переход `DefaultRow -> PeerVo` (см. `peerRow` в списке пиров).
*
* TypeScript даёт неявную индексную сигнатуру псевдонимам типов, но НЕ
* интерфейсам и не пересечениям с ними. Пока `PeerVo` был `interface`, он не
* был совместим с `DefaultRow` ни в одну сторону, и этот переход требовал
* двойного приведения через `unknown` — то есть утверждения, которое компилятор
* вообще не проверяет. С псевдонимом типа переход остаётся одним обычным
* приведением между связанными типами.
*/
export type PeerVo = {
// `id` объявлен здесь, а не унаследован от `IdDto`: пересечение с интерфейсом
// неявную индексную сигнатуру не получает.
id: number;
name: string;
remark: string;
authId: string;
@@ -39,7 +60,7 @@ export interface PeerVo extends IdDto {
createTime: string;
online: boolean;
onlineDevices: number;
}
};
export interface PeerClientConfigVo {
url: string;