添加类型定义、主题配置和状态管理

- 定义核心数据类型(HeritageItem、Inheritor、NewsArticle、Event等)
- 配置Ant Design主题色和组件样式
- 添加用户状态管理(Zustand)
This commit is contained in:
Leo 2025-10-09 23:43:59 +08:00
parent 7abc7becee
commit cb195382eb
5 changed files with 947 additions and 0 deletions

129
src/store/useUserStore.ts Normal file
View File

@ -0,0 +1,129 @@
/**
* - Zustand Store
*/
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
import type { User } from '@types/index'
import { login as apiLogin, getUserById } from '@services/api'
interface UserState {
user: User | null
isAuthenticated: boolean
isLoading: boolean
// Actions
login: (username: string, password: string) => Promise<boolean>
logout: () => void
updateUser: (userData: Partial<User>) => void
addFavorite: (heritageId: string) => void
removeFavorite: (heritageId: string) => void
followInheritor: (inheritorId: string) => void
unfollowInheritor: (inheritorId: string) => void
enrollCourse: (courseId: string) => void
}
export const useUserStore = create<UserState>()(
persist(
(set, get) => ({
user: null,
isAuthenticated: false,
isLoading: false,
login: async (username: string, password: string) => {
set({ isLoading: true })
try {
const user = await apiLogin(username, password)
if (user) {
set({ user, isAuthenticated: true, isLoading: false })
return true
}
set({ isLoading: false })
return false
} catch (error) {
console.error('Login failed:', error)
set({ isLoading: false })
return false
}
},
logout: () => {
set({ user: null, isAuthenticated: false })
},
updateUser: (userData: Partial<User>) => {
const { user } = get()
if (user) {
set({ user: { ...user, ...userData } })
}
},
addFavorite: (heritageId: string) => {
const { user } = get()
if (user && !user.favorites.includes(heritageId)) {
set({
user: {
...user,
favorites: [...user.favorites, heritageId],
},
})
}
},
removeFavorite: (heritageId: string) => {
const { user } = get()
if (user) {
set({
user: {
...user,
favorites: user.favorites.filter((id) => id !== heritageId),
},
})
}
},
followInheritor: (inheritorId: string) => {
const { user } = get()
if (user && !user.followedInheritors.includes(inheritorId)) {
set({
user: {
...user,
followedInheritors: [...user.followedInheritors, inheritorId],
},
})
}
},
unfollowInheritor: (inheritorId: string) => {
const { user } = get()
if (user) {
set({
user: {
...user,
followedInheritors: user.followedInheritors.filter((id) => id !== inheritorId),
},
})
}
},
enrollCourse: (courseId: string) => {
const { user } = get()
if (user && !user.enrolledCourses.includes(courseId)) {
set({
user: {
...user,
enrolledCourses: [...user.enrolledCourses, courseId],
},
})
}
},
}),
{
name: 'user-storage', // localStorage key
partialize: (state) => ({
user: state.user,
isAuthenticated: state.isAuthenticated,
}),
}
)
)

384
src/theme/components.ts Normal file
View File

