feat(dashboard): 新增比赛管理仪表盘功能
添加比赛管理仪表盘页面,包含比赛卡片列表、状态标签和新增功能 移除旧的mine页面和相关路由配置 强制设置i18n语言为中文并更新系统标题 更新VSCode设置中的typescript格式化工具
This commit is contained in:
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -13,7 +13,7 @@
|
|||||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||||
},
|
},
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||||
|
|||||||
@ -1,12 +1,17 @@
|
|||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
import { createI18n } from 'vue-i18n'
|
import { createI18n } from 'vue-i18n'
|
||||||
import { localStg } from '@/utils/storage'
|
|
||||||
import messages from './locale'
|
import messages from './locale'
|
||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
locale: localStg.get('lang') || 'zh-CN',
|
// locale: localStg.get('lang') || 'zh-CN',
|
||||||
fallbackLocale: 'en',
|
// fallbackLocale: 'en',
|
||||||
|
// messages,
|
||||||
|
// legacy: false,
|
||||||
|
locale: 'zh-CN', // 强制设为中文
|
||||||
|
fallbackLocale: 'zh-CN', // 回退语言也设为中文
|
||||||
|
allowComposition: true,
|
||||||
messages,
|
messages,
|
||||||
|
// 建议关闭旧版兼容模式,减少控制台警告
|
||||||
legacy: false,
|
legacy: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
const local: App.I18n.Schema = {
|
const local: App.I18n.Schema = {
|
||||||
system: {
|
system: {
|
||||||
title: 'reader-star 管理系统',
|
title: '阅读比赛配置后台',
|
||||||
updateTitle: '系统版本更新通知',
|
updateTitle: '系统版本更新通知',
|
||||||
updateContent: '检测到系统有新版本发布,是否立即刷新页面?',
|
updateContent: '检测到系统有新版本发布,是否立即刷新页面?',
|
||||||
updateConfirm: '立即刷新',
|
updateConfirm: '立即刷新',
|
||||||
|
|||||||
@ -20,6 +20,6 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
|||||||
500: () => import("@/views/_builtin/500/index.vue"),
|
500: () => import("@/views/_builtin/500/index.vue"),
|
||||||
"iframe-page": () => import("@/views/_builtin/iframe-page/[url].vue"),
|
"iframe-page": () => import("@/views/_builtin/iframe-page/[url].vue"),
|
||||||
login: () => import("@/views/_builtin/login/index.vue"),
|
login: () => import("@/views/_builtin/login/index.vue"),
|
||||||
|
dashboard: () => import("@/views/dashboard/index.vue"),
|
||||||
home: () => import("@/views/home/index.vue"),
|
home: () => import("@/views/home/index.vue"),
|
||||||
mine: () => import("@/views/mine/index.vue"),
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -39,6 +39,15 @@ export const generatedRoutes: GeneratedRoute[] = [
|
|||||||
hideInMenu: true
|
hideInMenu: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'dashboard',
|
||||||
|
path: '/dashboard',
|
||||||
|
component: 'layout.base$view.dashboard',
|
||||||
|
meta: {
|
||||||
|
title: 'dashboard',
|
||||||
|
i18nKey: 'route.dashboard'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'home',
|
name: 'home',
|
||||||
path: '/home',
|
path: '/home',
|
||||||
@ -74,14 +83,5 @@ export const generatedRoutes: GeneratedRoute[] = [
|
|||||||
constant: true,
|
constant: true,
|
||||||
hideInMenu: true
|
hideInMenu: true
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'mine',
|
|
||||||
path: '/mine',
|
|
||||||
component: 'layout.base$view.mine',
|
|
||||||
meta: {
|
|
||||||
title: 'mine',
|
|
||||||
i18nKey: 'route.mine'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@ -166,10 +166,10 @@ const routeMap: RouteMap = {
|
|||||||
"403": "/403",
|
"403": "/403",
|
||||||
"404": "/404",
|
"404": "/404",
|
||||||
"500": "/500",
|
"500": "/500",
|
||||||
|
"dashboard": "/dashboard",
|
||||||
"home": "/home",
|
"home": "/home",
|
||||||
"iframe-page": "/iframe-page/:url",
|
"iframe-page": "/iframe-page/:url",
|
||||||
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
|
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?"
|
||||||
"mine": "/mine"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,84 +1,66 @@
|
|||||||
/** Default theme settings */
|
/** Default theme settings */
|
||||||
export const themeSettings: App.Theme.ThemeSetting = {
|
export const themeSettings: App.Theme.ThemeSetting = {
|
||||||
themeScheme: 'light',
|
themeScheme: 'light', // light | dark
|
||||||
grayscale: false,
|
grayscale: false, // 是否开启灰度模式
|
||||||
colourWeakness: false,
|
colourWeakness: false, // 是否开启色弱模式
|
||||||
recommendColor: false,
|
recommendColor: false, // 是否开启推荐颜色
|
||||||
themeColor: '#646cff',
|
themeColor: '#646cff', // 主题颜色
|
||||||
themeRadius: 6,
|
themeRadius: 6, // 主题圆角
|
||||||
otherColor: {
|
otherColor: {
|
||||||
info: '#2080f0',
|
info: '#2080f0',
|
||||||
success: '#52c41a',
|
success: '#52c41a',
|
||||||
warning: '#faad14',
|
warning: '#faad14',
|
||||||
error: '#f5222d',
|
error: '#f5222d',
|
||||||
},
|
},
|
||||||
isInfoFollowPrimary: true,
|
isInfoFollowPrimary: true, // 是否开启信息类颜色跟随主题颜色
|
||||||
layout: {
|
layout: { mode: 'horizontal', scrollMode: 'content' }, // 布局模式 mode: horizontal | vertical
|
||||||
mode: 'vertical',
|
page: { animate: true, animateMode: 'fade-slide' }, // 页面动画模式
|
||||||
scrollMode: 'content',
|
|
||||||
},
|
|
||||||
page: {
|
|
||||||
animate: true,
|
|
||||||
animateMode: 'fade-slide',
|
|
||||||
},
|
|
||||||
header: {
|
header: {
|
||||||
height: 56,
|
height: 56, // 头部高度
|
||||||
breadcrumb: {
|
breadcrumb: { visible: true, showIcon: true }, // 是否显示面包屑
|
||||||
visible: true,
|
multilingual: { visible: true }, // 是否显示多语言切换
|
||||||
showIcon: true,
|
globalSearch: { visible: true }, // 是否显示全局搜索
|
||||||
},
|
|
||||||
multilingual: {
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
globalSearch: {
|
|
||||||
visible: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
tab: {
|
tab: {
|
||||||
visible: true,
|
visible: true, // 是否显示标签页
|
||||||
cache: true,
|
cache: true, // 是否开启标签页缓存
|
||||||
height: 44,
|
height: 44, // 标签页高度
|
||||||
mode: 'chrome',
|
mode: 'chrome', // 标签页模式 chrome | default
|
||||||
closeTabByMiddleClick: false,
|
closeTabByMiddleClick: false, // 是否开启通过中键关闭标签页
|
||||||
},
|
},
|
||||||
fixedHeaderAndTab: true,
|
fixedHeaderAndTab: true, // 是否固定头部和标签页
|
||||||
sider: {
|
sider: {
|
||||||
inverted: false,
|
inverted: false, // 是否开启侧边栏反色
|
||||||
width: 220,
|
width: 220, // 侧边栏宽度
|
||||||
collapsedWidth: 64,
|
collapsedWidth: 64, // 侧边栏折叠宽度
|
||||||
mixWidth: 90,
|
mixWidth: 90, // 侧边栏混合宽度
|
||||||
mixCollapsedWidth: 64,
|
mixCollapsedWidth: 64, // 侧边栏混合折叠宽度
|
||||||
mixChildMenuWidth: 200,
|
mixChildMenuWidth: 200, // 侧边栏混合子菜单宽度
|
||||||
autoSelectFirstMenu: false,
|
autoSelectFirstMenu: false, // 是否自动选择第一个菜单
|
||||||
},
|
|
||||||
footer: {
|
|
||||||
visible: true,
|
|
||||||
fixed: false,
|
|
||||||
height: 48,
|
|
||||||
right: true,
|
|
||||||
},
|
},
|
||||||
|
footer: { visible: true, fixed: false, height: 48, right: true }, // 是否固定底部
|
||||||
watermark: {
|
watermark: {
|
||||||
visible: false,
|
visible: false, // 是否显示水印
|
||||||
text: 'reader-starAdmin',
|
text: 'reader-starAdmin', // 水印文本
|
||||||
enableUserName: false,
|
enableUserName: false, // 是否开启显示用户名
|
||||||
enableTime: false,
|
enableTime: false, // 是否开启显示时间
|
||||||
timeFormat: 'YYYY-MM-DD HH:mm',
|
timeFormat: 'YYYY-MM-DD HH:mm', // 时间格式
|
||||||
},
|
},
|
||||||
tokens: {
|
tokens: {
|
||||||
light: {
|
light: { // 亮色主题
|
||||||
colors: {
|
colors: {
|
||||||
'container': 'rgb(255, 255, 255)',
|
'container': 'rgb(255, 255, 255)',
|
||||||
'layout': 'rgb(247, 250, 252)',
|
'layout': 'rgb(247, 250, 252)',
|
||||||
'inverted': 'rgb(0, 20, 40)',
|
'inverted': 'rgb(0, 20, 40)',
|
||||||
'base-text': 'rgb(31, 31, 31)',
|
'base-text': 'rgb(31, 31, 31)',
|
||||||
},
|
},
|
||||||
boxShadow: {
|
boxShadow: { // 亮色主题阴影
|
||||||
header: '0 1px 2px rgb(0, 21, 41, 0.08)',
|
header: '0 1px 2px rgb(0, 21, 41, 0.08)',
|
||||||
sider: '2px 0 8px 0 rgb(29, 35, 41, 0.05)',
|
sider: '2px 0 8px 0 rgb(29, 35, 41, 0.05)',
|
||||||
tab: '0 1px 2px rgb(0, 21, 41, 0.08)',
|
tab: '0 1px 2px rgb(0, 21, 41, 0.08)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dark: {
|
dark: { // 暗色主题
|
||||||
colors: {
|
colors: {
|
||||||
'container': 'rgb(28, 28, 28)',
|
'container': 'rgb(28, 28, 28)',
|
||||||
'layout': 'rgb(18, 18, 18)',
|
'layout': 'rgb(18, 18, 18)',
|
||||||
|
|||||||
2
apps/admin/src/typings/components.d.ts
vendored
2
apps/admin/src/typings/components.d.ts
vendored
@ -68,6 +68,7 @@ declare module 'vue' {
|
|||||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||||
NTab: typeof import('naive-ui')['NTab']
|
NTab: typeof import('naive-ui')['NTab']
|
||||||
NTabs: typeof import('naive-ui')['NTabs']
|
NTabs: typeof import('naive-ui')['NTabs']
|
||||||
|
NTag: typeof import('naive-ui')['NTag']
|
||||||
NThing: typeof import('naive-ui')['NThing']
|
NThing: typeof import('naive-ui')['NThing']
|
||||||
NTooltip: typeof import('naive-ui')['NTooltip']
|
NTooltip: typeof import('naive-ui')['NTooltip']
|
||||||
NWatermark: typeof import('naive-ui')['NWatermark']
|
NWatermark: typeof import('naive-ui')['NWatermark']
|
||||||
@ -143,6 +144,7 @@ declare global {
|
|||||||
const NSwitch: typeof import('naive-ui')['NSwitch']
|
const NSwitch: typeof import('naive-ui')['NSwitch']
|
||||||
const NTab: typeof import('naive-ui')['NTab']
|
const NTab: typeof import('naive-ui')['NTab']
|
||||||
const NTabs: typeof import('naive-ui')['NTabs']
|
const NTabs: typeof import('naive-ui')['NTabs']
|
||||||
|
const NTag: typeof import('naive-ui')['NTag']
|
||||||
const NThing: typeof import('naive-ui')['NThing']
|
const NThing: typeof import('naive-ui')['NThing']
|
||||||
const NTooltip: typeof import('naive-ui')['NTooltip']
|
const NTooltip: typeof import('naive-ui')['NTooltip']
|
||||||
const NWatermark: typeof import('naive-ui')['NWatermark']
|
const NWatermark: typeof import('naive-ui')['NWatermark']
|
||||||
|
|||||||
6
apps/admin/src/typings/elegant-router.d.ts
vendored
6
apps/admin/src/typings/elegant-router.d.ts
vendored
@ -20,10 +20,10 @@ declare module "@elegant-router/types" {
|
|||||||
"403": "/403";
|
"403": "/403";
|
||||||
"404": "/404";
|
"404": "/404";
|
||||||
"500": "/500";
|
"500": "/500";
|
||||||
|
"dashboard": "/dashboard";
|
||||||
"home": "/home";
|
"home": "/home";
|
||||||
"iframe-page": "/iframe-page/:url";
|
"iframe-page": "/iframe-page/:url";
|
||||||
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
|
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
|
||||||
"mine": "/mine";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,10 +58,10 @@ declare module "@elegant-router/types" {
|
|||||||
| "403"
|
| "403"
|
||||||
| "404"
|
| "404"
|
||||||
| "500"
|
| "500"
|
||||||
|
| "dashboard"
|
||||||
| "home"
|
| "home"
|
||||||
| "iframe-page"
|
| "iframe-page"
|
||||||
| "login"
|
| "login"
|
||||||
| "mine"
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -83,8 +83,8 @@ declare module "@elegant-router/types" {
|
|||||||
| "500"
|
| "500"
|
||||||
| "iframe-page"
|
| "iframe-page"
|
||||||
| "login"
|
| "login"
|
||||||
|
| "dashboard"
|
||||||
| "home"
|
| "home"
|
||||||
| "mine"
|
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { getPaletteColorByNumber, mixColor } from '@sa/color'
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { loginModuleRecord } from '@/constants/app'
|
import { loginModuleRecord } from '@/constants/app'
|
||||||
import { $t } from '@/locales'
|
import { $t } from '@/locales'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
|
||||||
import { useThemeStore } from '@/store/modules/theme'
|
import { useThemeStore } from '@/store/modules/theme'
|
||||||
import BindWechat from './modules/bind-wechat.vue'
|
import BindWechat from './modules/bind-wechat.vue'
|
||||||
import CodeLogin from './modules/code-login.vue'
|
import CodeLogin from './modules/code-login.vue'
|
||||||
@ -19,7 +18,7 @@ interface Props {
|
|||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
const appStore = useAppStore()
|
// const appStore = useAppStore()
|
||||||
const themeStore = useThemeStore()
|
const themeStore = useThemeStore()
|
||||||
|
|
||||||
interface LoginModule {
|
interface LoginModule {
|
||||||
@ -67,13 +66,13 @@ const bgColor = computed(() => {
|
|||||||
class="text-20px lt-sm:text-18px"
|
class="text-20px lt-sm:text-18px"
|
||||||
@switch="themeStore.toggleThemeScheme"
|
@switch="themeStore.toggleThemeScheme"
|
||||||
/>
|
/>
|
||||||
<LangSwitch
|
<!-- <LangSwitch
|
||||||
v-if="themeStore.header.multilingual.visible"
|
v-if="themeStore.header.multilingual.visible"
|
||||||
:lang="appStore.locale"
|
:lang="appStore.locale"
|
||||||
:lang-options="appStore.localeOptions"
|
:lang-options="appStore.localeOptions"
|
||||||
:show-tooltip="false"
|
:show-tooltip="false"
|
||||||
@change-lang="appStore.changeLocale"
|
@change-lang="appStore.changeLocale"
|
||||||
/>
|
/> -->
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main class="pt-24px">
|
<main class="pt-24px">
|
||||||
|
|||||||
@ -48,6 +48,7 @@ interface Account {
|
|||||||
password: string
|
password: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||||
const accounts = computed<Account[]>(() => [
|
const accounts = computed<Account[]>(() => [
|
||||||
{
|
{
|
||||||
key: 'super',
|
key: 'super',
|
||||||
@ -69,6 +70,7 @@ const accounts = computed<Account[]>(() => [
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||||
async function handleAccountLogin(account: Account) {
|
async function handleAccountLogin(account: Account) {
|
||||||
await authStore.login(account.userName, account.password)
|
await authStore.login(account.userName, account.password)
|
||||||
}
|
}
|
||||||
@ -105,14 +107,14 @@ async function handleAccountLogin(account: Account) {
|
|||||||
{{ $t(loginModuleRecord.register) }}
|
{{ $t(loginModuleRecord.register) }}
|
||||||
</NButton>
|
</NButton>
|
||||||
</div>
|
</div>
|
||||||
<NDivider class="text-14px text-#666 !m-0">
|
<!-- <NDivider class="text-14px text-#666 !m-0">
|
||||||
{{ $t('page.login.pwdLogin.otherAccountLogin') }}
|
{{ $t('page.login.pwdLogin.otherAccountLogin') }}
|
||||||
</NDivider>
|
</NDivider> -->
|
||||||
<div class="flex-center gap-12px">
|
<!-- <div class="flex-center gap-12px">
|
||||||
<NButton v-for="item in accounts" :key="item.key" type="primary" @click="handleAccountLogin(item)">
|
<NButton v-for="item in accounts" :key="item.key" type="primary" @click="handleAccountLogin(item)">
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</NButton>
|
</NButton>
|
||||||
</div>
|
</div> -->
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NForm>
|
</NForm>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
55
apps/admin/src/views/dashboard/index.vue
Normal file
55
apps/admin/src/views/dashboard/index.vue
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { NButton, NGi, NGrid, NSpace } from 'naive-ui'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useAppStore } from '@/store/modules/app'
|
||||||
|
import CompetitionCard from './modules/competition-card.vue'
|
||||||
|
import { competitionList } from './modules/data'
|
||||||
|
|
||||||
|
// 引入 store 用于判断移动端布局 (与你提供的风格保持一致)
|
||||||
|
const appStore = useAppStore()
|
||||||
|
const gap = computed(() => (appStore.isMobile ? 12 : 16))
|
||||||
|
|
||||||
|
// 模拟新增操作
|
||||||
|
function handleAdd() {
|
||||||
|
window.$message?.success('点击了新增比赛活动')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NSpace vertical :size="24">
|
||||||
|
<!-- 顶部 Header 区域 -->
|
||||||
|
<div class="flex flex-col justify-between gap-4 sm:flex-row sm:items-center">
|
||||||
|
<div>
|
||||||
|
<h2 class="m-0 text-xl text-gray-800 font-bold dark:text-white">
|
||||||
|
比赛列表
|
||||||
|
</h2>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">
|
||||||
|
管理并配置所有阅读大比拼活动
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<NButton type="primary" size="medium" class="px-6 font-medium" @click="handleAdd">
|
||||||
|
<template #icon>
|
||||||
|
<div class="i-carbon-add text-lg" />
|
||||||
|
</template>
|
||||||
|
新增比赛活动
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 卡片列表区域 -->
|
||||||
|
<!-- 响应式布局:手机1列,平板2列,中屏3列,大屏4列 -->
|
||||||
|
<NGrid :x-gap="gap" :y-gap="gap" responsive="screen" item-responsive>
|
||||||
|
<NGi
|
||||||
|
v-for="item in competitionList"
|
||||||
|
:key="item.id"
|
||||||
|
span="24 s:12 m:8 l:6"
|
||||||
|
>
|
||||||
|
<CompetitionCard :item="item" />
|
||||||
|
</NGi>
|
||||||
|
</NGrid>
|
||||||
|
</NSpace>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 针对不同屏幕微调间距 */
|
||||||
|
</style>
|
||||||
84
apps/admin/src/views/dashboard/modules/competition-card.vue
Normal file
84
apps/admin/src/views/dashboard/modules/competition-card.vue
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { CompetitionItem } from './data'
|
||||||
|
import { NButton, NCard, NTag } from 'naive-ui'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
item: CompetitionItem
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
// 状态配置:颜色和文本
|
||||||
|
const statusConfig = computed(() => {
|
||||||
|
if (props.item.status === 'ongoing') {
|
||||||
|
return { type: 'success', text: '进行中', bgClass: 'bg-green-100 text-green-600' }
|
||||||
|
}
|
||||||
|
return { type: 'default', text: '未开始', bgClass: 'bg-gray-100 text-gray-500' }
|
||||||
|
})
|
||||||
|
|
||||||
|
// 处理标题换行
|
||||||
|
const formattedTitle = computed(() => props.item.title.replace(/\n/g, '<br/>'))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NCard
|
||||||
|
:bordered="false"
|
||||||
|
class="group relative h-full overflow-hidden rounded-2xl transition-all duration-300 hover:shadow-lg"
|
||||||
|
content-style="padding: 24px; display: flex; flex-direction: column; height: 100%;"
|
||||||
|
>
|
||||||
|
<!-- 特殊背景装饰 (仅高亮卡片显示) -->
|
||||||
|
<div
|
||||||
|
v-if="item.isHighlight"
|
||||||
|
class="pointer-events-none absolute right-0 top-0 z-0 h-32 w-32 rounded-bl-full bg-blue-50/80 -mr-8 -mt-8"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="relative z-10 h-full flex flex-col justify-between">
|
||||||
|
<!-- 顶部内容 -->
|
||||||
|
<div>
|
||||||
|
<!-- 状态标签 -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<NTag
|
||||||
|
:bordered="false"
|
||||||
|
size="small"
|
||||||
|
round
|
||||||
|
class="px-3 font-medium" :class="[statusConfig.bgClass]"
|
||||||
|
>
|
||||||
|
{{ statusConfig.text }}
|
||||||
|
</NTag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 标题 -->
|
||||||
|
<h3
|
||||||
|
class="mb-3 min-h-[3rem] text-lg text-gray-800 font-bold leading-tight dark:text-white"
|
||||||
|
v-html="formattedTitle"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 日期 -->
|
||||||
|
<div class="mb-6 flex items-center text-sm text-gray-400">
|
||||||
|
<div class="i-carbon-calendar mr-2 text-base" />
|
||||||
|
<span>{{ item.date }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部内容 -->
|
||||||
|
<div class="flex items-center justify-between border-t border-gray-50 pt-4 dark:border-gray-800">
|
||||||
|
<span class="text-xs text-gray-500 font-medium">
|
||||||
|
{{ item.teamCount }} 支参赛队伍
|
||||||
|
</span>
|
||||||
|
<NButton
|
||||||
|
text
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
class="font-bold hover:underline"
|
||||||
|
>
|
||||||
|
查看配置详情
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NCard>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 可选:微调样式 */
|
||||||
|
</style>
|
||||||
70
apps/admin/src/views/dashboard/modules/data.ts
Normal file
70
apps/admin/src/views/dashboard/modules/data.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// src/views/competition/modules/data.ts
|
||||||
|
|
||||||
|
export interface CompetitionItem {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
date: string
|
||||||
|
teamCount: number
|
||||||
|
status: 'ongoing' | 'pending' // ongoing: 进行中, pending: 未开始
|
||||||
|
isHighlight?: boolean // 用于标记第一个卡片的特殊背景
|
||||||
|
}
|
||||||
|
|
||||||
|
export const competitionList: CompetitionItem[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: '阅读之星大赛\n辞海遨游环节', // 使用换行符或在组件中处理换行
|
||||||
|
date: '2026.01.12',
|
||||||
|
teamCount: 40,
|
||||||
|
status: 'ongoing',
|
||||||
|
isHighlight: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: '诗词大会总决赛',
|
||||||
|
date: '2026.01.12',
|
||||||
|
teamCount: 32,
|
||||||
|
status: 'pending',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
title: '趣味百科大作战',
|
||||||
|
date: '2026.01.12',
|
||||||
|
teamCount: 32,
|
||||||
|
status: 'pending',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
title: '经典文学研读周',
|
||||||
|
date: '2026.01.12',
|
||||||
|
teamCount: 32,
|
||||||
|
status: 'pending',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
title: '未来科学家阅读赛',
|
||||||
|
date: '2026.01.12',
|
||||||
|
teamCount: 32,
|
||||||
|
status: 'pending',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
title: '历史人文知识赛',
|
||||||
|
date: '2026.01.12',
|
||||||
|
teamCount: 32,
|
||||||
|
status: 'pending',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '7',
|
||||||
|
title: '少儿绘本创意营',
|
||||||
|
date: '2026.01.12',
|
||||||
|
teamCount: 32,
|
||||||
|
status: 'pending',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '8',
|
||||||
|
title: '唐诗三百首挑战',
|
||||||
|
date: '2026.01.12',
|
||||||
|
teamCount: 32,
|
||||||
|
status: 'pending',
|
||||||
|
},
|
||||||
|
]
|
||||||
@ -1,9 +0,0 @@
|
|||||||
<script lang="ts" setup></script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
<h1>Mine</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
Reference in New Issue
Block a user