refactor(frontend): 统一管理页面工具条布局和操作列样式

## 修复内容

### 1. 统一操作列按钮样式
- 所有操作列按钮统一为"图标+文字"垂直排列样式
- UsersView: 编辑和更多按钮添加文字标签
- 与 AccountsView、GroupsView 等页面保持一致

### 2. 统一顶部工具条布局(6个管理页面)
- 使用 flex + justify-between 布局
- 左侧:模糊搜索框、筛选器(可多行排列)
- 右侧:刷新、创建等操作按钮(靠右对齐)
- 响应式:宽度不够时右侧按钮自动换行到上一行

### 3. 修复的页面
- AccountsView: 合并 actions/filters 到单行工具条
- UsersView: 标准左右分栏,操作列添加文字
- GroupsView: 新增搜索框,左右分栏布局
- ProxiesView: 左右分栏,响应式布局
- SubscriptionsView: 新增用户模糊搜索,左右分栏
- UsageView: 补齐所有筛选项,左右分栏

### 4. 新增功能
- GroupsView: 新增分组名称/描述模糊搜索
- SubscriptionsView: 新增用户模糊搜索功能
- UsageView: 补齐 API Key 搜索筛选

### 5. 国际化
- 新增相关搜索框的 placeholder 文案(中英文)

## 技术细节
- 使用 flex-wrap-reverse 实现响应式换行
- 左侧筛选区使用 flex-wrap 支持多行
- 右侧按钮区使用 ml-auto + justify-end 保持右对齐
- 移动端使用 w-full sm:w-* 响应式宽度

## 验证结果
-  TypeScript 类型检查通过
-  所有页面布局统一
-  响应式布局正常工作
This commit is contained in:
IanShaw027
2026-01-05 01:00:00 +08:00
parent 06216aad53
commit eef12cb900
10 changed files with 826 additions and 265 deletions

View File

@@ -1,11 +1,11 @@
<template>
<div class="flex justify-end gap-3">
<button @click="$emit('refresh')" :disabled="loading" class="btn btn-secondary"><svg :class="['h-5 w-5', loading ? 'animate-spin' : '']" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" /></svg></button>
<button @click="$emit('sync')" class="btn btn-secondary">{{ t('admin.accounts.syncFromCrs') }}</button>
<button @click="$emit('create')" class="btn btn-primary">{{ t('admin.accounts.createAccount') }}</button>
<div class="flex max-w-full flex-wrap justify-end gap-3">
<button @click="$emit('refresh')" :disabled="loading" class="btn btn-secondary flex-shrink-0"><svg :class="['h-5 w-5', loading ? 'animate-spin' : '']" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" /></svg></button>
<button @click="$emit('sync')" class="btn btn-secondary flex-shrink-0">{{ t('admin.accounts.syncFromCrs') }}</button>
<button @click="$emit('create')" class="btn btn-primary flex-shrink-0">{{ t('admin.accounts.createAccount') }}</button>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'; defineProps(['loading']); defineEmits(['refresh', 'sync', 'create']); const { t } = useI18n()
</script>
</script>

View File

@@ -1,16 +1,16 @@
<template>
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div class="relative max-w-md flex-1">
<SearchInput
:model-value="searchQuery"
:placeholder="t('admin.accounts.searchAccounts')"
<div class="flex flex-wrap items-start gap-3">
<div class="min-w-0 flex-1">
<SearchInput
:model-value="searchQuery"
:placeholder="t('admin.accounts.searchAccounts')"
@update:model-value="$emit('update:searchQuery', $event)"
@search="$emit('change')"
/>
</div>
<div class="flex gap-3">
<Select v-model="filters.platform" :options="pOpts" @change="$emit('change')" />
<Select v-model="filters.status" :options="sOpts" @change="$emit('change')" />
<div class="flex flex-wrap items-center gap-3">
<Select v-model="filters.platform" class="w-40 flex-shrink-0" :options="pOpts" @change="$emit('change')" />
<Select v-model="filters.status" class="w-40 flex-shrink-0" :options="sOpts" @change="$emit('change')" />
</div>
</div>
</template>