mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-08-07 09:43:57 +08:00
feat(account): add user management view
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<section
|
||||
class="space-y-6 rounded-3xl border border-slate-200 bg-white p-6 shadow-sm dark:border-slate-800 dark:bg-slate-900"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col gap-5 sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<img
|
||||
v-if="user.avatarUrl"
|
||||
:src="user.avatarUrl"
|
||||
:alt="user.displayName"
|
||||
class="h-16 w-16 rounded-2xl border border-slate-200 object-cover dark:border-slate-700"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="flex h-16 w-16 items-center justify-center rounded-2xl bg-slate-100 text-2xl font-semibold text-slate-500 dark:bg-slate-800 dark:text-slate-300"
|
||||
>
|
||||
{{ userInitial }}
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-slate-900 dark:text-white">
|
||||
用户管理
|
||||
</h1>
|
||||
<p
|
||||
class="mt-1 text-lg font-medium text-slate-800 dark:text-slate-100"
|
||||
>
|
||||
{{ user.displayName }}
|
||||
</p>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">
|
||||
@{{ user.username }}
|
||||
</p>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">
|
||||
{{ user.forumLevel }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full border border-slate-200 px-4 py-2 text-sm font-medium text-slate-700 transition hover:border-sky-300 hover:text-sky-600 dark:border-slate-700 dark:text-slate-200 dark:hover:border-sky-500 dark:hover:text-sky-300"
|
||||
@click="emit('open-forum')"
|
||||
>
|
||||
论坛首页
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full bg-sky-600 px-4 py-2 text-sm font-medium text-white transition hover:bg-sky-500"
|
||||
@click="emit('edit-profile')"
|
||||
>
|
||||
修改论坛资料
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="visibleForumGroups.length > 0"
|
||||
class="flex flex-wrap gap-2 text-sm text-slate-600 dark:text-slate-300"
|
||||
>
|
||||
<span
|
||||
v-for="group in visibleForumGroups"
|
||||
:key="group"
|
||||
class="rounded-full bg-slate-100 px-3 py-1 dark:bg-slate-800"
|
||||
>
|
||||
{{ group }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col gap-4 rounded-2xl border border-slate-200 bg-slate-50 p-4 dark:border-slate-800 dark:bg-slate-950/40 sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
<label
|
||||
class="flex items-center gap-3 text-sm font-medium text-slate-700 dark:text-slate-200"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="h-4 w-4 rounded border-slate-300 text-sky-600 focus:ring-sky-500"
|
||||
aria-label="自动同步已安装应用"
|
||||
:checked="syncEnabled"
|
||||
@change="handleSyncToggle"
|
||||
/>
|
||||
自动同步已安装应用
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full border border-slate-200 px-4 py-2 text-sm font-medium text-slate-700 transition hover:border-sky-300 hover:text-sky-600 dark:border-slate-700 dark:text-slate-200 dark:hover:border-sky-500 dark:hover:text-sky-300"
|
||||
@click="emit('sync-now')"
|
||||
>
|
||||
立即同步
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section class="space-y-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-slate-900 dark:text-white">
|
||||
下载历史
|
||||
</h2>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">
|
||||
最近通过当前账号记录的应用安装历史。
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-full border border-slate-200 px-4 py-2 text-sm font-medium text-slate-700 transition hover:border-sky-300 hover:text-sky-600 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-200 dark:hover:border-sky-500 dark:hover:text-sky-300"
|
||||
:disabled="loading"
|
||||
@click="emit('refresh-downloads')"
|
||||
>
|
||||
刷新
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p v-if="loading" class="text-sm text-slate-500 dark:text-slate-400">
|
||||
正在加载下载历史...
|
||||
</p>
|
||||
<p v-else-if="error" class="text-sm text-red-600 dark:text-red-400">
|
||||
{{ error }}
|
||||
</p>
|
||||
<p
|
||||
v-else-if="downloadedApps.length === 0"
|
||||
class="text-sm text-slate-500 dark:text-slate-400"
|
||||
>
|
||||
暂无下载记录。
|
||||
</p>
|
||||
<ul v-else class="divide-y divide-slate-200 dark:divide-slate-800">
|
||||
<li
|
||||
v-for="app in downloadedApps"
|
||||
:key="app.id"
|
||||
class="flex flex-col gap-1 py-4 sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
<div>
|
||||
<p class="font-medium text-slate-900 dark:text-white">
|
||||
{{ app.name }}
|
||||
</p>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">
|
||||
{{ app.pkgname }} · {{ app.category }}
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-sm text-slate-500 dark:text-slate-400">
|
||||
{{ app.selectedOrigin.toUpperCase() }} · {{ app.version }} ·
|
||||
{{ app.packageArch }}
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
|
||||
import type { DownloadedAppRecord, SparkUser } from "@/global/typedefinition";
|
||||
|
||||
const props = defineProps<{
|
||||
user: SparkUser;
|
||||
downloadedApps: DownloadedAppRecord[];
|
||||
syncEnabled: boolean;
|
||||
loading: boolean;
|
||||
error: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
"open-forum": [];
|
||||
"edit-profile": [];
|
||||
"toggle-sync": [enabled: boolean];
|
||||
"sync-now": [];
|
||||
"refresh-downloads": [];
|
||||
}>();
|
||||
|
||||
const userInitial = computed(() =>
|
||||
(props.user.displayName || props.user.username || "?").slice(0, 1),
|
||||
);
|
||||
|
||||
const visibleForumGroups = computed(() =>
|
||||
props.user.forumGroups.filter((group) => group !== props.user.forumLevel),
|
||||
);
|
||||
|
||||
const handleSyncToggle = (event: Event): void => {
|
||||
const target = event.target;
|
||||
if (!(target instanceof HTMLInputElement)) return;
|
||||
emit("toggle-sync", target.checked);
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user