From fbb989c3fbae8dbc16d06257bed2f194e6d0a344 Mon Sep 17 00:00:00 2001 From: skycurtain Date: Wed, 7 Oct 2026 14:36:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E5=89=8D=E7=AB=AF=E9=80=BB?= =?UTF-8?q?=E8=BE=91=E9=AA=A8=E6=9E=B6=E2=80=94=E2=80=94http/router/stores?= =?UTF-8?q?/queries=20=E4=B8=8E=E8=96=84=E5=A3=B3=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 1 + apps/web/package.json | 6 +- apps/web/src/App.vue | 168 -------------------- apps/web/src/api/admin/audit.ts | 13 ++ apps/web/src/api/admin/engine.ts | 12 ++ apps/web/src/api/admin/keys.ts | 29 ++++ apps/web/src/api/admin/logs.ts | 11 ++ apps/web/src/api/admin/members.ts | 28 ++++ apps/web/src/api/admin/overview.ts | 38 +++++ apps/web/src/api/admin/payments.ts | 26 ++++ apps/web/src/api/admin/session.ts | 41 +++++ apps/web/src/api/auth-redirect.ts | 24 +++ apps/web/src/api/http.ts | 104 +++++++++++++ apps/web/src/api/member/me.ts | 55 +++++++ apps/web/src/api/query-keys.ts | 44 ++++++ apps/web/src/app.vue | 7 + apps/web/src/layouts/admin-layout.vue | 91 +++++++++++ apps/web/src/layouts/member-layout.vue | 39 +++++ apps/web/src/main.ts | 18 ++- apps/web/src/queries/admin.ts | 169 +++++++++++++++++++++ apps/web/src/queries/member.ts | 29 ++++ apps/web/src/queries/session.ts | 73 +++++++++ apps/web/src/router/admin.ts | 56 +++++++ apps/web/src/router/index.ts | 59 +++++++ apps/web/src/router/member.ts | 24 +++ apps/web/src/stores/admin-session.ts | 22 +++ apps/web/src/stores/member-token.ts | 48 ++++++ apps/web/src/utils/format.ts | 23 +++ apps/web/src/utils/time.ts | 47 ++++++ apps/web/src/views/admin/audit-view.vue | 10 ++ apps/web/src/views/admin/keys-view.vue | 10 ++ apps/web/src/views/admin/login-view.vue | 89 +++++++++++ apps/web/src/views/admin/members-view.vue | 10 ++ apps/web/src/views/admin/overview-view.vue | 10 ++ apps/web/src/views/admin/payments-view.vue | 10 ++ apps/web/src/views/admin/settings-view.vue | 10 ++ apps/web/src/views/member/home-view.vue | 10 ++ apps/web/src/views/member/login-view.vue | 58 +++++++ bun.lock | 84 +++++++++- 39 files changed, 1431 insertions(+), 175 deletions(-) delete mode 100644 apps/web/src/App.vue create mode 100644 apps/web/src/api/admin/audit.ts create mode 100644 apps/web/src/api/admin/engine.ts create mode 100644 apps/web/src/api/admin/keys.ts create mode 100644 apps/web/src/api/admin/logs.ts create mode 100644 apps/web/src/api/admin/members.ts create mode 100644 apps/web/src/api/admin/overview.ts create mode 100644 apps/web/src/api/admin/payments.ts create mode 100644 apps/web/src/api/admin/session.ts create mode 100644 apps/web/src/api/auth-redirect.ts create mode 100644 apps/web/src/api/http.ts create mode 100644 apps/web/src/api/member/me.ts create mode 100644 apps/web/src/api/query-keys.ts create mode 100644 apps/web/src/app.vue create mode 100644 apps/web/src/layouts/admin-layout.vue create mode 100644 apps/web/src/layouts/member-layout.vue create mode 100644 apps/web/src/queries/admin.ts create mode 100644 apps/web/src/queries/member.ts create mode 100644 apps/web/src/queries/session.ts create mode 100644 apps/web/src/router/admin.ts create mode 100644 apps/web/src/router/index.ts create mode 100644 apps/web/src/router/member.ts create mode 100644 apps/web/src/stores/admin-session.ts create mode 100644 apps/web/src/stores/member-token.ts create mode 100644 apps/web/src/utils/format.ts create mode 100644 apps/web/src/utils/time.ts create mode 100644 apps/web/src/views/admin/audit-view.vue create mode 100644 apps/web/src/views/admin/keys-view.vue create mode 100644 apps/web/src/views/admin/login-view.vue create mode 100644 apps/web/src/views/admin/members-view.vue create mode 100644 apps/web/src/views/admin/overview-view.vue create mode 100644 apps/web/src/views/admin/payments-view.vue create mode 100644 apps/web/src/views/admin/settings-view.vue create mode 100644 apps/web/src/views/member/home-view.vue create mode 100644 apps/web/src/views/member/login-view.vue diff --git a/AGENTS.md b/AGENTS.md index f4999b2..c8df551 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,6 +6,7 @@ - **Git**:仅当项目作者明确说"提交 / commit"时才执行提交;`git push` 永远由项目作者手工执行,AI 绝不推送 - **命名**:需求 / 设计 / 交互 / 技术 / 计划等**文档名**用中文(`README` / `AGENTS` / `LICENSE` 等约定俗成除外)、**目录**用英文;新文档按性质放入 `docs/design/`(我们定的方案与决策)或 `docs/references/`(平台可用接口参考——实证事实,不做设计决策),并同步更新 README 的「文档地图」;`docs/deprecated/` 只归档已废弃文档——**勿引用、勿作依据**(本分支暂无此目录,需要时再建) +- **代码文件命名**:所有**目录名与文件名**一律 kebab-case(如 `time-range-picker.vue` · `admin-session.ts`);**组件在模板中引用时用大驼峰**(``)——文件名 kebab、模板标签 PascalCase - **文档状态**:头部表格标「状态」(草案 → 讨论中 → 待审阅 → 定稿(日期) → 已实现);设计文档定稿后不搬家、只改状态;被取代时删除(git 历史即考古) - **部署**:交付物必须保证「一条命令链」可部署;**双形态并行已裁决**——完整前后端(运营方服务器)先行,Cloudflare Workers 抽取版(作者自部署)后续(见 `docs/design/需求清单.md` R13) - **不引入版本叙事**:不使用 v1/v2/v3 之类版本编号,不以「MVP」等标签或指称显式描述任何历史阶段;文档内容必须**自立**——不得引用、描述任何当前分支之外、可能被删除的分支内容 diff --git a/apps/web/package.json b/apps/web/package.json index 59fcb02..a1815f4 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -17,7 +17,11 @@ }, "dependencies": { "@new-api-key-usage/core": "workspace:*", - "antdv-next": "^1.6.0", + "@tanstack/vue-query": "^5.104.1", + "@vueuse/core": "15.0.0", + "antdv-next": "1.6.0", + "axios": "^1.20.0", + "dayjs": "1.11.23", "pinia": "^4.0.3", "vue": "^3.5.42", "vue-router": "^5.3.1" diff --git a/apps/web/src/App.vue b/apps/web/src/App.vue deleted file mode 100644 index f669321..0000000 --- a/apps/web/src/App.vue +++ /dev/null @@ -1,168 +0,0 @@ - - - diff --git a/apps/web/src/api/admin/audit.ts b/apps/web/src/api/admin/audit.ts new file mode 100644 index 0000000..20c18a8 --- /dev/null +++ b/apps/web/src/api/admin/audit.ts @@ -0,0 +1,13 @@ +import type { AuditLogItem, Paginated } from "@new-api-key-usage/core"; +import { adminHttp } from "../http.ts"; + +export async function fetchAuditLogs(params: { + page: number; + page_size: number; + domain?: string; +}): Promise> { + const { data } = await adminHttp.get>("/audit-logs", { + params, + }); + return data; +} diff --git a/apps/web/src/api/admin/engine.ts b/apps/web/src/api/admin/engine.ts new file mode 100644 index 0000000..2d898c9 --- /dev/null +++ b/apps/web/src/api/admin/engine.ts @@ -0,0 +1,12 @@ +import type { EngineStatus, QpuLatest } from "@new-api-key-usage/core"; +import { adminHttp } from "../http.ts"; + +export async function fetchEngine(): Promise { + const { data } = await adminHttp.get("/engine"); + return data; +} + +export async function fetchQpuLatest(): Promise { + const { data } = await adminHttp.get("/qpu/latest"); + return data; +} diff --git a/apps/web/src/api/admin/keys.ts b/apps/web/src/api/admin/keys.ts new file mode 100644 index 0000000..225f091 --- /dev/null +++ b/apps/web/src/api/admin/keys.ts @@ -0,0 +1,29 @@ +import type { KeyItem } from "@new-api-key-usage/core"; +import { adminHttp } from "../http.ts"; + +export type KeyListFilters = { + member_id?: number | "unassigned"; + status?: "active" | "disabled"; + recent_failed?: boolean; +}; + +export async function fetchKeys(filters: KeyListFilters = {}): Promise { + const { data } = await adminHttp.get("/keys", { params: filters }); + return data; +} + +export async function registerKey(body: { + sk_key: string; + member_id?: number | null; +}): Promise { + const { data } = await adminHttp.post("/keys", body); + return data; +} + +export async function patchKey( + id: number, + body: { status?: "active" | "disabled"; member_id?: number | null }, +): Promise { + const { data } = await adminHttp.patch(`/keys/${id}`, body); + return data; +} diff --git a/apps/web/src/api/admin/logs.ts b/apps/web/src/api/admin/logs.ts new file mode 100644 index 0000000..1ea9843 --- /dev/null +++ b/apps/web/src/api/admin/logs.ts @@ -0,0 +1,11 @@ +import type { LogItem, Paginated } from "@new-api-key-usage/core"; +import { adminHttp } from "../http.ts"; + +export async function fetchLogs( + range: { from: string; to: string; page: number; page_size: number }, +): Promise> { + const { data } = await adminHttp.get>("/logs", { + params: range, + }); + return data; +} diff --git a/apps/web/src/api/admin/members.ts b/apps/web/src/api/admin/members.ts new file mode 100644 index 0000000..96aadf0 --- /dev/null +++ b/apps/web/src/api/admin/members.ts @@ -0,0 +1,28 @@ +import type { MemberItem } from "@new-api-key-usage/core"; +import { adminHttp } from "../http.ts"; + +export async function fetchMembers(): Promise { + const { data } = await adminHttp.get("/members"); + return data; +} + +export async function createMember(body: { + name: string; + remark?: string; +}): Promise { + const { data } = await adminHttp.post("/members", body); + return data; +} + +export async function patchMember( + id: number, + body: { status: "active" | "disabled" }, +): Promise { + const { data } = await adminHttp.patch(`/members/${id}`, body); + return data; +} + +export async function issueMemberToken(id: number): Promise<{ token: string }> { + const { data } = await adminHttp.post<{ token: string }>(`/members/${id}/token`); + return data; +} diff --git a/apps/web/src/api/admin/overview.ts b/apps/web/src/api/admin/overview.ts new file mode 100644 index 0000000..3059516 --- /dev/null +++ b/apps/web/src/api/admin/overview.ts @@ -0,0 +1,38 @@ +import type { UsageTotals, UnixSec } from "@new-api-key-usage/core"; +import { adminHttp } from "../http.ts"; + +export type AdminOverview = { + data_cutoff: UnixSec | null; + unassigned_subscription: UsageTotals; + by_source: Array<{ + billing_source: string; + count: number; + quota: number; + quota_usd: string; + }>; + by_member: Array<{ + member_id: number | null; + member_name: string | null; + key_count: number; + data_cutoff: UnixSec | null; + usage: UsageTotals; + }>; + by_key: Array<{ + key_id: number; + token_name: string; + member_id: number | null; + status: string; + data_cutoff: UnixSec | null; + usage: UsageTotals; + }>; +}; + +export async function fetchOverview(range: { + from: string; + to: string; +}): Promise { + const { data } = await adminHttp.get("/overview", { + params: range, + }); + return data; +} diff --git a/apps/web/src/api/admin/payments.ts b/apps/web/src/api/admin/payments.ts new file mode 100644 index 0000000..513cd95 --- /dev/null +++ b/apps/web/src/api/admin/payments.ts @@ -0,0 +1,26 @@ +import type { PaymentItem } from "@new-api-key-usage/core"; +import { adminHttp } from "../http.ts"; + +export type PaymentBody = Record; + +export async function fetchPayments(): Promise { + const { data } = await adminHttp.get("/payments"); + return data; +} + +export async function createPayment(body: PaymentBody): Promise { + const { data } = await adminHttp.post("/payments", body); + return data; +} + +export async function updatePayment( + id: number, + body: PaymentBody, +): Promise { + const { data } = await adminHttp.put(`/payments/${id}`, body); + return data; +} + +export async function deletePayment(id: number): Promise { + await adminHttp.delete(`/payments/${id}`); +} diff --git a/apps/web/src/api/admin/session.ts b/apps/web/src/api/admin/session.ts new file mode 100644 index 0000000..ab7de62 --- /dev/null +++ b/apps/web/src/api/admin/session.ts @@ -0,0 +1,41 @@ +import { adminHttp } from "../http.ts"; + +export type SetupState = { initialized: boolean }; +export type UsernameBody = { username: string }; + +export async function fetchSetupState(): Promise { + const { data } = await adminHttp.get("/setup-state"); + return data; +} + +export async function setup(body: { + username: string; + password: string; +}): Promise { + const { data } = await adminHttp.post("/setup", body); + return data; +} + +export async function login(body: { + username: string; + password: string; +}): Promise { + const { data } = await adminHttp.post("/session", body); + return data; +} + +export async function fetchSession(): Promise { + const { data } = await adminHttp.get("/session"); + return data; +} + +export async function logout(): Promise { + await adminHttp.delete("/session"); +} + +export async function changePassword(body: { + old_password: string; + new_password: string; +}): Promise { + await adminHttp.put("/password", body); +} diff --git a/apps/web/src/api/auth-redirect.ts b/apps/web/src/api/auth-redirect.ts new file mode 100644 index 0000000..a960ccb --- /dev/null +++ b/apps/web/src/api/auth-redirect.ts @@ -0,0 +1,24 @@ +/** + * 401 分流出口:由 main.ts 接到 router,避免 http ↔ router 循环依赖。 + * 管理面任意 401 → /admin/login;成员视图任意 401 → /member/login(契约公共约定 §2)。 + */ +export type AuthWorld = "admin" | "member"; + +type AuthRedirectHandler = (world: AuthWorld, reason: "expired" | "unauthorized") => void; + +const fallback: AuthRedirectHandler = (world) => { + window.location.href = world === "admin" ? "/admin/login" : "/member/login"; +}; + +let handler: AuthRedirectHandler = fallback; + +export function setAuthRedirect(fn: AuthRedirectHandler): void { + handler = fn; +} + +export function redirectAuth( + world: AuthWorld, + reason: "expired" | "unauthorized" = "unauthorized", +): void { + handler(world, reason); +} diff --git a/apps/web/src/api/http.ts b/apps/web/src/api/http.ts new file mode 100644 index 0000000..6b0f778 --- /dev/null +++ b/apps/web/src/api/http.ts @@ -0,0 +1,104 @@ +import axios, { + AxiosError, + type AxiosInstance, + type InternalAxiosRequestConfig, +} from "axios"; +import type { ErrorEnvelope } from "@new-api-key-usage/core"; +import { redirectAuth, type AuthWorld } from "./auth-redirect.ts"; + +const MEMBER_TOKEN_KEY = "member_token"; + +export class ApiError extends Error { + readonly status: number; + readonly code: string; + readonly fields: Record | undefined; + readonly keyId: number | undefined; + + constructor( + status: number, + code: string, + message: string, + extra?: { fields?: Record; key_id?: number }, + ) { + super(message); + this.name = "ApiError"; + this.status = status; + this.code = code; + this.fields = extra?.fields; + this.keyId = extra?.key_id; + } +} + +function parseErrorEnvelope(data: unknown): ErrorEnvelope | null { + if (!data || typeof data !== "object") return null; + const env = data as Partial; + if (!env.error || typeof env.error !== "object") return null; + const { code, message } = env.error; + if (typeof code !== "string" || typeof message !== "string") return null; + return env as ErrorEnvelope; +} + +function toApiError(error: AxiosError, world: AuthWorld): ApiError { + const envelope = parseErrorEnvelope(error.response?.data); + if (envelope) { + return new ApiError( + error.response?.status ?? 0, + envelope.error.code, + envelope.error.message, + { + fields: envelope.error.fields, + key_id: envelope.error.key_id, + }, + ); + } + const status = error.response?.status ?? 0; + if (status === 401) { + return new ApiError(401, "unauthorized", "凭证无效或已过期"); + } + if (status === 0 || !error.response) { + return new ApiError(0, "network", "网络异常,请稍后重试"); + } + // 中间人非 JSON 错误体(代理 502 页等)回退通用文案 + return new ApiError(status, "internal", "服务暂时不可用,请稍后重试"); +} + +function attachInterceptors(client: AxiosInstance, world: AuthWorld): void { + if (world === "member") { + client.interceptors.request.use((config: InternalAxiosRequestConfig) => { + const token = sessionStorage.getItem(MEMBER_TOKEN_KEY); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }); + } + + client.interceptors.response.use( + (response) => response, + (error: AxiosError) => { + const apiError = toApiError(error, world); + if (apiError.status === 401) { + redirectAuth(world, "expired"); + } + return Promise.reject(apiError); + }, + ); +} + +function createClient(world: AuthWorld, baseURL: string): AxiosInstance { + const client = axios.create({ + baseURL, + timeout: 30_000, + // 管理面会话 cookie 需跨端口(Vite 5173 → API 18456)携带 + withCredentials: true, + headers: { Accept: "application/json" }, + }); + attachInterceptors(client, world); + return client; +} + +/** 管理面:`/api/admin/*`,会话 cookie */ +export const adminHttp = createClient("admin", "/api/admin"); + +/** 成员视图:`/api/member/*`,Bearer 令牌 */ +export const memberHttp = createClient("member", "/api/member"); diff --git a/apps/web/src/api/member/me.ts b/apps/web/src/api/member/me.ts new file mode 100644 index 0000000..7622647 --- /dev/null +++ b/apps/web/src/api/member/me.ts @@ -0,0 +1,55 @@ +import type { + LogItem, + Paginated, + PaymentItem, + UnixSec, + UsageTotals, +} from "@new-api-key-usage/core"; +import { memberHttp } from "../http.ts"; + +export type MemberMe = { + id: number; + name: string; +}; + +export type MemberOverview = { + member: { id: number | null; name: string | null }; + usage: UsageTotals; + by_source: Array<{ + billing_source: string; + count: number; + quota: number; + quota_usd: string; + }>; + keys: Array<{ + key_id: number; + token_name: string; + log_synced_at: UnixSec | null; + usage: UsageTotals; + }>; + payments: PaymentItem[]; +}; + +export async function fetchMe(): Promise { + const { data } = await memberHttp.get("/me"); + return data; +} + +export async function fetchOverview(range: { + from: string; + to: string; +}): Promise { + const { data } = await memberHttp.get("/overview", { + params: range, + }); + return data; +} + +export async function fetchLogs( + range: { from: string; to: string; page: number; page_size: number }, +): Promise> { + const { data } = await memberHttp.get>("/logs", { + params: range, + }); + return data; +} diff --git a/apps/web/src/api/query-keys.ts b/apps/web/src/api/query-keys.ts new file mode 100644 index 0000000..6c4e4f8 --- /dev/null +++ b/apps/web/src/api/query-keys.ts @@ -0,0 +1,44 @@ +/** + * vue-query key 工厂——失效与缓存的唯一命名源。 + * 约定:`[面, 端点, …参数]`,参数保持可 JSON 序列化。 + */ +export type KeyFilters = { + member_id?: number | "unassigned"; + status?: "active" | "disabled"; + recent_failed?: boolean; +}; + +export type PageQuery = { + page: number; + page_size: number; +}; + +export type TimeRange = { + from: string; + to: string; +}; + +export const qk = { + admin: { + setupState: () => ["admin", "setup-state"] as const, + session: () => ["admin", "session"] as const, + engine: () => ["admin", "engine"] as const, + overview: (range: TimeRange) => + ["admin", "overview", range.from, range.to] as const, + keys: (filters: KeyFilters) => ["admin", "keys", filters] as const, + logs: (range: TimeRange & PageQuery) => + ["admin", "logs", range.from, range.to, range.page, range.page_size] as const, + payments: () => ["admin", "payments"] as const, + qpuLatest: () => ["admin", "qpu-latest"] as const, + members: () => ["admin", "members"] as const, + auditLogs: (q: PageQuery & { domain?: string }) => + ["admin", "audit-logs", q.page, q.page_size, q.domain ?? ""] as const, + }, + member: { + me: () => ["member", "me"] as const, + overview: (range: TimeRange) => + ["member", "overview", range.from, range.to] as const, + logs: (range: TimeRange & PageQuery) => + ["member", "logs", range.from, range.to, range.page, range.page_size] as const, + }, +}; diff --git a/apps/web/src/app.vue b/apps/web/src/app.vue new file mode 100644 index 0000000..746f174 --- /dev/null +++ b/apps/web/src/app.vue @@ -0,0 +1,7 @@ + + + diff --git a/apps/web/src/layouts/admin-layout.vue b/apps/web/src/layouts/admin-layout.vue new file mode 100644 index 0000000..43dd0a5 --- /dev/null +++ b/apps/web/src/layouts/admin-layout.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/apps/web/src/layouts/member-layout.vue b/apps/web/src/layouts/member-layout.vue new file mode 100644 index 0000000..8cb042a --- /dev/null +++ b/apps/web/src/layouts/member-layout.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts index df572e4..203fc2c 100644 --- a/apps/web/src/main.ts +++ b/apps/web/src/main.ts @@ -1,9 +1,25 @@ import { createApp } from "vue"; +import { createPinia } from "pinia"; +import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query"; import Antd from "antdv-next"; import "antdv-next/dist/reset.css"; -import App from "./App.vue"; +import App from "./app.vue"; +import { bindAuthRedirectToRouter, router } from "./router/index.ts"; import "./assets/main.css"; +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: 1, + staleTime: 30_000, + }, + }, +}); + const app = createApp(App); +app.use(createPinia()); +bindAuthRedirectToRouter(); +app.use(router); +app.use(VueQueryPlugin, { queryClient }); app.use(Antd); app.mount("#app"); diff --git a/apps/web/src/queries/admin.ts b/apps/web/src/queries/admin.ts new file mode 100644 index 0000000..05c0e33 --- /dev/null +++ b/apps/web/src/queries/admin.ts @@ -0,0 +1,169 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query"; +import { + fetchKeys, + patchKey, + registerKey, + type KeyListFilters, +} from "@/api/admin/keys.ts"; +import { fetchOverview } from "@/api/admin/overview.ts"; +import { fetchEngine } from "@/api/admin/engine.ts"; +import { + createPayment, + deletePayment, + fetchPayments, + updatePayment, +} from "@/api/admin/payments.ts"; +import { + createMember, + fetchMembers, + issueMemberToken, + patchMember, +} from "@/api/admin/members.ts"; +import { fetchAuditLogs } from "@/api/admin/audit.ts"; +import { fetchLogs } from "@/api/admin/logs.ts"; +import { qk, type PageQuery, type TimeRange } from "@/api/query-keys.ts"; + +export function useAdminOverview(range: () => TimeRange) { + return useQuery({ + queryKey: () => qk.admin.overview(range()), + queryFn: () => fetchOverview(range()), + }); +} + +export function useEngine() { + return useQuery({ + queryKey: qk.admin.engine(), + queryFn: fetchEngine, + refetchInterval: 60_000, + }); +} + +export function useKeys(filters: () => KeyListFilters) { + return useQuery({ + queryKey: () => qk.admin.keys(filters()), + queryFn: () => fetchKeys(filters()), + }); +} + +export function useRegisterKey() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: registerKey, + onSuccess: () => { + void qc.invalidateQueries({ queryKey: ["admin", "keys"] }); + }, + }); +} + +export function usePatchKey() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: ({ + id, + body, + }: { + id: number; + body: { status?: "active" | "disabled"; member_id?: number | null }; + }) => patchKey(id, body), + onSuccess: () => { + void qc.invalidateQueries({ queryKey: ["admin", "keys"] }); + }, + }); +} + +export function useAdminLogs(params: () => TimeRange & PageQuery) { + return useQuery({ + queryKey: () => qk.admin.logs(params()), + queryFn: () => fetchLogs(params()), + }); +} + +export function usePayments() { + return useQuery({ + queryKey: qk.admin.payments(), + queryFn: fetchPayments, + }); +} + +export function useCreatePayment() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: createPayment, + onSuccess: () => { + void qc.invalidateQueries({ queryKey: qk.admin.payments() }); + }, + }); +} + +export function useUpdatePayment() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: ({ id, body }: { id: number; body: Record }) => + updatePayment(id, body), + onSuccess: () => { + void qc.invalidateQueries({ queryKey: qk.admin.payments() }); + }, + }); +} + +export function useDeletePayment() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: deletePayment, + onSuccess: () => { + void qc.invalidateQueries({ queryKey: qk.admin.payments() }); + }, + }); +} + +export function useMembers() { + return useQuery({ + queryKey: qk.admin.members(), + queryFn: fetchMembers, + }); +} + +export function useCreateMember() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: createMember, + onSuccess: () => { + void qc.invalidateQueries({ queryKey: qk.admin.members() }); + }, + }); +} + +export function usePatchMember() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: ({ + id, + body, + }: { + id: number; + body: { status: "active" | "disabled" }; + }) => patchMember(id, body), + onSuccess: () => { + void qc.invalidateQueries({ queryKey: qk.admin.members() }); + }, + }); +} + +export function useIssueMemberToken() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: issueMemberToken, + onSuccess: () => { + void qc.invalidateQueries({ queryKey: qk.admin.members() }); + }, + }); +} + +export function useAuditLogs( + params: () => PageQuery & { domain?: string }, +) { + return useQuery({ + queryKey: () => qk.admin.auditLogs(params()), + queryFn: () => fetchAuditLogs(params()), + }); +} diff --git a/apps/web/src/queries/member.ts b/apps/web/src/queries/member.ts new file mode 100644 index 0000000..b593d62 --- /dev/null +++ b/apps/web/src/queries/member.ts @@ -0,0 +1,29 @@ +import { useQuery } from "@tanstack/vue-query"; +import { + fetchLogs, + fetchMe, + fetchOverview, +} from "@/api/member/me.ts"; +import { qk, type PageQuery, type TimeRange } from "@/api/query-keys.ts"; + +export function useMemberMe() { + return useQuery({ + queryKey: qk.member.me(), + queryFn: fetchMe, + retry: false, + }); +} + +export function useMemberOverview(range: () => TimeRange) { + return useQuery({ + queryKey: () => qk.member.overview(range()), + queryFn: () => fetchOverview(range()), + }); +} + +export function useMemberLogs(params: () => TimeRange & PageQuery) { + return useQuery({ + queryKey: () => qk.member.logs(params()), + queryFn: () => fetchLogs(params()), + }); +} diff --git a/apps/web/src/queries/session.ts b/apps/web/src/queries/session.ts new file mode 100644 index 0000000..af743de --- /dev/null +++ b/apps/web/src/queries/session.ts @@ -0,0 +1,73 @@ +import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query"; +import { + changePassword, + fetchSession, + fetchSetupState, + login, + logout, + setup, +} from "@/api/admin/session.ts"; +import { qk } from "@/api/query-keys.ts"; +import { useAdminSessionStore } from "@/stores/admin-session.ts"; + +export function useSetupState() { + return useQuery({ + queryKey: qk.admin.setupState(), + queryFn: fetchSetupState, + staleTime: 60_000, + }); +} + +export function useSession() { + const store = useAdminSessionStore(); + return useQuery({ + queryKey: qk.admin.session(), + queryFn: async () => { + const data = await fetchSession(); + store.signIn(data.username); + return data; + }, + staleTime: 60_000, + retry: false, + }); +} + +export function useLogin() { + const store = useAdminSessionStore(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: login, + onSuccess: (data) => { + store.signIn(data.username); + void qc.invalidateQueries({ queryKey: qk.admin.session() }); + }, + }); +} + +export function useSetup() { + const store = useAdminSessionStore(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: setup, + onSuccess: (data) => { + store.signIn(data.username); + void qc.invalidateQueries({ queryKey: qk.admin.setupState() }); + }, + }); +} + +export function useLogout() { + const store = useAdminSessionStore(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: logout, + onSuccess: () => { + store.signOut(); + qc.clear(); + }, + }); +} + +export function useChangePassword() { + return useMutation({ mutationFn: changePassword }); +} diff --git a/apps/web/src/router/admin.ts b/apps/web/src/router/admin.ts new file mode 100644 index 0000000..e541796 --- /dev/null +++ b/apps/web/src/router/admin.ts @@ -0,0 +1,56 @@ +import type { RouteRecordRaw } from "vue-router"; +import AdminLayout from "@/layouts/admin-layout.vue"; + +/** + * 管理面路由 `/admin/*`(契约:页面路由永不进入 `/api`)。 + * 侧边栏六项 + 账号设置(交互共性 §2)。 + */ +export const adminRoutes: RouteRecordRaw[] = [ + { + path: "/admin/login", + name: "admin-login", + component: () => import("@/views/admin/login-view.vue"), + meta: { public: true, world: "admin" }, + }, + { + path: "/admin", + component: AdminLayout, + meta: { requiresAdmin: true, world: "admin" }, + children: [ + { + path: "", + redirect: { name: "admin-overview" }, + }, + { + path: "overview", + name: "admin-overview", + component: () => import("@/views/admin/overview-view.vue"), + }, + { + path: "keys", + name: "admin-keys", + component: () => import("@/views/admin/keys-view.vue"), + }, + { + path: "payments", + name: "admin-payments", + component: () => import("@/views/admin/payments-view.vue"), + }, + { + path: "members", + name: "admin-members", + component: () => import("@/views/admin/members-view.vue"), + }, + { + path: "audit", + name: "admin-audit", + component: () => import("@/views/admin/audit-view.vue"), + }, + { + path: "settings", + name: "admin-settings", + component: () => import("@/views/admin/settings-view.vue"), + }, + ], + }, +]; diff --git a/apps/web/src/router/index.ts b/apps/web/src/router/index.ts new file mode 100644 index 0000000..e51f499 --- /dev/null +++ b/apps/web/src/router/index.ts @@ -0,0 +1,59 @@ +import { createRouter, createWebHistory, type Router } from "vue-router"; +import { adminRoutes } from "./admin.ts"; +import { memberRoutes } from "./member.ts"; +import { setAuthRedirect } from "@/api/auth-redirect.ts"; +import { useAdminSessionStore } from "@/stores/admin-session.ts"; +import { useMemberTokenStore } from "@/stores/member-token.ts"; + +declare module "vue-router" { + interface RouteMeta { + public?: boolean; + requiresAdmin?: boolean; + requiresMember?: boolean; + world?: "admin" | "member"; + } +} + +export const router: Router = createRouter({ + history: createWebHistory(), + routes: [ + { path: "/", redirect: "/admin/overview" }, + ...adminRoutes, + ...memberRoutes, + { path: "/:pathMatch(.*)*", redirect: "/admin/overview" }, + ], +}); + +router.beforeEach((to) => { + if (to.meta.public) return true; + + const memberToken = useMemberTokenStore(); + + // 管理面:硬刷新后 Pinia 为空但会话 cookie 仍在——交给 GET /session 与 401 分流裁决 + if (to.meta.requiresMember && !memberToken.hasToken) { + return { name: "member-login" }; + } + + return true; +}); + +/** 401 分流接入 router(由 main.ts 调用一次) */ +export function bindAuthRedirectToRouter(): void { + setAuthRedirect((world, reason) => { + const adminSession = useAdminSessionStore(); + const memberToken = useMemberTokenStore(); + if (world === "admin") { + adminSession.signOut(); + void router.push({ + name: "admin-login", + query: reason === "expired" ? { reason: "expired" } : {}, + }); + } else { + memberToken.clearToken(); + void router.push({ + name: "member-login", + query: reason === "expired" ? { reason: "expired" } : {}, + }); + } + }); +} diff --git a/apps/web/src/router/member.ts b/apps/web/src/router/member.ts new file mode 100644 index 0000000..ea04fda --- /dev/null +++ b/apps/web/src/router/member.ts @@ -0,0 +1,24 @@ +import type { RouteRecordRaw } from "vue-router"; +import MemberLayout from "@/layouts/member-layout.vue"; + +/** 成员视图 `/member/*`:登录壳 → 单页(交互共性 §1) */ +export const memberRoutes: RouteRecordRaw[] = [ + { + path: "/member/login", + name: "member-login", + component: () => import("@/views/member/login-view.vue"), + meta: { public: true, world: "member" }, + }, + { + path: "/member", + component: MemberLayout, + meta: { requiresMember: true, world: "member" }, + children: [ + { + path: "", + name: "member-home", + component: () => import("@/views/member/home-view.vue"), + }, + ], + }, +]; diff --git a/apps/web/src/stores/admin-session.ts b/apps/web/src/stores/admin-session.ts new file mode 100644 index 0000000..e41e910 --- /dev/null +++ b/apps/web/src/stores/admin-session.ts @@ -0,0 +1,22 @@ +import { defineStore } from "pinia"; + +/** + * 管理面登录态(客户端镜像)。 + * 服务端事实以 GET /api/admin/session 为准——刷新后由 queries/session 恢复。 + */ +export const useAdminSessionStore = defineStore("admin-session", { + state: () => ({ + username: "" as string, + }), + getters: { + isLoggedIn: (s) => s.username.length > 0, + }, + actions: { + signIn(username: string) { + this.username = username; + }, + signOut() { + this.username = ""; + }, + }, +}); diff --git a/apps/web/src/stores/member-token.ts b/apps/web/src/stores/member-token.ts new file mode 100644 index 0000000..3c3bfe3 --- /dev/null +++ b/apps/web/src/stores/member-token.ts @@ -0,0 +1,48 @@ +import { defineStore } from "pinia"; + +const STORAGE_KEY = "member_token"; + +function readToken(): string { + try { + return sessionStorage.getItem(STORAGE_KEY) ?? ""; + } catch { + return ""; + } +} + +function writeToken(token: string): void { + try { + if (token) sessionStorage.setItem(STORAGE_KEY, token); + else sessionStorage.removeItem(STORAGE_KEY); + } catch { + // 隐私模式等场景忽略 + } +} + +/** + * 成员明文令牌——仅存在于登录壳输入与 sessionStorage(交互登录篇 §4)。 + * 无登出端点:关标签即失效;作者重发令牌后旧令牌下一请求自然 401。 + */ +export const useMemberTokenStore = defineStore("member-token", { + state: () => ({ + token: readToken(), + memberName: "", + }), + getters: { + hasToken: (s) => s.token.length > 0, + }, + actions: { + setToken(token: string) { + this.token = token; + writeToken(token); + }, + setMemberName(name: string) { + this.memberName = name; + }, + clearToken() { + this.token = ""; + this.memberName = ""; + writeToken(""); + }, + }, +}); diff --git a/apps/web/src/utils/format.ts b/apps/web/src/utils/format.ts new file mode 100644 index 0000000..4c0f792 --- /dev/null +++ b/apps/web/src/utils/format.ts @@ -0,0 +1,23 @@ +/** 呈现层格式化(不做业务换算——换算责任在后端,契约公共约定 §5) */ + +/** 千分位整数;口径内精确显示,不四舍五入 */ +export function formatInt(value: number | null | undefined): string { + if (value == null) return "—"; + return value.toLocaleString("en-US"); +} + +/** quota 原值 + 美元双单位展示片段 */ +export function formatQuotaDual( + quota: number | null | undefined, + quotaUsd: string | null | undefined, +): string { + if (quota == null) return "—"; + const usd = quotaUsd ?? "—"; + return `${formatInt(quota)}($${usd})`; +} + +/** 支付金额:纯数字、无货币符号(R9 币种无关) */ +export function formatPaidAmount(value: number | null | undefined): string { + if (value == null) return "—"; + return String(value); +} diff --git a/apps/web/src/utils/time.ts b/apps/web/src/utils/time.ts new file mode 100644 index 0000000..4b696fa --- /dev/null +++ b/apps/web/src/utils/time.ts @@ -0,0 +1,47 @@ +/** + * 时间展示:API 响应时刻为 Unix 秒(UTC);界面一律北京时间(交互共性 §3.3)。 + */ +import dayjs from "dayjs"; +import utc from "dayjs/plugin/utc"; +import timezone from "dayjs/plugin/timezone"; + +dayjs.extend(utc); +dayjs.extend(timezone); + +/** 北京时区展示(固定 +08:00,不随主机时区变) */ +const DISPLAY_TZ = "Asia/Shanghai"; + +/** Unix 秒 → 北京时间「YYYY-MM-DD HH:mm:ss」 */ +export function formatDateTime(unixSec: number | null | undefined): string { + if (unixSec == null) return "—"; + return dayjs.unix(unixSec).tz(DISPLAY_TZ).format("YYYY-MM-DD HH:mm:ss"); +} + +/** Unix 秒 → 北京时间「YYYY-MM-DD」(统计口径到日) */ +export function formatDate(unixSec: number | null | undefined): string { + if (unixSec == null) return "—"; + return dayjs.unix(unixSec).tz(DISPLAY_TZ).format("YYYY-MM-DD"); +} + +/** Unix 秒 → 北京时间「HH:mm:ss」(明细时刻) */ +export function formatTimeOfDay(unixSec: number | null | undefined): string { + if (unixSec == null) return "—"; + return dayjs.unix(unixSec).tz(DISPLAY_TZ).format("HH:mm:ss"); +} + +/** 北京时间日初 → 请求用 ISO 8601 UTC(契约 §4:from/to 收 ISO) */ +export function beijingDayStartToIso(date: string): string { + return dayjs.tz(date, DISPLAY_TZ).startOf("day").toISOString(); +} + +/** 北京时间日界(次日零点,左闭右开的 to)→ ISO UTC */ +export function beijingDayEndToIso(date: string): string { + return dayjs.tz(date, DISPLAY_TZ).add(1, "day").startOf("day").toISOString(); +} + +/** 快捷项:最近 N 天(含今日),返回 [from, to) 的 ISO UTC */ +export function recentDaysRange(days: number): { from: string; to: string } { + const to = dayjs().tz(DISPLAY_TZ).add(1, "day").startOf("day"); + const from = to.subtract(days, "day"); + return { from: from.toISOString(), to: to.toISOString() }; +} diff --git a/apps/web/src/views/admin/audit-view.vue b/apps/web/src/views/admin/audit-view.vue new file mode 100644 index 0000000..055b405 --- /dev/null +++ b/apps/web/src/views/admin/audit-view.vue @@ -0,0 +1,10 @@ + + + diff --git a/apps/web/src/views/admin/keys-view.vue b/apps/web/src/views/admin/keys-view.vue new file mode 100644 index 0000000..fc4bb00 --- /dev/null +++ b/apps/web/src/views/admin/keys-view.vue @@ -0,0 +1,10 @@ + + + diff --git a/apps/web/src/views/admin/login-view.vue b/apps/web/src/views/admin/login-view.vue new file mode 100644 index 0000000..3971f4a --- /dev/null +++ b/apps/web/src/views/admin/login-view.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/apps/web/src/views/admin/members-view.vue b/apps/web/src/views/admin/members-view.vue new file mode 100644 index 0000000..2b446fe --- /dev/null +++ b/apps/web/src/views/admin/members-view.vue @@ -0,0 +1,10 @@ + + + diff --git a/apps/web/src/views/admin/overview-view.vue b/apps/web/src/views/admin/overview-view.vue new file mode 100644 index 0000000..bab4bab --- /dev/null +++ b/apps/web/src/views/admin/overview-view.vue @@ -0,0 +1,10 @@ + + + diff --git a/apps/web/src/views/admin/payments-view.vue b/apps/web/src/views/admin/payments-view.vue new file mode 100644 index 0000000..f54eb27 --- /dev/null +++ b/apps/web/src/views/admin/payments-view.vue @@ -0,0 +1,10 @@ + + + diff --git a/apps/web/src/views/admin/settings-view.vue b/apps/web/src/views/admin/settings-view.vue new file mode 100644 index 0000000..4fb44ea --- /dev/null +++ b/apps/web/src/views/admin/settings-view.vue @@ -0,0 +1,10 @@ + + + diff --git a/apps/web/src/views/member/home-view.vue b/apps/web/src/views/member/home-view.vue new file mode 100644 index 0000000..e4c8e89 --- /dev/null +++ b/apps/web/src/views/member/home-view.vue @@ -0,0 +1,10 @@ + + + diff --git a/apps/web/src/views/member/login-view.vue b/apps/web/src/views/member/login-view.vue new file mode 100644 index 0000000..93a760d --- /dev/null +++ b/apps/web/src/views/member/login-view.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/bun.lock b/bun.lock index 34239f1..3f9a282 100644 --- a/bun.lock +++ b/bun.lock @@ -37,7 +37,11 @@ "version": "0.0.0", "dependencies": { "@new-api-key-usage/core": "workspace:*", - "antdv-next": "^1.6.0", + "@tanstack/vue-query": "^5.104.1", + "@vueuse/core": "15.0.0", + "antdv-next": "1.6.0", + "axios": "^1.20.0", + "dayjs": "1.11.23", "pinia": "^4.0.3", "vue": "^3.5.42", "vue-router": "^5.3.1", @@ -401,6 +405,12 @@ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@tanstack/match-sorter-utils": ["@tanstack/match-sorter-utils@9.2.6", "", { "dependencies": { "remove-accents": "0.5.0" } }, "sha512-DIpUSOhPfQ7u/qfUi/Wgm0wBO56MyBYXGvnlI0NcauDRPqdBUlAZZjW2Dxm92m+k1UWVhKw8YJ4f47+75Aujyw=="], + + "@tanstack/query-core": ["@tanstack/query-core@5.104.1", "", {}, "sha512-TiRghcrGkUTE+M4JBxvpPSpBhxvIzQairw+cksrHYGr+2uwC77yW1SY9nYxnYRYOppvucf8rEg6b5byoz4rTpw=="], + + "@tanstack/vue-query": ["@tanstack/vue-query@5.104.1", "", { "dependencies": { "@tanstack/match-sorter-utils": "^9.1.2", "@tanstack/query-core": "5.104.1", "@vue/devtools-api": "^6.6.3", "vue-demi": "^0.14.10" }, "peerDependencies": { "@vue/composition-api": "^1.1.2", "vue": "^2.6.0 || ^3.3.0" }, "optionalPeers": ["@vue/composition-api"] }, "sha512-3858wOmIoIazR9mZ2g8/QsKNccQlzxIY+4/g55OHbhhMeBEi+ig/8vne2e5N+Xx9g5MXuB7Nscfb6B4REPDWlw=="], + "@tsconfig/node24": ["@tsconfig/node24@24.0.5", "", {}, "sha512-DWC54fGPuhZgg3Ighg4Y/A/bA3ZdB5hnzAuv/MkQ/X85sVsBsMQxu8t7khutdYeiNksBbFdDyeqv1HSW2Zl8aA=="], "@types/bun": ["@types/bun@1.4.2", "", { "dependencies": { "bun-types": "1.4.2" } }, "sha512-GimotNn7+ZV0uVArItBbriZsR1oNf0+WTzPkdcFrzShI7k2norL0uzEaJT8T33dWr7O/c9ZDuAFQrctKCi72oQ=="], @@ -611,7 +621,7 @@ "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.43", "", { "dependencies": { "@vue/compiler-dom": "3.5.43", "@vue/shared": "3.5.43" } }, "sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg=="], - "@vue/devtools-api": ["@vue/devtools-api@8.2.1", "", { "dependencies": { "@vue/devtools-kit": "^8.2.1" } }, "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A=="], + "@vue/devtools-api": ["@vue/devtools-api@6.6.4", "", {}, "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="], "@vue/devtools-core": ["@vue/devtools-core@8.2.1", "", { "dependencies": { "@vue/devtools-kit": "^8.2.1", "@vue/devtools-shared": "^8.2.1" }, "peerDependencies": { "vue": "^3.0.0" } }, "sha512-s/VfAY9oDTb/kFEWmy461jaFde2MIV1RO/gi1vwM+PAZBZ/Pc2Ndu3BNBdZUze8QDUuyYvElbEEGA83syjJfzA=="], @@ -637,11 +647,11 @@ "@vue/tsconfig": ["@vue/tsconfig@0.9.1", "", { "peerDependencies": { "typescript": ">= 5.8", "vue": "^3.4.0" }, "optionalPeers": ["typescript", "vue"] }, "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w=="], - "@vueuse/core": ["@vueuse/core@14.4.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.4.0", "@vueuse/shared": "14.4.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ=="], + "@vueuse/core": ["@vueuse/core@15.0.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "15.0.0", "@vueuse/shared": "15.0.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-Zv67h5mrk7YHDJsRObx+DXaCBz6RKBUpxqLesJ+3xZNPG6yuQs7ja7kg3veFrrouYLWAqydQ7CwGEbLVxB43Ng=="], - "@vueuse/metadata": ["@vueuse/metadata@14.4.0", "", {}, "sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g=="], + "@vueuse/metadata": ["@vueuse/metadata@15.0.0", "", {}, "sha512-sfWE5LADAKJ0KnjtGliMdSN8Ij/L7uP6rXIZlgPloAI4zvvefbw6H5Lt7KJA6RqQOYgVqWF5Guj50HHMIzygBg=="], - "@vueuse/shared": ["@vueuse/shared@14.4.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g=="], + "@vueuse/shared": ["@vueuse/shared@15.0.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-vbNXhGrbV53xPOMSyOzMHQRvhMgZf7oNDw2RChUetVxwTIEkInQ1joZ9JbT0G3z2n2AJNjvmCWeM9ySYrnvogA=="], "abbrev": ["abbrev@5.0.0", "", {}, "sha512-/XrFJgzQQQHpti1raDJC6m4ws6aNktmjBlhk8Fdlk7LwCEuDoieEJJY9OFHjfiFJFFRM2tK+Ky/IsfbbmlMu1w=="], @@ -649,6 +659,8 @@ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], "alien-signals": ["alien-signals@3.2.1", "", {}, "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g=="], @@ -665,6 +677,10 @@ "ast-walker-scope": ["ast-walker-scope@0.9.0", "", { "dependencies": { "@babel/parser": "^7.29.2", "@babel/types": "^7.29.0", "ast-kit": "^2.2.0" } }, "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A=="], + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "axios": ["axios@1.20.0", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg=="], + "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "baseline-browser-mapping": ["baseline-browser-mapping@2.11.27", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ=="], @@ -689,12 +705,16 @@ "cacheable": ["cacheable@2.5.0", "", { "dependencies": { "@cacheable/memory": "^2.2.0", "@cacheable/utils": "^2.5.0", "hookified": "^1.15.0", "keyv": "^5.6.0", "qified": "^0.10.1" } }, "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g=="], + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + "caniuse-lite": ["caniuse-lite@1.0.30001814", "", {}, "sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA=="], "chai": ["chai@6.3.0", "", {}, "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg=="], "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], "compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="], @@ -729,12 +749,16 @@ "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "drizzle-kit": ["drizzle-kit@0.31.11", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-YCYqxTLIB2OCqf6w9/Vef13baBVbwQIPcbT4Y56AfO6B9ajZhDhD8Jkveg/hJvT/iuMloKD/IYYkyMMNhr7Kqg=="], "drizzle-orm": ["drizzle-orm@0.45.3", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@netlify/db": ">=0.4.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "prisma": "*", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@netlify/db", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "prisma", "sql.js", "sqlite3"] }, "sha512-CAloER21cDdcgZ1OmjrZX82EeRNsdP+y0onX/eyoYDaYzki/adrAa91lzM5jWh7zZPK4qUbw/6LGv9J+A+uizA=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + "editorconfig": ["editorconfig@3.0.2", "", { "dependencies": { "@one-ini/wasm": "0.2.1", "commander": "^14.0.3", "minimatch": "~10.2.4", "semver": "^7.7.4" }, "bin": { "editorconfig": "bin/editorconfig" } }, "sha512-T0ix8GhtxyKVfUFEcvdNDt3YGqlwkFHbD4/5bgFUDgFmxhI/cSRAeJ87/Sz//Cq8Eam6JX/e23RkoFO71P7aAA=="], "electron-to-chromium": ["electron-to-chromium@1.5.444", "", {}, "sha512-5ss/uJfoDYDHT0lfJzT6FbcskIzROIOPf0BbbFkGcvDzoJU7i//9GDrwwIHQVmIsrAGiF3ihpADBRIsrEFt1rQ=="], @@ -743,8 +767,16 @@ "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + "es-module-lexer": ["es-module-lexer@2.3.2", "", {}, "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw=="], + "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + "es-toolkit": ["es-toolkit@1.52.0", "", {}, "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA=="], "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], @@ -803,18 +835,36 @@ "flatted": ["flatted@3.4.4", "", {}, "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q=="], + "follow-redirects": ["follow-redirects@1.16.1", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg=="], + + "form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + "get-tsconfig": ["get-tsconfig@4.14.3", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA=="], "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + "hashery": ["hashery@1.5.1", "", { "dependencies": { "hookified": "^1.15.0" } }, "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ=="], + "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], + "hono": ["hono@4.13.13", "", {}, "sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA=="], "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], @@ -823,6 +873,8 @@ "html-encoding-sniffer": ["html-encoding-sniffer@7.0.0", "", { "dependencies": { "@exodus/bytes": "^1.15.1" } }, "sha512-UikN5yr7xsCDAq87Or5or0PAlD3HJJOKVzM05az588WnpDJ4Ux7a2A53Qi6gofGg2/EtvF/H4hCi/TXfCW4Y6w=="], + "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], @@ -909,6 +961,8 @@ "magic-string-ast": ["magic-string-ast@1.0.3", "", { "dependencies": { "magic-string": "^0.30.19" } }, "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], "memorystream": ["memorystream@0.3.1", "", {}, "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="], @@ -917,6 +971,10 @@ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], @@ -1013,6 +1071,8 @@ "proto-list": ["proto-list@1.2.4", "", {}, "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="], + "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "qified": ["qified@0.10.1", "", { "dependencies": { "hookified": "^2.1.1" } }, "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA=="], @@ -1025,6 +1085,8 @@ "readdirp": ["readdirp@5.1.1", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="], + "remove-accents": ["remove-accents@0.5.0", "", {}, "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], "resize-observer-polyfill": ["resize-observer-polyfill@1.5.1", "", {}, "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="], @@ -1141,6 +1203,8 @@ "vue-component-type-helpers": ["vue-component-type-helpers@3.3.12", "", {}, "sha512-HwCqmgTfLFskQc4P5TRnbduCMyPat2Ezu0elLWOGn0dD/6BKHP15B5bxce6aDi+E4kblPtru8amIBQzu0Di3qQ=="], + "vue-demi": ["vue-demi@0.14.10", "", { "peerDependencies": { "@vue/composition-api": "^1.0.0-rc.1", "vue": "^3.0.0-0 || ^2.6.0" }, "optionalPeers": ["@vue/composition-api"], "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" } }, "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg=="], + "vue-eslint-parser": ["vue-eslint-parser@10.4.1", "", { "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0 || ^9.0.0", "eslint-visitor-keys": "^4.2.0 || ^5.0.0", "espree": "^10.3.0 || ^11.0.0", "esquery": "^1.6.0", "semver": "^7.6.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0" } }, "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA=="], "vue-router": ["vue-router@5.3.1", "", { "dependencies": { "@vue-macros/common": "^3.1.3", "@vue/devtools-api": "^8.1.5", "ast-walker-scope": "^0.9.0", "chokidar": "^5.0.0", "confbox": "^0.2.4", "local-pkg": "^1.2.1", "magic-string": "^0.30.21", "mlly": "^1.8.2", "muggle-string": "^0.4.1", "nostics": "^1.1.4", "pathe": "^2.0.3", "picomatch": "^4.0.5", "scule": "^1.3.0", "tinyglobby": "^0.2.17", "unplugin": "^3.3.0", "unplugin-utils": "^0.3.2" }, "peerDependencies": { "@pinia/colada": ">=0.21.2", "@vue/compiler-sfc": "^3.5.34 || ^4.0.0", "pinia": "^3.0.4 || ^4.0.2", "vite": "^7.3.0 || ^8.0.0", "vue": "^3.5.34 || ^4.0.0" }, "optionalPeers": ["@pinia/colada", "@vue/compiler-sfc", "pinia", "vite"] }, "sha512-GDBZzgmILxA/kFnkFbJjQZdZ2QQbngnIMMuoUcjhZIfH1RGMaPjPwX5ASnV38qamuA9uhO0RDjSBHTDNG2uXyQ=="], @@ -1211,6 +1275,8 @@ "@vue/compiler-sfc/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "antdv-next/@vueuse/core": ["@vueuse/core@14.4.0", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "14.4.0", "@vueuse/shared": "14.4.0" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ=="], + "bun-types/@types/node": ["@types/node@26.6.4", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg=="], "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], @@ -1221,6 +1287,8 @@ "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + "pinia/@vue/devtools-api": ["@vue/devtools-api@8.2.1", "", { "dependencies": { "@vue/devtools-kit": "^8.2.1" } }, "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A=="], + "pkg-types/confbox": ["confbox@0.3.1", "", {}, "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA=="], "qified/hookified": ["hookified@2.2.0", "", {}, "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA=="], @@ -1231,6 +1299,8 @@ "vite-plugin-vue-inspector/@vue/babel-plugin-jsx": ["@vue/babel-plugin-jsx@1.5.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.2", "@vue/babel-helper-vue-transform-on": "1.5.0", "@vue/babel-plugin-resolve-type": "1.5.0", "@vue/shared": "^3.5.18" }, "peerDependencies": { "@babel/core": "^7.0.0-0" }, "optionalPeers": ["@babel/core"] }, "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw=="], + "vue-router/@vue/devtools-api": ["@vue/devtools-api@8.2.1", "", { "dependencies": { "@vue/devtools-kit": "^8.2.1" } }, "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A=="], + "@antdv-next/icons/@ant-design/colors/@ant-design/fast-color": ["@ant-design/fast-color@2.0.6", "", { "dependencies": { "@babel/runtime": "^7.24.7" } }, "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], @@ -1281,6 +1351,10 @@ "@types/pg/@types/node/undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="], + "antdv-next/@vueuse/core/@vueuse/metadata": ["@vueuse/metadata@14.4.0", "", {}, "sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g=="], + + "antdv-next/@vueuse/core/@vueuse/shared": ["@vueuse/shared@14.4.0", "", { "peerDependencies": { "vue": "^3.5.0" } }, "sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g=="], + "bun-types/@types/node/undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="], "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],