feat: 添加Pinia全局状态管理并重构路由逻辑

添加Pinia作为全局状态管理工具,重构路由参数传递方式
移除URL中的动态参数,改为通过Pinia store传递数据
优化导航菜单样式和交互效果,修复路由激活状态问题
新增全局状态管理示例文档,说明基础使用方法
调整页面布局和样式,提升用户体验
This commit is contained in:
qingfeng1121
2025-10-20 11:59:53 +08:00
parent 02d17d7260
commit b042e2a511
12 changed files with 525 additions and 90 deletions

64
package-lock.json generated
View File

@@ -12,6 +12,7 @@
"antd": "^5.27.3",
"axios": "^1.12.2",
"element-plus": "^2.11.2",
"pinia": "^3.0.3",
"vue": "^3.5.18",
"vue-router": "^4.5.1"
},
@@ -2681,7 +2682,6 @@
"version": "2.5.0",
"resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.5.0.tgz",
"integrity": "sha512-VSWO/W6nNQdyP520F1mhf+Lc2f8pjGQOtoHHm7Ze8Go1kX7akpVIrtTa0fn+HB0QJEDVacl6aO08YE0PgXfdnQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
@@ -2938,7 +2938,6 @@
"version": "3.0.5",
"resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-3.0.5.tgz",
"integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-what": "^4.1.8"
@@ -3693,7 +3692,6 @@
"version": "5.5.3",
"resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz",
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
"dev": true,
"license": "MIT"
},
"node_modules/html-encoding-sniffer": {
@@ -3917,7 +3915,6 @@
"version": "4.1.16",
"resolved": "https://registry.npmmirror.com/is-what/-/is-what-4.1.16.tgz",
"integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.13"
@@ -4227,7 +4224,6 @@
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"dev": true,
"license": "MIT"
},
"node_modules/mlly": {
@@ -4486,7 +4482,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
"dev": true,
"license": "MIT"
},
"node_modules/picocolors": {
@@ -4508,6 +4503,60 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pinia": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.3.tgz",
"integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^7.7.2"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"typescript": ">=4.4.4",
"vue": "^2.7.0 || ^3.5.11"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/pinia/node_modules/@vue/devtools-api": {
"version": "7.7.7",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.7.tgz",
"integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==",
"license": "MIT",
"dependencies": {
"@vue/devtools-kit": "^7.7.7"
}
},
"node_modules/pinia/node_modules/@vue/devtools-kit": {
"version": "7.7.7",
"resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz",
"integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==",
"license": "MIT",
"dependencies": {
"@vue/devtools-shared": "^7.7.7",
"birpc": "^2.3.0",
"hookable": "^5.5.3",
"mitt": "^3.0.1",
"perfect-debounce": "^1.0.0",
"speakingurl": "^14.0.1",
"superjson": "^2.2.2"
}
},
"node_modules/pinia/node_modules/@vue/devtools-shared": {
"version": "7.7.7",
"resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz",
"integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==",
"license": "MIT",
"dependencies": {
"rfdc": "^1.4.1"
}
},
"node_modules/pkg-types": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.0.tgz",
@@ -5275,7 +5324,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true,
"license": "MIT"
},
"node_modules/rollup": {
@@ -5472,7 +5520,6 @@
"version": "14.0.1",
"resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz",
"integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@@ -5645,7 +5692,6 @@
"version": "2.2.2",
"resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.2.tgz",
"integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"copy-anything": "^3.0.2"

View File

@@ -17,6 +17,7 @@
"antd": "^5.27.3",
"axios": "^1.12.2",
"element-plus": "^2.11.2",
"pinia": "^3.0.3",
"vue": "^3.5.18",
"vue-router": "^4.5.1"
},

View File