@ -0,0 +1,384 @@
/**
* -
* Ant Design
*/
export const componentTokens = {
// ===== 通用组件 =====
Button: {
colorPrimary: '#C8363D',
colorPrimaryHover: '#A82E34',
colorPrimaryActive: '#8B252B',
primaryShadow: '0 2px 0 rgba(200, 54, 61, 0.1)',
defaultShadow: '0 2px 0 rgba(0, 0, 0, 0.02)',
controlHeight: 40,
controlHeightLG: 48,
controlHeightSM: 32,
borderRadius: 8,
borderRadiusLG: 12,
borderRadiusSM: 6,
fontWeight: 500,
},
FloatButton: {
colorPrimary: '#C8363D',
colorPrimaryHover: '#A82E34',
boxShadow: '0 4px 16px rgba(200, 54, 61, 0.15)',
},
Typography: {
colorTextHeading: '#2C2C2C',
colorText: '#2C2C2C',
fontSizeHeading1: 38,
fontSizeHeading2: 30,
fontSizeHeading3: 24,
fontSizeHeading4: 20,
fontSizeHeading5: 16,
fontWeightStrong: 600,
},
// ===== 布局组件 =====
Layout: {
headerBg: '#FFFFFF',
headerHeight: 64,
headerPadding: '0 50px',
footerBg: '#2C2C2C',
footerPadding: '48px 50px',
bodyBg: '#FAFAF8',
siderBg: '#FFFFFF',
},
Divider: {
colorSplit: '#E8E3DB',
marginLG: 24,
},
Space: {
marginXS: 8,
marginSM: 12,
marginMD: 16,
marginLG: 24,
marginXL: 32,
},
// ===== 导航组件 =====
Menu: {
itemBg: 'transparent',
itemColor: '#666666',
itemSelectedBg: 'rgba(200, 54, 61, 0.08)',
itemSelectedColor: '#C8363D',
itemHoverBg: 'rgba(200, 54, 61, 0.05)',
itemHoverColor: '#C8363D',
itemActiveBg: 'rgba(200, 54, 61, 0.1)',
itemHeight: 48,
itemBorderRadius: 8,
iconSize: 18,
fontSize: 14,
},
Breadcrumb: {
linkColor: '#666666',
linkHoverColor: '#C8363D',
lastItemColor: '#2C2C2C',
fontSize: 14,
},
Pagination: {
itemActiveBg: '#C8363D',
itemActiveColorDisabled: '#FFFFFF',
colorPrimary: '#C8363D',
colorPrimaryHover: '#A82E34',
itemLinkBg: '#FFFFFF',
itemBg: '#FFFFFF',
borderRadius: 8,
colorText: '#1a1a1a',
colorTextDisabled: '#d9d9d9',
},
Tabs: {
itemColor: '#666666',
itemSelectedColor: '#C8363D',
itemHoverColor: '#C8363D',
itemActiveColor: '#C8363D',
inkBarColor: '#C8363D',
cardBg: '#F5F0E8',
cardPadding: '12px 16px',
titleFontSize: 16,
},
Steps: {
finishIconBorderColor: '#C8363D',
colorPrimary: '#C8363D',
},
// ===== 数据录入组件 =====
Input: {
borderRadius: 8,
controlHeight: 40,
controlHeightLG: 48,
controlHeightSM: 32,
colorBorder: '#E8E3DB',
colorBgContainer: '#FFFFFF',
colorTextPlaceholder: '#CCCCCC',
activeBorderColor: '#C8363D',
hoverBorderColor: '#C8363D',
activeShadow: '0 0 0 2px rgba(200, 54, 61, 0.1)',
},
InputNumber: {
borderRadius: 8,
controlHeight: 40,
handleVisible: true,
},
Select: {
borderRadius: 8,
controlHeight: 40,
optionSelectedBg: 'rgba(200, 54, 61, 0.08)',
optionActiveBg: 'rgba(200, 54, 61, 0.05)',
optionSelectedColor: '#C8363D',
},
Checkbox: {
borderRadiusSM: 4,
colorPrimary: '#C8363D',
colorPrimaryHover: '#A82E34',
},
Radio: {
colorPrimary: '#C8363D',
dotSize: 10,
},
Switch: {
colorPrimary: '#C8363D',
colorPrimaryHover: '#A82E34',
},
Slider: {
trackBg: '#C8363D',
trackHoverBg: '#A82E34',
handleColor: '#C8363D',
handleActiveColor: '#8B252B',
dotBorderColor: '#E8E3DB',
dotActiveBorderColor: '#C8363D',
},
DatePicker: {
borderRadius: 8,
controlHeight: 40,
cellActiveWithRangeBg: 'rgba(200, 54, 61, 0.1)',
cellHoverBg: 'rgba(200, 54, 61, 0.05)',
},
Rate: {
colorFillContent: '#E8E3DB',
starColor: '#D4A574',
starSize: 20,
},
Form: {
labelColor: '#2C2C2C',
labelFontSize: 14,
labelHeight: 32,
labelRequiredMarkColor: '#FF4D4F',
itemMarginBottom: 24,
},
Upload: {
colorBorder: '#E8E3DB',
colorBorderHover: '#C8363D',
colorPrimary: '#C8363D',
},
// ===== 数据展示组件 =====
Card: {
borderRadiusLG: 12,
boxShadowTertiary: '0 2px 12px rgba(0, 0, 0, 0.08)',
headerBg: '#FFFFFF',
headerFontSize: 16,
headerHeight: 48,
paddingLG: 24,
colorBorderSecondary: '#E8E3DB',
},
Carousel: {
dotHeight: 8,
dotWidth: 24,
dotWidthActive: 32,
dotGap: 8,
},
Collapse: {
headerBg: '#F5F0E8',
headerPadding: '12px 16px',
contentBg: '#FFFFFF',
contentPadding: '16px',
borderRadiusLG: 12,
},
Descriptions: {
labelBg: '#F5F0E8',
titleColor: '#2C2C2C',
contentColor: '#666666',
itemPaddingBottom: 16,
},
Empty: {
colorTextDescription: '#999999',
fontSize: 14,
},
Image: {
previewOperationColor: '#FFFFFF',
previewOperationColorDisabled: 'rgba(255, 255, 255, 0.3)',
},
List: {
itemPadding: '12px 0',
itemPaddingSM: '8px 16px',
itemPaddingLG: '16px 24px',
},
Table: {
headerBg: '#F5F0E8',
headerColor: '#2C2C2C',
rowHoverBg: '#FFF9F0',
rowSelectedBg: 'rgba(200, 54, 61, 0.05)',
rowSelectedHoverBg: 'rgba(200, 54, 61, 0.08)',
borderColor: '#E8E3DB',
headerSplitColor: '#E8E3DB',
borderRadius: 12,
cellPaddingBlock: 16,
cellFontSize: 14,
},
Tag: {
defaultBg: '#F5F0E8',
defaultColor: '#666666',
borderRadiusSM: 6,
fontSizeSM: 12,
},
Timeline: {
dotBorderWidth: 2,
dotBg: '#FFFFFF',
tailColor: '#E8E3DB',
tailWidth: 2,
},
Tooltip: {
colorBgSpotlight: 'rgba(44, 44, 44, 0.9)',
borderRadius: 8,
},
Statistic: {
titleFontSize: 14,
contentFontSize: 24,
fontFamily: 'monospace',
},
Badge: {
colorError: '#C8363D',
dotSize: 8,
statusSize: 8,
},
Avatar: {
borderRadius: 8,
containerSize: 40,
containerSizeLG: 48,
containerSizeSM: 32,
},
// ===== 反馈组件 =====
Alert: {
borderRadiusLG: 12,
colorInfoBg: '#E6F7FF',
colorSuccessBg: '#F6FFED',
colorWarningBg: '#FFFBE6',
colorErrorBg: '#FFF1F0',
defaultPadding: '12px 16px',
},
Modal: {
headerBg: '#FFFFFF',
contentBg: '#FFFFFF',
footerBg: '#FFFFFF',
borderRadiusLG: 12,
boxShadow: '0 6px 48px rgba(0, 0, 0, 0.12)',
titleColor: '#2C2C2C',
titleFontSize: 18,
},
Drawer: {
colorBgElevated: '#FFFFFF',
paddingLG: 24,
footerPaddingBlock: 12,
footerPaddingInline: 16,
},
Message: {
contentBg: 'rgba(44, 44, 44, 0.9)',
contentPadding: '10px 16px',
borderRadiusLG: 8,
},
Notification: {
width: 384,
borderRadiusLG: 12,
boxShadow: '0 6px 24px rgba(0, 0, 0, 0.12)',
paddingContentHorizontal: 24,
paddingContentVertical: 16,
},
Progress: {
defaultColor: '#C8363D',
circleTextColor: '#2C2C2C',
remainingColor: '#F5F0E8',
},
Result: {
titleFontSize: 24,
subtitleFontSize: 14,
iconFontSize: 72,
},
Skeleton: {
color: '#F5F0E8',
colorGradientEnd: 'rgba(245, 240, 232, 0.2)',
},
Spin: {
colorPrimary: '#C8363D',
dotSize: 20,
dotSizeSM: 14,
dotSizeLG: 32,
},
Popconfirm: {
borderRadiusLG: 12,
minWidth: 280,
},
// ===== 其他组件 =====
Anchor: {
linkPaddingBlock: 4,
linkPaddingInlineStart: 16,
},
Segmented: {
borderRadius: 8,
itemSelectedBg: '#C8363D',
itemSelectedColor: '#FFFFFF',
itemHoverBg: 'rgba(200, 54, 61, 0.05)',
},
Watermark: {
colorFill: 'rgba(0, 0, 0, 0.05)',
fontSize: 16,
},
}
// 导出类型
export type ComponentTokens = typeof componentTokens

