feat(登录): 完善登录逻辑和用户信息处理
refactor(文章): 重构文章创建和分类选择功能 style(布局): 调整主布局样式和响应式设计 fix(状态管理): 修正全局状态存储和清除逻辑 feat(登出): 添加登出功能按钮和逻辑 docs(类型): 扩展文章类型定义字段
This commit is contained in:
@@ -52,19 +52,21 @@
|
||||
<h1 class="typewriter">{{ heroText }}</h1>
|
||||
</div>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<!-- 提示区域 -->
|
||||
<div id="content-section" :class="{ 'visible': isconts }">
|
||||
<div class="nonsensetitle" v-if="classnonsenset">
|
||||
<div class="nonsensetitleconst">
|
||||
<h1>发癫中QAQ</h1>
|
||||
<h1>{{Cardtitle}}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 左侧模块 -->
|
||||
<LeftModule class="leftmodluepage" :class="{ 'nonsensetmargintop': classnonsenset }" v-if="windowwidth" />
|
||||
<div class="leftmodluecontainer" v-if="isleftmodluecontainer">
|
||||
<LeftModule class="leftmodluepage" :class="{ 'nonsensetmargintop': classnonsenset }" v-if="windowwidth" />
|
||||
</div>
|
||||
|
||||
<!-- 内容模块 -->
|
||||
<RouterView class="RouterViewpage" :class="{ 'nonsensetmargintop': classnonsenset }" />
|
||||
<RouterView class="RouterViewpage" :class="{'forbidwidth': !isleftmodluecontainer, 'nonsensetmargintop': classnonsenset }" />
|
||||
</div>
|
||||
|
||||
<!-- 分页区域 -->
|
||||
@@ -84,10 +86,13 @@ const router = useRouter();
|
||||
const route = useRoute();
|
||||
// 全局状态管理
|
||||
import { useGlobalStore } from '@/store/globalStore'
|
||||
import { Card } from 'ant-design-vue';
|
||||
const globalStore = useGlobalStore()
|
||||
const Login = computed(() => globalStore.Login)
|
||||
|
||||
// 响应式状态
|
||||
const Cardtitle = ref('');
|
||||
const isleftmodluecontainer = ref(true);
|
||||
const classhero = ref(false);
|
||||
const isconts = ref(false);
|
||||
const isScrollingleftmodlue = ref(false);
|
||||
@@ -97,7 +102,7 @@ const windowwidth = ref(true);
|
||||
const activeIndex = ref('home');
|
||||
const localhome= 'home';
|
||||
|
||||
let rpsliturl = route.path.split('/')[1];
|
||||
let rpsliturl = route.path.split('/');
|
||||
|
||||
// 搜索相关状态
|
||||
const isSearchBoxOpen = ref(false);
|
||||
@@ -105,7 +110,7 @@ const searchKeyword = ref('');
|
||||
let searchCloseTimer: number | undefined;
|
||||
|
||||
// 打字机效果相关
|
||||
let fullHeroText = '测试打字机效果';
|
||||
let fullHeroText = '清疯不颠';
|
||||
const heroText = ref('');
|
||||
let heroIndex = 0;
|
||||
let heroTimer: number | undefined;
|
||||
@@ -218,7 +223,7 @@ const handleResize = () => {
|
||||
windowwidth.value = window.innerWidth > 768;
|
||||
|
||||
// 根据屏幕大小调整内容区可见性
|
||||
if (rpsliturl === localhome) {
|
||||
if (rpsliturl[1] === localhome) {
|
||||
isconts.value = window.innerWidth <= 768 ? true : false;
|
||||
}
|
||||
};
|
||||
@@ -241,7 +246,7 @@ const handleScroll = () => {
|
||||
}
|
||||
|
||||
// 首页内容区滚动动画
|
||||
if (rpsliturl === localhome) {
|
||||
if (rpsliturl[1] === localhome) {
|
||||
isconts.value = window.scrollY > 200;
|
||||
isScrollingleftmodlue.value = window.scrollY > 600;
|
||||
}
|
||||
@@ -250,30 +255,34 @@ const handleScroll = () => {
|
||||
/**
|
||||
* 监听路由变化
|
||||
*/
|
||||
watch(() => route.path, (newPath) => {
|
||||
rpsliturl = route.path.split('/')[1];
|
||||
updatePageState(rpsliturl);
|
||||
setActiveIndex(rpsliturl);
|
||||
watch(() => route.path, () => {
|
||||
rpsliturl = route.path.split('/');
|
||||
updatePageState(rpsliturl[1]);
|
||||
setActiveIndex(rpsliturl[1]);
|
||||
|
||||
const localname = route.path.split('/')[2];
|
||||
console.log(rpsliturl[1])
|
||||
let articledata;
|
||||
// 优先使用attributeId参数(新接口)
|
||||
if (localname==='aericletype') {
|
||||
if (rpsliturl[2]==='aericletype') {
|
||||
articledata = globalStore.getValue('attribute')
|
||||
}
|
||||
// 搜索标题
|
||||
if (localname==='aericletitle') {
|
||||
if (rpsliturl[2]==='aericletitle') {
|
||||
articledata = globalStore.getValue('title')
|
||||
}
|
||||
if (rpsliturl[1]==='nonsense') {
|
||||
articledata = "疯言疯语"
|
||||
}
|
||||
// hero 标题
|
||||
if (articledata) {
|
||||
fullHeroText = articledata.name
|
||||
Cardtitle.value = articledata.name
|
||||
classhero.value = true;
|
||||
}
|
||||
// 跳转后回到顶部
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
// 首页内容区滚动动画仅大屏下生效
|
||||
if (newPath.split('/')[1] === localhome) {
|
||||
isconts.value = window.innerWidth <= 768 ? true : false;
|
||||
if (rpsliturl[1] === localhome && rpsliturl[2] == '') {
|
||||
// isconts.value = window.innerWidth <= 768 ? true : false;
|
||||
// 首页时启动打字机效果
|
||||
startTypewriter();
|
||||
} else {
|
||||
@@ -281,6 +290,12 @@ watch(() => route.path, (newPath) => {
|
||||
heroText.value =fullHeroText;
|
||||
if (heroTimer) clearInterval(heroTimer);
|
||||
}
|
||||
// 非首页时关闭左侧状态栏
|
||||
if (rpsliturl[1] == "articlesave") {
|
||||
isleftmodluecontainer.value = false;
|
||||
} else {
|
||||
isleftmodluecontainer.value = true;
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRouter } from 'vue-router';
|
||||
import { articleService, messageService, categoryAttributeService } from '@/services'
|
||||
import { articleService, messageService, categoryAttributeService ,loginService} from '@/services'
|
||||
// 全局状态管理
|
||||
import { useGlobalStore } from '@/store/globalStore'
|
||||
const globalStore = useGlobalStore()
|
||||
@@ -32,7 +32,8 @@ const buttons = [
|
||||
{ id: 1, label: '新建文章', icon: 'icon-add-article' },
|
||||
{ id: 2, label: '新建分类', icon: 'icon-create-category' },
|
||||
{ id: 3, label: '疯言疯语', icon: 'icon-upload-file' },
|
||||
{ id: 4, label: '新建标签', icon: 'icon-new-tag' }
|
||||
{ id: 4, label: '新建标签', icon: 'icon-new-tag' },
|
||||
{ id: 5, label: '登出', icon: 'icon-logout' }
|
||||
]
|
||||
const buttonsave = [
|
||||
{ id: 1, label: '修改文章', icon: 'icon-add-article' },
|
||||
@@ -98,6 +99,24 @@ const handleButtonClick = (button) => {
|
||||
// 取消删除
|
||||
})
|
||||
}
|
||||
if (button.label == '登出') {
|
||||
// 调用登出接口
|
||||
loginService.logout().then(response => {
|
||||
if (response.code == 200) {
|
||||
ElMessage.success('登出成功')
|
||||
// 清空全局状态
|
||||
globalStore.clearAll()
|
||||
// 清除localStorage中的token
|
||||
localStorage.removeItem('token')
|
||||
// 跳转首页
|
||||
router.push({ path: '/' })
|
||||
} else {
|
||||
ElMessage.error(response.message || '登出失败')
|
||||
}
|
||||
}).catch(err => {
|
||||
ElMessage.error(err.message || '登出失败')
|
||||
})
|
||||
}
|
||||
isExpanded.value = false // 点击后收起
|
||||
}
|
||||
|
||||
@@ -235,6 +254,11 @@ onBeforeUnmount(() => {
|
||||
margin-left: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
.action-button.show:nth-child(5) {
|
||||
transition: all 0.2s ease-in-out;
|
||||
margin-left: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 确保按钮按顺序关闭,形成卷帘效果 */
|
||||
.action-button:nth-child(1) {
|
||||
@@ -261,7 +285,11 @@ onBeforeUnmount(() => {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
.action-button:nth-child(5) {
|
||||
transition: all 1.1s ease-in-out;
|
||||
margin-left: 150px;
|
||||
opacity: 1;
|
||||
}
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.establish-container {
|
||||
|
||||
@@ -54,8 +54,8 @@ class ArticleService {
|
||||
* @param {import('../types').ArticleDto} articleData - 文章数据
|
||||
* @returns {Promise<import('../types').ApiResponse<import('../types').Article>>}
|
||||
*/
|
||||
createArticle(Article) {
|
||||
return api.post('/articles', Article)
|
||||
createArticle(articleData) {
|
||||
return api.post('/articles', articleData)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,9 @@ class LoginService {
|
||||
* 登出
|
||||
*/
|
||||
logout() {
|
||||
|
||||
return api.post("/auth/logout");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,7 +22,7 @@ export const useGlobalStore = defineStore('global', {
|
||||
// 全局数据对象,存储所有需要共享的数据
|
||||
globalData: initialGlobalData,
|
||||
// 特定状态属性,从localStorage读取初始值
|
||||
user: initialSpecificData.user || null,
|
||||
username: initialSpecificData.username || null,
|
||||
Login: initialSpecificData.Login || false,
|
||||
notifications: initialSpecificData.notifications || []
|
||||
}
|
||||
@@ -80,7 +80,7 @@ export const useGlobalStore = defineStore('global', {
|
||||
localStorage.setItem('globalStoreData', JSON.stringify(this.globalData))
|
||||
// 持久化特定状态属性
|
||||
localStorage.setItem('globalStoreSpecificData', JSON.stringify({
|
||||
user: this.user,
|
||||
username: this.username,
|
||||
Login: this.Login,
|
||||
notifications: this.notifications
|
||||
}))
|
||||
@@ -118,13 +118,13 @@ export const useGlobalStore = defineStore('global', {
|
||||
*/
|
||||
clearAll() {
|
||||
this.globalData = {}
|
||||
this.user = null
|
||||
this.username = null
|
||||
this.Login = false
|
||||
this.notifications = []
|
||||
// 清除localStorage中的数据
|
||||
try {
|
||||
localStorage.removeItem('globalStoreData')
|
||||
// localStorage.removeItem('globalStoreSpecificData')
|
||||
localStorage.removeItem('globalStoreSpecificData')
|
||||
} catch (error) {
|
||||
console.error('Failed to clear data from localStorage:', error)
|
||||
}
|
||||
@@ -134,8 +134,8 @@ export const useGlobalStore = defineStore('global', {
|
||||
* 设置用户信息
|
||||
* @param {Object} userInfo - 用户信息对象
|
||||
*/
|
||||
setUser(userInfo) {
|
||||
this.user = userInfo
|
||||
setUsername(username) {
|
||||
this.username = username
|
||||
// 持久化到localStorage
|
||||
this._persistData()
|
||||
},
|
||||
|
||||
@@ -240,6 +240,9 @@ p {
|
||||
margin: var(--content-margin);
|
||||
}
|
||||
|
||||
.RouterViewpage.forbidwidth {
|
||||
width: 100%;
|
||||
}
|
||||
/* 分页区样式 */
|
||||
.Pagination {
|
||||
align-self: center;
|
||||
@@ -247,7 +250,7 @@ p {
|
||||
}
|
||||
|
||||
/* 左侧状态栏样式 */
|
||||
.leftmodluepage {
|
||||
.leftmodluecontainer {
|
||||
width: var(--leftmodlue-width);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ export interface ArticleDto {
|
||||
attributeid: number
|
||||
img?: string
|
||||
status?: number
|
||||
viewCount?: number
|
||||
likes?: number
|
||||
markdownscontent: string
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,35 +1,51 @@
|
||||
<template>
|
||||
<div id="allstyle">
|
||||
<div class="article-header-section">
|
||||
<h1 class="article-main-title">{{ Articleform.title }}11</h1>
|
||||
<div class="article-meta-info">
|
||||
<span class="meta-item">
|
||||
<i class="el-icon-date"></i>
|
||||
<!-- {{ formatDate(Articleform.createdAt) }} -->
|
||||
22
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<i class="el-icon-folder"></i>
|
||||
{{ Articleform.categoryName || '未分类' }}33
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<i class="el-icon-view"></i>
|
||||
{{ Articleform.viewCount || 0 }} 阅读
|
||||
</span>
|
||||
</div>
|
||||
<div id="allstyle">
|
||||
<div class="article-header-section">
|
||||
<div style="text-align: center;">
|
||||
<input type="text" v-model="Articleform.title" class="article-main-title" placeholder="请输入标题"
|
||||
@focus="($event.target as HTMLInputElement).placeholder = ''"
|
||||
@blur="($event.target as HTMLInputElement).placeholder = '请输入标题'"
|
||||
style="border: none; outline: none; background: transparent; width: 100%; font-size: 2.5rem; font-weight: 700; line-height: 1.2; text-align: center;" />
|
||||
</div>
|
||||
<div>
|
||||
<MdEditor v-model="Articleform.markdownscontent" htmlPreview preview={false} class="markdown-editor" @on-save="handleSave" />
|
||||
<div class="article-meta-info" style="text-align: center;">
|
||||
<span class="meta-item">
|
||||
|
||||
|
||||
<i class="el-icon-document"></i>
|
||||
<span> {{ new Date().toLocaleDateString() }} </span>
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<i class="el-icon-document"></i>
|
||||
<el-select v-model="Articleform.status" placeholder="请选择状态">
|
||||
<el-option v-for="item in statusoptions" :key="item.value" :label="item.label" :value="item.value">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</span>
|
||||
<span class="meta-item">
|
||||
<i class="el-icon-folder"></i>
|
||||
<el-cascader :options="categorieoptions" v-model="selectedValues" @change="handleCascaderChange"
|
||||
placeholder="选择分类和属性">
|
||||
<template #default="{ node, data }">
|
||||
<span>{{ data.label }}</span>
|
||||
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
|
||||
</template>
|
||||
</el-cascader>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<MdEditor v-model="Articleform.markdownscontent" class="markdown-editor" @on-save="handleSave" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { MdEditor } from 'md-editor-v3';
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
import { articleService } from '@/services';
|
||||
import { categoryService, categoryAttributeService, articleService } from '@/services';
|
||||
import type { Article } from '@/types/index.ts';
|
||||
import { ElMessage } from 'element-plus';
|
||||
const Articleform = ref<Article>({
|
||||
articleid: 0,
|
||||
title: '',
|
||||
@@ -41,28 +57,373 @@ const Articleform = ref<Article>({
|
||||
markdownscontent: ''
|
||||
})
|
||||
|
||||
// 用于级联选择器的值绑定
|
||||
const selectedValues = ref([]);
|
||||
const categorieoptions = ref([]);
|
||||
const statusoptions = ref([
|
||||
{
|
||||
label: '未发布',
|
||||
value: '0'
|
||||
},
|
||||
{
|
||||
label: '发布',
|
||||
value: '1'
|
||||
}
|
||||
]);
|
||||
const categories = ref([]);
|
||||
|
||||
// 初始化加载分类和属性,构建级联选择器的options
|
||||
const loadCategories = async () => {
|
||||
try {
|
||||
const response = await categoryService.getAllCategories();
|
||||
if (response.code === 200) {
|
||||
categories.value = response.data;
|
||||
|
||||
// 为每个分类加载对应的属性,并构建options格式
|
||||
const optionsData = await Promise.all(
|
||||
categories.value.map(async (category) => {
|
||||
try {
|
||||
const attrResponse = await categoryAttributeService.getAttributesByCategory(category.typeid);
|
||||
const children = attrResponse.code === 200 && attrResponse.data ?
|
||||
attrResponse.data.map(attr => ({
|
||||
label: attr.attributename,
|
||||
value: attr.attributeid.toString()
|
||||
})) : [];
|
||||
|
||||
return {
|
||||
label: category.typename,
|
||||
value: category.typeid.toString(),
|
||||
children
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`加载分类 ${category.typename} 的属性失败:`, error);
|
||||
return {
|
||||
label: category.typename,
|
||||
value: category.typeid.toString(),
|
||||
children: []
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
categorieoptions.value = optionsData;
|
||||
console.log(optionsData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载分类失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理级联选择变化
|
||||
const handleCascaderChange = (values) => {
|
||||
if (values && values.length > 0) {
|
||||
// 最后一个值是属性ID
|
||||
Articleform.value.attributeid = Number(values[values.length - 1]);
|
||||
} else {
|
||||
Articleform.value.attributeid = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// 组件挂载时加载分类和属性
|
||||
loadCategories();
|
||||
|
||||
const handleSave = (markdown) => {
|
||||
console.log(Articleform.value);
|
||||
Articleform.value.markdownscontent = markdown;
|
||||
// 这里可以添加保存逻辑,比如发送到服务器
|
||||
articleService.createArticle({
|
||||
|
||||
// 验证必填字段
|
||||
if (!Articleform.value.title || !Articleform.value.attributeid) {
|
||||
ElMessage.warning('请填写必填字段:标题和分类属性');
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建请求数据
|
||||
const articleData = {
|
||||
title: Articleform.value.title,
|
||||
content: Articleform.value.content,
|
||||
attributeid: Articleform.value.attributeid,
|
||||
categoryName: Articleform.value.categoryName,
|
||||
attributeid: Number(Articleform.value.attributeid),
|
||||
status: Number(Articleform.value.status),
|
||||
viewCount: 0,
|
||||
likes: 0,
|
||||
markdownscontent: Articleform.value.markdownscontent
|
||||
}).then(res => {
|
||||
};
|
||||
|
||||
console.log('发送文章数据:', articleData);
|
||||
console.log('当前认证token是否存在:', !!localStorage.getItem('token'));
|
||||
|
||||
// 保存文章
|
||||
articleService.createArticle(articleData)
|
||||
.then(res => {
|
||||
console.log('API响应:', res);
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('文章保存成功')
|
||||
ElMessage.success('文章保存成功')
|
||||
} else {
|
||||
ElMessage.error(res.msg || '文章保存失败')
|
||||
ElMessage.error(res.message || '文章保存失败')
|
||||
}
|
||||
}).catch(err => {
|
||||
ElMessage.error(err.message || '文章保存失败')
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('保存失败错误详情:', err);
|
||||
// 更详细的错误信息
|
||||
if (err.response) {
|
||||
console.error('错误状态码:', err.response.status);
|
||||
console.error('错误响应数据:', err.response.data);
|
||||
|
||||
if (err.response.status === 401) {
|
||||
ElMessage.error('未授权访问,请先登录');
|
||||
} else if (err.response.status === 403) {
|
||||
ElMessage.error('没有权限创建文章,请检查账号权限');
|
||||
} else if (err.response.status === 400) {
|
||||
ElMessage.error('数据验证失败: ' + (err.response.data?.message || '请检查输入'));
|
||||
} else {
|
||||
ElMessage.error('请求被拒绝,错误代码: ' + err.response.status);
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(err.message || '文章保存失败')
|
||||
}
|
||||
})
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
<style scoped>
|
||||
.error-state-container .el-button {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 文章头部区域 */
|
||||
.article-header-section {
|
||||
margin-bottom: 32px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 2px solid #ecf0f1;
|
||||
}
|
||||
|
||||
/* 文章标题 */
|
||||
.article-main-title {
|
||||
font-size: 2rem;
|
||||
color: #2c3e50;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 20px;
|
||||
font-weight: 600;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* 文章元信息 */
|
||||
.article-meta-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
color: #7f8c8d;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
/* 元信息项 */
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 文章内容区域 */
|
||||
.article-content-area {
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.8;
|
||||
color: #34495e;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
/* 文章内容中的段落 */
|
||||
.article-content-area p {
|
||||
margin-bottom: 16px;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
/* 文章内容中的二级标题 */
|
||||
.article-content-area h2 {
|
||||
color: #2c3e50;
|
||||
font-size: 1.5rem;
|
||||
margin: 32px 0 16px 0;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #ecf0f1;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 文章内容中的三级标题 */
|
||||
.article-content-area h3 {
|
||||
color: #34495e;
|
||||
font-size: 1.3rem;
|
||||
margin: 24px 0 12px 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 文章内容中的图片 */
|
||||
.article-content-area img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
margin: 20px auto;
|
||||
display: block;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 文章内容中的引用 */
|
||||
.article-content-area blockquote {
|
||||
border-left: 4px solid #3498db;
|
||||
padding-left: 16px;
|
||||
color: #7f8c8d;
|
||||
margin: 16px 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 文章底部区域 */
|
||||
.article-footer-section {
|
||||
padding-top: 24px;
|
||||
border-top: 2px solid #ecf0f1;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
/* 标签列表 */
|
||||
.article-tag-list {
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* 文章操作按钮组 */
|
||||
.article-actions-group {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/* 相关文章区域 */
|
||||
.related-articles-section {
|
||||
padding-top: 32px;
|
||||
border-top: 2px solid #ecf0f1;
|
||||
}
|
||||
|
||||
/* 相关文章标题 */
|
||||
.related-articles-section h3 {
|
||||
font-size: 1.3rem;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 相关文章列表容器 */
|
||||
.related-articles-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 相关文章卡片 */
|
||||
.related-article-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
/* 相关文章卡片悬停效果 */
|
||||
.related-article-card:hover {
|
||||
background-color: #e9ecef;
|
||||
transform: translateX(5px);
|
||||
border-color: #3498db;
|
||||
}
|
||||
|
||||
.related-article-card i {
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
.related-article-card span {
|
||||
font-size: 1rem;
|
||||
color: #495057;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.related-article-card:hover span {
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
/* 评论区样式 */
|
||||
.comment-section {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
/* 响应式设计 - 平板和手机 */
|
||||
@media (max-width: 768px) {
|
||||
#article-detail-page {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.article-detail-wrapper,
|
||||
.loading-state-container,
|
||||
.error-state-container,
|
||||
.empty-state-container {
|
||||
padding: 20px;
|
||||
margin: 0 15px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.article-main-title {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.article-meta-info {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.article-content-area {
|
||||
font-size: 1rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.article-content-area h2 {
|
||||
font-size: 1.3rem;
|
||||
margin: 24px 0 12px 0;
|
||||
}
|
||||
|
||||
.article-content-area h3 {
|
||||
font-size: 1.2rem;
|
||||
margin: 20px 0 10px 0;
|
||||
}
|
||||
|
||||
.related-articles-section h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.related-article-card {
|
||||
padding: 10px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.related-article-card span {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 - 小屏幕手机 */
|
||||
@media (max-width: 480px) {
|
||||
.article-detail-wrapper {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.article-main-title {
|
||||
font-size: 1.35rem;
|
||||
}
|
||||
|
||||
.article-meta-info {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -144,24 +144,28 @@ const handleLogin = async () => {
|
||||
}
|
||||
try {
|
||||
// 模拟登录请求
|
||||
let user = await loginService.login(loginForm)
|
||||
let user = await (await loginService.login(loginForm)).data
|
||||
if (!user) {
|
||||
ElMessage.error('登录失败,请检查用户名和密码')
|
||||
return
|
||||
}
|
||||
console.log('登录成功', user)
|
||||
// 这里应该是实际的登录API调用
|
||||
// console.log('登录请求数据:', loginForm)
|
||||
|
||||
// 模拟登录成功
|
||||
ElMessage.success('登录成功')
|
||||
// 登录成功后,设置全局状态为已登录
|
||||
globalStore.setLoginStatus(user.success)
|
||||
globalStore.setLoading(user.success)
|
||||
globalStore.setLoginStatus(true)
|
||||
console.log('globalStore.Login', globalStore.Login)
|
||||
// 保存登录状态
|
||||
// if (loginForm.rememberMe) {
|
||||
// localStorage.setItem('username', loginForm.username)
|
||||
// }
|
||||
// 保存登录状态token
|
||||
if (user.token) {
|
||||
localStorage.setItem('token', user.token)
|
||||
}
|
||||
if (user.username) {
|
||||
// 记住用户名
|
||||
globalStore.setUsername(user.username)
|
||||
}
|
||||
// 跳转到首页
|
||||
router.push('/')
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user