@@ -15,7 +15,7 @@
</div>
<div class="cont2">
<el-menu :default-active="activeIndex" class="el-menu-vertical-demo" @select="handleSelect">
<el-menu-item index="/:type">
<el-menu-item index="/home">
<el-icon>
</el-icon>
<span>首页</span>
@@ -144,9 +144,18 @@ onUnmounted(() => {
display: block;
background-color: rgba(0, 0, 0,0 ); /* 白色半透明背景 */
}
.cont2 .el-menu-vertical-demo ul li:hover{
background-color: rgba(220, 53, 69, 0.9); /* 红色半透明背景 */
color: white;
.cont2 .el-menu-vertical-demo .el-menu-item:nth-child(3){
border-radius: 0 0 10px 10px;
}
.cont2 .el-menu-vertical-demo .el-menu-item:hover{
background-color: rgba(64, 158, 255, 0.9);
}
.cont2 .el-menu-vertical-demo .el-menu-item.is-active:hover{
color: black; /* 蓝色半透明背景 */
}
.cont2 .el-menu-vertical-demo .el-menu-item.is-active{
color: var(--nav-is-active);
}
/* 分类列表样式 */

View File

@@ -9,19 +9,19 @@
<el-col :span="14" justify="center">
<div class="grid-content ep-bg-purple-dark">
<el-menu :default-active="activeIndex" class="el-menu-demo" :collapse="false" @select="handleSelect">
<el-menu-item index="/home">
<el-menu-item index="home">
首页
</el-menu-item>
<el-menu-item index="/article-list">
<el-menu-item index="article-list">
目录
</el-menu-item>
<el-menu-item index="/nonsense">
<el-menu-item index="nonsense">
疯言疯语
</el-menu-item>
<el-menu-item index="/about">
<el-menu-item index="about">
关于
</el-menu-item>
<el-menu-item index="/message">
<el-menu-item index="message">
留言板
</el-menu-item>
</el-menu>
@@ -81,6 +81,9 @@ import LeftModule from '@/components/LeftModule.vue';
// 路由相关
const router = useRouter();
const route = useRoute();
// 全局状态管理
import { useGlobalStore } from '@/store/globalStore'
const globalStore = useGlobalStore()
// 响应式状态
const classhero = ref(false);
@@ -89,9 +92,10 @@ const isScrollingleftmodlue = ref(false);
const elrowtop = ref('transparent');
const classnonsenset = ref(false);
const windowwidth = ref(true);
const activeIndex = ref('/home');
const localhome= '/home';
const activeIndex = ref('home');
const localhome= 'home';
let rpsliturl = route.path.split('/')[1];
// 搜索相关状态
const isSearchBoxOpen = ref(false);
@@ -99,7 +103,7 @@ const searchKeyword = ref('');
let searchCloseTimer: number | undefined;
// 打字机效果相关
const fullHeroText = '测试打字机效果';
let fullHeroText = '测试打字机效果';
const heroText = ref('');
let heroIndex = 0;
let heroTimer: number | undefined;
@@ -125,7 +129,7 @@ const startTypewriter = () => {
* 菜单选择跳转
*/
const handleSelect = (key: string) => {
router.push({ path: key });
router.push({ path: '/' + key });
};
/**
@@ -181,16 +185,19 @@ const performSearch = () => {
*/
const updatePageState = (url: string) => {
classhero.value = url !== localhome;
classnonsenset.value = url === '/nonsense';
classnonsenset.value = url === 'nonsense';
};
/**
* 设置当前激活的菜单项
*/
const setActiveIndex = (path: string) => {
activeIndex.value = path;
console.log('设置激活索引:', path);
activeIndex.value =path;
};
/**
* 处理窗口大小变化
*/
@@ -198,7 +205,7 @@ const handleResize = () => {
windowwidth.value = window.innerWidth > 768;
// 根据屏幕大小调整内容区可见性
if (route.path === localhome) {
if (rpsliturl === localhome) {
isconts.value = window.innerWidth <= 768 ? true : false;
}
};
@@ -221,7 +228,7 @@ const handleScroll = () => {
}
// 首页内容区滚动动画
if (route.path === localhome) {
if (rpsliturl === localhome) {
isconts.value = window.scrollY > 200;
isScrollingleftmodlue.value = window.scrollY > 600;
}
@@ -231,16 +238,30 @@ const handleScroll = () => {
* 监听路由变化
*/
watch(() => route.path, (newPath) => {
updatePageState(newPath);
setActiveIndex(newPath);
rpsliturl = route.path.split('/')[1];
updatePageState(rpsliturl);
setActiveIndex(rpsliturl);
const localname = route.path.split('/')[2];
let articledata;
// 优先使用attributeId参数新接口
if (localname==='aericletype') {
articledata = globalStore.getValue('attribute')
}
// 搜索标题
if (localname==='aericletitle') {
articledata = globalStore.getValue('title')
}
// hero 标题
if (articledata) {
fullHeroText = articledata.name
}
// 跳转后回到顶部
window.scrollTo({ top: 0, behavior: 'smooth' });
// 首页内容区滚动动画仅大屏下生效
if (newPath === localhome) {
if (newPath.split('/')[1] === localhome) {
isconts.value = window.innerWidth <= 768 ? true : false;
// 首页时启动打字机
// 首页时启动打字机效果
startTypewriter();
} else {
isconts.value = true;
@@ -257,7 +278,6 @@ onMounted(() => {
handleResize();
// 添加事件监听器
window.addEventListener('resize', handleResize);
window.addEventListener('scroll', handleScroll);

View File

@@ -1,11 +1,16 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import Router from './router/Router'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import './styles/MainLayout.css'
// 创建Pinia实例
const pinia = createPinia()
const app = createApp(App)
app.use(Router)
app.use(ElementPlus)
app.use(pinia) // 添加Pinia支持
app.mount('#app')

View File

@@ -32,11 +32,11 @@ const routes = [
meta: { title: '首页' },
children: [
{
path: 'aericletype/:type',
path: 'aericletype',
name: 'homeByType'
},
{
path: 'aericletitle/:title',
path: 'aericletitle',
name: 'homeByTitle'
}
]

124
src/store/globalStore.js Normal file
View File

@@ -0,0 +1,124 @@
import { defineStore } from 'pinia'
/**
* 全局状态管理store
* 提供全局的传值和获取值的功能
* 任何页面都可以调用和获取其中的值
*/
export const useGlobalStore = defineStore('global', {
// 状态定义
state: () => ({
// 全局数据对象,存储所有需要共享的数据
globalData: {},
// 可以在这里定义特定的状态属性,便于类型提示和直接使用
user: null,
loading: false,
notifications: []
}),
// 计算属性 - 用于获取和转换状态
getters: {
/**
* 检查全局数据中是否存在指定的键
* @param {string} key - 要检查的键名
* @returns {boolean} - 如果存在返回true否则返回false
*/
hasValue: (state) => (key) => {
return Object.prototype.hasOwnProperty.call(state.globalData, key)
},
/**
* 获取指定键的值
* @param {string} key - 要获取的键名
* @returns {any} - 对应的值如果不存在则返回undefined
*/
getValue: (state) => (key) => {
return state.globalData[key]
},
/**
* 获取所有全局数据
* @returns {Object} - 所有全局数据
*/
getAllData: (state) => {
return { ...state.globalData }
}
},
// 操作方法 - 用于修改状态
actions: {
/**
* 设置全局数据
* @param {string} key - 键名
* @param {any} value - 要存储的值
*/
setValue(key, value) {
this.globalData[key] = value
},
/**
* 批量设置多个键值对
* @param {Object} data - 包含多个键值对的对象
*/
setMultipleValues(data) {
if (typeof data === 'object' && data !== null) {
Object.assign(this.globalData, data)
}
},
/**
* 删除指定的全局数据
* @param {string} key - 要删除的键名
*/
removeValue(key) {
if (Object.prototype.hasOwnProperty.call(this.globalData, key)) {
delete this.globalData[key]
}
},
/**
* 清空所有全局数据
*/
clearAll() {
this.globalData = {}
},
/**
* 设置用户信息
* @param {Object} userInfo - 用户信息对象
*/
setUser(userInfo) {
this.user = userInfo
},
/**
* 设置加载状态
* @param {boolean} status - 加载状态
*/
setLoading(status) {
this.loading = status
},
/**
* 添加通知
* @param {Object} notification - 通知对象
*/
addNotification(notification) {
this.notifications.push({
id: Date.now(),
...notification
})
},
/**
* 移除通知
* @param {number} id - 通知ID
*/
removeNotification(id) {
const index = this.notifications.findIndex(notification => notification.id === id)
if (index !== -1) {
this.notifications.splice(index, 1)
}
}
}
})

208
src/store/useExample.md Normal file
View File

@@ -0,0 +1,208 @@
# Pinia 全局状态管理使用示例
## 1. 基础使用方法
### 在任意组件中使用全局状态
```vue
<template>
<div>
<h2>全局状态管理示例</h2>
<!-- 显示全局数据 -->
<div v-if="globalStore.hasValue('exampleData')">
<p>从全局store获取的数据: {{ globalStore.getValue('exampleData') }}</p>
</div>
<div v-else>
<p>全局数据尚未设置</p>
</div>
<!-- 设置全局数据 -->
<el-input v-model="inputValue" placeholder="输入要存储的全局数据" />
<el-button @click="setGlobalData">设置全局数据</el-button>
<el-button @click="checkGlobalData">检查是否存在</el-button>
<el-button @click="clearGlobalData" type="danger">清空全局数据</el-button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useGlobalStore } from '@/store/globalStore'
// 获取全局store实例
const globalStore = useGlobalStore()
const inputValue = ref('')
// 设置全局数据
const setGlobalData = () => {
globalStore.setValue('exampleData', inputValue.value)
ElMessage.success('数据已保存到全局状态')
}
// 检查数据是否存在
const checkGlobalData = () => {
if (globalStore.hasValue('exampleData')) {
ElMessage.info(`数据存在: ${globalStore.getValue('exampleData')}`)
} else {
ElMessage.warning('数据不存在')
}
}
// 清空全局数据
const clearGlobalData = () => {
globalStore.removeValue('exampleData')
ElMessage.success('数据已从全局状态中移除')
}
</script>
```
## 2. 页面间数据传递示例
### 页面A - 设置数据
```vue
<script setup>
import { useGlobalStore } from '@/store/globalStore'
import { useRouter } from 'vue-router'
const globalStore = useGlobalStore()
const router = useRouter()
const navigateToPageB = () => {
// 存储页面间需要传递的数据
globalStore.setValue('sharedData', {
id: 123,
name: '测试数据',
timestamp: Date.now()
})
// 跳转到页面B
router.push('/page-b')
}
</script>
```
### 页面B - 获取数据
```vue
<script setup>
import { onMounted } from 'vue'
import { useGlobalStore } from '@/store/globalStore'
const globalStore = useGlobalStore()
onMounted(() => {
// 检查并获取从页面A传递过来的数据
if (globalStore.hasValue('sharedData')) {
const data = globalStore.getValue('sharedData')
console.log('从页面A接收的数据:', data)
// 使用接收到的数据进行操作
}
})
</script>
```
## 3. 批量操作示例
```javascript
import { useGlobalStore } from '@/store/globalStore'
const globalStore = useGlobalStore()
// 批量设置多个键值对
globalStore.setMultipleValues({
appConfig: {
theme: 'dark',
language: 'zh-CN'
},
userPreferences: {
notifications: true,
fontSize: 16
},
lastUpdated: new Date().toISOString()
})
// 获取所有全局数据
const allData = globalStore.getAllData()
console.log('所有全局数据:', allData)
// 清空所有数据
globalStore.clearAll()
```
## 4. 用户状态管理示例
```vue
<template>
<div>
<div v-if="globalStore.user">
<h3>欢迎回来{{ globalStore.user.username }}</h3>
<el-button @click="logout">登出</el-button>
</div>
<div v-else>
<h3>请登录</h3>
<!-- 登录表单 -->
</div>
<!-- 加载状态指示器 -->
<el-loading v-loading="globalStore.loading" :text="'加载中...'">
<!-- 内容区域 -->
</el-loading>
</div>
</template>
<script setup>
import { useGlobalStore } from '@/store/globalStore'
const globalStore = useGlobalStore()
const login = async (credentials) => {
try {
// 设置加载状态
globalStore.setLoading(true)
// 模拟登录请求
const response = await authService.login(credentials)
// 保存用户信息到全局状态
globalStore.setUser(response.user)
// 添加登录成功通知
globalStore.addNotification({
type: 'success',
message: '登录成功!'
})
} catch (error) {
console.error('登录失败:', error)
} finally {
// 无论成功失败都清除加载状态
globalStore.setLoading(false)
}
}
const logout = () => {
// 清除用户信息
globalStore.setUser(null)
// 添加登出通知
globalStore.addNotification({
type: 'info',
message: '您已成功登出'
})
}
</script>
```
## 5. 注意事项
1. **数据持久化**默认情况下Pinia状态只在内存中存在页面刷新后会丢失。如果需要持久化可以考虑
- 使用localStorage/sessionStorage结合Pinia插件
- 使用Cookie存储关键信息
2. **性能考虑**:避免在全局状态中存储过大的数据,这可能会影响应用性能
3. **类型安全**如果使用TypeScript可以为store添加类型定义
4. **模块化**随着应用复杂度增加可以考虑将不同功能的数据分开存储到多个store中
5. **调试**Pinia提供了良好的开发工具支持可以通过Vue Devtools进行调试

View File

@@ -6,7 +6,7 @@
/* 内容区圆角 */
/* 首页 hero 区域高度和间距 */
--hero-height: 600px;
--hero-height: 768px;
/* hero 默认高度 */
--hero-height-small: 150px;
/* hero 收缩后高度 */
@@ -48,9 +48,9 @@
/* 分页区背景色 */
/* 导航栏样式 */
--nav-padding: 20px 40px;
--nav-padding: 15px 35px;
/* 导航栏默认内边距 */
--nav-padding-small: 15px 40px;
--nav-padding-small: 10px 25px;
/* 导航栏收缩后内边距 */
--nav-bg: rgba(145, 196, 238, 0.85);
/* 导航栏背景色 */
@@ -60,7 +60,8 @@
/* 导航栏隐藏时背景 */
--nav-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
/* 导航栏阴影 */
--nav-is-active: rgba(255, 0, 255, 0.5);
/* 导航栏激活项字体颜色 */
/* 字体颜色和菜单样式 */
--font-color-title: #AF7AC5;
/* 标题字体颜色 */
@@ -115,7 +116,7 @@ p {
width: 100%;
padding: var(--nav-padding);
z-index: 1000;
transition: all 0.4s ease;
transition: all 1s ease;
font-size: inherit;
/* display: flex; */
justify-content: space-between;
@@ -179,12 +180,21 @@ p {
.el-menu-demo .el-menu-item {
margin-left: 10px;
font-size: inherit;
color: rgba(255, 255, 255, .95);
}
.el-menu-demo .el-menu-item.is-active{
color: var(--nav-is-active);
}
/* 菜单项悬停和激活状态 */
.el-menu-item:hover,
.el-menu-item.is-active {
.el-menu-demo .el-menu-item:hover{
background-color: transparent;
color: rgba(255, 200, 255, 0.5);
opacity: 0.85;
}
.el-menu-demo .el-menu-item.is-active {
background-color: transparent;
color: var(--nav-is-active);
opacity: 0.85;
}
/* 去除菜单项悬停和激活时的背景色 */

View File

@@ -17,30 +17,30 @@
<div class="about-skills">
<h3>前端技术栈</h3>
<div class="skills-list">
<el-tag type="primary">HTML5</el-tag>
<el-tag type="primary">CSS3</el-tag>
<el-tag type="primary">JavaScript</el-tag>
<el-tag type="primary">TypeScript</el-tag>
<el-tag type="primary">Vue.js</el-tag>
<el-tag type="primary">React</el-tag>
<el-tag type="primary">Node.js</el-tag>
<el-tag type="primary">Webpack</el-tag>
<el-tag type="primary">Git</el-tag>
<a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML" target="_blank" class="skill-link"><el-tag type="primary">HTML5</el-tag></a>
<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS" target="_blank" class="skill-link"><el-tag type="primary">CSS3</el-tag></a>
<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript" target="_blank" class="skill-link"><el-tag type="primary">JavaScript</el-tag></a>
<a href="https://www.typescriptlang.org/" target="_blank" class="skill-link"><el-tag type="primary">TypeScript</el-tag></a>
<a href="https://vuejs.org/" target="_blank" class="skill-link"><el-tag type="primary">Vue.js</el-tag></a>
<a href="https://react.dev/" target="_blank" class="skill-link"><el-tag type="primary">React</el-tag></a>
<a href="https://nodejs.org/" target="_blank" class="skill-link"><el-tag type="primary">Node.js</el-tag></a>
<a href="https://webpack.js.org/" target="_blank" class="skill-link"><el-tag type="primary">Webpack</el-tag></a>
<a href="https://git-scm.com/" target="_blank" class="skill-link"><el-tag type="primary">Git</el-tag></a>
</div>
</div>
<div class="about-skills">
<h3>后端技术栈</h3>
<div class="skills-list">
<el-tag type="success">Spring Boot 2.6.13</el-tag>
<el-tag type="success">Spring Security</el-tag>
<el-tag type="success">Spring Data JPA</el-tag>
<el-tag type="success">MyBatis</el-tag>
<el-tag type="success">MySQL</el-tag>
<el-tag type="success">Lombok</el-tag>
<el-tag type="success">EHCache</el-tag>
<el-tag type="success">Maven</el-tag>
<el-tag type="success">Java 8</el-tag>
<a href="https://spring.io/projects/spring-boot" target="_blank" class="skill-link"><el-tag type="success">Spring Boot 2.6.13</el-tag></a>
<a href="https://spring.io/projects/spring-security" target="_blank" class="skill-link"><el-tag type="success">Spring Security</el-tag></a>
<a href="https://spring.io/projects/spring-data-jpa" target="_blank" class="skill-link"><el-tag type="success">Spring Data JPA</el-tag></a>
<a href="https://mybatis.org/mybatis-3/" target="_blank" class="skill-link"><el-tag type="success">MyBatis</el-tag></a>
<a href="https://www.mysql.com/" target="_blank" class="skill-link"><el-tag type="success">MySQL</el-tag></a>
<a href="https://projectlombok.org/" target="_blank" class="skill-link"><el-tag type="success">Lombok</el-tag></a>
<a href="https://www.ehcache.org/" target="_blank" class="skill-link"><el-tag type="success">EHCache</el-tag></a>
<a href="https://maven.apache.org/" target="_blank" class="skill-link"><el-tag type="success">Maven</el-tag></a>
<a href="https://www.oracle.com/java/technologies/java8.html" target="_blank" class="skill-link"><el-tag type="success">Java 8</el-tag></a>
</div>
</div>
@@ -139,6 +139,20 @@ const goToMessageBoard = () => {
gap: 10px;
}
/* 技能链接样式 */
.skill-link {
text-decoration: none;
transition: transform 0.2s ease;
}
.skill-link:hover {
transform: translateY(-2px);
}
.skill-link:hover .el-tag {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
/* 兴趣爱好 */
.about-hobbies {
margin-bottom: 32px;

View File

@@ -26,9 +26,9 @@
<span class="badge badge-primary"> {{ categoryGroup.attributes.length }} </span>
<ul class="wp-block-list">
<li v-for="category in categoryGroup.attributes" :key="category.typeid">
<a class="btn" @click="handleCategoryClick(category.categoryid)"><kbd>{{ category.attributename
&nbsp;&nbsp;<a class="btn" @click="handleCategoryClick(category)"><kbd>{{ category.attributename
}}</kbd></a>
&nbsp; ({{ category.articles.length }})
&nbsp; ({{ category.articles.length }})
</li>
</ul>
</div>
@@ -50,7 +50,9 @@ import { ref, onMounted } from 'vue'
// 以下代码仅作示例,实际需根据 '@/services' 模块的真实导出调整
import { ElMessage } from 'element-plus'
import { categoryService, categoryAttributeService, articleService } from '@/services'
import { useGlobalStore } from '@/store/globalStore'
const globalStore = useGlobalStore()
const router = useRouter()
// 响应式状态
@@ -141,12 +143,14 @@ const getCategoryAttributes = async (processedCategories: any[]) => {
* 注意现在实际上使用的是属性ID而不是分类ID
* @param {string | number} attributeId - 属性ID
*/
const handleCategoryClick = (attributeId: string | number) => {
const handleCategoryClick = (attribute: any) => {
globalStore.setValue('attribute', {
id: attribute.typeid || attribute.categoryid,
name: attribute.attributename || attribute.typename || '未命名属性',
})
console.log(attribute)
router.push({
path: '/home/aericletype',
query: {
attributeId: String(attributeId)
}
})
}
@@ -192,7 +196,6 @@ onMounted(() => {
.category-group ul li {
display: flex;
align-items: center;
padding: 5px 0;
}
.category-group ul li .btn::before {
@@ -201,7 +204,6 @@ onMounted(() => {
.category-group ul li .btn:hover {
background-color: rgb(245, 247, 250, 0.7);
border-bottom: #4b64f0 2px solid;
}
.alert:not(.alert-secondary) {

View File

@@ -12,16 +12,15 @@
<div
class="article-card"
v-for="item in datas"
:key="item.id || (item.title + item.publishedAt)"
:key="item.articleid"
@click="handleArticleClick(item.articleid)"
>
<h2 class="article-title">{{ item.title }}</h2>
<div class="article-meta">
<span class="article-author">{{ item.author || '清疯不颠' }}</span>
<span class="article-date">{{ formatDateDisplay(item.publishedAt || item.createTime) }}</span>
<!-- <span class="article-date">{{ formatDateDisplay(item.publishedAt || item.createTime) }}</span> -->
<span v-if="item.categoryName" class="article-category">{{ item.categoryName }}</span>
<span v-if="item.views" class="article-views">{{ item.views }} 阅读</span>
<span v-if="item.commentCount" class="article-comments">{{ item.commentCount }} 评论</span>
<span v-if="item.viewCount" class="article-views">{{ item.views }} 阅读</span>
<!-- <span v-if="item.content" class="article-comments">{{ item.commentCount }} 评论</span> -->
</div>
<div v-if="item.mg" class="article-tag">mg</div>
<p class="article-preview">{{ formatContentPreview(item.content, 150) }}</p>
@@ -42,7 +41,9 @@ import { articleService } from '@/services'
import { formatDate, formatRelativeTime } from '@/utils/dateUtils'
import { formatContentPreview } from '@/utils/stringUtils'
import { ElMessage } from 'element-plus'
import { useGlobalStore } from '@/store/globalStore'
const globalStore = useGlobalStore()
// 路由实例
const router = useRouter()
const route = useRoute()
@@ -63,33 +64,28 @@ const fetchArticles = async () => {
let res = {} // 使用let而不是const
// 检查URL参数
const query = route.query
const params = route.params
const localhome = route.path.split('/')[2];
// 优先使用attributeId参数新接口
if (query.attributeId) {
console.log('根据属性ID获取文章列表')
res = await articleService.getArticlesByAttribute(query.attributeId)
}
// 兼容旧的type参数
else if (params.type) {
console.log('根据类型ID获取文章列表兼容模式')
res = await articleService.getArticlesByCategory(params.type)
if (localhome==='aericletype') {
const data = globalStore.getValue('attribute')
res = await articleService.getArticlesByAttributeId(data.id)
}
// 搜索标题
else if (params.title || query.title) {
const title = params.title || query.title
console.log('根据标题获取文章列表')
res = await articleService.getArticlesByTitle(title)
else if (localhome==='aericletitle') {
const data = globalStore.getValue('title')
res = await articleService.getArticlesByTitle(data.title)
}
// 获取所有文章
else {
res = await articleService.getAllArticles()
console.log('获取所有文章列表')
res = await articleService.getAllArticles()
}
// 修复使用正确的属性名data而不是date
console.log('获取文章列表成功:', res.data)
datas.value = res.data || []
console.log('获取文章列表成功:', datas.value)
console.log('文章数据已赋值:', datas.value)
} catch (error) {
console.error('获取文章列表失败:', error)
ElMessage.error('获取文章列表失败,请稍后重试')