27
src/theme/index.ts Normal file
View File

@ -0,0 +1,27 @@
/**
* -
* Token ConfigProvider
*/
import type { ThemeConfig } from 'antd'
import { themeTokens } from './tokens'
import { componentTokens } from './components'
/**
* Ant Design
*
*/
export const heritageTheme: ThemeConfig = {
token: themeTokens,
components: componentTokens,
cssVar: true, // 启用 CSS 变量
hashed: true, // 启用样式哈希
}
// 导出子模块
export { themeTokens } from './tokens'
export { componentTokens } from './components'
// 导出类型
export type { ThemeTokens } from './tokens'
export type { ComponentTokens } from './components'

90
src/theme/tokens.ts Normal file
View File

@ -0,0 +1,90 @@
/**
* - Token
*
*/
export const themeTokens = {
// ===== 主色调 =====
colorPrimary: '#C8363D', // 朱砂红 - 象征传统文化的热情与活力
colorInfo: '#4A5F7F', // 青黛蓝 - 象征深邃与智慧
colorSuccess: '#52C41A', // 成功绿
colorWarning: '#FAAD14', // 警告黄
colorError: '#FF4D4F', // 错误红
// ===== 辅助色 =====
colorAccent: '#D4A574', // 金沙黄 - 象征精湛与珍贵
colorAuxiliary1: '#2A5E4D', // 墨绿 - 象征沉稳与生命力
colorAuxiliary2: '#4A5F7F', // 青黛蓝
// ===== 背景色系统 =====
colorBgBase: '#FAFAF8', // 宣纸色 - 基础背景
colorBgContainer: '#FFFFFF', // 纯白 - 容器背景
colorBgElevated: '#FFFFFF', // 浮层背景
colorBgLayout: '#F5F0E8', // 浅米黄 - 布局背景
colorBgSection: '#F5F0E8', // Section 背景
// ===== 文本色系统 =====
colorTextBase: '#2C2C2C', // 深灰黑 - 主文本
colorText: '#2C2C2C', // 主文本
colorTextSecondary: '#666666', // 中灰 - 次要文本
colorTextTertiary: '#999999', // 浅灰 - 辅助文本
colorTextQuaternary: '#CCCCCC', // 极浅灰 - 占位文本
// ===== 边框色 =====
colorBorder: '#E8E3DB', // 主边框色
colorBorderSecondary: '#F0EBE3', // 次要边框色
// ===== 圆角系统 =====
borderRadius: 8, // 基础圆角
borderRadiusLG: 12, // 大圆角
borderRadiusSM: 6, // 小圆角
borderRadiusXS: 4, // 极小圆角
// ===== 字体系统 =====
fontSize: 14, // 基础字号
fontSizeLG: 16, // 大字号
fontSizeSM: 12, // 小字号
fontSizeHeading1: 38, // 标题1
fontSizeHeading2: 30, // 标题2
fontSizeHeading3: 24, // 标题3
fontSizeHeading4: 20, // 标题4
fontSizeHeading5: 16, // 标题5
// 字体家族
fontFamily: `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji', 'Noto Sans SC', 'Microsoft YaHei'`,
fontFamilySerif: `'Noto Serif SC', 'Songti SC', Georgia, serif`,
// ===== 行高 =====
lineHeight: 1.5715,
lineHeightHeading1: 1.2,
lineHeightHeading2: 1.3,
lineHeightHeading3: 1.35,
// ===== 阴影系统 =====
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
boxShadowSecondary: '0 4px 16px rgba(0, 0, 0, 0.12)',
boxShadowTertiary: '0 6px 24px rgba(0, 0, 0, 0.16)',
// ===== 控件高度 =====
controlHeight: 40, // 基础控件高度
controlHeightLG: 48, // 大控件高度
controlHeightSM: 32, // 小控件高度
controlHeightXS: 24, // 极小控件高度
// ===== 动画 =====
motionUnit: 0.1,
motionBase: 0,
motionEaseInOut: 'cubic-bezier(0.645, 0.045, 0.355, 1)',
motionEaseOut: 'cubic-bezier(0.215, 0.61, 0.355, 1)',
// ===== 其他 =====
wireframe: false, // 关闭线框模式
zIndexBase: 0,
zIndexPopupBase: 1000,
}
// 导出类型
export type ThemeTokens = typeof themeTokens

