mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-06-22 14:13:49 +08:00
feat(account): record downloads and show reviews
This commit is contained in:
@@ -40,6 +40,23 @@ function getAppVersion(): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSystemInfo(): { distro: string } {
|
||||||
|
try {
|
||||||
|
const raw = fs.readFileSync("/etc/os-release", "utf8");
|
||||||
|
const fields = Object.fromEntries(
|
||||||
|
raw
|
||||||
|
.split("\n")
|
||||||
|
.map((line) => line.match(/^([A-Z_]+)=(.*)$/))
|
||||||
|
.filter((match): match is RegExpMatchArray => match !== null)
|
||||||
|
.map((match) => [match[1], match[2].replace(/^"|"$/g, "")]),
|
||||||
|
);
|
||||||
|
const distro = fields.PRETTY_NAME || fields.NAME || "unknown";
|
||||||
|
return { distro };
|
||||||
|
} catch {
|
||||||
|
return { distro: "unknown" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 处理 --version 参数(在单实例检查之前)
|
// 处理 --version 参数(在单实例检查之前)
|
||||||
if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
||||||
console.log(getAppVersion());
|
console.log(getAppVersion());
|
||||||
@@ -118,6 +135,7 @@ ipcMain.handle("get-store-filter", (): "spark" | "apm" | "both" =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle("get-app-version", (): string => getAppVersion());
|
ipcMain.handle("get-app-version", (): string => getAppVersion());
|
||||||
|
ipcMain.handle("get-system-info", (): { distro: string } => getSystemInfo());
|
||||||
|
|
||||||
ipcMain.handle("request-flarum-token", async (_event, payload: unknown) => {
|
ipcMain.handle("request-flarum-token", async (_event, payload: unknown) => {
|
||||||
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
||||||
|
|||||||
+28
-2
@@ -286,6 +286,7 @@ import {
|
|||||||
exchangeFlarumToken,
|
exchangeFlarumToken,
|
||||||
listFavoriteFolders,
|
listFavoriteFolders,
|
||||||
listFavoriteItems,
|
listFavoriteItems,
|
||||||
|
recordDownloadedApp,
|
||||||
} from "./modules/backendApi";
|
} from "./modules/backendApi";
|
||||||
import { requestFlarumToken } from "./modules/flarumAuth";
|
import { requestFlarumToken } from "./modules/flarumAuth";
|
||||||
import {
|
import {
|
||||||
@@ -306,6 +307,7 @@ import {
|
|||||||
buildFavoriteAppKey,
|
buildFavoriteAppKey,
|
||||||
buildReviewTags,
|
buildReviewTags,
|
||||||
getDisplayApp,
|
getDisplayApp,
|
||||||
|
parsePackageArch,
|
||||||
} from "./modules/appIdentity";
|
} from "./modules/appIdentity";
|
||||||
import { resolveFavoriteItems } from "./modules/favoriteAvailability";
|
import { resolveFavoriteItems } from "./modules/favoriteAvailability";
|
||||||
import type {
|
import type {
|
||||||
@@ -324,6 +326,7 @@ import type {
|
|||||||
FavoriteItem,
|
FavoriteItem,
|
||||||
InstalledAppInfo,
|
InstalledAppInfo,
|
||||||
ResolvedFavoriteItem,
|
ResolvedFavoriteItem,
|
||||||
|
SystemInfo,
|
||||||
} from "./global/typedefinition";
|
} from "./global/typedefinition";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import type { IpcRendererEvent } from "electron";
|
import type { IpcRendererEvent } from "electron";
|
||||||
@@ -403,6 +406,7 @@ const favoriteTargetApp = ref<App | null>(null);
|
|||||||
const favoriteLoading = ref(false);
|
const favoriteLoading = ref(false);
|
||||||
const favoriteError = ref("");
|
const favoriteError = ref("");
|
||||||
const favoriteRequestGeneration = ref(0);
|
const favoriteRequestGeneration = ref(0);
|
||||||
|
const systemInfo = ref<SystemInfo>({ distro: "unknown" });
|
||||||
|
|
||||||
/** 启动参数 --no-apm => 仅 Spark;--no-spark => 仅 APM;由主进程 IPC 提供 */
|
/** 启动参数 --no-apm => 仅 Spark;--no-spark => 仅 APM;由主进程 IPC 提供 */
|
||||||
const storeFilter = ref<"spark" | "apm" | "both">("both");
|
const storeFilter = ref<"spark" | "apm" | "both">("both");
|
||||||
@@ -500,7 +504,7 @@ const currentReviewTags = computed<ReviewTags | null>(() => {
|
|||||||
if (!currentDisplayApp.value) return null;
|
if (!currentDisplayApp.value) return null;
|
||||||
return buildReviewTags(currentDisplayApp.value, {
|
return buildReviewTags(currentDisplayApp.value, {
|
||||||
clientArch: clientArch.value,
|
clientArch: clientArch.value,
|
||||||
distro: "unknown",
|
distro: systemInfo.value.distro,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1236,7 +1240,22 @@ const onDetailRemove = (app: App) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onDetailInstall = async (app: App) => {
|
const onDetailInstall = async (app: App) => {
|
||||||
await handleInstall(app);
|
const download = await handleInstall(app);
|
||||||
|
if (!download || !isLoggedIn.value) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await recordDownloadedApp({
|
||||||
|
appKey: buildFavoriteAppKey(app),
|
||||||
|
pkgname: app.pkgname,
|
||||||
|
name: app.name,
|
||||||
|
category: app.category,
|
||||||
|
selectedOrigin: app.origin,
|
||||||
|
version: app.version,
|
||||||
|
packageArch: app.arch || parsePackageArch(app.filename),
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
logger.warn({ err: error }, "记录下载应用失败");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDetailFavorite = async (app: App) => {
|
const onDetailFavorite = async (app: App) => {
|
||||||
@@ -1783,6 +1802,13 @@ onMounted(async () => {
|
|||||||
initTheme();
|
initTheme();
|
||||||
updateCenterStore.bind();
|
updateCenterStore.bind();
|
||||||
|
|
||||||
|
try {
|
||||||
|
systemInfo.value = await window.ipcRenderer.invoke("get-system-info");
|
||||||
|
} catch (error: unknown) {
|
||||||
|
logger.warn({ err: error }, "读取系统信息失败");
|
||||||
|
systemInfo.value = { distro: "unknown" };
|
||||||
|
}
|
||||||
|
|
||||||
// 从主进程获取启动参数(--no-apm / --no-spark),再加载数据
|
// 从主进程获取启动参数(--no-apm / --no-spark),再加载数据
|
||||||
storeFilter.value = await window.ipcRenderer.invoke("get-store-filter");
|
storeFilter.value = await window.ipcRenderer.invoke("get-store-filter");
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { render, screen } from "@testing-library/vue";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import ReviewsPanel from "@/components/ReviewsPanel.vue";
|
||||||
|
import type { ReviewTags } from "@/global/typedefinition";
|
||||||
|
|
||||||
|
const tags: ReviewTags = {
|
||||||
|
origin: "apm",
|
||||||
|
category: "office",
|
||||||
|
pkgname: "wps",
|
||||||
|
version: "1.0.0",
|
||||||
|
packageArch: "amd64",
|
||||||
|
clientArch: "amd64",
|
||||||
|
distro: "deepin 25",
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("ReviewsPanel", () => {
|
||||||
|
it("shows anonymous login prompt and read-only review tags", () => {
|
||||||
|
render(ReviewsPanel, {
|
||||||
|
props: { appKey: "apm:amd64-apm:office:wps", tags, loggedIn: false },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText("登录后发表评论")).toBeTruthy();
|
||||||
|
expect(screen.getByText("1.0.0")).toBeTruthy();
|
||||||
|
expect(screen.getByText("deepin 25")).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -107,4 +107,44 @@ describe("processInstall queue forwarding", () => {
|
|||||||
expect.stringContaining('"id":5'),
|
expect.stringContaining('"id":5'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("returns queued download metadata for account records", async () => {
|
||||||
|
vi.doMock("axios", () => ({
|
||||||
|
default: {
|
||||||
|
create: vi.fn(() => ({
|
||||||
|
post: vi.fn(() => Promise.resolve({ data: { ok: true } })),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
Object.assign(window.ipcRenderer, {
|
||||||
|
on: vi.fn(),
|
||||||
|
send: vi.fn(),
|
||||||
|
invoke: vi.fn(() => Promise.resolve(true)),
|
||||||
|
});
|
||||||
|
window.apm_store.arch = "amd64";
|
||||||
|
const { handleInstall } = await import("@/modules/processInstall");
|
||||||
|
|
||||||
|
const result = await handleInstall({
|
||||||
|
name: "WPS",
|
||||||
|
pkgname: "wps",
|
||||||
|
version: "1.0.0",
|
||||||
|
filename: "wps_1.0.0_amd64.deb",
|
||||||
|
torrent_address: "",
|
||||||
|
author: "",
|
||||||
|
contributor: "",
|
||||||
|
website: "",
|
||||||
|
update: "",
|
||||||
|
size: "",
|
||||||
|
more: "",
|
||||||
|
tags: "",
|
||||||
|
img_urls: [],
|
||||||
|
icons: "",
|
||||||
|
category: "office",
|
||||||
|
origin: "apm",
|
||||||
|
currentStatus: "not-installed",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result?.pkgname).toBe("wps");
|
||||||
|
expect(result?.origin).toBe("apm");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -194,6 +194,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<p v-else class="text-sm text-slate-400">暂无应用截图</p>
|
<p v-else class="text-sm text-slate-400">暂无应用截图</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ReviewsPanel
|
||||||
|
v-if="reviewAppKey && reviewTags"
|
||||||
|
:app-key="reviewAppKey"
|
||||||
|
:tags="reviewTags"
|
||||||
|
:logged-in="loggedIn"
|
||||||
|
@request-login="$emit('request-login', $event)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -201,6 +209,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from "vue";
|
import { computed, ref, watch } from "vue";
|
||||||
|
import ReviewsPanel from "@/components/ReviewsPanel.vue";
|
||||||
import {
|
import {
|
||||||
APM_STORE_BASE_URL,
|
APM_STORE_BASE_URL,
|
||||||
getHybridDefaultOrigin,
|
getHybridDefaultOrigin,
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
<template>
|
||||||
|
<section
|
||||||
|
class="rounded-2xl border border-slate-200/60 bg-slate-50/50 p-5 dark:border-slate-800/60 dark:bg-slate-800/30"
|
||||||
|
>
|
||||||
|
<div class="mb-4 flex items-center justify-between gap-3">
|
||||||
|
<h2 class="flex items-center gap-2 text-base font-semibold">
|
||||||
|
<i class="fas fa-comments text-slate-400"></i>
|
||||||
|
应用评价
|
||||||
|
</h2>
|
||||||
|
<p class="text-sm text-slate-500 dark:text-slate-400">
|
||||||
|
{{ ratingText }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<dl class="mb-4 grid gap-2 text-xs text-slate-500 sm:grid-cols-2">
|
||||||
|
<div
|
||||||
|
class="flex justify-between gap-3 rounded-xl bg-white px-3 py-2 dark:bg-slate-900/60"
|
||||||
|
>
|
||||||
|
<dt>版本</dt>
|
||||||
|
<dd class="font-medium text-slate-700 dark:text-slate-300">
|
||||||
|
{{ tags.version }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex justify-between gap-3 rounded-xl bg-white px-3 py-2 dark:bg-slate-900/60"
|
||||||
|
>
|
||||||
|
<dt>发行版</dt>
|
||||||
|
<dd class="font-medium text-slate-700 dark:text-slate-300">
|
||||||
|
{{ tags.distro }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex justify-between gap-3 rounded-xl bg-white px-3 py-2 dark:bg-slate-900/60"
|
||||||
|
>
|
||||||
|
<dt>架构</dt>
|
||||||
|
<dd class="font-medium text-slate-700 dark:text-slate-300">
|
||||||
|
{{ tags.packageArch }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex justify-between gap-3 rounded-xl bg-white px-3 py-2 dark:bg-slate-900/60"
|
||||||
|
>
|
||||||
|
<dt>来源</dt>
|
||||||
|
<dd class="font-medium uppercase text-slate-700 dark:text-slate-300">
|
||||||
|
{{ tags.origin }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="!loggedIn"
|
||||||
|
type="button"
|
||||||
|
class="mb-4 inline-flex items-center rounded-xl bg-slate-800 px-4 py-2 text-sm font-medium text-white transition hover:bg-slate-700 dark:bg-slate-700 dark:hover:bg-slate-600"
|
||||||
|
@click="emit('request-login', '登录后发表评论')"
|
||||||
|
>
|
||||||
|
登录后发表评论
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<form v-else class="mb-4 space-y-3" @submit.prevent="submit">
|
||||||
|
<label
|
||||||
|
class="block text-sm font-medium text-slate-600 dark:text-slate-300"
|
||||||
|
>
|
||||||
|
评分
|
||||||
|
<select
|
||||||
|
v-model.number="rating"
|
||||||
|
class="mt-1 block w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900"
|
||||||
|
>
|
||||||
|
<option v-for="value in ratingOptions" :key="value" :value="value">
|
||||||
|
{{ value }} 星
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
class="block text-sm font-medium text-slate-600 dark:text-slate-300"
|
||||||
|
>
|
||||||
|
评论
|
||||||
|
<textarea
|
||||||
|
v-model="content"
|
||||||
|
rows="3"
|
||||||
|
class="mt-1 block w-full rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm dark:border-slate-700 dark:bg-slate-900"
|
||||||
|
placeholder="分享你的使用体验"
|
||||||
|
></textarea>
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="inline-flex items-center rounded-xl bg-blue-600 px-4 py-2 text-sm font-medium text-white transition hover:bg-blue-500 disabled:opacity-50"
|
||||||
|
:disabled="submitting"
|
||||||
|
>
|
||||||
|
发表评论
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p v-if="loading" class="text-sm text-slate-400">正在加载评价...</p>
|
||||||
|
<p v-else-if="error" class="text-sm text-rose-500">{{ error }}</p>
|
||||||
|
<div v-else-if="reviews.length" class="space-y-3">
|
||||||
|
<article
|
||||||
|
v-for="review in reviews"
|
||||||
|
:key="review.id"
|
||||||
|
class="rounded-xl bg-white p-3 text-sm dark:bg-slate-900/60"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between gap-3">
|
||||||
|
<strong class="text-slate-700 dark:text-slate-200">
|
||||||
|
{{ review.userDisplayName || "星火用户" }}
|
||||||
|
</strong>
|
||||||
|
<span class="text-xs text-slate-400">{{ review.rating }} 星</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 whitespace-pre-wrap text-slate-600 dark:text-slate-300">
|
||||||
|
{{ review.content || "暂无评论内容" }}
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<p v-else class="text-sm text-slate-400">暂无评价</p>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref, watch } from "vue";
|
||||||
|
|
||||||
|
import {
|
||||||
|
fetchRatingSummary,
|
||||||
|
fetchReviews,
|
||||||
|
submitReview,
|
||||||
|
} from "@/modules/backendApi";
|
||||||
|
import type {
|
||||||
|
AppReview,
|
||||||
|
RatingSummary,
|
||||||
|
ReviewTags,
|
||||||
|
} from "@/global/typedefinition";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
appKey: string;
|
||||||
|
tags: ReviewTags;
|
||||||
|
loggedIn: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
"request-login": [message: string];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const ratingOptions = [5, 4, 3, 2, 1];
|
||||||
|
const rating = ref(5);
|
||||||
|
const content = ref("");
|
||||||
|
const reviews = ref<AppReview[]>([]);
|
||||||
|
const summary = ref<RatingSummary | null>(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
const submitting = ref(false);
|
||||||
|
const error = ref("");
|
||||||
|
|
||||||
|
const ratingText = computed(() => {
|
||||||
|
if (!summary.value || summary.value.reviewCount === 0) return "暂无评分";
|
||||||
|
return `${summary.value.averageRating.toFixed(1)} / 5 (${summary.value.reviewCount})`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadReviews = async () => {
|
||||||
|
if (!props.appKey) return;
|
||||||
|
loading.value = true;
|
||||||
|
error.value = "";
|
||||||
|
try {
|
||||||
|
const [nextSummary, nextReviews] = await Promise.all([
|
||||||
|
fetchRatingSummary(props.appKey),
|
||||||
|
fetchReviews(props.appKey),
|
||||||
|
]);
|
||||||
|
summary.value = nextSummary;
|
||||||
|
reviews.value = nextReviews;
|
||||||
|
} catch (caught: unknown) {
|
||||||
|
error.value = (caught as Error)?.message || "加载评价失败";
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
submitting.value = true;
|
||||||
|
error.value = "";
|
||||||
|
try {
|
||||||
|
await submitReview(props.appKey, {
|
||||||
|
rating: rating.value,
|
||||||
|
content: content.value.trim(),
|
||||||
|
tags: props.tags,
|
||||||
|
});
|
||||||
|
content.value = "";
|
||||||
|
await loadReviews();
|
||||||
|
} catch (caught: unknown) {
|
||||||
|
error.value = (caught as Error)?.message || "发表评论失败";
|
||||||
|
} finally {
|
||||||
|
submitting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(loadReviews);
|
||||||
|
watch(() => props.appKey, loadReviews);
|
||||||
|
</script>
|
||||||
@@ -21,16 +21,18 @@ import axios from "axios";
|
|||||||
|
|
||||||
const logger = pino({ name: "processInstall.ts" });
|
const logger = pino({ name: "processInstall.ts" });
|
||||||
|
|
||||||
export const handleInstall = async (appObj?: App) => {
|
export const handleInstall = async (
|
||||||
|
appObj?: App,
|
||||||
|
): Promise<DownloadItem | null> => {
|
||||||
const targetApp = appObj || currentApp.value;
|
const targetApp = appObj || currentApp.value;
|
||||||
if (!targetApp?.pkgname) return;
|
if (!targetApp?.pkgname) return null;
|
||||||
|
|
||||||
// APM 应用:在创建下载任务前检查 APM 是否可用
|
// APM 应用:在创建下载任务前检查 APM 是否可用
|
||||||
if (targetApp.origin === "apm") {
|
if (targetApp.origin === "apm") {
|
||||||
const hasApm = await window.ipcRenderer.invoke("check-apm-available");
|
const hasApm = await window.ipcRenderer.invoke("check-apm-available");
|
||||||
if (!hasApm) {
|
if (!hasApm) {
|
||||||
showApmInstallDialog.value = true;
|
showApmInstallDialog.value = true;
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +44,7 @@ export const handleInstall = async (appObj?: App) => {
|
|||||||
logger.info(
|
logger.info(
|
||||||
`任务已存在,忽略重复添加: ${targetApp.pkgname} (${targetApp.origin})`,
|
`任务已存在,忽略重复添加: ${targetApp.pkgname} (${targetApp.origin})`,
|
||||||
);
|
);
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建下载任务
|
// 创建下载任务
|
||||||
@@ -98,6 +100,7 @@ export const handleInstall = async (appObj?: App) => {
|
|||||||
.then((response) => {
|
.then((response) => {
|
||||||
logger.info("下载次数统计已发送,状态:", response.data);
|
logger.info("下载次数统计已发送,状态:", response.data);
|
||||||
});
|
});
|
||||||
|
return download;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handleRetry = (download_: DownloadItem) => {
|
export const handleRetry = (download_: DownloadItem) => {
|
||||||
|
|||||||
Vendored
+2
-1
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
import type { UpdateCenterBridge } from "@/global/typedefinition";
|
import type { SystemInfo, UpdateCenterBridge } from "@/global/typedefinition";
|
||||||
|
|
||||||
declare module "*.vue" {
|
declare module "*.vue" {
|
||||||
import type { DefineComponent } from "vue";
|
import type { DefineComponent } from "vue";
|
||||||
@@ -34,6 +34,7 @@ interface IpcRendererFacade {
|
|||||||
// IPC channel type definitions
|
// IPC channel type definitions
|
||||||
declare interface IpcChannels {
|
declare interface IpcChannels {
|
||||||
"get-app-version": () => string;
|
"get-app-version": () => string;
|
||||||
|
"get-system-info": () => Promise<SystemInfo>;
|
||||||
"request-flarum-token": (payload: {
|
"request-flarum-token": (payload: {
|
||||||
identification: string;
|
identification: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user