| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955 |
- <script setup>
- import { ref, onMounted, reactive, computed, inject } from 'vue'
- import {
- listTeamDomains,
- createTeamDomain,
- updateTeamDomain,
- deleteTeamDomain,
- getTeamDomain,
- showTeamDomains,
- getTeamDomainDailyStatistics,
- getTeamDomainAllStatistics
- } from '@/services/api'
- import { useToast } from 'primevue/usetoast'
- import { useConfirm } from 'primevue/useconfirm'
- import DataTable from 'primevue/datatable'
- import Column from 'primevue/column'
- import Button from 'primevue/button'
- import InputText from 'primevue/inputtext'
- import Select from 'primevue/select'
- import Dialog from 'primevue/dialog'
- import Textarea from 'primevue/textarea'
- import { useDateFormat } from '@vueuse/core'
- import { useUserStore } from '@/stores/user'
- import { useTeamStore } from '@/stores/team'
- const toast = useToast()
- const confirm = useConfirm()
- const userStore = useUserStore()
- const teamStore = useTeamStore()
- // 注入权限信息
- const isAdmin = inject('isAdmin')
- const isTeam = inject('isTeam')
- const isPromoter = inject('isPromoter')
- const tableRef = ref(null)
- const tableData = ref({
- data: [],
- meta: {
- total: 0,
- page: 0,
- size: 20
- }
- })
- // 管理员专用的分组数据
- const adminGroupedData = ref({})
- // 域名统计数据
- const domainStatistics = ref({})
- const domainAllStatistics = ref({})
- const selectedDomain = ref(null)
- const dialogVisible = ref(false)
- const isEditing = ref(false)
- const domainModel = reactive({
- teamId: null,
- domain: '',
- description: ''
- })
- // 搜索表单
- const searchForm = ref({
- domain: '',
- teamId: null
- })
- // 计算当前用户的团队ID
- const currentTeamId = computed(() => {
- if (isAdmin.value) {
- // 管理员可以选择团队,这里先返回null,在创建/编辑时手动指定
- return null
- } else if (isTeam.value) {
- // 队长从team表获取teamId
- return userStore.userInfo?.teamId
- } else if (isPromoter.value) {
- // 推广员从team-members表获取teamId
- return userStore.userInfo?.teamId
- }
- return null
- })
- // 计算是否有操作权限
- const canCreate = computed(() => isAdmin.value)
- const canUpdate = computed(() => isAdmin.value || isTeam.value)
- const canDelete = computed(() => isAdmin.value || isTeam.value)
- const canView = computed(() => isAdmin.value || isTeam.value || isPromoter.value)
- const fetchData = async (page = 0) => {
- try {
- if (isAdmin.value) {
- // 管理员使用新的分组接口
- const result = await showTeamDomains(undefined, searchForm.value.teamId, searchForm.value.domain || undefined)
- adminGroupedData.value = result || {}
- // 同时获取统计数据
- await fetchDomainStatistics()
- } else {
- // 其他角色使用原有接口
- const result = await listTeamDomains(
- page,
- tableData.value.meta?.size || 20,
- undefined,
- searchForm.value.teamId || currentTeamId.value,
- searchForm.value.domain || undefined
- )
- tableData.value = {
- data: result?.content || [],
- meta: {
- total: result?.metadata?.total || 0,
- page: result?.metadata?.page || 0,
- size: result?.metadata?.size || 20,
- totalPages: Math.ceil((result?.metadata?.total || 0) / (result?.metadata?.size || 20))
- }
- }
- }
- } catch (error) {
- console.error('获取域名列表失败', error)
- toast.add({ severity: 'error', summary: '错误', detail: '获取域名列表失败', life: 3000 })
- if (isAdmin.value) {
- adminGroupedData.value = {}
- } else {
- tableData.value = {
- data: [],
- meta: {
- total: 0,
- page: 0,
- size: 20,
- totalPages: 0
- }
- }
- }
- }
- }
- const handlePageChange = (event) => {
- if (!isAdmin.value) {
- fetchData(event.page)
- }
- }
- const refreshData = () => {
- if (isAdmin.value) {
- fetchData(0)
- } else {
- const page = tableData.value.meta?.page || 0
- fetchData(page)
- }
- }
- const handleSearch = () => {
- if (!isAdmin.value && tableData.value.meta) {
- tableData.value.meta.page = 0
- }
- fetchData(0)
- }
- const handleRefresh = () => {
- searchForm.value = {
- domain: '',
- teamId: null
- }
- if (!isAdmin.value && tableData.value.meta) {
- tableData.value.meta.page = 0
- }
- fetchData(0)
- }
- const formatDate = (date) => {
- if (!date) return '-'
- return useDateFormat(new Date(date), 'YYYY-MM-DD HH:mm:ss').value
- }
- // 复制域名到剪贴板
- const copyDomain = async (domain) => {
- try {
- await navigator.clipboard.writeText(domain)
- } catch (error) {
- console.error('复制失败:', error)
- toast.add({
- severity: 'error',
- summary: '复制失败',
- detail: '无法复制到剪贴板',
- life: 3000
- })
- }
- }
- // 获取域名统计数据
- const fetchDomainStatistics = async () => {
- if (!isAdmin.value) return
- try {
- // 获取今日统计数据
- const todayResult = await getTeamDomainDailyStatistics()
- const todayStatsMap = {}
- if (Array.isArray(todayResult)) {
- todayResult.forEach((stat) => {
- todayStatsMap[stat.domain] = {
- todayNewUsers: stat.todayNewUsers || 0,
- todayIncome: stat.todayIncome || 0
- }
- })
- }
- domainStatistics.value = todayStatsMap
- // 获取所有统计数据
- const allResult = await getTeamDomainAllStatistics()
- const allStatsMap = {}
- if (Array.isArray(allResult)) {
- allResult.forEach((stat) => {
- allStatsMap[stat.domain] = {
- totalNewUsers: stat.totalNewUsers || 0,
- totalIncome: stat.totalIncome || 0
- }
- })
- }
- domainAllStatistics.value = allStatsMap
- } catch (error) {
- console.error('获取域名统计数据失败', error)
- domainStatistics.value = {}
- domainAllStatistics.value = {}
- }
- }
- const resetModel = () => {
- domainModel.teamId = currentTeamId.value
- domainModel.domain = ''
- domainModel.description = ''
- }
- const onEdit = async (domain = null) => {
- resetModel()
- if (domain) {
- isEditing.value = true
- selectedDomain.value = domain
- try {
- const detail = await getTeamDomain(domain.id)
- // 填充表单数据
- domainModel.teamId = detail.teamId
- domainModel.domain = detail.domain
- domainModel.description = detail.description
- } catch (error) {
- toast.add({ severity: 'error', summary: '错误', detail: '获取域名详情失败', life: 3000 })
- return
- }
- } else {
- isEditing.value = false
- }
- dialogVisible.value = true
- }
- const onCancel = () => {
- dialogVisible.value = false
- }
- const validateForm = () => {
- if (!domainModel.domain) {
- toast.add({ severity: 'warn', summary: '警告', detail: '域名不能为空', life: 3000 })
- return false
- }
- // 验证域名格式
- const domains = domainModel.domain.split('\n').filter((domain) => domain.trim())
- if (domains.length === 0) {
- toast.add({ severity: 'warn', summary: '警告', detail: '请输入有效的域名', life: 3000 })
- return false
- }
- // 管理员必须指定teamId
- if (isAdmin.value && !domainModel.teamId) {
- toast.add({ severity: 'warn', summary: '警告', detail: '请选择团队', life: 3000 })
- return false
- }
- return true
- }
- const onSubmit = async () => {
- if (!validateForm()) return
- try {
- if (isEditing.value) {
- // 编辑模式:只支持单个域名
- const domainData = {
- domain: domainModel.domain.trim(),
- description: domainModel.description
- }
- // 管理员需要传递teamId
- if (isAdmin.value) {
- domainData.teamId = domainModel.teamId
- }
- await updateTeamDomain(selectedDomain.value.id, domainData)
- toast.add({ severity: 'success', summary: '成功', detail: '更新域名成功', life: 3000 })
- } else {
- // 创建模式:支持批量创建
- const domains = domainModel.domain.split('\n').filter((domain) => domain.trim())
- let successCount = 0
- let failCount = 0
- for (const domain of domains) {
- try {
- const domainData = {
- domain: domain.trim(),
- description: domainModel.description
- }
- // 管理员需要传递teamId
- if (isAdmin.value) {
- domainData.teamId = domainModel.teamId
- }
- await createTeamDomain(domainData)
- successCount++
- } catch (error) {
- console.error(`创建域名 ${domain} 失败:`, error)
- failCount++
- }
- }
- if (successCount > 0) {
- toast.add({
- severity: 'success',
- summary: '成功',
- detail: `成功创建 ${successCount} 个域名${failCount > 0 ? `,${failCount} 个失败` : ''}`,
- life: 3000
- })
- } else {
- toast.add({ severity: 'error', summary: '失败', detail: '所有域名创建失败', life: 3000 })
- }
- }
- dialogVisible.value = false
- refreshData()
- } catch (error) {
- toast.add({
- severity: 'error',
- summary: '错误',
- detail: isEditing.value ? '更新域名失败' : '创建域名失败',
- life: 3000
- })
- }
- }
- const onDelete = async (domain) => {
- confirm.require({
- message: `确定要删除域名 "${domain.domain}" 吗?`,
- header: '删除确认',
- icon: 'pi pi-exclamation-triangle',
- rejectLabel: '取消',
- rejectProps: {
- label: '取消',
- severity: 'secondary'
- },
- acceptLabel: '删除',
- acceptProps: {
- label: '删除',
- severity: 'danger'
- },
- accept: async () => {
- try {
- await deleteTeamDomain(domain.id)
- toast.add({ severity: 'success', summary: '成功', detail: '删除域名成功', life: 3000 })
- refreshData()
- } catch (error) {
- toast.add({ severity: 'error', summary: '错误', detail: '删除域名失败', life: 3000 })
- }
- }
- })
- }
- // 计算团队选项(仅管理员需要)
- const teamOptions = computed(() => {
- if (!isAdmin.value) return []
- return [
- { label: '全部团队', value: null },
- ...teamStore.teams.map((team) => ({
- label: team.name,
- value: team.id
- }))
- ]
- })
- // 获取团队名称
- const getTeamName = (teamId) => {
- if (!teamId) return '-'
- const team = teamStore.teams.find((t) => t.id === teamId)
- return team ? team.name : '-'
- }
- // 计算管理员的分组数据,转换为数组格式
- const adminGroupedList = computed(() => {
- if (!isAdmin.value) return []
- return Object.keys(adminGroupedData.value).map((teamId) => ({
- teamId: parseInt(teamId),
- teamName: getTeamName(parseInt(teamId)),
- domains: adminGroupedData.value[teamId] || []
- }))
- })
- onMounted(() => {
- fetchData()
- })
- </script>
- <template>
- <div class="rounded-lg p-4 bg-[var(--p-content-background)]">
- <!-- 权限检查 -->
- <div v-if="!canView" class="text-center py-8">
- <p class="text-gray-500">您没有权限访问域名管理</p>
- </div>
- <!-- 主要内容 -->
- <div v-else>
- <!-- 操作栏 -->
- <div class="search-toolbar">
- <div class="toolbar-left">
- <Button icon="pi pi-refresh" @click="refreshData" size="small" label="刷新" />
- <Button v-if="canCreate" icon="pi pi-plus" @click="onEdit()" label="添加" size="small" />
- </div>
- <div class="toolbar-right">
- <div class="search-group">
- <InputText
- v-model="searchForm.domain"
- placeholder="域名搜索"
- size="small"
- class="search-field"
- @keyup.enter="handleSearch"
- />
- <Select
- v-if="isAdmin"
- v-model="searchForm.teamId"
- :options="teamOptions"
- optionLabel="label"
- optionValue="value"
- placeholder="选择团队"
- size="small"
- class="team-field"
- clearable
- />
- </div>
- <div class="action-group">
- <Button icon="pi pi-search" @click="handleSearch" label="搜索" size="small" severity="secondary" />
- <Button icon="pi pi-refresh" @click="handleRefresh" label="重置" size="small" />
- </div>
- </div>
- </div>
- <!-- 管理员卡片展示 -->
- <div v-if="isAdmin" class="space-y-6">
- <div v-for="team in adminGroupedList" :key="team.teamId" class="team-card">
- <div class="team-header">
- <h3 class="team-name">{{ team.teamName }}</h3>
- <span class="domain-count">{{ team.domains.length }} 个域名</span>
- </div>
- <div class="domains-grid">
- <div v-for="domain in team.domains" :key="domain.id" class="domain-card">
- <div class="domain-info">
- <div class="domain-name" @click="copyDomain(domain.domain)" :title="'点击复制: ' + domain.domain">
- {{ domain.domain }}
- <i class="pi pi-copy copy-icon"></i>
- </div>
- <div class="domain-description">{{ domain.description || '暂无描述' }}</div>
- <div class="domain-time">创建时间: {{ formatDate(domain.createdAt) }}</div>
- </div>
- <div class="domain-stats-section">
- <div class="domain-stats-row">
- <span class="stat-label">今日:</span>
- <span class="stat-item">
- <i class="pi pi-user text-xs"></i>
- <span class="stat-text">{{ domainStatistics[domain.domain]?.todayNewUsers || 0 }}</span>
- </span>
- <span class="stat-item">
- <i class="pi pi-dollar text-xs"></i>
- <span class="stat-text">{{ (domainStatistics[domain.domain]?.todayIncome || 0).toFixed(2) }}</span>
- </span>
- </div>
- <div class="domain-stats-row">
- <span class="stat-label">总计:</span>
- <span class="stat-item">
- <i class="pi pi-user text-xs"></i>
- <span class="stat-text">{{ domainAllStatistics[domain.domain]?.totalNewUsers || 0 }}</span>
- </span>
- <span class="stat-item">
- <i class="pi pi-dollar text-xs"></i>
- <span class="stat-text">{{
- (domainAllStatistics[domain.domain]?.totalIncome || 0).toFixed(2)
- }}</span>
- </span>
- </div>
- <div class="domain-actions">
- <Button v-if="canUpdate" icon="pi pi-pencil" @click="onEdit(domain)" size="small" text />
- <Button
- v-if="canDelete"
- icon="pi pi-trash"
- @click="onDelete(domain)"
- size="small"
- severity="danger"
- text
- />
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 空数据提示 -->
- <div v-if="adminGroupedList.length === 0" class="text-center py-8 text-gray-500">暂无域名数据</div>
- </div>
- <!-- 其他角色表格展示 -->
- <div v-else>
- <DataTable
- ref="tableRef"
- :value="tableData.data"
- :paginator="true"
- paginatorTemplate="CurrentPageReport FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown JumpToPageInput"
- currentPageReportTemplate="{totalRecords} 条记录 "
- :rows="Number(tableData.meta?.size || 20)"
- :rowsPerPageOptions="[10, 20, 50, 100]"
- :totalRecords="tableData.meta?.total || 0"
- @page="handlePageChange"
- lazy
- scrollable
- stripedRows
- showGridlines
- >
- <Column field="domain" header="域名" style="min-width: 200px" headerClass="font-bold"></Column>
- <Column field="description" header="描述" style="min-width: 250px" headerClass="font-bold">
- <template #body="slotProps">
- <div class="max-w-xl truncate">
- {{ slotProps.data.description || '-' }}
- </div>
- </template>
- </Column>
- <Column field="createdAt" header="创建时间" style="min-width: 150px" headerClass="font-bold">
- <template #body="slotProps">
- {{ formatDate(slotProps.data.createdAt) }}
- </template>
- </Column>
- <Column header="操作" style="min-width: 120px" headerClass="font-bold" align="center">
- <template #body="slotProps">
- <div class="flex gap-2 justify-center">
- <Button v-if="canUpdate" icon="pi pi-pencil" @click="onEdit(slotProps.data)" size="small" />
- <Button
- v-if="canDelete"
- icon="pi pi-trash"
- @click="onDelete(slotProps.data)"
- size="small"
- severity="danger"
- />
- </div>
- </template>
- </Column>
- </DataTable>
- </div>
- <!-- 添加/编辑域名对话框 -->
- <Dialog
- v-model:visible="dialogVisible"
- :modal="true"
- :header="isEditing ? '编辑域名' : '添加域名'"
- :style="{ width: '550px' }"
- >
- <div class="grid grid-cols-1 gap-4 p-4">
- <!-- 管理员选择团队 -->
- <div v-if="isAdmin" class="flex flex-col gap-2">
- <label class="font-medium">选择团队</label>
- <Select
- v-model="domainModel.teamId"
- :options="teamOptions"
- optionLabel="label"
- optionValue="value"
- placeholder="请选择团队"
- :disabled="isEditing"
- :showClear="true"
- />
- <small v-if="!domainModel.teamId" class="text-red-500">请选择团队</small>
- </div>
- <div class="flex flex-col gap-2">
- <label class="font-medium">域名</label>
- <Textarea v-model="domainModel.domain" placeholder="请输入域名,多个域名请换行输入" rows="4" />
- <small v-if="!domainModel.domain" class="text-red-500">请输入域名</small>
- </div>
- <div class="flex flex-col gap-2">
- <label class="font-medium">描述</label>
- <Textarea v-model="domainModel.description" placeholder="请输入域名描述" rows="3" />
- </div>
- </div>
- <template #footer>
- <Button label="取消" icon="pi pi-times" @click="onCancel" class="p-button-text" />
- <Button label="保存" icon="pi pi-check" @click="onSubmit" />
- </template>
- </Dialog>
- </div>
- </div>
- </template>
- <style scoped>
- .team-name-text {
- color: #7c3aed;
- font-weight: 500;
- }
- /* 管理员卡片样式 */
- .team-card {
- background: white;
- border-radius: 12px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- overflow: hidden;
- transition: box-shadow 0.3s ease;
- }
- .team-card:hover {
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
- }
- .team-header {
- background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
- color: #475569;
- padding: 16px 20px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .team-name {
- font-size: 18px;
- font-weight: 600;
- margin: 0;
- }
- .domain-count {
- background: rgba(71, 85, 105, 0.1);
- color: #475569;
- padding: 4px 12px;
- border-radius: 20px;
- font-size: 14px;
- font-weight: 500;
- }
- .domains-grid {
- padding: 20px;
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
- gap: 16px;
- }
- .domain-card {
- background: #f8fafc;
- border: 1px solid #e2e8f0;
- border-radius: 8px;
- padding: 16px;
- display: flex;
- flex-direction: column;
- transition: all 0.3s ease;
- }
- .domain-card:hover {
- background: #f1f5f9;
- border-color: #cbd5e1;
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
- }
- .domain-info {
- flex: 1;
- margin-right: 12px;
- }
- .domain-name {
- font-size: 16px;
- font-weight: 600;
- color: #1e40af;
- margin-bottom: 8px;
- background: #dbeafe;
- padding: 8px 12px;
- border-radius: 6px;
- cursor: pointer;
- display: flex;
- justify-content: space-between;
- align-items: center;
- transition: all 0.3s ease;
- border: 1px solid #bfdbfe;
- }
- .domain-name:hover {
- background: #bfdbfe;
- border-color: #93c5fd;
- transform: translateY(-1px);
- box-shadow: 0 2px 8px rgba(37, 99, 235, 0.15);
- }
- .copy-icon {
- font-size: 14px;
- opacity: 0.6;
- transition: opacity 0.3s ease;
- }
- .domain-name:hover .copy-icon {
- opacity: 1;
- }
- .domain-description {
- font-size: 14px;
- color: #475569;
- margin-bottom: 8px;
- line-height: 1.4;
- background: #f1f5f9;
- padding: 12px;
- border-radius: 6px;
- min-height: 60px;
- display: flex;
- align-items: flex-start;
- }
- .domain-time {
- font-size: 12px;
- color: #94a3b8;
- }
- .domain-actions {
- display: flex;
- gap: 8px;
- flex-shrink: 0;
- }
- .domain-stats-section {
- margin-top: 12px;
- padding-top: 12px;
- border-top: 1px solid #e2e8f0;
- position: relative;
- }
- .domain-stats-row {
- display: flex;
- gap: 12px;
- align-items: center;
- margin-bottom: 6px;
- }
- .domain-stats-row:last-of-type {
- margin-bottom: 12px;
- }
- .domain-actions {
- position: absolute;
- bottom: 0;
- right: 0;
- display: flex;
- gap: 8px;
- }
- .stat-label {
- font-size: 12px;
- color: #475569;
- font-weight: 600;
- }
- .stat-item {
- display: flex;
- align-items: center;
- gap: 4px;
- font-size: 12px;
- color: #64748b;
- }
- .stat-item i {
- color: #94a3b8;
- }
- .stat-text {
- font-weight: 500;
- }
- .domain-actions-bottom .p-button {
- width: 28px;
- height: 28px;
- padding: 0;
- background: transparent !important;
- border: none !important;
- box-shadow: none !important;
- }
- .domain-actions-bottom .p-button:hover {
- background: rgba(0, 0, 0, 0.05) !important;
- }
- .domain-actions-bottom .p-button.p-button-danger:hover {
- background: rgba(239, 68, 68, 0.1) !important;
- }
- /* 搜索工具栏样式 */
- .search-toolbar {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- justify-content: space-between;
- gap: 16px;
- margin-bottom: 20px;
- padding: 16px;
- background: #f8fafc;
- border-radius: 8px;
- border: 1px solid #e2e8f0;
- }
- .toolbar-left {
- display: flex;
- gap: 8px;
- align-items: center;
- }
- .toolbar-left .p-button {
- font-size: 13px;
- padding: 6px 12px;
- border-radius: 6px;
- font-weight: 500;
- }
- .toolbar-right {
- display: flex;
- gap: 12px;
- align-items: center;
- flex-wrap: wrap;
- }
- .search-group {
- display: flex;
- gap: 8px;
- align-items: center;
- }
- .search-field {
- width: 140px;
- font-size: 13px;
- padding: 6px 10px;
- border-radius: 6px;
- border: 1px solid #d1d5db;
- transition: all 0.2s ease;
- }
- .search-field:focus {
- border-color: #3b82f6;
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
- }
- .team-field {
- width: 160px;
- font-size: 13px;
- }
- .team-field .p-select {
- border-radius: 6px;
- }
- .action-group {
- display: flex;
- gap: 8px;
- align-items: center;
- }
- .action-group .p-button {
- font-size: 13px;
- padding: 6px 12px;
- border-radius: 6px;
- font-weight: 500;
- }
- /* 响应式设计 */
- @media (max-width: 768px) {
- .domains-grid {
- grid-template-columns: 1fr;
- }
- .team-header {
- flex-direction: column;
- gap: 8px;
- text-align: center;
- }
- .domain-card {
- flex-direction: column;
- }
- .domain-stats-section {
- position: relative;
- }
- .domain-stats-row {
- justify-content: center;
- }
- .domain-actions {
- position: static;
- justify-content: center;
- margin-top: 8px;
- }
- /* 移动端搜索工具栏适配 */
- .search-toolbar {
- flex-direction: column;
- gap: 12px;
- align-items: stretch;
- padding: 12px;
- }
- .toolbar-left {
- justify-content: center;
- gap: 8px;
- }
- .toolbar-left .p-button {
- flex: 1;
- max-width: 120px;
- font-size: 13px;
- padding: 8px 12px;
- }
- .toolbar-right {
- flex-direction: column;
- gap: 12px;
- align-items: stretch;
- }
- .search-group {
- flex-direction: column;
- gap: 8px;
- }
- .search-field,
- .team-field {
- width: 100%;
- font-size: 14px;
- padding: 10px 12px;
- }
- .action-group {
- justify-content: center;
- gap: 8px;
- }
- .action-group .p-button {
- flex: 1;
- max-width: 140px;
- font-size: 13px;
- padding: 8px 12px;
- }
- }
- </style>
|