317
src/types/index.ts Normal file
View File

@ -0,0 +1,317 @@
/**
* - TypeScript
*/
// ===== 非遗项目类型 =====
export interface HeritageItem {
id: string
name: string
category: HeritageCategory
province: string
city?: string
level: HeritageLevel
coverImage: string
images?: string[]
description: string
history: string
skills: string
significance: string
inheritors: string[] // 传承人ID列表
relatedWorks?: Work[]
videoUrl?: string
virtualTourUrl?: string
status: 'active' | 'endangered' | 'revived'
tags: string[]
viewCount: number
likeCount: number
createdAt: string
updatedAt: string
}
// 非遗分类
export type HeritageCategory =
| 'folk-literature' // 民间文学
| 'traditional-music' // 传统音乐
| 'traditional-dance' // 传统舞蹈
| 'traditional-opera' // 传统戏剧
| 'folk-art' // 曲艺
| 'sports-acrobatics' // 传统体育、游艺与杂技
| 'traditional-craft' // 传统技艺
| 'traditional-medicine'// 传统医药
| 'folk-custom' // 民俗
| 'traditional-art' // 传统美术
// 非遗级别
export type HeritageLevel =
| 'world' // 世界级
| 'national' // 国家级
| 'provincial' // 省级
| 'municipal' // 市级
| 'county' // 县级
// ===== 传承人类型 =====
export interface Inheritor {
id: string
name: string
avatar: string
coverImage?: string
gender: 'male' | 'female'
birthYear: number
province: string
city?: string
level: 'national' | 'provincial' | 'municipal'
heritageItems: string[] // 关联的非遗项目ID
title: string // 称号:如"国家级代表性传承人"
bio: string
masterSkills: string
achievements: Achievement[]
awards: Award[]
works: Work[]
videos: Video[]
contactInfo?: ContactInfo
socialMedia?: SocialMedia
followers: number
viewCount: number
createdAt: string
updatedAt: string
}
export interface Achievement {
id: string
title: string
description: string
date: string
images?: string[]
}
export interface Award {
id: string
name: string
level: string
year: number
organization: string
}
export interface Work {
id: string
name: string
image: string
description: string
year: number
materials?: string
dimensions?: string
price?: number
}
export interface Video {
id: string
title: string
cover: string
url: string
duration: number // 秒
description?: string
viewCount: number
publishDate: string
}
export interface ContactInfo {
phone?: string
email?: string
address?: string
website?: string
}
export interface SocialMedia {
weibo?: string
wechat?: string
douyin?: string
bilibili?: string
}
// ===== 活动与资讯类型 =====
export interface NewsArticle {
id: string
title: string
subtitle?: string
cover: string
category: 'exhibition' | 'activity' | 'policy' | 'research' | 'story'
content: string
summary: string
author: string
publishDate: string
tags: string[]
viewCount: number
likeCount: number
relatedHeritage?: string[]
relatedInheritors?: string[]
}
export interface Event {
id: string
title: string
cover: string
type: 'exhibition' | 'workshop' | 'performance' | 'lecture' | 'festival'
location: string
address: string
startDate: string
endDate: string
startTime?: string
endTime?: string
description: string
organizer: string
capacity?: number
enrolled: number
price: number
isFree: boolean
tags: string[]
status: 'upcoming' | 'ongoing' | 'finished' | 'cancelled'
registrationRequired: boolean
contactInfo: ContactInfo
relatedHeritage?: string[]
images: string[]
viewCount: number
}
// ===== 用户相关类型 =====
export interface User {
id: string
username: string
nickname: string
avatar: string
email: string
phone?: string
bio?: string
favorites: string[] // 收藏的非遗项目ID
followedInheritors: string[] // 关注的传承人ID
enrolledCourses: string[] // 已报名课程ID
registeredEvents: string[] // 已报名活动ID
points: number
level: number
createdAt: string
}
// ===== 体验相关类型 =====
export interface VirtualTour {
id: string
title: string
cover: string
description: string
panoramaUrl: string
hotspots: Hotspot[]
heritageId: string
}
export interface Hotspot {
id: string
position: { x: number; y: number; z: number }
title: string
description: string
mediaType: 'image' | 'video' | 'audio' | 'text'
mediaUrl?: string
}
export interface Workshop {
id: string
title: string
cover: string
heritageId: string
inheritorId: string
location: string
address: string
duration: number // 小时
maxParticipants: number
enrolled: number
price: number
dates: string[] // 可预约日期
description: string
whatToLearn: string[]
requirements: string[]
providedMaterials: string[]
}
// ===== 统计数据类型 =====
export interface Statistics {
totalHeritageItems: number
totalInheritors: number
totalProvinces: number
totalCities: number
worldHeritage: number
nationalHeritage: number
provincialHeritage: number
endangeredCount: number
activePreservation: number
}
// ===== 通用类型 =====
export interface PaginationParams {
page: number
pageSize: number
}
export interface PaginationResult<T> {
data: T[]
total: number
page: number
pageSize: number
totalPages: number
}
export interface FilterParams {
category?: HeritageCategory | HeritageCategory[]
level?: HeritageLevel | HeritageLevel[]
province?: string | string[]
type?: string | string[]
status?: string | string[]
search?: string
sortBy?: 'name' | 'viewCount' | 'likeCount' | 'createdAt'
sortOrder?: 'asc' | 'desc'
}
export interface ApiResponse<T = any> {
code: number
message: string
data: T
timestamp: number
}
// ===== 评论类型 =====
export interface Comment {
id: string
userId: string
userName: string
userAvatar: string
targetType: 'heritage' | 'inheritor' | 'news'
targetId: string
content: string
rating?: number // 1-5 评分
images?: string[]
likeCount: number
replyCount: number
replies?: Comment[]
createdAt: string
updatedAt: string
}
// ===== 搜索相关类型 =====
export interface SearchResult {
type: 'heritage' | 'inheritor' | 'news'
id: string
title: string
subtitle?: string
cover: string
description: string
tags: string[]
score: number // 搜索相关度评分
}
export interface SearchResults {
heritages: HeritageItem[]
inheritors: Inheritor[]
news: NewsArticle[]
}
export interface SearchHistory {
id: string
keyword: string
timestamp: string
}