Compare commits

..
2 Commits
Author SHA1 Message Date
skycurtain 8816c32025 chore: vite 固定 5175,脚本统一 --cwd,清理未用导入 2026-10-10 01:36:57 +08:00
skycurtain 4be0853ade feat(web): 表格工具行统一刷新钮
纯图标 + tooltip,筛选下一行右对齐;覆盖管理端与成员端列表;清理 overview 未用 ref。
2026-10-09 23:42:24 +08:00
14 changed files with 99 additions and 6 deletions
-1
View File
@@ -26,7 +26,6 @@ Bun 工具链(选型见 `docs/design/技术选型.md`)。**Windows 上 `bun`
| 命令 | 用途 | | 命令 | 用途 |
|------|------| |------|------|
| `bun install` | 安装 / 链接 workspace 依赖(在仓库根) | | `bun install` | 安装 / 链接 workspace 依赖(在仓库根) |
| `bun test` | 全部测试(core 契约 + server HTTP + **集成**);集成需 `DATABASE_URL`(compose 中的 PG),无库时自动跳过 |
| `bun run db:generate` | 生成 SQL 迁移(`packages/db/drizzle/`) | | `bun run db:generate` | 生成 SQL 迁移(`packages/db/drizzle/`) |
| `bun run db:push` | 推送到本地库(开发期) | | `bun run db:push` | 推送到本地库(开发期) |
| `bun run db:studio` | Drizzle Studio 看库 | | `bun run db:studio` | Drizzle Studio 看库 |
@@ -2,7 +2,7 @@
/** /**
* 订阅选择弹层:登录(仅一次/标签页)+ 列表选用,与登记表单解耦 * 订阅选择弹层:登录(仅一次/标签页)+ 列表选用,与登记表单解耦
*/ */
import { computed, ref, watch } from "vue"; import { ref, watch } from "vue";
import { import {
listPlatformSubscriptions, listPlatformSubscriptions,
loginPlatform, loginPlatform,
@@ -0,0 +1,33 @@
<script setup lang="ts">
/**
* 表格工具行刷新钮(纯图标 + tooltip)——放在筛选行下一行、右对齐
*/
import { ReloadOutlined } from "@antdv-next/icons";
defineProps<{ loading?: boolean }>();
const emit = defineEmits<{ (e: "refresh"): void }>();
</script>
<template>
<div class="table-refresh">
<a-tooltip title="刷新">
<a-button
:loading="loading"
aria-label="刷新"
@click="emit('refresh')"
>
<template #icon>
<ReloadOutlined />
</template>
</a-button>
</a-tooltip>
</div>
</template>
<style scoped>
.table-refresh {
display: flex;
justify-content: flex-end;
margin-bottom: 8px;
}
</style>
+6
View File
@@ -5,6 +5,7 @@
*/ */
import { computed, onMounted, onUnmounted, ref, watch } from "vue"; import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import PageHeader from "@/components/common/page-header.vue"; import PageHeader from "@/components/common/page-header.vue";
import TableRefresh from "@/components/common/table-refresh.vue";
import TimeRangePicker from "@/components/common/time-range-picker.vue"; import TimeRangePicker from "@/components/common/time-range-picker.vue";
import { useAuditLogs } from "@/queries/admin.ts"; import { useAuditLogs } from "@/queries/admin.ts";
import { formatDateTime, recentDaysRange } from "@/utils/time.ts"; import { formatDateTime, recentDaysRange } from "@/utils/time.ts";
@@ -88,6 +89,11 @@ onUnmounted(() => {
</template> </template>
</PageHeader> </PageHeader>
<TableRefresh
:loading="auditQuery.isFetching.value"
@refresh="() => auditQuery.refetch()"
/>
<div ref="boxRef" class="audit__box"> <div ref="boxRef" class="audit__box">
<a-table <a-table
:columns="columns" :columns="columns"
+6
View File
@@ -9,6 +9,7 @@ import { useRoute, useRouter } from "vue-router";
import type { KeyItem } from "@new-api-key-usage/core"; import type { KeyItem } from "@new-api-key-usage/core";
import type { ApiError } from "@/api/http.ts"; import type { ApiError } from "@/api/http.ts";
import PageHeader from "@/components/common/page-header.vue"; import PageHeader from "@/components/common/page-header.vue";
import TableRefresh from "@/components/common/table-refresh.vue";
import RegisterKeyModal from "@/components/admin/register-key-modal.vue"; import RegisterKeyModal from "@/components/admin/register-key-modal.vue";
import { useEngine, useKeys, useMembers, usePatchKey } from "@/queries/admin.ts"; import { useEngine, useKeys, useMembers, usePatchKey } from "@/queries/admin.ts";
import { formatInt } from "@/utils/format.ts"; import { formatInt } from "@/utils/format.ts";
@@ -230,6 +231,11 @@ function onRowClass(record: KeyItem) {
</a-space> </a-space>
</div> </div>
<TableRefresh
:loading="keysQuery.isFetching.value"
@refresh="() => keysQuery.refetch()"
/>
<a-empty <a-empty
v-if="!keysQuery.isPending.value && rows.length === 0" v-if="!keysQuery.isPending.value && rows.length === 0"
:description=" :description="
+7 -1
View File
@@ -6,11 +6,12 @@ import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import type { LogItem } from "@new-api-key-usage/core"; import type { LogItem } from "@new-api-key-usage/core";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import PageHeader from "@/components/common/page-header.vue"; import PageHeader from "@/components/common/page-header.vue";
import TableRefresh from "@/components/common/table-refresh.vue";
import TimeRangePicker from "@/components/common/time-range-picker.vue"; import TimeRangePicker from "@/components/common/time-range-picker.vue";
import QuotaText from "@/components/common/quota-text.vue"; import QuotaText from "@/components/common/quota-text.vue";
import { useLiveTimeRange } from "@/composables/use-live-time-range.ts"; import { useLiveTimeRange } from "@/composables/use-live-time-range.ts";
import { useAdminLogs, useKeys, useMembers } from "@/queries/admin.ts"; import { useAdminLogs, useKeys, useMembers } from "@/queries/admin.ts";
import { formatDateTime, recentDaysRange } from "@/utils/time.ts"; import { formatDateTime } from "@/utils/time.ts";
import { formatInt } from "@/utils/format.ts"; import { formatInt } from "@/utils/format.ts";
const route = useRoute(); const route = useRoute();
@@ -272,6 +273,11 @@ onUnmounted(() => {
/> />
</div> </div>
<TableRefresh
:loading="logsQuery.isFetching.value"
@refresh="() => logsQuery.refetch()"
/>
<a-empty <a-empty
v-if="!logsQuery.isPending.value && rows.length === 0" v-if="!logsQuery.isPending.value && rows.length === 0"
description="该时段内无调用日志" description="该时段内无调用日志"
@@ -12,6 +12,7 @@ const router = useRouter();
import type { MemberItem } from "@new-api-key-usage/core"; import type { MemberItem } from "@new-api-key-usage/core";
import type { ApiError } from "@/api/http.ts"; import type { ApiError } from "@/api/http.ts";
import PageHeader from "@/components/common/page-header.vue"; import PageHeader from "@/components/common/page-header.vue";
import TableRefresh from "@/components/common/table-refresh.vue";
import MemberTokenModal from "@/components/admin/member-token-modal.vue"; import MemberTokenModal from "@/components/admin/member-token-modal.vue";
import { import {
useCreateMember, useCreateMember,
@@ -195,6 +196,11 @@ async function onToggle(record: MemberItem) {
/> />
</div> </div>
<TableRefresh
:loading="membersQuery.isFetching.value"
@refresh="() => membersQuery.refetch()"
/>
<a-empty <a-empty
v-if="!membersQuery.isPending.value && rows.length === 0" v-if="!membersQuery.isPending.value && rows.length === 0"
:description="hasFilter ? '没有符合筛选的成员' : '还没有成员——为每位分摊朋友开户并发放访问令牌'" :description="hasFilter ? '没有符合筛选的成员' : '还没有成员——为每位分摊朋友开户并发放访问令牌'"
+6 -1
View File
@@ -2,11 +2,12 @@
/** /**
* 全员总览(交互设计-全员总览):健康带 · 时间范围 · 按成员 · 按 key * 全员总览(交互设计-全员总览):健康带 · 时间范围 · 按成员 · 按 key
*/ */
import { computed, h, ref } from "vue"; import { computed, h } from "vue";
import { Tooltip } from "antdv-next"; import { Tooltip } from "antdv-next";
import { QuestionCircleOutlined } from "@antdv-next/icons"; import { QuestionCircleOutlined } from "@antdv-next/icons";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import PageHeader from "@/components/common/page-header.vue"; import PageHeader from "@/components/common/page-header.vue";
import TableRefresh from "@/components/common/table-refresh.vue";
import TimeRangePicker from "@/components/common/time-range-picker.vue"; import TimeRangePicker from "@/components/common/time-range-picker.vue";
import QuotaText from "@/components/common/quota-text.vue"; import QuotaText from "@/components/common/quota-text.vue";
import SourceBar from "@/components/common/source-bar.vue"; import SourceBar from "@/components/common/source-bar.vue";
@@ -217,6 +218,10 @@ const keyColumns = [
<!-- 按成员 --> <!-- 按成员 -->
<section class="overview-block"> <section class="overview-block">
<h2 class="overview-block__title">按成员汇总</h2> <h2 class="overview-block__title">按成员汇总</h2>
<TableRefresh
:loading="overviewQuery.isFetching.value"
@refresh="() => overviewQuery.refetch()"
/>
<a-empty <a-empty
v-if="!loading && byMember.length === 0" v-if="!loading && byMember.length === 0"
description="本时段内没有任何消费调用,因此没有成员用量可汇总" description="本时段内没有任何消费调用,因此没有成员用量可汇总"
@@ -9,6 +9,7 @@ import type { ApiError } from "@/api/http.ts";
import { fetchLogs } from "@/api/admin/logs.ts"; import { fetchLogs } from "@/api/admin/logs.ts";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import PageHeader from "@/components/common/page-header.vue"; import PageHeader from "@/components/common/page-header.vue";
import TableRefresh from "@/components/common/table-refresh.vue";
import PaymentFormModal from "@/components/admin/payment-form-modal.vue"; import PaymentFormModal from "@/components/admin/payment-form-modal.vue";
import QuotaText from "@/components/common/quota-text.vue"; import QuotaText from "@/components/common/quota-text.vue";
import { import {
@@ -215,6 +216,10 @@ watch(
</a-alert> </a-alert>
<template v-if="tab === 'subs'"> <template v-if="tab === 'subs'">
<TableRefresh
:loading="subsQuery.isFetching.value"
@refresh="() => subsQuery.refetch()"
/>
<a-empty <a-empty
v-if="!subsQuery.isPending.value && subRows.length === 0" v-if="!subsQuery.isPending.value && subRows.length === 0"
description="还没有订阅登记——从平台选用或手工登记" description="还没有订阅登记——从平台选用或手工登记"
@@ -272,6 +277,10 @@ watch(
</template> </template>
<template v-else> <template v-else>
<TableRefresh
:loading="topupsQuery.isFetching.value"
@refresh="() => topupsQuery.refetch()"
/>
<a-empty <a-empty
v-if="!topupsQuery.isPending.value && topRows.length === 0" v-if="!topupsQuery.isPending.value && topRows.length === 0"
description="还没有充值登记" description="还没有充值登记"
+7 -1
View File
@@ -9,6 +9,7 @@ import { Tooltip } from "antdv-next";
import { QuestionCircleOutlined } from "@antdv-next/icons"; import { QuestionCircleOutlined } from "@antdv-next/icons";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import PageHeader from "@/components/common/page-header.vue"; import PageHeader from "@/components/common/page-header.vue";
import TableRefresh from "@/components/common/table-refresh.vue";
import TimeRangePicker from "@/components/common/time-range-picker.vue"; import TimeRangePicker from "@/components/common/time-range-picker.vue";
import QuotaText from "@/components/common/quota-text.vue"; import QuotaText from "@/components/common/quota-text.vue";
import { useMemberRange } from "@/composables/use-member-range.ts"; import { useMemberRange } from "@/composables/use-member-range.ts";
@@ -275,6 +276,11 @@ watch(
/> />
</div> </div>
<TableRefresh
:loading="logsQuery.isFetching.value"
@refresh="() => logsQuery.refetch()"
/>
<a-alert <a-alert
v-if="logsQuery.isError.value" v-if="logsQuery.isError.value"
type="error" type="error"
@@ -457,7 +463,7 @@ watch(
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 8px; gap: 8px;
margin-bottom: 16px; margin-bottom: 12px;
} }
.member-logs__box { .member-logs__box {
@@ -5,6 +5,7 @@
import { computed } from "vue"; import { computed } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import PageHeader from "@/components/common/page-header.vue"; import PageHeader from "@/components/common/page-header.vue";
import TableRefresh from "@/components/common/table-refresh.vue";
import TimeRangePicker from "@/components/common/time-range-picker.vue"; import TimeRangePicker from "@/components/common/time-range-picker.vue";
import SourceBar from "@/components/common/source-bar.vue"; import SourceBar from "@/components/common/source-bar.vue";
import QuotaText from "@/components/common/quota-text.vue"; import QuotaText from "@/components/common/quota-text.vue";
@@ -131,6 +132,10 @@ const keyColumns = [
<section class="mv-block"> <section class="mv-block">
<h2 class="mv-block__title">我的 Key</h2> <h2 class="mv-block__title">我的 Key</h2>
<TableRefresh
:loading="overviewQuery.isFetching.value"
@refresh="() => overviewQuery.refetch()"
/>
<a-table <a-table
:columns="keyColumns" :columns="keyColumns"
:data-source="keys" :data-source="keys"
@@ -4,6 +4,7 @@
*/ */
import { computed } from "vue"; import { computed } from "vue";
import PageHeader from "@/components/common/page-header.vue"; import PageHeader from "@/components/common/page-header.vue";
import TableRefresh from "@/components/common/table-refresh.vue";
import TimeRangePicker from "@/components/common/time-range-picker.vue"; import TimeRangePicker from "@/components/common/time-range-picker.vue";
import QuotaText from "@/components/common/quota-text.vue"; import QuotaText from "@/components/common/quota-text.vue";
import { useMemberRange } from "@/composables/use-member-range.ts"; import { useMemberRange } from "@/composables/use-member-range.ts";
@@ -111,6 +112,10 @@ const topColumns = [
/> />
<template v-else> <template v-else>
<h3 class="mv-sec">订阅</h3> <h3 class="mv-sec">订阅</h3>
<TableRefresh
:loading="overviewQuery.isFetching.value"
@refresh="() => overviewQuery.refetch()"
/>
<a-empty v-if="subs.length === 0" description="暂无订阅登记" /> <a-empty v-if="subs.length === 0" description="暂无订阅登记" />
<a-table <a-table
v-else v-else
+3
View File
@@ -17,6 +17,9 @@ export default defineConfig({
}, },
}, },
server: { server: {
// 固定开发口:避免在 5173/5174 被占时静默跳到 5175(strictPort 不自动改口)
port: 5175,
strictPort: true,
proxy: { proxy: {
// 契约:/api/* 专属接口;SPA 路由不进 /api(公共约定 §2) // 契约:/api/* 专属接口;SPA 路由不进 /api(公共约定 §2)
"/api": { "/api": {
+5 -1
View File
@@ -8,7 +8,11 @@
"typecheck": "tsc -p tsconfig.json --noEmit", "typecheck": "tsc -p tsconfig.json --noEmit",
"dev:server": "bun --hot --env-file=.env apps/server/src/index.ts", "dev:server": "bun --hot --env-file=.env apps/server/src/index.ts",
"dev:engine": "bun --env-file=.env apps/engine/src/index.ts", "dev:engine": "bun --env-file=.env apps/engine/src/index.ts",
"dev:web": "bun --filter @new-api-key-usage/web dev" "dev:web": "bun run --cwd apps/web dev",
"db:generate": "bun run --cwd packages/db db:generate",
"db:push": "bun run --cwd packages/db db:push",
"db:studio": "bun run --cwd packages/db db:studio",
"build:web": "bun run --cwd apps/web build"
}, },
"devDependencies": { "devDependencies": {
"@types/bun": "^1.4.2", "@types/bun": "^1.4.2",