refactor(components): 重构核心组件代码块顺序
- 调整App.vue和所有通用组件为template→script→style顺序 - 涉及NovaDialog、UserCenter、IconSelect等核心组件 - 统一组件结构,提升代码可读性和维护性 - 符合项目新的Vue组件代码块排布规范
This commit is contained in:
parent
696c8b1417
commit
797c42aea1
22
src/App.vue
22
src/App.vue
@ -1,3 +1,14 @@
|
||||
<template>
|
||||
<n-config-provider
|
||||
class="wh-full" inline-theme-disabled :theme="appStore.colorMode === 'dark' ? darkTheme : null"
|
||||
:locale="naiveLocale.locale" :date-locale="naiveLocale.dateLocale" :theme-overrides="appStore.theme"
|
||||
>
|
||||
<naive-provider>
|
||||
<router-view />
|
||||
</naive-provider>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { naiveI18nOptions } from '@/utils'
|
||||
import { darkTheme } from 'naive-ui'
|
||||
@ -10,14 +21,3 @@ const naiveLocale = computed(() => {
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-config-provider
|
||||
class="wh-full" inline-theme-disabled :theme="appStore.colorMode === 'dark' ? darkTheme : null"
|
||||
:locale="naiveLocale.locale" :date-locale="naiveLocale.dateLocale" :theme-overrides="appStore.theme"
|
||||
>
|
||||
<naive-provider>
|
||||
<router-view />
|
||||
</naive-provider>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
|
||||
@ -1,6 +1,3 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<naive-provider>
|
||||
<div id="loading-container">
|
||||
@ -34,6 +31,9 @@
|
||||
</naive-provider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#loading-container {
|
||||
width: 100vw;
|
||||
|
||||
@ -1,6 +1,3 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-el
|
||||
tag="div"
|
||||
@ -16,6 +13,9 @@
|
||||
</n-el>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el {
|
||||
color: var(--n-text-color);
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
<template>
|
||||
<n-popselect :value="appStore.storeColorMode" :render-label="renderLabel" :options="options" trigger="click" @update:value="appStore.setColorMode">
|
||||
<CommonWrapper>
|
||||
<icon-park-outline-moon v-if="appStore.storeColorMode === 'dark'" />
|
||||
<icon-park-outline-sun-one v-if="appStore.storeColorMode === 'light'" />
|
||||
<icon-park-outline-laptop-computer v-if="appStore.storeColorMode === 'auto'" />
|
||||
</CommonWrapper>
|
||||
</n-popselect>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from '@/store'
|
||||
import IconAuto from '~icons/icon-park-outline/laptop-computer'
|
||||
@ -39,14 +49,4 @@ function renderLabel(option: any) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-popselect :value="appStore.storeColorMode" :render-label="renderLabel" :options="options" trigger="click" @update:value="appStore.setColorMode">
|
||||
<CommonWrapper>
|
||||
<icon-park-outline-moon v-if="appStore.storeColorMode === 'dark'" />
|
||||
<icon-park-outline-sun-one v-if="appStore.storeColorMode === 'light'" />
|
||||
<icon-park-outline-laptop-computer v-if="appStore.storeColorMode === 'auto'" />
|
||||
</CommonWrapper>
|
||||
</n-popselect>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@ -1,11 +1,3 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
/** 异常类型 403 404 500 */
|
||||
type: '403' | '404' | '500'
|
||||
}>()
|
||||
const router = useRouter()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-col-center h-full">
|
||||
<img
|
||||
@ -34,3 +26,11 @@ const router = useRouter()
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
/** 异常类型 403 404 500 */
|
||||
type: '403' | '404' | '500'
|
||||
}>()
|
||||
const router = useRouter()
|
||||
</script>
|
||||
|
||||
@ -1,11 +1,3 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
message: string
|
||||
}
|
||||
|
||||
const { message } = defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-tooltip :show-arrow="false" trigger="hover">
|
||||
<template #trigger>
|
||||
@ -14,3 +6,11 @@ const { message } = defineProps<Props>()
|
||||
{{ message }}
|
||||
</n-tooltip>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
message: string
|
||||
}
|
||||
|
||||
const { message } = defineProps<Props>()
|
||||
</script>
|
||||
|
||||
@ -1,3 +1,69 @@
|
||||
<template>
|
||||
<n-input-group disabled>
|
||||
<n-button v-if="value" :disabled="disabled" type="primary">
|
||||
<template #icon>
|
||||
<nova-icon :icon="value" />
|
||||
</template>
|
||||
</n-button>
|
||||
<n-input :value="value" readonly :placeholder="$t('components.iconSelector.inputPlaceholder')" />
|
||||
<n-button type="primary" ghost :disabled="disabled" @click="showModal = true">
|
||||
{{ $t('common.choose') }}
|
||||
</n-button>
|
||||
</n-input-group>
|
||||
<n-modal
|
||||
v-model:show="showModal" preset="card" :title="$t('components.iconSelector.selectorTitle')" size="small" class="w-800px" :bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-button type="warning" size="small" ghost @click="clearIcon">
|
||||
{{ $t('components.iconSelector.clearIcon') }}
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
<n-tabs :value="currentTab" type="line" animated placement="left" @update:value="handleChangeTab">
|
||||
<n-tab-pane v-for="(list, index) in iconList" :key="list.prefix" :name="index" :tab="list.title">
|
||||
<n-flex vertical>
|
||||
<n-flex size="small">
|
||||
<n-tag
|
||||
v-for="(_v, k) in list.categories" :key="k"
|
||||
:checked="currentTag === k" round checkable size="small"
|
||||
@update:checked="handleSelectIconTag(k)"
|
||||
>
|
||||
{{ k }}
|
||||
</n-tag>
|
||||
</n-flex>
|
||||
|
||||
<n-input
|
||||
v-model:value="searchValue" type="text" clearable
|
||||
:placeholder="$t('components.iconSelector.searchPlaceholder')"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<n-flex :size="2">
|
||||
<n-el
|
||||
v-for="(icon) in visibleIcons" :key="icon"
|
||||
class="hover:(text-[var(--primary-color)] ring-1) ring-[var(--primary-color)] p-1 rounded flex-center"
|
||||
:title="`${list.prefix}:${icon}`"
|
||||
@click="handleSelectIcon(`${list.prefix}:${icon}`)"
|
||||
>
|
||||
<nova-icon :icon="`${list.prefix}:${icon}`" :size="24" />
|
||||
</n-el>
|
||||
<n-empty v-if="visibleIcons.length === 0" class="w-full" />
|
||||
</n-flex>
|
||||
</div>
|
||||
|
||||
<n-flex justify="center">
|
||||
<n-pagination
|
||||
v-model:page="currentPage"
|
||||
:item-count="filteredIcons.length"
|
||||
:page-size="200"
|
||||
/>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
disabled?: boolean
|
||||
@ -121,68 +187,5 @@ function clearIcon() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-input-group disabled>
|
||||
<n-button v-if="value" :disabled="disabled" type="primary">
|
||||
<template #icon>
|
||||
<nova-icon :icon="value" />
|
||||
</template>
|
||||
</n-button>
|
||||
<n-input :value="value" readonly :placeholder="$t('components.iconSelector.inputPlaceholder')" />
|
||||
<n-button type="primary" ghost :disabled="disabled" @click="showModal = true">
|
||||
{{ $t('common.choose') }}
|
||||
</n-button>
|
||||
</n-input-group>
|
||||
<n-modal
|
||||
v-model:show="showModal" preset="card" :title="$t('components.iconSelector.selectorTitle')" size="small" class="w-800px" :bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-button type="warning" size="small" ghost @click="clearIcon">
|
||||
{{ $t('components.iconSelector.clearIcon') }}
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
<n-tabs :value="currentTab" type="line" animated placement="left" @update:value="handleChangeTab">
|
||||
<n-tab-pane v-for="(list, index) in iconList" :key="list.prefix" :name="index" :tab="list.title">
|
||||
<n-flex vertical>
|
||||
<n-flex size="small">
|
||||
<n-tag
|
||||
v-for="(_v, k) in list.categories" :key="k"
|
||||
:checked="currentTag === k" round checkable size="small"
|
||||
@update:checked="handleSelectIconTag(k)"
|
||||
>
|
||||
{{ k }}
|
||||
</n-tag>
|
||||
</n-flex>
|
||||
|
||||
<n-input
|
||||
v-model:value="searchValue" type="text" clearable
|
||||
:placeholder="$t('components.iconSelector.searchPlaceholder')"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<n-flex :size="2">
|
||||
<n-el
|
||||
v-for="(icon) in visibleIcons" :key="icon"
|
||||
class="hover:(text-[var(--primary-color)] ring-1) ring-[var(--primary-color)] p-1 rounded flex-center"
|
||||
:title="`${list.prefix}:${icon}`"
|
||||
@click="handleSelectIcon(`${list.prefix}:${icon}`)"
|
||||
>
|
||||
<nova-icon :icon="`${list.prefix}:${icon}`" :size="24" />
|
||||
</n-el>
|
||||
<n-empty v-if="visibleIcons.length === 0" class="w-full" />
|
||||
</n-flex>
|
||||
</div>
|
||||
|
||||
<n-flex justify="center">
|
||||
<n-pagination
|
||||
v-model:page="currentPage"
|
||||
:item-count="filteredIcons.length"
|
||||
:page-size="200"
|
||||
/>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-modal>
|
||||
</template>
|
||||
<style scoped>
|
||||
</style>
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
<template>
|
||||
<n-popselect :value="appStore.lang" :options="options" trigger="click" @update:value="appStore.setAppLang">
|
||||
<CommonWrapper>
|
||||
<icon-park-outline-translate />
|
||||
</CommonWrapper>
|
||||
</n-popselect>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from '@/store'
|
||||
|
||||
@ -14,12 +22,4 @@ const options = [
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-popselect :value="appStore.lang" :options="options" trigger="click" @update:value="appStore.setAppLang">
|
||||
<CommonWrapper>
|
||||
<icon-park-outline-translate />
|
||||
</CommonWrapper>
|
||||
</n-popselect>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@ -1,3 +1,16 @@
|
||||
<template>
|
||||
<n-loading-bar-provider>
|
||||
<n-dialog-provider>
|
||||
<n-notification-provider>
|
||||
<n-message-provider>
|
||||
<slot />
|
||||
<NaiveProviderContent />
|
||||
</n-message-provider>
|
||||
</n-notification-provider>
|
||||
</n-dialog-provider>
|
||||
</n-loading-bar-provider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useDialog, useLoadingBar, useMessage, useNotification } from 'naive-ui'
|
||||
|
||||
@ -20,17 +33,4 @@ const NaiveProviderContent = defineComponent({
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-loading-bar-provider>
|
||||
<n-dialog-provider>
|
||||
<n-notification-provider>
|
||||
<n-message-provider>
|
||||
<slot />
|
||||
<NaiveProviderContent />
|
||||
</n-message-provider>
|
||||
</n-notification-provider>
|
||||
</n-dialog-provider>
|
||||
</n-loading-bar-provider>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@ -1,3 +1,66 @@
|
||||
<template>
|
||||
<n-modal
|
||||
v-model:show="visible"
|
||||
:mask-closable="false"
|
||||
:close-on-esc="false"
|
||||
:auto-focus="autoFocus"
|
||||
preset="card"
|
||||
:loading="confirmLoading"
|
||||
:show-icon="false"
|
||||
:style="{
|
||||
width: typeof width === 'number' ? `${width}px` : width,
|
||||
}"
|
||||
class="nova-dialog"
|
||||
@close="handleClose"
|
||||
>
|
||||
<!-- 头部插槽 -->
|
||||
<template #header>
|
||||
<div v-if="$slots.header" class="nova-dialog-custom-header">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<div v-else class="nova-dialog-header">
|
||||
<div class="nova-dialog-title">
|
||||
{{ title }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div
|
||||
class="nova-dialog-content"
|
||||
:style="{
|
||||
height: fullscreen ? 'auto' : (typeof height === 'number' ? `${height}px` : height),
|
||||
overflow: height === 'auto' ? 'visible' : 'auto',
|
||||
}"
|
||||
>
|
||||
<slot name="content" />
|
||||
</div>
|
||||
|
||||
<!-- 底部操作区域 -->
|
||||
<template #footer>
|
||||
<div v-if="!footerHidden" class="nova-dialog-footer">
|
||||
<NSpace justify="center">
|
||||
<NButton
|
||||
size="medium"
|
||||
@click="handleCancel"
|
||||
>
|
||||
{{ cancelText }}
|
||||
</NButton>
|
||||
<NButton
|
||||
type="primary"
|
||||
size="medium"
|
||||
:loading="confirmLoading"
|
||||
@click="handleConfirm"
|
||||
>
|
||||
{{ confirmText }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</div>
|
||||
<div v-else />
|
||||
</template>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, toRefs } from 'vue'
|
||||
import { NButton, NSpace } from 'naive-ui'
|
||||
@ -93,69 +156,6 @@ defineExpose({
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal
|
||||
v-model:show="visible"
|
||||
:mask-closable="false"
|
||||
:close-on-esc="false"
|
||||
:auto-focus="autoFocus"
|
||||
preset="card"
|
||||
:loading="confirmLoading"
|
||||
:show-icon="false"
|
||||
:style="{
|
||||
width: typeof width === 'number' ? `${width}px` : width,
|
||||
}"
|
||||
class="nova-dialog"
|
||||
@close="handleClose"
|
||||
>
|
||||
<!-- 头部插槽 -->
|
||||
<template #header>
|
||||
<div v-if="$slots.header" class="nova-dialog-custom-header">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<div v-else class="nova-dialog-header">
|
||||
<div class="nova-dialog-title">
|
||||
{{ title }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div
|
||||
class="nova-dialog-content"
|
||||
:style="{
|
||||
height: fullscreen ? 'auto' : (typeof height === 'number' ? `${height}px` : height),
|
||||
overflow: height === 'auto' ? 'visible' : 'auto',
|
||||
}"
|
||||
>
|
||||
<slot name="content" />
|
||||
</div>
|
||||
|
||||
<!-- 底部操作区域 -->
|
||||
<template #footer>
|
||||
<div v-if="!footerHidden" class="nova-dialog-footer">
|
||||
<NSpace justify="center">
|
||||
<NButton
|
||||
size="medium"
|
||||
@click="handleCancel"
|
||||
>
|
||||
{{ cancelText }}
|
||||
</NButton>
|
||||
<NButton
|
||||
type="primary"
|
||||
size="medium"
|
||||
:loading="confirmLoading"
|
||||
@click="handleConfirm"
|
||||
>
|
||||
{{ confirmText }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</div>
|
||||
<div v-else />
|
||||
</template>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 弹框标题样式 */
|
||||
.nova-dialog :deep(.n-card-header) {
|
||||
|
||||
@ -1,3 +1,19 @@
|
||||
<template>
|
||||
<n-icon
|
||||
v-if="icon"
|
||||
:size="size"
|
||||
:depth="depth"
|
||||
:color="color"
|
||||
>
|
||||
<template v-if="isLocal">
|
||||
<i v-html="getLocalIcon(icon)" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<Icon :icon="icon" />
|
||||
</template>
|
||||
</n-icon>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
|
||||
@ -28,19 +44,3 @@ function getLocalIcon(icon: string) {
|
||||
return svg[`/src/assets/svg-icons/${svgName}.svg`]
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-icon
|
||||
v-if="icon"
|
||||
:size="size"
|
||||
:depth="depth"
|
||||
:color="color"
|
||||
>
|
||||
<template v-if="isLocal">
|
||||
<i v-html="getLocalIcon(icon)" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<Icon :icon="icon" />
|
||||
</template>
|
||||
</n-icon>
|
||||
</template>
|
||||
|
||||
@ -1,3 +1,17 @@
|
||||
<template>
|
||||
<n-pagination
|
||||
v-if="count > 0"
|
||||
v-model:page="page"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 30, 50]"
|
||||
:item-count="count"
|
||||
:display-order="displayOrder"
|
||||
show-size-picker
|
||||
@update-page="changePage"
|
||||
@update-page-size="changePage"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
count?: number
|
||||
@ -19,18 +33,4 @@ function changePage() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-pagination
|
||||
v-if="count > 0"
|
||||
v-model:page="page"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 30, 50]"
|
||||
:item-count="count"
|
||||
:display-order="displayOrder"
|
||||
show-size-picker
|
||||
@update-page="changePage"
|
||||
@update-page-size="changePage"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@ -1,46 +1,3 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import { coiMsgBox } from '@/utils/coi'
|
||||
import IconUser from '~icons/icon-park-outline/user'
|
||||
import IconLogout from '~icons/icon-park-outline/logout'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const router = useRouter()
|
||||
|
||||
// 获取用户信息
|
||||
const userInfo = computed(() => authStore.userInfo)
|
||||
|
||||
// 获取用户显示名称
|
||||
const displayName = computed(() => {
|
||||
if (!userInfo.value)
|
||||
return '未知用户'
|
||||
return userInfo.value.userName || '未知用户'
|
||||
})
|
||||
|
||||
// 获取用户头像
|
||||
const avatar = computed(() => {
|
||||
if (!userInfo.value?.avatar)
|
||||
return ''
|
||||
return userInfo.value.avatar
|
||||
})
|
||||
|
||||
// 处理个人中心点击
|
||||
function handlePersonalCenter() {
|
||||
router.push('/personal-center')
|
||||
}
|
||||
|
||||
// 处理退出登录
|
||||
function handleLogout() {
|
||||
coiMsgBox('确定要退出登录吗?', '退出确认').then(() => {
|
||||
authStore.logout()
|
||||
}).catch(() => {
|
||||
// 取消操作
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-dropdown
|
||||
placement="bottom-end"
|
||||
@ -93,6 +50,49 @@ function handleLogout() {
|
||||
</n-dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, h } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import { coiMsgBox } from '@/utils/coi'
|
||||
import IconUser from '~icons/icon-park-outline/user'
|
||||
import IconLogout from '~icons/icon-park-outline/logout'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const router = useRouter()
|
||||
|
||||
// 获取用户信息
|
||||
const userInfo = computed(() => authStore.userInfo)
|
||||
|
||||
// 获取用户显示名称
|
||||
const displayName = computed(() => {
|
||||
if (!userInfo.value)
|
||||
return '未知用户'
|
||||
return userInfo.value.userName || '未知用户'
|
||||
})
|
||||
|
||||
// 获取用户头像
|
||||
const avatar = computed(() => {
|
||||
if (!userInfo.value?.avatar)
|
||||
return ''
|
||||
return userInfo.value.avatar
|
||||
})
|
||||
|
||||
// 处理个人中心点击
|
||||
function handlePersonalCenter() {
|
||||
router.push('/personal-center')
|
||||
}
|
||||
|
||||
// 处理退出登录
|
||||
function handleLogout() {
|
||||
coiMsgBox('确定要退出登录吗?', '退出确认').then(() => {
|
||||
authStore.logout()
|
||||
}).catch(() => {
|
||||
// 取消操作
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dark .hover\:bg-gray-50:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user