mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-26 10:13:48 +02:00
replace el-tabs
This commit is contained in:
@@ -1,21 +1,20 @@
|
||||
<template>
|
||||
<div id="chart" class="x-container">
|
||||
<el-tabs v-model="activeTab" class="charts-tabs">
|
||||
<el-tab-pane :label="t('view.charts.instance_activity.header')" name="instance"></el-tab-pane>
|
||||
<el-tab-pane :label="t('view.charts.mutual_friend.tab_label')" name="mutual"></el-tab-pane>
|
||||
</el-tabs>
|
||||
<div v-show="activeTab === 'instance'">
|
||||
<InstanceActivity />
|
||||
</div>
|
||||
<div v-show="activeTab === 'mutual'">
|
||||
<MutualFriends />
|
||||
</div>
|
||||
<TabsUnderline v-model="activeTab" :items="chartTabs" :unmount-on-hide="false" class="charts-tabs">
|
||||
<template #instance>
|
||||
<InstanceActivity />
|
||||
</template>
|
||||
<template #mutual>
|
||||
<MutualFriends />
|
||||
</template>
|
||||
</TabsUnderline>
|
||||
<el-backtop target="#chart" :right="30" :bottom="30"></el-backtop>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import { computed, defineAsyncComponent } from 'vue';
|
||||
import { TabsUnderline } from '@/components/ui/tabs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -27,10 +26,14 @@
|
||||
const { t } = useI18n();
|
||||
const chartsStore = useChartsStore();
|
||||
const { activeTab } = storeToRefs(chartsStore);
|
||||
const chartTabs = computed(() => [
|
||||
{ value: 'instance', label: t('view.charts.instance_activity.header') },
|
||||
{ value: 'mutual', label: t('view.charts.mutual_friend.tab_label') }
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-tabs__header) {
|
||||
:deep(.charts-tabs [data-slot='tabs-list']) {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<template>
|
||||
<div class="friend-view x-container">
|
||||
<div v-if="settingsReady" class="friend-view__toolbar">
|
||||
<el-segmented v-model="activeSegment" :options="segmentedOptions" />
|
||||
<Tabs v-model="activeSegment" class="friend-view__tabs">
|
||||
<TabsList>
|
||||
<TabsTrigger v-for="option in segmentedOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<div class="friend-view__actions">
|
||||
<InputGroupSearch
|
||||
v-model="searchTerm"
|
||||
class="friend-view__search"
|
||||
placeholder="Search Friend" />
|
||||
<InputGroupSearch v-model="searchTerm" class="friend-view__search" placeholder="Search Friend" />
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div>
|
||||
@@ -161,9 +164,10 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, nextTick, onBeforeMount, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import { Loading } from '@element-plus/icons-vue';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { InputGroupSearch } from '@/components/ui/input-group';
|
||||
import { Loading } from '@element-plus/icons-vue';
|
||||
import { Settings } from 'lucide-vue-next';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -724,6 +728,10 @@
|
||||
padding: 6px 2px 0 2px;
|
||||
}
|
||||
|
||||
.friend-view__tabs {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.friend-view__toolbar--loading {
|
||||
justify-content: flex-end;
|
||||
color: var(--el-text-color-secondary);
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
</div>
|
||||
</TooltipWrapper>
|
||||
</div>
|
||||
<el-tabs type="card">
|
||||
<el-tab-pane :label="t('view.player_list.photon.current')">
|
||||
<TabsUnderline default-value="current" :items="photonTabs" :unmount-on-hide="false">
|
||||
<template #current>
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="currentTable"
|
||||
@@ -47,8 +47,8 @@
|
||||
:total-items="currentTotal"
|
||||
:on-page-size-change="handleCurrentPageSizeChange"
|
||||
style="margin-bottom: 10px" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('view.player_list.photon.previous')">
|
||||
</template>
|
||||
<template #previous>
|
||||
<DataTableLayout
|
||||
class="min-w-0 w-full"
|
||||
:table="previousTable"
|
||||
@@ -58,8 +58,8 @@
|
||||
:total-items="previousTotal"
|
||||
:on-page-size-change="handlePreviousPageSizeChange"
|
||||
style="margin-bottom: 10px" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
</TabsUnderline>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DataTableLayout } from '@/components/ui/data-table';
|
||||
import { InputGroupField } from '@/components/ui/input-group';
|
||||
import { TabsUnderline } from '@/components/ui/tabs';
|
||||
import { localeIncludes } from '@/shared/utils';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -91,6 +92,10 @@
|
||||
const { t } = useI18n();
|
||||
|
||||
const photonStore = usePhotonStore();
|
||||
const photonTabs = computed(() => [
|
||||
{ value: 'current', label: t('view.player_list.photon.current') },
|
||||
{ value: 'previous', label: t('view.player_list.photon.previous') }
|
||||
]);
|
||||
const {
|
||||
photonEventTableTypeFilter,
|
||||
photonEventTableFilter,
|
||||
|
||||
@@ -14,329 +14,341 @@
|
||||
/></Button>
|
||||
</TooltipWrapper>
|
||||
</div>
|
||||
<el-tabs ref="searchTabRef" style="margin-top: 15px" @tab-click="searchText = ''">
|
||||
<el-tab-pane v-loading="isSearchUserLoading" :label="t('view.search.user.header')" style="min-height: 60px">
|
||||
<label class="inline-flex items-center gap-2" style="margin-left: 10px">
|
||||
<Checkbox v-model="searchUserByBio" />
|
||||
<span>{{ t('view.search.user.search_by_bio') }}</span>
|
||||
</label>
|
||||
<label class="inline-flex items-center gap-2" style="margin-left: 10px">
|
||||
<Checkbox v-model="searchUserSortByLastLoggedIn" />
|
||||
<span>{{ t('view.search.user.sort_by_last_logged_in') }}</span>
|
||||
</label>
|
||||
<div class="x-friend-list" style="min-height: 500px">
|
||||
<div
|
||||
v-for="user in searchUserResults"
|
||||
:key="user.id"
|
||||
class="x-friend-item"
|
||||
@click="showUserDialog(user.id)">
|
||||
<div class="avatar">
|
||||
<img :src="userImage(user, true)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="user.displayName"></span>
|
||||
<span
|
||||
v-if="randomUserColours"
|
||||
class="extra"
|
||||
:class="user.$trustClass"
|
||||
v-text="user.$trustLevel"></span>
|
||||
<span
|
||||
v-else
|
||||
class="extra"
|
||||
:style="{ color: user.$userColour }"
|
||||
v-text="user.$trustLevel"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ButtonGroup v-if="searchUserResults.length" style="margin-top: 15px">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="!searchUserParams.offset"
|
||||
@click="handleMoreSearchUser(-1)">
|
||||
<Back />
|
||||
{{ t('view.search.prev_page') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="searchUserResults.length < 10"
|
||||
@click="handleMoreSearchUser(1)">
|
||||
<Right />
|
||||
{{ t('view.search.next_page') }}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane
|
||||
v-loading="isSearchWorldLoading"
|
||||
:label="t('view.search.world.header')"
|
||||
style="min-height: 60px">
|
||||
<div class="inline-flex justify-between mb-4 w-full">
|
||||
<Select
|
||||
:model-value="searchWorldCategoryIndex"
|
||||
@update:modelValue="handleSearchWorldCategorySelect"
|
||||
style="margin-bottom: 15px">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue :placeholder="t('view.search.world.category')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="row in cachedConfig.dynamicWorldRows"
|
||||
:key="row.index"
|
||||
:value="row.index">
|
||||
{{ row.name }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<TabsUnderline
|
||||
v-model="activeSearchTab"
|
||||
:items="searchTabs"
|
||||
aria-label="Search tabs"
|
||||
:unmount-on-hide="false"
|
||||
style="margin-top: 15px">
|
||||
<template #user>
|
||||
<div v-loading="isSearchUserLoading" style="min-height: 60px">
|
||||
<label class="inline-flex items-center gap-2" style="margin-left: 10px">
|
||||
<Checkbox v-model="searchWorldLabs" />
|
||||
<span>{{ t('view.search.world.community_lab') }}</span>
|
||||
<Checkbox v-model="searchUserByBio" />
|
||||
<span>{{ t('view.search.user.search_by_bio') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="x-friend-list" style="min-height: 500px">
|
||||
<div
|
||||
v-for="world in searchWorldResults"
|
||||
:key="world.id"
|
||||
class="x-friend-item"
|
||||
@click="showWorldDialog(world.id)">
|
||||
<div class="avatar">
|
||||
<img :src="world.thumbnailImageUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="world.name"></span>
|
||||
<span v-if="world.occupants" class="extra"
|
||||
>{{ world.authorName }} ({{ world.occupants }})</span
|
||||
>
|
||||
<span v-else class="extra" v-text="world.authorName"></span>
|
||||
<label class="inline-flex items-center gap-2" style="margin-left: 10px">
|
||||
<Checkbox v-model="searchUserSortByLastLoggedIn" />
|
||||
<span>{{ t('view.search.user.sort_by_last_logged_in') }}</span>
|
||||
</label>
|
||||
<div class="x-friend-list" style="min-height: 500px">
|
||||
<div
|
||||
v-for="user in searchUserResults"
|
||||
:key="user.id"
|
||||
class="x-friend-item"
|
||||
@click="showUserDialog(user.id)">
|
||||
<div class="avatar">
|
||||
<img :src="userImage(user, true)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="user.displayName"></span>
|
||||
<span
|
||||
v-if="randomUserColours"
|
||||
class="extra"
|
||||
:class="user.$trustClass"
|
||||
v-text="user.$trustLevel"></span>
|
||||
<span
|
||||
v-else
|
||||
class="extra"
|
||||
:style="{ color: user.$userColour }"
|
||||
v-text="user.$trustLevel"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ButtonGroup v-if="searchUserResults.length" style="margin-top: 15px">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="!searchUserParams.offset"
|
||||
@click="handleMoreSearchUser(-1)">
|
||||
<Back />
|
||||
{{ t('view.search.prev_page') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="searchUserResults.length < 10"
|
||||
@click="handleMoreSearchUser(1)">
|
||||
<Right />
|
||||
{{ t('view.search.next_page') }}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
<ButtonGroup v-if="searchWorldResults.length" style="margin-top: 15px">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="!searchWorldParams.offset"
|
||||
@click="moreSearchWorld(-1)">
|
||||
<Back />
|
||||
{{ t('view.search.prev_page') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="searchWorldResults.length < 10"
|
||||
@click="moreSearchWorld(1)">
|
||||
<Right />
|
||||
{{ t('view.search.next_page') }}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane
|
||||
v-loading="isSearchAvatarLoading"
|
||||
:label="t('view.search.avatar.header')"
|
||||
style="min-height: 60px">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<div style="display: flex; align-items: center">
|
||||
</template>
|
||||
<template #world>
|
||||
<div v-loading="isSearchWorldLoading" style="min-height: 60px">
|
||||
<div class="inline-flex justify-between mb-4 w-full">
|
||||
<Select
|
||||
v-if="avatarRemoteDatabaseProviderList.length > 1"
|
||||
:model-value="avatarRemoteDatabaseProvider"
|
||||
@update:modelValue="setAvatarProvider"
|
||||
style="margin-right: 5px">
|
||||
:model-value="searchWorldCategoryIndex"
|
||||
@update:modelValue="handleSearchWorldCategorySelect"
|
||||
style="margin-bottom: 15px">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue :placeholder="t('view.search.avatar.search_provider')" />
|
||||
<SelectValue :placeholder="t('view.search.world.category')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="provider in avatarRemoteDatabaseProviderList"
|
||||
:key="provider"
|
||||
:value="provider">
|
||||
{{ provider }}
|
||||
v-for="row in cachedConfig.dynamicWorldRows"
|
||||
:key="row.index"
|
||||
:value="row.index">
|
||||
{{ row.name }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<TooltipWrapper side="bottom" :content="t('view.search.avatar.refresh_tooltip')">
|
||||
<Button
|
||||
class="rounded-full ml-1"
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
:disabled="userDialog.isAvatarsLoading"
|
||||
@click="refreshUserDialogAvatars">
|
||||
<Spinner v-if="userDialog.isAvatarsLoading" />
|
||||
<Refresh v-else />
|
||||
</Button>
|
||||
</TooltipWrapper>
|
||||
<span style="font-size: 14px; margin-left: 5px; margin-right: 5px">{{
|
||||
t('view.search.avatar.result_count', {
|
||||
count: searchAvatarResults.length
|
||||
})
|
||||
}}</span>
|
||||
<label class="inline-flex items-center gap-2" style="margin-left: 10px">
|
||||
<Checkbox v-model="searchWorldLabs" />
|
||||
<span>{{ t('view.search.world.community_lab') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<RadioGroup
|
||||
:model-value="searchAvatarFilter"
|
||||
class="flex items-center gap-4"
|
||||
style="margin: 5px"
|
||||
@update:modelValue="handleSearchAvatarFilterChange">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarFilter-all" value="all" />
|
||||
<label for="searchAvatarFilter-all">{{ t('view.search.avatar.all') }}</label>
|
||||
<div class="x-friend-list" style="min-height: 500px">
|
||||
<div
|
||||
v-for="world in searchWorldResults"
|
||||
:key="world.id"
|
||||
class="x-friend-item"
|
||||
@click="showWorldDialog(world.id)">
|
||||
<div class="avatar">
|
||||
<img :src="world.thumbnailImageUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarFilter-public" value="public" />
|
||||
<label for="searchAvatarFilter-public">{{ t('view.search.avatar.public') }}</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarFilter-private" value="private" />
|
||||
<label for="searchAvatarFilter-private">{{ t('view.search.avatar.private') }}</label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<RadioGroup
|
||||
:model-value="searchAvatarFilterRemote"
|
||||
class="flex items-center gap-4"
|
||||
style="margin: 5px"
|
||||
@update:modelValue="handleSearchAvatarFilterRemoteChange">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarFilterRemote-all" value="all" />
|
||||
<label for="searchAvatarFilterRemote-all">{{ t('view.search.avatar.all') }}</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarFilterRemote-local" value="local" />
|
||||
<label for="searchAvatarFilterRemote-local">{{ t('view.search.avatar.local') }}</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem
|
||||
id="searchAvatarFilterRemote-remote"
|
||||
value="remote"
|
||||
:disabled="!avatarRemoteDatabase" />
|
||||
<label for="searchAvatarFilterRemote-remote">{{
|
||||
t('view.search.avatar.remote')
|
||||
}}</label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: end">
|
||||
<RadioGroup
|
||||
:model-value="searchAvatarSort"
|
||||
:disabled="searchAvatarFilterRemote !== 'local'"
|
||||
class="flex items-center gap-4"
|
||||
style="margin: 5px"
|
||||
@update:modelValue="handleSearchAvatarSortChange">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarSort-name" value="name" />
|
||||
<label for="searchAvatarSort-name">{{ t('view.search.avatar.sort_name') }}</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarSort-update" value="update" />
|
||||
<label for="searchAvatarSort-update">{{ t('view.search.avatar.sort_update') }}</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarSort-created" value="created" />
|
||||
<label for="searchAvatarSort-created">{{ t('view.search.avatar.sort_created') }}</label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div class="x-friend-list" style="margin-top: 20px; min-height: 500px">
|
||||
<div
|
||||
v-for="avatar in searchAvatarPage"
|
||||
:key="avatar.id"
|
||||
class="x-friend-item"
|
||||
@click="showAvatarDialog(avatar.id)">
|
||||
<div class="avatar">
|
||||
<img v-if="avatar.thumbnailImageUrl" :src="avatar.thumbnailImageUrl" loading="lazy" />
|
||||
<img v-else-if="avatar.imageUrl" :src="avatar.imageUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="avatar.name"></span>
|
||||
<span
|
||||
v-if="avatar.releaseStatus === 'public'"
|
||||
class="extra"
|
||||
style="color: var(--el-color-success)"
|
||||
v-text="avatar.releaseStatus"></span>
|
||||
<span
|
||||
v-else-if="avatar.releaseStatus === 'private'"
|
||||
class="extra"
|
||||
style="color: var(--el-color-danger)"
|
||||
v-text="avatar.releaseStatus"></span>
|
||||
<span v-else class="extra" v-text="avatar.releaseStatus"></span>
|
||||
<span class="extra" v-text="avatar.authorName"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ButtonGroup v-if="searchAvatarPage.length" style="margin-top: 15px">
|
||||
<Button variant="outline" size="sm" :disabled="!searchAvatarPageNum" @click="moreSearchAvatar(-1)">
|
||||
<Back />
|
||||
{{ t('view.search.prev_page') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="
|
||||
searchAvatarResults.length < 10 ||
|
||||
(searchAvatarPageNum + 1) * 10 >= searchAvatarResults.length
|
||||
"
|
||||
@click="moreSearchAvatar(1)">
|
||||
<Right />
|
||||
{{ t('view.search.next_page') }}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane
|
||||
v-loading="isSearchGroupLoading"
|
||||
:label="t('view.search.group.header')"
|
||||
style="min-height: 60px">
|
||||
<div class="x-friend-list" style="min-height: 500px">
|
||||
<div
|
||||
v-for="group in searchGroupResults"
|
||||
:key="group.id"
|
||||
class="x-friend-item"
|
||||
@click="showGroupDialog(group.id)">
|
||||
<div class="avatar">
|
||||
<img :src="getSmallThumbnailUrl(group.iconUrl)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name">
|
||||
<span v-text="group.name"></span>
|
||||
<span style="margin-left: 5px; font-weight: normal">({{ group.memberCount }})</span>
|
||||
<span
|
||||
style="
|
||||
margin-left: 5px;
|
||||
color: #909399;
|
||||
font-weight: normal;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
"
|
||||
>{{ group.shortCode }}.{{ group.discriminator }}</span
|
||||
<div class="detail">
|
||||
<span class="name" v-text="world.name"></span>
|
||||
<span v-if="world.occupants" class="extra"
|
||||
>{{ world.authorName }} ({{ world.occupants }})</span
|
||||
>
|
||||
</span>
|
||||
<span class="extra" v-text="group.description"></span>
|
||||
<span v-else class="extra" v-text="world.authorName"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ButtonGroup v-if="searchWorldResults.length" style="margin-top: 15px">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="!searchWorldParams.offset"
|
||||
@click="moreSearchWorld(-1)">
|
||||
<Back />
|
||||
{{ t('view.search.prev_page') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="searchWorldResults.length < 10"
|
||||
@click="moreSearchWorld(1)">
|
||||
<Right />
|
||||
{{ t('view.search.next_page') }}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
<ButtonGroup v-if="searchGroupResults.length" style="margin-top: 15px">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="!searchGroupParams.offset"
|
||||
@click="moreSearchGroup(-1)">
|
||||
<Back />
|
||||
{{ t('view.search.prev_page') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="searchGroupResults.length < 10"
|
||||
@click="moreSearchGroup(1)">
|
||||
<Right />
|
||||
{{ t('view.search.next_page') }}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
<template #avatar>
|
||||
<div v-loading="isSearchAvatarLoading" style="min-height: 60px">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between">
|
||||
<div style="display: flex; align-items: center">
|
||||
<Select
|
||||
v-if="avatarRemoteDatabaseProviderList.length > 1"
|
||||
:model-value="avatarRemoteDatabaseProvider"
|
||||
@update:modelValue="setAvatarProvider"
|
||||
style="margin-right: 5px">
|
||||
<SelectTrigger size="sm">
|
||||
<SelectValue :placeholder="t('view.search.avatar.search_provider')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem
|
||||
v-for="provider in avatarRemoteDatabaseProviderList"
|
||||
:key="provider"
|
||||
:value="provider">
|
||||
{{ provider }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<TooltipWrapper side="bottom" :content="t('view.search.avatar.refresh_tooltip')">
|
||||
<Button
|
||||
class="rounded-full ml-1"
|
||||
variant="outline"
|
||||
size="icon-sm"
|
||||
:disabled="userDialog.isAvatarsLoading"
|
||||
@click="refreshUserDialogAvatars">
|
||||
<Spinner v-if="userDialog.isAvatarsLoading" />
|
||||
<Refresh v-else />
|
||||
</Button>
|
||||
</TooltipWrapper>
|
||||
<span style="font-size: 14px; margin-left: 5px; margin-right: 5px">{{
|
||||
t('view.search.avatar.result_count', {
|
||||
count: searchAvatarResults.length
|
||||
})
|
||||
}}</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<RadioGroup
|
||||
:model-value="searchAvatarFilter"
|
||||
class="flex items-center gap-4"
|
||||
style="margin: 5px"
|
||||
@update:modelValue="handleSearchAvatarFilterChange">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarFilter-all" value="all" />
|
||||
<label for="searchAvatarFilter-all">{{ t('view.search.avatar.all') }}</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarFilter-public" value="public" />
|
||||
<label for="searchAvatarFilter-public">{{ t('view.search.avatar.public') }}</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarFilter-private" value="private" />
|
||||
<label for="searchAvatarFilter-private">{{
|
||||
t('view.search.avatar.private')
|
||||
}}</label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<RadioGroup
|
||||
:model-value="searchAvatarFilterRemote"
|
||||
class="flex items-center gap-4"
|
||||
style="margin: 5px"
|
||||
@update:modelValue="handleSearchAvatarFilterRemoteChange">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarFilterRemote-all" value="all" />
|
||||
<label for="searchAvatarFilterRemote-all">{{ t('view.search.avatar.all') }}</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarFilterRemote-local" value="local" />
|
||||
<label for="searchAvatarFilterRemote-local">{{
|
||||
t('view.search.avatar.local')
|
||||
}}</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem
|
||||
id="searchAvatarFilterRemote-remote"
|
||||
value="remote"
|
||||
:disabled="!avatarRemoteDatabase" />
|
||||
<label for="searchAvatarFilterRemote-remote">{{
|
||||
t('view.search.avatar.remote')
|
||||
}}</label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: end">
|
||||
<RadioGroup
|
||||
:model-value="searchAvatarSort"
|
||||
:disabled="searchAvatarFilterRemote !== 'local'"
|
||||
class="flex items-center gap-4"
|
||||
style="margin: 5px"
|
||||
@update:modelValue="handleSearchAvatarSortChange">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarSort-name" value="name" />
|
||||
<label for="searchAvatarSort-name">{{ t('view.search.avatar.sort_name') }}</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarSort-update" value="update" />
|
||||
<label for="searchAvatarSort-update">{{ t('view.search.avatar.sort_update') }}</label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem id="searchAvatarSort-created" value="created" />
|
||||
<label for="searchAvatarSort-created">{{ t('view.search.avatar.sort_created') }}</label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div class="x-friend-list" style="margin-top: 20px; min-height: 500px">
|
||||
<div
|
||||
v-for="avatar in searchAvatarPage"
|
||||
:key="avatar.id"
|
||||
class="x-friend-item"
|
||||
@click="showAvatarDialog(avatar.id)">
|
||||
<div class="avatar">
|
||||
<img v-if="avatar.thumbnailImageUrl" :src="avatar.thumbnailImageUrl" loading="lazy" />
|
||||
<img v-else-if="avatar.imageUrl" :src="avatar.imageUrl" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name" v-text="avatar.name"></span>
|
||||
<span
|
||||
v-if="avatar.releaseStatus === 'public'"
|
||||
class="extra"
|
||||
style="color: var(--el-color-success)"
|
||||
v-text="avatar.releaseStatus"></span>
|
||||
<span
|
||||
v-else-if="avatar.releaseStatus === 'private'"
|
||||
class="extra"
|
||||
style="color: var(--el-color-danger)"
|
||||
v-text="avatar.releaseStatus"></span>
|
||||
<span v-else class="extra" v-text="avatar.releaseStatus"></span>
|
||||
<span class="extra" v-text="avatar.authorName"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ButtonGroup v-if="searchAvatarPage.length" style="margin-top: 15px">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="!searchAvatarPageNum"
|
||||
@click="moreSearchAvatar(-1)">
|
||||
<Back />
|
||||
{{ t('view.search.prev_page') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="
|
||||
searchAvatarResults.length < 10 ||
|
||||
(searchAvatarPageNum + 1) * 10 >= searchAvatarResults.length
|
||||
"
|
||||
@click="moreSearchAvatar(1)">
|
||||
<Right />
|
||||
{{ t('view.search.next_page') }}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</template>
|
||||
<template #group>
|
||||
<div v-loading="isSearchGroupLoading" style="min-height: 60px">
|
||||
<div class="x-friend-list" style="min-height: 500px">
|
||||
<div
|
||||
v-for="group in searchGroupResults"
|
||||
:key="group.id"
|
||||
class="x-friend-item"
|
||||
@click="showGroupDialog(group.id)">
|
||||
<div class="avatar">
|
||||
<img :src="getSmallThumbnailUrl(group.iconUrl)" loading="lazy" />
|
||||
</div>
|
||||
<div class="detail">
|
||||
<span class="name">
|
||||
<span v-text="group.name"></span>
|
||||
<span style="margin-left: 5px; font-weight: normal">({{ group.memberCount }})</span>
|
||||
<span
|
||||
style="
|
||||
margin-left: 5px;
|
||||
color: #909399;
|
||||
font-weight: normal;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
"
|
||||
>{{ group.shortCode }}.{{ group.discriminator }}</span
|
||||
>
|
||||
</span>
|
||||
<span class="extra" v-text="group.description"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ButtonGroup v-if="searchGroupResults.length" style="margin-top: 15px">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="!searchGroupParams.offset"
|
||||
@click="moreSearchGroup(-1)">
|
||||
<Back />
|
||||
{{ t('view.search.prev_page') }}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="searchGroupResults.length < 10"
|
||||
@click="moreSearchGroup(1)">
|
||||
<Right />
|
||||
{{ t('view.search.next_page') }}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</template>
|
||||
</TabsUnderline>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -344,13 +356,14 @@
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Back, Refresh, Right } from '@element-plus/icons-vue';
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||||
import { computed, ref } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ButtonGroup } from '@/components/ui/button-group';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { InputGroupField } from '@/components/ui/input-group';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { TabsUnderline } from '@/components/ui/tabs';
|
||||
import { Trash2 } from 'lucide-vue-next';
|
||||
import { ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -390,7 +403,13 @@
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const searchTabRef = ref(null);
|
||||
const activeSearchTab = ref('user');
|
||||
const searchTabs = computed(() => [
|
||||
{ value: 'user', label: t('view.search.user.header') },
|
||||
{ value: 'world', label: t('view.search.world.header') },
|
||||
{ value: 'avatar', label: t('view.search.avatar.header') },
|
||||
{ value: 'group', label: t('view.search.group.header') }
|
||||
]);
|
||||
|
||||
const searchUserParams = ref({});
|
||||
const searchUserByBio = ref(false);
|
||||
@@ -453,18 +472,23 @@
|
||||
searchText.value = text;
|
||||
}
|
||||
|
||||
function handleSearchTabChange(tabName) {
|
||||
searchText.value = '';
|
||||
activeSearchTab.value = tabName;
|
||||
}
|
||||
|
||||
function search() {
|
||||
switch (searchTabRef.value.currentName) {
|
||||
case '0':
|
||||
switch (activeSearchTab.value) {
|
||||
case 'user':
|
||||
searchUser();
|
||||
break;
|
||||
case '1':
|
||||
case 'world':
|
||||
searchWorld({});
|
||||
break;
|
||||
case '2':
|
||||
case 'avatar':
|
||||
searchAvatar();
|
||||
break;
|
||||
case '3':
|
||||
case 'group':
|
||||
searchGroup();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3,34 +3,39 @@
|
||||
<div class="options-container" style="margin-top: 0; padding: 5px">
|
||||
<span class="header">{{ t('view.settings.header') }}</span>
|
||||
</div>
|
||||
<el-tabs style="height: calc(100% - 51px)">
|
||||
<el-tab-pane :label="t('view.settings.category.general')">
|
||||
<TabsUnderline
|
||||
default-value="general"
|
||||
:items="settingsTabs"
|
||||
:unmount-on-hide="false"
|
||||
style="height: calc(100% - 51px)">
|
||||
<template #general>
|
||||
<GeneralTab />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane lazy :label="t('view.settings.category.appearance')">
|
||||
</template>
|
||||
<template #appearance>
|
||||
<AppearanceTab />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane lazy :label="t('view.settings.category.notifications')">
|
||||
</template>
|
||||
<template #notifications>
|
||||
<NotificationsTab />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane lazy :label="t('view.settings.category.wrist_overlay')">
|
||||
</template>
|
||||
<template #wrist-overlay>
|
||||
<WristOverlayTab />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane lazy :label="t('view.settings.category.discord_presence')">
|
||||
</template>
|
||||
<template #discord>
|
||||
<DiscordPresenceTab />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane lazy :label="t('view.settings.category.pictures')">
|
||||
</template>
|
||||
<template #pictures>
|
||||
<PicturesTab />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane lazy :label="t('view.settings.category.advanced')">
|
||||
</template>
|
||||
<template #advanced>
|
||||
<AdvancedTab />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
</TabsUnderline>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onBeforeMount } from 'vue';
|
||||
import { computed, onBeforeMount } from 'vue';
|
||||
import { TabsUnderline } from '@/components/ui/tabs';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import AdvancedTab from './components/Tabs/AdvancedTab.vue';
|
||||
@@ -42,6 +47,15 @@
|
||||
import WristOverlayTab from './components/Tabs/WristOverlayTab.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const settingsTabs = computed(() => [
|
||||
{ value: 'general', label: t('view.settings.category.general') },
|
||||
{ value: 'appearance', label: t('view.settings.category.appearance') },
|
||||
{ value: 'notifications', label: t('view.settings.category.notifications') },
|
||||
{ value: 'wrist-overlay', label: t('view.settings.category.wrist_overlay') },
|
||||
{ value: 'discord', label: t('view.settings.category.discord_presence') },
|
||||
{ value: 'pictures', label: t('view.settings.category.pictures') },
|
||||
{ value: 'advanced', label: t('view.settings.category.advanced') }
|
||||
]);
|
||||
|
||||
onBeforeMount(() => {
|
||||
const menuItem = document.querySelector('li[role="menuitem"].is-active');
|
||||
|
||||
@@ -73,33 +73,44 @@
|
||||
</TooltipWrapper>
|
||||
</div>
|
||||
</div>
|
||||
<el-tabs class="zero-margin-tabs" stretch style="height: calc(100% - 70px); margin-top: 5px">
|
||||
<el-tab-pane>
|
||||
<template #label>
|
||||
<span>{{ t('side_panel.friends') }}</span>
|
||||
<span class="sidebar-tab-count"> ({{ onlineFriendCount }}/{{ friends.size }}) </span>
|
||||
</template>
|
||||
<el-backtop target=".zero-margin-tabs .el-tabs__content" :bottom="20" :right="20"></el-backtop>
|
||||
<FriendsSidebar @confirm-delete-friend="confirmDeleteFriend" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane lazy>
|
||||
<template #label>
|
||||
<span>{{ t('side_panel.groups') }}</span>
|
||||
<span class="sidebar-tab-count"> ({{ groupInstances.length }}) </span>
|
||||
</template>
|
||||
<GroupsSidebar :group-instances="groupInstances" :group-order="inGameGroupOrder" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<TabsUnderline
|
||||
default-value="friends"
|
||||
:items="sidebarTabs"
|
||||
:unmount-on-hide="false"
|
||||
variant="equal"
|
||||
class="zero-margin-tabs"
|
||||
style="height: calc(100% - 70px); margin-top: 5px">
|
||||
<template #label-friends>
|
||||
<span>{{ t('side_panel.friends') }}</span>
|
||||
<span class="sidebar-tab-count"> ({{ onlineFriendCount }}/{{ friends.size }}) </span>
|
||||
</template>
|
||||
<template #label-groups>
|
||||
<span>{{ t('side_panel.groups') }}</span>
|
||||
<span class="sidebar-tab-count"> ({{ groupInstances.length }}) </span>
|
||||
</template>
|
||||
<template #friends>
|
||||
<div class="el-tabs__content">
|
||||
<el-backtop target=".zero-margin-tabs .el-tabs__content" :bottom="20" :right="20"></el-backtop>
|
||||
<FriendsSidebar @confirm-delete-friend="confirmDeleteFriend" />
|
||||
</div>
|
||||
</template>
|
||||
<template #groups>
|
||||
<div class="el-tabs__content">
|
||||
<GroupsSidebar :group-instances="groupInstances" :group-order="inGameGroupOrder" />
|
||||
</div>
|
||||
</template>
|
||||
</TabsUnderline>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { ref, watch } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Refresh } from '@element-plus/icons-vue';
|
||||
import { Spinner } from '@/components/ui/spinner';
|
||||
import { TabsUnderline } from '@/components/ui/tabs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -115,6 +126,10 @@
|
||||
const { quickSearchItems } = storeToRefs(useSearchStore());
|
||||
const { inGameGroupOrder, groupInstances } = storeToRefs(useGroupStore());
|
||||
const { t } = useI18n();
|
||||
const sidebarTabs = computed(() => [
|
||||
{ value: 'friends', label: t('side_panel.friends') },
|
||||
{ value: 'groups', label: t('side_panel.groups') }
|
||||
]);
|
||||
|
||||
const quickSearchQuery = ref('');
|
||||
const isQuickSearchOpen = ref(false);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,40 +5,40 @@
|
||||
:title="t('dialog.edit_invite_messages.header')"
|
||||
width="1000px"
|
||||
@close="closeDialog">
|
||||
<el-tabs v-model="activeTab" style="margin-top: 10px">
|
||||
<el-tab-pane :label="t('dialog.edit_invite_messages.invite_message_tab')" name="message">
|
||||
<TabsUnderline v-model="activeTab" :items="editInviteTabs" :unmount-on-hide="false" class="mt-2.5">
|
||||
<template #message>
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
:table="inviteMessageTanstackTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleEditInviteMessageRowClick" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('dialog.edit_invite_messages.invite_request_tab')" name="request">
|
||||
</template>
|
||||
<template #request>
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
:table="inviteRequestTanstackTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleEditInviteMessageRowClick" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('dialog.edit_invite_messages.invite_request_response_tab')" name="requestResponse">
|
||||
</template>
|
||||
<template #requestResponse>
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
:table="inviteRequestResponseTanstackTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleEditInviteMessageRowClick" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('dialog.edit_invite_messages.invite_response_tab')" name="response">
|
||||
</template>
|
||||
<template #response>
|
||||
<DataTableLayout
|
||||
style="margin-top: 10px; cursor: pointer"
|
||||
:table="inviteResponseTanstackTable"
|
||||
:loading="false"
|
||||
:show-pagination="false"
|
||||
:on-row-click="handleEditInviteMessageRowClick" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
</TabsUnderline>
|
||||
</el-dialog>
|
||||
<template v-if="isEditInviteMessagesDialogVisible">
|
||||
<EditInviteMessageDialog
|
||||
@@ -52,6 +52,7 @@
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { DataTableLayout } from '@/components/ui/data-table';
|
||||
import { TabsUnderline } from '@/components/ui/tabs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { toast } from 'vue-sonner';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -82,6 +83,12 @@
|
||||
});
|
||||
|
||||
const activeTab = ref('message');
|
||||
const editInviteTabs = computed(() => [
|
||||
{ value: 'message', label: t('dialog.edit_invite_messages.invite_message_tab') },
|
||||
{ value: 'request', label: t('dialog.edit_invite_messages.invite_request_tab') },
|
||||
{ value: 'requestResponse', label: t('dialog.edit_invite_messages.invite_request_response_tab') },
|
||||
{ value: 'response', label: t('dialog.edit_invite_messages.invite_response_tab') }
|
||||
]);
|
||||
|
||||
const isEditInviteMessageDialogVisible = ref(false);
|
||||
const inviteMessage = ref({});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<el-dialog :title="t('dialog.export_friends_list.header')" v-model="isVisible" width="650px">
|
||||
<el-tabs>
|
||||
<el-tab-pane :label="t('dialog.export_friends_list.csv')">
|
||||
<TabsUnderline default-value="csv" :items="exportFriendsTabs" :unmount-on-hide="false" class="mt-2.5">
|
||||
<template #csv>
|
||||
<InputGroupTextareaField
|
||||
v-model="exportFriendsListCsv"
|
||||
:rows="15"
|
||||
@@ -9,8 +9,8 @@
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none"
|
||||
@click="$event.target.tagName === 'TEXTAREA' && $event.target.select()" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="t('dialog.export_friends_list.json')">
|
||||
</template>
|
||||
<template #json>
|
||||
<InputGroupTextareaField
|
||||
v-model="exportFriendsListJson"
|
||||
:rows="15"
|
||||
@@ -18,15 +18,16 @@
|
||||
style="margin-top: 15px"
|
||||
input-class="resize-none"
|
||||
@click="$event.target.tagName === 'TEXTAREA' && $event.target.select()" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
</TabsUnderline>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { InputGroupTextareaField } from '@/components/ui/input-group';
|
||||
import { TabsUnderline } from '@/components/ui/tabs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { useUserStore } from '../../../stores';
|
||||
@@ -49,6 +50,10 @@
|
||||
|
||||
const exportFriendsListCsv = ref('');
|
||||
const exportFriendsListJson = ref('');
|
||||
const exportFriendsTabs = computed(() => [
|
||||
{ value: 'csv', label: t('dialog.export_friends_list.csv') },
|
||||
{ value: 'json', label: t('dialog.export_friends_list.json') }
|
||||
]);
|
||||
|
||||
const isVisible = computed({
|
||||
get() {
|
||||
|
||||
Reference in New Issue
Block a user