This commit is contained in:
2026-01-10 21:41:23 +08:00
parent 2f5ee49594
commit 1d8144bf87
28 changed files with 434 additions and 589 deletions

View File

@@ -1,3 +1,2 @@
/node_modules/*
/dist/*
/src/main.ts
/dist/*

View File

@@ -42,6 +42,6 @@
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@@ -4,31 +4,31 @@
</ElConfigProvider>
</template>
<script setup lang="ts">
import { useUserStore } from './store/modules/user'
import zh from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/es/locale/lang/en'
import { systemUpgrade } from './utils/sys'
import { toggleTransition } from './utils/ui/animation'
import { checkStorageCompatibility } from './utils/storage'
import { initializeTheme } from './hooks/core/useTheme'
<script setup>
import { useUserStore } from './store/modules/user'
import zh from 'element-plus/es/locale/lang/zh-cn'
import en from 'element-plus/es/locale/lang/en'
import { systemUpgrade } from './utils/sys'
import { toggleTransition } from './utils/ui/animation'
import { checkStorageCompatibility } from './utils/storage'
import { initializeTheme } from './hooks/core/useTheme'
const userStore = useUserStore()
const { language } = storeToRefs(userStore)
const userStore = useUserStore()
const { language } = storeToRefs(userStore)
const locales = {
zh: zh,
en: en
}
const locales = {
zh: zh,
en: en
}
onBeforeMount(() => {
toggleTransition(true)
initializeTheme()
})
onBeforeMount(() => {
toggleTransition(true)
initializeTheme()
})
onMounted(() => {
checkStorageCompatibility()
toggleTransition(false)
systemUpgrade()
})
onMounted(() => {
checkStorageCompatibility()
toggleTransition(false)
systemUpgrade()
})
</script>

View File

@@ -5,8 +5,8 @@ import request from '@/utils/http'
* @param params 登录参数
* @returns 登录响应
*/
export function fetchLogin(params: Api.Auth.LoginParams) {
return request.post<Api.Auth.LoginResponse>({
export function fetchLogin(params) {
return request.post({
url: '/api/auth/login',
params
// showSuccessMessage: true // 显示成功消息
@@ -19,7 +19,7 @@ export function fetchLogin(params: Api.Auth.LoginParams) {
* @returns 用户信息
*/
export function fetchGetUserInfo() {
return request.get<Api.Auth.UserInfo>({
return request.get({
url: '/api/user/info'
// 自定义请求头
// headers: {

24
src/api/system-manage.js Normal file
View File

@@ -0,0 +1,24 @@
import request from '@/utils/http'
// 获取用户列表
export function fetchGetUserList(params) {
return request.get({
url: '/api/user/list',
params
})
}
// 获取角色列表
export function fetchGetRoleList(params) {
return request.get({
url: '/api/role/list',
params
})
}
// 获取菜单列表
export function fetchGetMenuList() {
return request.get({
url: '/api/v3/system/menus/simple'
})
}

View File

@@ -1,25 +0,0 @@
import request from '@/utils/http'
import { AppRouteRecord } from '@/types/router'
// 获取用户列表
export function fetchGetUserList(params: Api.SystemManage.UserSearchParams) {
return request.get<Api.SystemManage.UserList>({
url: '/api/user/list',
params
})
}
// 获取角色列表
export function fetchGetRoleList(params: Api.SystemManage.RoleSearchParams) {
return request.get<Api.SystemManage.RoleList>({
url: '/api/role/list',
params
})
}
// 获取菜单列表
export function fetchGetMenuList() {
return request.get<AppRouteRecord[]>({
url: '/api/v3/system/menus/simple'
})
}

View File

@@ -22,8 +22,6 @@
*/
import { createI18n } from 'vue-i18n'
import type { I18n, I18nOptions } from 'vue-i18n'
import { LanguageEnum } from '@/enums/appEnum'
import { getSystemStorage } from '@/utils/storage'
import { StorageKeyManager } from '@/utils/storage/storage-key-manager'
@@ -40,8 +38,8 @@ const storageKeyManager = new StorageKeyManager()
* 语言消息对象
*/
const messages = {
[LanguageEnum.EN]: enMessages,
[LanguageEnum.ZH]: zhMessages
'en': enMessages,
'zh': zhMessages
}
/**
@@ -49,15 +47,15 @@ const messages = {
* 用于语言切换下拉框
*/
export const languageOptions = [
{ value: LanguageEnum.ZH, label: '简体中文' },
{ value: LanguageEnum.EN, label: 'English' }
{ value: 'zh', label: '简体中文' },
{ value: 'en', label: 'English' }
]
/**
* 从存储中获取语言设置
* @returns 语言设置如果获取失败则返回默认语言
*/
const getDefaultLanguage = (): LanguageEnum => {
const getDefaultLanguage = () => {
// 尝试从版本化的存储中获取语言设置
try {
const storageKey = storageKeyManager.getStorageKey('user')
@@ -65,7 +63,7 @@ const getDefaultLanguage = (): LanguageEnum => {
if (userStore) {
const { language } = JSON.parse(userStore)
if (language && Object.values(LanguageEnum).includes(language)) {
if (language && Object.values(['zh', 'en']).includes(language)) {
return language
}
}
@@ -78,7 +76,7 @@ const getDefaultLanguage = (): LanguageEnum => {
const sys = getSystemStorage()
if (sys) {
const { user } = JSON.parse(sys)
if (user?.language && Object.values(LanguageEnum).includes(user.language)) {
if (user?.language && Object.values(['zh', 'en']).includes(user.language)) {
return user.language
}
}
@@ -87,37 +85,30 @@ const getDefaultLanguage = (): LanguageEnum => {
}
// 返回默认语言
console.debug('[i18n] 使用默认语言:', LanguageEnum.ZH)
return LanguageEnum.ZH
console.debug('[i18n] 使用默认语言:', 'zh')
return 'zh'
}
/**
* i18n 配置选项
*/
const i18nOptions: I18nOptions = {
const i18nOptions = {
locale: getDefaultLanguage(),
legacy: false,
globalInjection: true,
fallbackLocale: LanguageEnum.ZH,
fallbackLocale: 'zh',
messages
}
/**
* i18n 实例
*/
const i18n: I18n = createI18n(i18nOptions)
/**
* 翻译函数类型
*/
interface Translation {
(key: string): string
}
const i18n = createI18n(i18nOptions)
/**
* 全局翻译函数
* 可在任何地方使用无需导入 useI18n
*/
export const $t = i18n.global.t as Translation
export const $t = i18n.global.t
export default i18n

20
src/main.js Normal file
View File

@@ -0,0 +1,20 @@
import App from './App.vue'
import { createApp } from 'vue'
import { initStore } from './store' // Store
import { initRouter } from './router' // Router
import language from './locales' // 国际化
import '@styles/core/tailwind.css' // tailwind
import '@styles/index.scss' // 样式
import { setupGlobDirectives } from './directives'
import { setupErrorHandle } from './utils/sys/error-handle'
document.addEventListener('touchstart', function () {}, { passive: false })
const app = createApp(App)
initStore(app)
initRouter(app)
setupGlobDirectives(app)
setupErrorHandle(app)
app.use(language)
app.mount('#app')

View File

@@ -1,25 +0,0 @@
import App from './App.vue'
import { createApp } from 'vue'
import { initStore } from './store' // Store
import { initRouter } from './router' // Router
import language from './locales' // 国际化
import '@styles/core/tailwind.css' // tailwind
import '@styles/index.scss' // 样式
import '@utils/sys/console.ts' // 控制台输出内容
import { setupGlobDirectives } from './directives'
import { setupErrorHandle } from './utils/sys/error-handle'
document.addEventListener(
'touchstart',
function () {},
{ passive: false }
)
const app = createApp(App)
initStore(app)
initRouter(app)
setupGlobDirectives(app)
setupErrorHandle(app)
app.use(language)
app.mount('#app')

View File

@@ -1,13 +0,0 @@
// ANSI 转义码生成网站 https://patorjk.com/software/taag/#p=display&f=Big&t=ABB%0A
const asciiArt = `
\x1b[32m欢迎使用 Art Design Pro
\x1b[0m
\x1b[36m哇你居然在用我的项目 好用的话别忘了去 GitHub 点个 ★Star 呀,你的支持就是我更新的超强动力!祝使用体验满分💯
\x1b[0m
\x1b[33mGitHub: https://github.com/Daymychen/art-design-pro
\x1b[0m
\x1b[31m技术支持QQ群: 1038930070和开发者一起交流 群里有小伙伴实时答疑,遇到问题不用慌!
\x1b[0m
`
console.log(asciiArt)

View File

@@ -11,21 +11,13 @@
<p class="sub-title">{{ $t('forgetPassword.subTitle') }}</p>
<div class="mt-5">
<span class="input-label" v-if="showInputLabel">账号</span>
<ElInput
class="custom-height"
:placeholder="$t('forgetPassword.placeholder')"
v-model.trim="username"
/>
<ElInput class="custom-height" :placeholder="$t('forgetPassword.placeholder')"
v-model.trim="username" />
</div>
<div style="margin-top: 15px">
<ElButton
class="w-full custom-height"
type="primary"
@click="register"
:loading="loading"
v-ripple
>
<ElButton class="w-full custom-height" type="primary" @click="register" :loading="loading"
v-ripple>
{{ $t('forgetPassword.submitBtnText') }}
</ElButton>
</div>
@@ -41,22 +33,22 @@
</div>
</template>
<script setup lang="ts">
defineOptions({ name: 'ForgetPassword' })
<script setup>
defineOptions({ name: 'ForgetPassword' })
const router = useRouter()
const showInputLabel = ref(false)
const router = useRouter()
const showInputLabel = ref(false)
const username = ref('')
const loading = ref(false)
const username = ref('')
const loading = ref(false)
const register = async () => {}
const register = async () => { }
const toLogin = () => {
router.push({ name: 'Login' })
}
const toLogin = () => {
router.push({ name: 'Login' })
}
</script>
<style scoped>
@import '../login/style.css';
@import '../login/style.css';
</style>

View File

@@ -10,65 +10,36 @@
<div class="form">
<h3 class="title">{{ $t('login.title') }}</h3>
<p class="sub-title">{{ $t('login.subTitle') }}</p>
<ElForm
ref="formRef"
:model="formData"
:rules="rules"
:key="formKey"
@keyup.enter="handleSubmit"
style="margin-top: 25px"
>
<ElForm ref="formRef" :model="formData" :rules="rules" :key="formKey" @keyup.enter="handleSubmit"
style="margin-top: 25px">
<ElFormItem prop="account">
<ElSelect v-model="formData.account" @change="setupAccount">
<ElOption
v-for="account in accounts"
:key="account.key"
:label="account.label"
:value="account.key"
>
<ElOption v-for="account in accounts" :key="account.key" :label="account.label"
:value="account.key">
<span>{{ account.label }}</span>
</ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem prop="username">
<ElInput
class="custom-height"
:placeholder="$t('login.placeholder.username')"
v-model.trim="formData.username"
/>
<ElInput class="custom-height" :placeholder="$t('login.placeholder.username')"
v-model.trim="formData.username" />
</ElFormItem>
<ElFormItem prop="password">
<ElInput
class="custom-height"
:placeholder="$t('login.placeholder.password')"
v-model.trim="formData.password"
type="password"
autocomplete="off"
show-password
/>
<ElInput class="custom-height" :placeholder="$t('login.placeholder.password')"
v-model.trim="formData.password" type="password" autocomplete="off" show-password />
</ElFormItem>
<!-- 推拽验证 -->
<div class="relative pb-5 mt-6">
<div
class="relative z-[2] overflow-hidden select-none rounded-lg border border-transparent tad-300"
:class="{ '!border-[#FF4E4F]': !isPassing && isClickPass }"
>
<ArtDragVerify
ref="dragVerify"
v-model:value="isPassing"
:text="$t('login.sliderText')"
textColor="var(--art-gray-700)"
:successText="$t('login.sliderSuccessText')"
progressBarBg="var(--main-color)"
:background="isDark ? '#26272F' : '#F1F1F4'"
handlerBg="var(--default-box-color)"
/>
<div class="relative z-[2] overflow-hidden select-none rounded-lg border border-transparent tad-300"
:class="{ '!border-[#FF4E4F]': !isPassing && isClickPass }">
<ArtDragVerify ref="dragVerify" v-model:value="isPassing" :text="$t('login.sliderText')"
textColor="var(--art-gray-700)" :successText="$t('login.sliderSuccessText')"
progressBarBg="var(--main-color)" :background="isDark ? '#26272F' : '#F1F1F4'"
handlerBg="var(--default-box-color)" />
</div>
<p
class="absolute top-0 z-[1] px-px mt-2 text-xs text-[#f56c6c] tad-300"
:class="{ 'translate-y-10': !isPassing && isClickPass }"
>
<p class="absolute top-0 z-[1] px-px mt-2 text-xs text-[#f56c6c] tad-300"
:class="{ 'translate-y-10': !isPassing && isClickPass }">
{{ $t('login.placeholder.slider') }}
</p>
</div>
@@ -83,13 +54,8 @@
</div>
<div style="margin-top: 30px">
<ElButton
class="w-full custom-height"
type="primary"
@click="handleSubmit"
:loading="loading"
v-ripple
>
<ElButton class="w-full custom-height" type="primary" @click="handleSubmit"
:loading="loading" v-ripple>
{{ $t('login.btnText') }}
</ElButton>
</div>
@@ -107,178 +73,168 @@
</div>
</template>
<script setup lang="ts">
import AppConfig from '@/config'
import { useUserStore } from '@/store/modules/user'
import { useI18n } from 'vue-i18n'
import { HttpError } from '@/utils/http/error'
import { fetchLogin } from '@/api/auth'
import { ElNotification, type FormInstance, type FormRules } from 'element-plus'
import { useSettingStore } from '@/store/modules/setting'
<script setup>
import AppConfig from '@/config'
import { useUserStore } from '@/store/modules/user'
import { useI18n } from 'vue-i18n'
import { HttpError } from '@/utils/http/error'
import { fetchLogin } from '@/api/auth'
import { ElNotification } from 'element-plus'
import { useSettingStore } from '@/store/modules/setting'
defineOptions({ name: 'Login' })
defineOptions({ name: 'Login' })
const settingStore = useSettingStore()
const { isDark } = storeToRefs(settingStore)
const { t, locale } = useI18n()
const formKey = ref(0)
const settingStore = useSettingStore()
const { isDark } = storeToRefs(settingStore)
const { t, locale } = useI18n()
const formKey = ref(0)
// 监听语言切换,重置表单
watch(locale, () => {
formKey.value++
})
// 监听语言切换,重置表单
watch(locale, () => {
formKey.value++
})
type AccountKey = 'super' | 'admin' | 'user'
export interface Account {
key: AccountKey
label: string
userName: string
password: string
roles: string[]
const accounts = computed(() => [
{
key: 'super',
label: t('login.roles.super'),
userName: 'Super',
password: '123456',
roles: ['R_SUPER']
},
{
key: 'admin',
label: t('login.roles.admin'),
userName: 'Admin',
password: '123456',
roles: ['R_ADMIN']
},
{
key: 'user',
label: t('login.roles.user'),
userName: 'User',
password: '123456',
roles: ['R_USER']
}
])
const accounts = computed<Account[]>(() => [
{
key: 'super',
label: t('login.roles.super'),
userName: 'Super',
password: '123456',
roles: ['R_SUPER']
},
{
key: 'admin',
label: t('login.roles.admin'),
userName: 'Admin',
password: '123456',
roles: ['R_ADMIN']
},
{
key: 'user',
label: t('login.roles.user'),
userName: 'User',
password: '123456',
roles: ['R_USER']
const dragVerify = ref()
const userStore = useUserStore()
const router = useRouter()
const route = useRoute()
const isPassing = ref(false)
const isClickPass = ref(false)
const systemName = AppConfig.systemInfo.name
const formRef = ref()
const formData = reactive({
account: '',
username: '',
password: '',
rememberPassword: true
})
const rules = computed(() => ({
username: [{ required: true, message: t('login.placeholder.username'), trigger: 'blur' }],
password: [{ required: true, message: t('login.placeholder.password'), trigger: 'blur' }]
}))
const loading = ref(false)
onMounted(() => {
setupAccount('super')
})
// 设置账号
const setupAccount = (key) => {
const selectedAccount = accounts.value.find((account) => account.key === key)
formData.account = key
formData.username = selectedAccount?.userName ?? ''
formData.password = selectedAccount?.password ?? ''
}
// 登录
const handleSubmit = async () => {
if (!formRef.value) return
try {
// 表单验证
const valid = await formRef.value.validate()
if (!valid) return
// 拖拽验证
if (!isPassing.value) {
isClickPass.value = true
return
}
])
const dragVerify = ref()
loading.value = true
const userStore = useUserStore()
const router = useRouter()
const route = useRoute()
const isPassing = ref(false)
const isClickPass = ref(false)
// 登录请求
const { username, password } = formData
const systemName = AppConfig.systemInfo.name
const formRef = ref<FormInstance>()
const { token, refreshToken } = await fetchLogin({
userName: username,
password
})
const formData = reactive({
account: '',
username: '',
password: '',
rememberPassword: true
})
const rules = computed<FormRules>(() => ({
username: [{ required: true, message: t('login.placeholder.username'), trigger: 'blur' }],
password: [{ required: true, message: t('login.placeholder.password'), trigger: 'blur' }]
}))
const loading = ref(false)
onMounted(() => {
setupAccount('super')
})
// 设置账号
const setupAccount = (key: AccountKey) => {
const selectedAccount = accounts.value.find((account: Account) => account.key === key)
formData.account = key
formData.username = selectedAccount?.userName ?? ''
formData.password = selectedAccount?.password ?? ''
}
// 登录
const handleSubmit = async () => {
if (!formRef.value) return
try {
// 表单验证
const valid = await formRef.value.validate()
if (!valid) return
// 拖拽验证
if (!isPassing.value) {
isClickPass.value = true
return
}
loading.value = true
// 登录请求
const { username, password } = formData
const { token, refreshToken } = await fetchLogin({
userName: username,
password
})
// 验证token
if (!token) {
throw new Error('Login failed - no token received')
}
// 存储 token 和登录状态
userStore.setToken(token, refreshToken)
userStore.setLoginStatus(true)
// 登录成功处理
showLoginSuccessNotice()
// 获取 redirect 参数,如果存在则跳转到指定页面,否则跳转到首页
const redirect = route.query.redirect as string
router.push(redirect || '/')
} catch (error) {
// 处理 HttpError
if (error instanceof HttpError) {
// console.log(error.code)
} else {
// 处理非 HttpError
// ElMessage.error('登录失败,请稍后重试')
console.error('[Login] Unexpected error:', error)
}
} finally {
loading.value = false
resetDragVerify()
// 验证token
if (!token) {
throw new Error('Login failed - no token received')
}
}
// 重置拖拽验证
const resetDragVerify = () => {
dragVerify.value.reset()
}
// 存储 token 和登录状态
userStore.setToken(token, refreshToken)
userStore.setLoginStatus(true)
// 登录成功提示
const showLoginSuccessNotice = () => {
setTimeout(() => {
ElNotification({
title: t('login.success.title'),
type: 'success',
duration: 2500,
zIndex: 10000,
message: `${t('login.success.message')}, ${systemName}!`
})
}, 1000)
// 登录成功处理
showLoginSuccessNotice()
// 获取 redirect 参数,如果存在则跳转到指定页面,否则跳转到首页
const redirect = route.query.redirect
router.push(redirect || '/')
} catch (error) {
// 处理 HttpError
if (error instanceof HttpError) {
// console.log(error.code)
} else {
// 处理非 HttpError
// ElMessage.error('登录失败,请稍后重试')
console.error('[Login] Unexpected error:', error)
}
} finally {
loading.value = false
resetDragVerify()
}
}
// 重置拖拽验证
const resetDragVerify = () => {
dragVerify.value.reset()
}
// 登录成功提示
const showLoginSuccessNotice = () => {
setTimeout(() => {
ElNotification({
title: t('login.success.title'),
type: 'success',
duration: 2500,
zIndex: 10000,
message: `${t('login.success.message')}, ${systemName}!`
})
}, 1000)
}
</script>
<style scoped>
@import './style.css';
@import './style.css';
</style>
<style lang="scss" scoped>
:deep(.el-select__wrapper) {
height: 40px !important;
}
:deep(.el-select__wrapper) {
height: 40px !important;
}
</style>

View File

@@ -10,64 +10,36 @@
<div class="form">
<h3 class="title">{{ $t('register.title') }}</h3>
<p class="sub-title">{{ $t('register.subTitle') }}</p>
<ElForm
class="mt-7.5"
ref="formRef"
:model="formData"
:rules="rules"
label-position="top"
:key="formKey"
>
<ElForm class="mt-7.5" ref="formRef" :model="formData" :rules="rules" label-position="top"
:key="formKey">
<ElFormItem prop="username">
<ElInput
class="custom-height"
v-model.trim="formData.username"
:placeholder="$t('register.placeholder.username')"
/>
<ElInput class="custom-height" v-model.trim="formData.username"
:placeholder="$t('register.placeholder.username')" />
</ElFormItem>
<ElFormItem prop="password">
<ElInput
class="custom-height"
v-model.trim="formData.password"
:placeholder="$t('register.placeholder.password')"
type="password"
autocomplete="off"
show-password
/>
<ElInput class="custom-height" v-model.trim="formData.password"
:placeholder="$t('register.placeholder.password')" type="password" autocomplete="off"
show-password />
</ElFormItem>
<ElFormItem prop="confirmPassword">
<ElInput
class="custom-height"
v-model.trim="formData.confirmPassword"
:placeholder="$t('register.placeholder.confirmPassword')"
type="password"
autocomplete="off"
@keyup.enter="register"
show-password
/>
<ElInput class="custom-height" v-model.trim="formData.confirmPassword"
:placeholder="$t('register.placeholder.confirmPassword')" type="password"
autocomplete="off" @keyup.enter="register" show-password />
</ElFormItem>
<ElFormItem prop="agreement">
<ElCheckbox v-model="formData.agreement">
{{ $t('register.agreeText') }}
<RouterLink
style="color: var(--theme-color); text-decoration: none"
to="/privacy-policy"
>{{ $t('register.privacyPolicy') }}</RouterLink
>
<RouterLink style="color: var(--theme-color); text-decoration: none"
to="/privacy-policy">{{ $t('register.privacyPolicy') }}</RouterLink>
</ElCheckbox>
</ElFormItem>
<div style="margin-top: 15px">
<ElButton
class="w-full custom-height"
type="primary"
@click="register"
:loading="loading"
v-ripple
>
<ElButton class="w-full custom-height" type="primary" @click="register" :loading="loading"
v-ripple>
{{ $t('register.submitBtnText') }}
</ElButton>
</div>
@@ -85,160 +57,152 @@
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import type { FormInstance, FormRules } from 'element-plus'
<script setup>
import { useI18n } from 'vue-i18n'
defineOptions({ name: 'Register' })
defineOptions({ name: 'Register' })
interface RegisterForm {
username: string
password: string
confirmPassword: string
agreement: boolean
const USERNAME_MIN_LENGTH = 3
const USERNAME_MAX_LENGTH = 20
const PASSWORD_MIN_LENGTH = 6
const REDIRECT_DELAY = 1000
const { t, locale } = useI18n()
const router = useRouter()
const formRef = ref()
const loading = ref(false)
const formKey = ref(0)
// 监听语言切换,重置表单
watch(locale, () => {
formKey.value++
})
const formData = reactive({
username: '',
password: '',
confirmPassword: '',
agreement: false
})
/**
* 验证密码
* 当密码输入后,如果确认密码已填写,则触发确认密码的验证
*/
const validatePassword = (_rule, value, callback) => {
if (!value) {
callback(new Error(t('register.placeholder.password')))
return
}
const USERNAME_MIN_LENGTH = 3
const USERNAME_MAX_LENGTH = 20
const PASSWORD_MIN_LENGTH = 6
const REDIRECT_DELAY = 1000
const { t, locale } = useI18n()
const router = useRouter()
const formRef = ref<FormInstance>()
const loading = ref(false)
const formKey = ref(0)
// 监听语言切换,重置表单
watch(locale, () => {
formKey.value++
})
const formData = reactive<RegisterForm>({
username: '',
password: '',
confirmPassword: '',
agreement: false
})
/**
* 验证密码
* 当密码输入后,如果确认密码已填写,则触发确认密码的验证
*/
const validatePassword = (_rule: any, value: string, callback: (error?: Error) => void) => {
if (!value) {
callback(new Error(t('register.placeholder.password')))
return
}
if (formData.confirmPassword) {
formRef.value?.validateField('confirmPassword')
}
callback()
if (formData.confirmPassword) {
formRef.value?.validateField('confirmPassword')
}
/**
* 验证确认密码
* 检查确认密码是否与密码一致
*/
const validateConfirmPassword = (
_rule: any,
value: string,
callback: (error?: Error) => void
) => {
if (!value) {
callback(new Error(t('register.rule.confirmPasswordRequired')))
return
}
callback()
}
if (value !== formData.password) {
callback(new Error(t('register.rule.passwordMismatch')))
return
}
callback()
/**
* 验证确认密码
* 检查确认密码是否与密码一致
*/
const validateConfirmPassword = (
_rule,
value,
callback
) => {
if (!value) {
callback(new Error(t('register.rule.confirmPasswordRequired')))
return
}
/**
* 验证用户协议
* 确保用户已勾选同意协议
*/
const validateAgreement = (_rule: any, value: boolean, callback: (error?: Error) => void) => {
if (!value) {
callback(new Error(t('register.rule.agreementRequired')))
return
}
callback()
if (value !== formData.password) {
callback(new Error(t('register.rule.passwordMismatch')))
return
}
const rules = computed<FormRules<RegisterForm>>(() => ({
username: [
{ required: true, message: t('register.placeholder.username'), trigger: 'blur' },
{
min: USERNAME_MIN_LENGTH,
max: USERNAME_MAX_LENGTH,
message: t('register.rule.usernameLength'),
trigger: 'blur'
}
],
password: [
{ required: true, validator: validatePassword, trigger: 'blur' },
{
min: PASSWORD_MIN_LENGTH,
message: t('register.rule.passwordLength'),
trigger: 'blur'
}
],
confirmPassword: [{ required: true, validator: validateConfirmPassword, trigger: 'blur' }],
agreement: [{ validator: validateAgreement, trigger: 'change' }]
}))
callback()
}
/**
* 注册用户
* 验证表单后提交注册请求
*/
const register = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
loading.value = true
// TODO: 替换为真实 API 调用
// const params = {
// username: formData.username,
// password: formData.password
// }
// const res = await AuthService.register(params)
// if (res.code === ApiStatus.success) {
// ElMessage.success('注册成功')
// toLogin()
// }
// 模拟注册请求
setTimeout(() => {
loading.value = false
ElMessage.success('注册成功')
toLogin()
}, REDIRECT_DELAY)
} catch (error) {
console.error('表单验证失败:', error)
loading.value = false
}
/**
* 验证用户协议
* 确保用户已勾选同意协议
*/
const validateAgreement = (_rule, value, callback) => {
if (!value) {
callback(new Error(t('register.rule.agreementRequired')))
return
}
callback()
}
/**
* 跳转到登录页面
*/
const toLogin = () => {
const rules = computed(() => ({
username: [
{ required: true, message: t('register.placeholder.username'), trigger: 'blur' },
{
min: USERNAME_MIN_LENGTH,
max: USERNAME_MAX_LENGTH,
message: t('register.rule.usernameLength'),
trigger: 'blur'
}
],
password: [
{ required: true, validator: validatePassword, trigger: 'blur' },
{
min: PASSWORD_MIN_LENGTH,
message: t('register.rule.passwordLength'),
trigger: 'blur'
}
],
confirmPassword: [{ required: true, validator: validateConfirmPassword, trigger: 'blur' }],
agreement: [{ validator: validateAgreement, trigger: 'change' }]
}))
/**
* 注册用户
* 验证表单后提交注册请求
*/
const register = async () => {
if (!formRef.value) return
try {
await formRef.value.validate()
loading.value = true
// TODO: 替换为真实 API 调用
// const params = {
// username: formData.username,
// password: formData.password
// }
// const res = await AuthService.register(params)
// if (res.code === ApiStatus.success) {
// ElMessage.success('注册成功')
// toLogin()
// }
// 模拟注册请求
setTimeout(() => {
router.push({ name: 'Login' })
loading.value = false
ElMessage.success('注册成功')
toLogin()
}, REDIRECT_DELAY)
} catch (error) {
console.error('表单验证失败:', error)
loading.value = false
}
}
/**
* 跳转到登录页面
*/
const toLogin = () => {
setTimeout(() => {
router.push({ name: 'Login' })
}, REDIRECT_DELAY)
}
</script>
<style scoped>
@import '../login/style.css';
@import '../login/style.css';
</style>

View File

@@ -28,14 +28,14 @@
</div>
</template>
<script setup lang="ts">
import CardList from './modules/card-list.vue'
import ActiveUser from './modules/active-user.vue'
import SalesOverview from './modules/sales-overview.vue'
import NewUser from './modules/new-user.vue'
import Dynamic from './modules/dynamic-stats.vue'
import TodoList from './modules/todo-list.vue'
import AboutProject from './modules/about-project.vue'
<script setup>
import CardList from './modules/card-list.vue'
import ActiveUser from './modules/active-user.vue'
import SalesOverview from './modules/sales-overview.vue'
import NewUser from './modules/new-user.vue'
import Dynamic from './modules/dynamic-stats.vue'
import TodoList from './modules/todo-list.vue'
import AboutProject from './modules/about-project.vue'
defineOptions({ name: 'Console' })
defineOptions({ name: 'Console' })
</script>

View File

@@ -21,7 +21,7 @@
</div>
</template>
<script setup lang="ts">
<script setup>
import AppConfig from '@/config'
import { WEB_LINKS } from '@/utils/constants'
@@ -38,7 +38,7 @@
* 在新标签页中打开指定 URL
* @param url 要打开的网页地址
*/
const goPage = (url: string): void => {
const goPage = (url) => {
window.open(url, '_blank', 'noopener,noreferrer')
}
</script>

View File

@@ -24,11 +24,7 @@
</div>
</template>
<script setup lang="ts">
interface UserStatItem {
name: string
num: string
}
<script setup>
// 最近9个月
const xAxisLabels = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月']
@@ -40,7 +36,7 @@
* 用户统计数据列表
* 包含总用户量、总访问量、日访问量和周同比等关键指标
*/
const list: UserStatItem[] = [
const list = [
{ name: '总用户量', num: '32k' },
{ name: '总访问量', num: '128k' },
{ name: '日访问量', num: '1.2k' },

View File

@@ -27,21 +27,13 @@
</ElRow>
</template>
<script setup lang="ts">
interface CardDataItem {
des: string
icon: string
startVal: number
duration: number
num: number
change: string
}
<script setup>
/**
* 卡片统计数据列表
* 展示总访问次数、在线访客数、点击量和新用户等核心数据指标
*/
const dataList = reactive<CardDataItem[]>([
const dataList = reactive([
{
des: '总访问次数',
icon: 'ri:pie-chart-line',

View File

@@ -23,18 +23,12 @@
</div>
</template>
<script setup lang="ts">
interface DynamicItem {
username: string
type: string
target: string
}
<script setup>
/**
* 用户动态列表
* 记录用户的关注、发文、提问、兑换等各类活动
*/
const list = reactive<DynamicItem[]>([
const list = reactive([
{
username: '中小鱼',
type: '关注了',

View File

@@ -54,7 +54,7 @@
</div>
</template>
<script setup lang="ts">
<script setup>
import avatar1 from '@/assets/images/avatar/avatar1.webp'
import avatar2 from '@/assets/images/avatar/avatar2.webp'
import avatar3 from '@/assets/images/avatar/avatar3.webp'
@@ -62,16 +62,6 @@
import avatar5 from '@/assets/images/avatar/avatar5.webp'
import avatar6 from '@/assets/images/avatar/avatar6.webp'
interface UserTableItem {
username: string
province: string
sex: 0 | 1
age: number
percentage: number
pro: number
color: string
avatar: string
}
const ANIMATION_DELAY = 100
@@ -81,7 +71,7 @@
* 新用户表格数据
* 包含用户基本信息和完成进度
*/
const tableData = reactive<UserTableItem[]>([
const tableData = reactive([
{
username: '中小鱼',
province: '北京',
@@ -148,7 +138,7 @@
* 添加进度条动画效果
* 延迟后将进度值从 0 更新到目标百分比,触发动画
*/
const addAnimation = (): void => {
const addAnimation = () => {
setTimeout(() => {
tableData.forEach((item) => {
item.pro = item.percentage

View File

@@ -16,7 +16,7 @@
</div>
</template>
<script setup lang="ts">
<script setup>
/**
* 全年访问量数据
* 记录每月的访问量统计

View File

@@ -25,18 +25,12 @@
</div>
</template>
<script setup lang="ts">
interface TodoItem {
username: string
date: string
complate: boolean
}
<script setup>
/**
* 待办事项列表
* 记录每日工作任务及完成状态
*/
const list = reactive<TodoItem[]>([
const list = reactive([
{
username: '查看今天工作内容',
date: '上午 09:30',

View File

@@ -10,7 +10,7 @@
/>
</template>
<script setup lang="ts">
<script setup>
import imgUrl from '@imgs/svg/403.svg'
defineOptions({ name: 'Exception403' })
</script>

View File

@@ -10,7 +10,7 @@
/>
</template>
<script setup lang="ts">
<script setup>
import imgUrl from '@imgs/svg/404.svg'
defineOptions({ name: 'Exception404' })
</script>

View File

@@ -10,7 +10,7 @@
/>
</template>
<script setup lang="ts">
<script setup>
import imgUrl from '@imgs/svg/500.svg'
defineOptions({ name: 'Exception500' })
</script>

View File

@@ -20,10 +20,6 @@
</div>
</template>
<script setup lang="ts">
defineOptions({ name: 'AppLayout' })
</script>
<style lang="scss" scoped>
@use './style';
</style>

View File

@@ -10,7 +10,7 @@
</div>
</template>
<script setup lang="ts">
<script setup>
import { IframeRouteManager } from '@/router/core'
defineOptions({ name: 'IframeView' })
@@ -18,7 +18,7 @@
const route = useRoute()
const isLoading = ref(true)
const iframeUrl = ref('')
const iframeRef = ref<HTMLIFrameElement | null>(null)
const iframeRef = ref(null)
/**
* 初始化 iframe URL
@@ -36,7 +36,7 @@
* 处理 iframe 加载完成事件
* 隐藏加载状态
*/
const handleIframeLoad = (): void => {
const handleIframeLoad = () => {
isLoading.value = false
}
</script>

View File

@@ -23,6 +23,6 @@
</ArtResultPage>
</template>
<script setup lang="ts">
<script setup>
defineOptions({ name: 'ResultFail' })
</script>

View File

@@ -16,6 +16,6 @@
</ArtResultPage>
</template>
<script setup lang="ts">
<script setup>
defineOptions({ name: 'ResultSuccess' })
</script>