chore(frontend): introduce prettier + eslint and prune unused code
Backend CI / Backend (push) Has been cancelled
Frontend CI / Frontend (push) Failing after 1m39s

- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports)
- Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier
- Add root scripts: format, format:check, lint, lint:fix
- Reformat 257 files across admin-web, ops-web, desktop-client, packages
- Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters
- Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex
- Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 20:39:09 +08:00
parent 21c7892ee4
commit 162abdc97c
258 changed files with 42261 additions and 37556 deletions
+46 -47
View File
@@ -1,97 +1,96 @@
import axios, { type AxiosError, type AxiosInstance } from "axios";
import axios, { type AxiosError, type AxiosInstance } from 'axios'
import { clearStoredSession, readStoredSession } from "@/lib/storage";
import { clearStoredSession, readStoredSession } from '@/lib/storage'
export class OpsApiError extends Error {
status?: number;
code: number;
detail?: string;
requestId?: string;
status?: number
code: number
detail?: string
requestId?: string
constructor(input: {
message: string;
code?: number;
status?: number;
detail?: string;
requestId?: string;
message: string
code?: number
status?: number
detail?: string
requestId?: string
}) {
super(input.message);
this.name = "OpsApiError";
this.code = input.code ?? 50000;
this.status = input.status;
this.detail = input.detail;
this.requestId = input.requestId;
super(input.message)
this.name = 'OpsApiError'
this.code = input.code ?? 50000
this.status = input.status
this.detail = input.detail
this.requestId = input.requestId
}
}
interface ApiEnvelope<T> {
code: number;
message: string;
detail?: string;
data: T;
request_id?: string;
code: number
message: string
detail?: string
data: T
request_id?: string
}
let onUnauthorized: (() => void) | null = null;
let onUnauthorized: (() => void) | null = null
export function bindUnauthorizedHandler(handler: () => void): void {
onUnauthorized = handler;
onUnauthorized = handler
}
const client: AxiosInstance = axios.create({
baseURL: "/api/ops",
baseURL: '/api/ops',
timeout: 30000,
});
})
client.interceptors.request.use((config) => {
const { session } = readStoredSession();
const { session } = readStoredSession()
if (session.accessToken) {
config.headers = config.headers ?? {};
config.headers["Authorization"] = `Bearer ${session.accessToken}`;
config.headers = config.headers ?? {}
config.headers['Authorization'] = `Bearer ${session.accessToken}`
}
return config;
});
return config
})
client.interceptors.response.use(
(response) => response,
(error: AxiosError<ApiEnvelope<unknown>>) => {
const status = error.response?.status;
const payload = error.response?.data;
const status = error.response?.status
const payload = error.response?.data
if (status === 401) {
clearStoredSession();
onUnauthorized?.();
clearStoredSession()
onUnauthorized?.()
}
return Promise.reject(
new OpsApiError({
message: payload?.message ?? error.message ?? "request_failed",
code: typeof payload?.code === "number" ? payload.code : undefined,
message: payload?.message ?? error.message ?? 'request_failed',
code: typeof payload?.code === 'number' ? payload.code : undefined,
status,
detail: typeof payload?.detail === "string" ? payload.detail : undefined,
requestId: typeof payload?.request_id === "string" ? payload.request_id : undefined,
detail: typeof payload?.detail === 'string' ? payload.detail : undefined,
requestId: typeof payload?.request_id === 'string' ? payload.request_id : undefined,
}),
);
)
},
);
)
async function unwrap<T>(promise: Promise<{ data: ApiEnvelope<T> }>): Promise<T> {
const { data } = await promise;
const { data } = await promise
if (data.code !== 0) {
throw new OpsApiError({
message: data.message,
code: data.code,
detail: data.detail,
requestId: data.request_id,
});
})
}
return data.data;
return data.data
}
export const http = {
get: <T>(url: string, params?: Record<string, unknown>) =>
unwrap<T>(client.get(url, { params })),
get: <T>(url: string, params?: Record<string, unknown>) => unwrap<T>(client.get(url, { params })),
post: <T, B = unknown>(url: string, body?: B) => unwrap<T>(client.post(url, body)),
patch: <T, B = unknown>(url: string, body?: B) => unwrap<T>(client.patch(url, body)),
delete: <T>(url: string) => unwrap<T>(client.delete(url)),
};
}
+49 -49
View File
@@ -1,120 +1,120 @@
const SESSION_KEY = "geo.ops-web.session";
const PREFERENCE_KEY = "geo.ops-web.login-preference";
const SESSION_KEY = 'geo.ops-web.session'
const PREFERENCE_KEY = 'geo.ops-web.login-preference'
export interface OperatorView {
id: number;
username: string;
display_name: string;
email: string | null;
role: string;
status: string;
id: number
username: string
display_name: string
email: string | null
role: string
status: string
}
export interface OpsSession {
accessToken: string | null;
expiresAt: number | null;
operator: OperatorView | null;
accessToken: string | null
expiresAt: number | null
operator: OperatorView | null
}
export interface LoginPreference {
remember: boolean;
lastUsername: string;
remember: boolean
lastUsername: string
}
export interface StoredSession {
session: OpsSession;
persistent: boolean;
session: OpsSession
persistent: boolean
}
function emptySession(): OpsSession {
return { accessToken: null, expiresAt: null, operator: null };
return { accessToken: null, expiresAt: null, operator: null }
}
function emptyPreference(): LoginPreference {
return { remember: false, lastUsername: "" };
return { remember: false, lastUsername: '' }
}
function hasWindow(): boolean {
return typeof window !== "undefined";
return typeof window !== 'undefined'
}
function readFrom(storage: Storage): OpsSession | null {
try {
const raw = storage.getItem(SESSION_KEY);
if (!raw) return null;
const parsed = JSON.parse(raw) as Partial<OpsSession>;
if (typeof parsed.accessToken !== "string" || !parsed.accessToken) return null;
const raw = storage.getItem(SESSION_KEY)
if (!raw) return null
const parsed = JSON.parse(raw) as Partial<OpsSession>
if (typeof parsed.accessToken !== 'string' || !parsed.accessToken) return null
return {
accessToken: parsed.accessToken,
expiresAt: typeof parsed.expiresAt === "number" ? parsed.expiresAt : null,
expiresAt: typeof parsed.expiresAt === 'number' ? parsed.expiresAt : null,
operator:
parsed.operator && typeof parsed.operator === "object"
parsed.operator && typeof parsed.operator === 'object'
? (parsed.operator as OperatorView)
: null,
};
}
} catch {
return null;
return null
}
}
export function readStoredSession(): StoredSession {
if (!hasWindow()) return { session: emptySession(), persistent: false };
const persistent = readFrom(window.localStorage);
if (persistent) return { session: persistent, persistent: true };
const ephemeral = readFrom(window.sessionStorage);
if (ephemeral) return { session: ephemeral, persistent: false };
return { session: emptySession(), persistent: false };
if (!hasWindow()) return { session: emptySession(), persistent: false }
const persistent = readFrom(window.localStorage)
if (persistent) return { session: persistent, persistent: true }
const ephemeral = readFrom(window.sessionStorage)
if (ephemeral) return { session: ephemeral, persistent: false }
return { session: emptySession(), persistent: false }
}
export function writeStoredSession(session: OpsSession, persistent: boolean): void {
if (!hasWindow()) return;
const target = persistent ? window.localStorage : window.sessionStorage;
const other = persistent ? window.sessionStorage : window.localStorage;
if (!hasWindow()) return
const target = persistent ? window.localStorage : window.sessionStorage
const other = persistent ? window.sessionStorage : window.localStorage
try {
target.setItem(SESSION_KEY, JSON.stringify(session));
target.setItem(SESSION_KEY, JSON.stringify(session))
} catch {
// ignore quota / serialization errors
}
try {
other.removeItem(SESSION_KEY);
other.removeItem(SESSION_KEY)
} catch {
// noop
}
}
export function clearStoredSession(): void {
if (!hasWindow()) return;
if (!hasWindow()) return
try {
window.localStorage.removeItem(SESSION_KEY);
window.localStorage.removeItem(SESSION_KEY)
} catch {
// noop
}
try {
window.sessionStorage.removeItem(SESSION_KEY);
window.sessionStorage.removeItem(SESSION_KEY)
} catch {
// noop
}
}
export function readLoginPreference(): LoginPreference {
if (!hasWindow()) return emptyPreference();
if (!hasWindow()) return emptyPreference()
try {
const raw = window.localStorage.getItem(PREFERENCE_KEY);
if (!raw) return emptyPreference();
const parsed = JSON.parse(raw) as Partial<LoginPreference>;
const raw = window.localStorage.getItem(PREFERENCE_KEY)
if (!raw) return emptyPreference()
const parsed = JSON.parse(raw) as Partial<LoginPreference>
return {
remember: parsed.remember === true,
lastUsername: typeof parsed.lastUsername === "string" ? parsed.lastUsername : "",
};
lastUsername: typeof parsed.lastUsername === 'string' ? parsed.lastUsername : '',
}
} catch {
return emptyPreference();
return emptyPreference()
}
}
export function writeLoginPreference(pref: LoginPreference): void {
if (!hasWindow()) return;
if (!hasWindow()) return
try {
window.localStorage.setItem(PREFERENCE_KEY, JSON.stringify(pref));
window.localStorage.setItem(PREFERENCE_KEY, JSON.stringify(pref))
} catch {
// noop
}