refactor(components): 重构核心组件代码块顺序

- 调整App.vue和所有通用组件为template→script→style顺序
- 涉及NovaDialog、UserCenter、IconSelect等核心组件
- 统一组件结构,提升代码可读性和维护性
- 符合项目新的Vue组件代码块排布规范
This commit is contained in:
Leo 2025-07-07 00:16:44 +08:00
parent 696c8b1417
commit 797c42aea1
13 changed files with 268 additions and 265 deletions

View File

@ -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>

View File

@ -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;

View File

@ -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);

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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) {

View File

@ -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>

View File

@ -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>

View File

@ -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);