redesign: site-wide UI/UX overhaul per web-interface-guidelines
- unified design system: focus-visible rings, reduced-motion, color-scheme, theme persistence, tightened emerald radius tokens (checkbox circle bug) - redesigned all pages: Home/Login/Signup/404, dashboard shell, Overview hero (time-based gradient), list pages with empty states, form modals, detail pages - role-scoped navigation: console menu vs admin console with cross-switch entries, Chinese route titles and breadcrumbs - theme switcher (light/dark/auto) via composables/theme, home + dashboard - state-driven dropdowns (touch-safe logout), menu divider pointer-events fix - btn-primary unified to black-on-white / white-on-black per theme - update REFACTOR_PLAN.md (increments 2-6)
This commit is contained in:
@@ -1,105 +1,103 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-base-100 p-4">
|
||||
<!-- Breadcrumb and Title -->
|
||||
<div class="space-y-5">
|
||||
<BreadcrumbHeader />
|
||||
|
||||
<div class="flex flex-wrap gap-2 mb-4 justify-end items-center">
|
||||
|
||||
<button class="btn btn-outline btn-success btn-sm gap-1" onclick="myModal.showModal()">
|
||||
<PlusIcon class="w-3.5 h-3.5" />New
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<p class="text-sm text-base-content/60">Tokens authenticate OpenAI-compatible clients with your team.</p>
|
||||
<button class="btn btn-primary btn-sm" @click="openModal" aria-label="Create new token">
|
||||
<PlusIcon class="h-4 w-4" aria-hidden="true" />New Token
|
||||
</button>
|
||||
<dialog id="myModal" class="modal" ref="modalRef">
|
||||
<div class="modal-box w-11/12 max-w-5xl h-screen px-0 sm:px-8">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||
</form>
|
||||
<TokenNew @closeModal="closeModal" />
|
||||
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>关闭</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div class="card card-bordered bg-base-100 shadow-sm mt-6">
|
||||
|
||||
<div class="card bg-base-100 shadow-xs overflow-x-auto dark:bg-base-200" v-if="user">
|
||||
<table class="table table-sm w-full">
|
||||
<div v-if="user" class="card border border-base-300/60 bg-base-100 shadow-sm">
|
||||
<div class="overflow-x-auto" v-if="user.tokens && user.tokens.length">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr class="text-xs text-base-content/70 uppercase bg-base-200">
|
||||
<th class="px-2 py-3">Token</th>
|
||||
<th class="px-2 py-3">Status</th>
|
||||
<!-- <th class="px-2 py-3">Key</th> -->
|
||||
<th class="px-2 py-3">Expired</th>
|
||||
<th class="px-2 py-3">Quota</th>
|
||||
<th class="px-2 py-3">Used</th>
|
||||
<th class="text-right px-2 py-3"></th>
|
||||
<tr class="text-xs uppercase tracking-wider text-base-content/50">
|
||||
<th class="pl-4">Token</th>
|
||||
<th>Status</th>
|
||||
<th>Expired</th>
|
||||
<th class="text-right">Quota</th>
|
||||
<th class="text-right">Used</th>
|
||||
<th class="pr-4 text-right"><span class="sr-only">Actions</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="token in user.tokens" :key="token.id" class="hover">
|
||||
<td class="font-mono text-xs px-2 py-3">{{ token.name }}</td>
|
||||
<tr v-for="token in user.tokens" :key="token.id" class="border-base-300/40 hover:bg-base-200/50">
|
||||
<td class="pl-4 font-medium">{{ token.name }}</td>
|
||||
<td>
|
||||
<input type="checkbox" class="toggle toggle-xs"
|
||||
:class="token.active ? 'toggle-success' : 'toggle-error'" v-model="token.active"
|
||||
@change="updateStatus(token)" />
|
||||
<input type="checkbox" class="toggle toggle-success toggle-sm"
|
||||
:class="!token.active && 'toggle-error'" v-model="token.active"
|
||||
@change="updateStatus(token)" :aria-label="`Toggle token ${token.name}`" />
|
||||
</td>
|
||||
<!-- <td class="font-mono text-xs px-2 py-3">{{ token.key }}</td> -->
|
||||
<td class="px-2 py-3">{{ token.expired_at == 0 ? 'Never' : unixToDate(token.expired_at ?? 0) }}</td>
|
||||
<td class="px-2 py-3">
|
||||
<td class="tabular-nums text-base-content/70">{{ token.expired_at == 0 ? 'Never' : unixToDate(token.expired_at ?? 0) }}</td>
|
||||
<td class="text-right tabular-nums">
|
||||
<template v-if="token.unlimited_quota">
|
||||
<Infinity />
|
||||
<Infinity class="inline h-4 w-4 text-base-content/60" aria-label="Unlimited quota" />
|
||||
<span class="sr-only">Unlimited</span>
|
||||
</template>
|
||||
<template v-else>{{ token.quota }}</template>
|
||||
</td>
|
||||
<td class="px-2 py-3">{{ token.used_quota }}</td>
|
||||
<td class="text-right px-1 py-3">
|
||||
<div class="flex items-center gap-2.5">
|
||||
<div class="lg:tooltip lg:tooltip-top lg:tooltip-open pt-1" data-tip="预览">
|
||||
<button class="btn btn-ghost btn-xs btn-square" onclick="" @click="viewToken(token)">
|
||||
<EyeIcon class="w-5 h-5 dark:text-white" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="md:tooltip" data-tip="clean used">
|
||||
<button class="btn btn-ghost btn-xs btn-square text-sky-300 mt-1" @click="cleanUsedToken(token)"
|
||||
aria-label="Revoke token">
|
||||
<Eraser class="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button v-if="token.name !== 'default'"
|
||||
class="btn btn-ghost btn-xs btn-square text-error items-center" @click="confirmRevokeToken(token)"
|
||||
aria-label="Revoke token">
|
||||
<TrashIcon class="w-5 h-5" />
|
||||
<td class="text-right tabular-nums">{{ token.used_quota }}</td>
|
||||
<td class="pr-3 text-right">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<button class="btn btn-ghost btn-xs btn-square" @click="viewToken(token)" aria-label="Preview token key">
|
||||
<EyeIcon class="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
<button v-if="(token.used_quota ?? 0) > 0" class="btn btn-ghost btn-xs btn-square text-info"
|
||||
@click="cleanUsedToken(token)" aria-label="Reset used quota">
|
||||
<Eraser class="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
<button v-if="token.name !== 'default'" class="btn btn-ghost btn-xs btn-square text-error"
|
||||
@click="confirmRevokeToken(token)" aria-label="Revoke token">
|
||||
<TrashIcon class="h-4 w-4" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<dialog id="myToken" class="modal" ref="tokenRef">
|
||||
<div class="modal-box px-0 sm:px-8">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||
</form>
|
||||
<QRCodeCard :value="qrCodeValue" :size="120" />
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button>关闭</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
|
||||
<p v-else class="text-center text-base-content/70 py-4">No tokens found</p>
|
||||
<!-- Empty state -->
|
||||
<div v-else class="flex flex-col items-center gap-2 px-4 py-14 text-center">
|
||||
<Braces class="h-10 w-10 text-base-content/20" aria-hidden="true" />
|
||||
<h2 class="text-sm font-semibold">No tokens yet</h2>
|
||||
<p class="max-w-xs text-sm text-base-content/60">
|
||||
Create a token to connect OpenCat, BotGem and other OpenAI-compatible clients.
|
||||
</p>
|
||||
<button class="btn btn-primary btn-sm mt-2" @click="openModal">
|
||||
<PlusIcon class="h-4 w-4" aria-hidden="true" />Create Token
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
|
||||
|
||||
<!-- New token modal -->
|
||||
<dialog ref="modalRef" class="modal">
|
||||
<div class="modal-box max-w-3xl px-0 sm:px-6">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-circle btn-ghost btn-sm absolute right-2 top-2" aria-label="Close dialog">✕</button>
|
||||
</form>
|
||||
<TokenNew @closeModal="closeModal" />
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button aria-label="Close dialog">close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<!-- Token QR modal -->
|
||||
<dialog ref="tokenRef" class="modal">
|
||||
<div class="modal-box max-w-sm px-0 sm:px-6">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-circle btn-ghost btn-sm absolute right-2 top-2" aria-label="Close dialog">✕</button>
|
||||
</form>
|
||||
<QRCodeCard :value="qrCodeValue" :size="120" />
|
||||
</div>
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button aria-label="Close dialog">close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -110,7 +108,7 @@ import QRCodeCard from '@/components/common/QRCodeCard.vue';
|
||||
import TokenNew from '@/views/dashboard/TokenNew.vue';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import {
|
||||
EyeIcon, PlusIcon, TrashIcon, Infinity, Eraser
|
||||
EyeIcon, PlusIcon, TrashIcon, Infinity, Eraser, Braces
|
||||
} from '@lucide/vue';
|
||||
import { unixToDate } from '@/utils/format-date';
|
||||
import { useToast } from '@/composables/toast';
|
||||
@@ -203,6 +201,9 @@ const qrCodeValue = ref('');
|
||||
|
||||
// 关闭模态框
|
||||
const modalRef = ref<HTMLDialogElement | null>(null);
|
||||
const openModal = () => {
|
||||
modalRef.value?.showModal();
|
||||
};
|
||||
const closeModal = async () => {
|
||||
if (modalRef.value) {
|
||||
modalRef.value.close();
|
||||
|
||||
Reference in New Issue
Block a user