diff --git a/src/App.vue b/src/App.vue index 9669f98..b07feae 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,15 +1,18 @@ \ No newline at end of file + + + \ No newline at end of file diff --git a/src/components/author/WorkItem.vue b/src/components/author/WorkItem.vue new file mode 100644 index 0000000..9c4dc95 --- /dev/null +++ b/src/components/author/WorkItem.vue @@ -0,0 +1,226 @@ + + + + + \ No newline at end of file diff --git a/src/components/book/BookComments.vue b/src/components/book/BookComments.vue index 04f6b8f..7f73226 100644 --- a/src/components/book/BookComments.vue +++ b/src/components/book/BookComments.vue @@ -31,7 +31,7 @@
{{ comment.content }}
+ + +
+ +
+ 取消 + 回复 +
+
+ + +
+
+
+ +
+
+
+ {{ reply.username }} + {{ reply.time }} +
+
{{ reply.content }}
+
+
+
@@ -82,12 +107,14 @@ export default defineComponent({ username: '万年捧', avatar: 'https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKlR5PibUEEsVjXGfH4c1eR5hXDicoH0EJUTHYwDO3EvZLXXgON8GrNTbRg8DnzaddicibYnGcfq28tYg/132', time: '2022-07-03', - content: '这是本年内看的唯一一部完结的!看的人真幸运,发家文和风险防控写的都是一流' + content: '这是本年内看的唯一一部完结的!看的人真幸运,发家文和风险防控写的都是一流', + replies: [] }, { username: '残生往事', avatar: 'https://thirdwx.qlogo.cn/mmopen/vi_32/3F4feeHnMyoGjqKfP8vGKCHwyvovMHiaO0Q1QkQMRTGibLcyJbUcUJ4LmdkkDqC5ZcqP1rvqKMviaYAyehqYb6ciaA/132', - content: '我很喜欢男主的性格,不小心眼,有格局,做事情多考虑下一步,商业和情感都处理得不错,就是那个林涵有点没必要吧?' + content: '我很喜欢男主的性格,不小心眼,有格局,做事情多考虑下一步,商业和情感都处理得不错,就是那个林涵有点没必要吧?', + replies: [] } ]); @@ -96,6 +123,10 @@ export default defineComponent({ const currentPage = ref(1); const pageSize = ref(10); const totalComments = ref(comments.value.length); + + // 回复相关状态 + const activeReplyIndex = ref(null); + const replyText = ref(''); const submitComment = () => { if (!commentText.value.trim()) { @@ -108,7 +139,8 @@ export default defineComponent({ username: '当前用户', avatar: defaultAvatar.value, time: new Date().toLocaleDateString(), - content: commentText.value + content: commentText.value, + replies: [] }); totalComments.value = comments.value.length; @@ -121,6 +153,47 @@ export default defineComponent({ currentPage.value = page; // 实际应用中这里应该加载对应页的数据 }; + + // 切换回复表单的显示状态 + const toggleReplyForm = (index) => { + if (activeReplyIndex.value === index) { + activeReplyIndex.value = null; + } else { + activeReplyIndex.value = index; + replyText.value = ''; + } + }; + + // 取消回复 + const cancelReply = () => { + activeReplyIndex.value = null; + replyText.value = ''; + }; + + // 提交回复 + const submitReply = (index) => { + if (!replyText.value.trim()) { + ElMessage.warning('请输入回复内容'); + return; + } + + if (!comments.value[index].replies) { + comments.value[index].replies = []; + } + + // 添加回复 + comments.value[index].replies.push({ + username: '当前用户', + avatar: defaultAvatar.value, + time: new Date().toLocaleDateString(), + content: replyText.value + }); + + // 重置状态 + replyText.value = ''; + activeReplyIndex.value = null; + ElMessage.success('回复成功!'); + }; return { comments, @@ -131,7 +204,12 @@ export default defineComponent({ pageSize, totalComments, submitComment, - handlePageChange + handlePageChange, + activeReplyIndex, + replyText, + toggleReplyForm, + cancelReply, + submitReply }; } }); @@ -258,6 +336,66 @@ export default defineComponent({ } } } + + // 回复表单样式 + .reply-form { + margin-top: 12px; + padding: 12px; + background-color: #f9f9f9; + border-radius: 4px; + + .reply-actions { + display: flex; + justify-content: flex-end; + margin-top: 8px; + gap: 8px; + } + } + + // 回复列表样式 + .replies-list { + margin-top: 16px; + padding-left: 12px; + border-left: 2px solid #eee; + + .reply-item { + display: flex; + padding: 12px 0; + + .user-avatar { + width: 36px; + height: 36px; + margin-right: 12px; + } + + .reply-content { + flex: 1; + + .reply-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; + + .username { + font-size: 14px; + font-weight: 500; + } + + .time { + font-size: 12px; + color: #999; + } + } + + .reply-text { + font-size: 14px; + line-height: 1.5; + color: #333; + } + } + } + } } } } diff --git a/src/components/book/InteractiveReward.vue b/src/components/book/InteractiveReward.vue new file mode 100644 index 0000000..b4c1a75 --- /dev/null +++ b/src/components/book/InteractiveReward.vue @@ -0,0 +1,381 @@ + + + + + \ No newline at end of file diff --git a/src/layout/index.vue b/src/layout/index.vue index 12cf3a5..55110e1 100644 --- a/src/layout/index.vue +++ b/src/layout/index.vue @@ -1,25 +1,21 @@ @@ -44,26 +40,15 @@ export default defineComponent({ @use '@/assets/styles/variables.scss' as vars; .layout-container { - display: flex; - flex-direction: column; - min-height: 100vh; + display: flex; + flex-direction: column; + min-height: 100vh; } .layout-main { - flex: 1; - max-width: 1240px; - margin: 0 auto; - width: 100%; -} - -// 页面切换动画 -.fade-enter-active, -.fade-leave-active { - transition: opacity 0.3s ease; -} - -.fade-enter-from, -.fade-leave-to { - opacity: 0; + flex: 1; + max-width: 1240px; + margin: 0 auto; + width: 100%; } diff --git a/src/layout/layout/Header.vue b/src/layout/layout/Header.vue index 82f8638..c9b7b82 100644 --- a/src/layout/layout/Header.vue +++ b/src/layout/layout/Header.vue @@ -23,8 +23,8 @@ 玄幻 排行榜 其他 - 书架 - 作家专区 + 书架 + 作家专区 @@ -214,6 +214,38 @@ export default { // 已登录,直接打开申请弹窗 authorApplicationContext.openApplicationModal(); }; + + // 前往作家专区 + const goToAuthorCenter = () => { + // 检查是否登录 + if (!isLoggedIn.value) { + // 未登录,打开登录弹窗 + authContext.openLogin(() => { + // 登录成功后检查是否是作家 + if (!store.isAuthor) { + // 不是作家,打开申请弹窗 + authorApplicationContext.openApplicationModal(() => { + router.push('/author'); + }); + } else { + // 是作家,直接跳转 + router.push('/author'); + } + }); + return; + } + + // 已登录,检查是否是作家 + if (!store.isAuthor) { + // 不是作家,打开申请弹窗 + authorApplicationContext.openApplicationModal(() => { + router.push('/author'); + }); + } else { + // 是作家,直接跳转 + router.push('/author'); + } + }; return { isLoggedIn, @@ -228,7 +260,9 @@ export default { goToBookshelf, goToSettings, logout, - applyForAuthor + applyForAuthor, + goToAuthorCenter, + router, }; } }; diff --git a/src/main.js b/src/main.js index 13bc171..a7263ab 100644 --- a/src/main.js +++ b/src/main.js @@ -5,6 +5,8 @@ import { createPinia } from 'pinia'; import App from './App.vue'; import router from './router'; import { useMainStore } from './store'; +// 导入调试工具 +import './utils/debug'; // 导入全局样式 import './assets/styles/global.scss'; @@ -14,13 +16,22 @@ const pinia = createPinia(); app.use(ElementPlus); app.use(pinia); -app.use(router); -// 初始化身份验证状态 +// 先初始化身份验证状态 const store = useMainStore(); store.initializeAuth(); // 全局挂载pinia,用于路由守卫 window.$pinia = pinia; +// 确保在状态恢复后再挂载路由 +app.use(router); + +// 打印初始状态 +if (import.meta.env.DEV) { + setTimeout(() => { + window.$debug.logUserState(); + }, 0); +} + app.mount('#app'); \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js index be430d4..5abb8d6 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -17,22 +17,26 @@ const routes = [ { path: 'book/:id', name: 'BookDetail', - component: () => import('../views/book/index.vue') + component: () => import('../views/book/index.vue'), + props: true }, { path: 'book/:id/chapter/:chapterId', name: 'ChapterDetail', - component: () => import('../views/book/chapter.vue') + component: () => import('../views/book/chapter.vue'), + props: true }, { path: 'category', name: 'Category', - component: () => import('../views/home/category.vue') + component: () => import('../views/home/category.vue'), + props: true }, { path: 'category/:id', name: 'CategoryDetail', - component: () => import('../views/home/category.vue') + component: () => import('../views/home/category.vue'), + props: true }, { path: 'ranking', @@ -44,6 +48,45 @@ const routes = [ name: 'Bookshelf', component: () => import('../views/home/Bookshelf.vue'), meta: { requiresAuth: true } + }, + { + path: 'author', + name: 'authorCenter', + component: () => import('../views/author/AuthorCenter.vue'), + meta: { requiresAuth: true, requiresAuthor: true }, + redirect: { name: 'authorWorks' }, + children: [ + { + path: 'works', + name: 'authorWorks', + component: () => import('../views/author/components/WorksManagement.vue'), + meta: { requiresAuth: true, requiresAuthor: true } + }, + { + path: 'readers', + name: 'authorReaders', + component: () => import('../views/author/components/ReadersManagement.vue'), + meta: { requiresAuth: true, requiresAuthor: true } + } + ] + }, + { + path: 'author/work/create', + name: 'createWork', + component: () => import('../views/author/CreateWork.vue'), + meta: { requiresAuth: true, requiresAuthor: true } + }, + { + path: 'author/work/:id/setup', + name: 'workSetup', + component: () => import('../views/author/WorkSetup.vue'), + meta: { requiresAuth: true, requiresAuthor: true } + }, + { + path: 'author/work/:id/edit', + name: 'workEdit', + component: () => import('../views/author/WorkEdit.vue'), + meta: { requiresAuth: true, requiresAuthor: true } } ] }, @@ -66,14 +109,37 @@ export const routerEvents = { // 全局路由守卫 router.beforeEach((to, from, next) => { - const store = window.$pinia?.state.value?.main; + // 调试信息 + console.log('[Router] 路由切换:', { + from: from.path, + to: to.path, + meta: to.meta + }); + + // 首先尝试从localStorage获取登录和作家状态 + const token = localStorage.getItem('token'); + const isLoggedIn = !!token; + const isAuthor = localStorage.getItem('isAuthor') === 'true'; + + // 输出当前状态 + console.log('[Router] 当前状态:', { isLoggedIn, isAuthor }); + + // 获取路由需要的权限 const requiresAuth = to.matched.some(record => record.meta.requiresAuth); + const requiresAuthor = to.matched.some(record => record.meta.requiresAuthor); - if (requiresAuth && (!store || !store.isLoggedIn)) { - // 保存当前要访问的路由信息 + // 如果路由不需要任何权限,直接放行 + if (!requiresAuth && !requiresAuthor) { + next(); + return; + } + + // 处理需要登录的路由 + if (requiresAuth && !isLoggedIn) { const targetRoute = to.fullPath; + console.log('[Router] 需要登录权限,未登录,跳转到首页'); - // 立即尝试调用登录弹窗 + // 尝试调用登录弹窗 setTimeout(() => { const authContext = window.$authContext; if (authContext && typeof authContext.openLogin === 'function') { @@ -94,16 +160,61 @@ router.beforeEach((to, from, next) => { } }, 0); - // 如果不是首页,则跳转到首页 - if (to.name !== 'Home') { - next({ path: '/' }); - } else { - // 如果已经在首页则直接渲染 + // 跳转到首页 + next({ path: '/' }); + return; + } + + // 处理需要作家权限的路由 + if (requiresAuthor) { + // 如果已经是作家,直接放行 + if (isAuthor) { + console.log('[Router] 需要作家权限,已是作家,直接放行'); next(); + return; } - } else { - next(); + + console.log('[Router] 需要作家权限,非作家,跳转到首页'); + + // 未登录或不是作家,需要先登录再申请成为作家 + if (!isLoggedIn) { + const targetRoute = to.fullPath; + setTimeout(() => { + const authContext = window.$authContext; + if (authContext && typeof authContext.openLogin === 'function') { + authContext.openLogin(() => { + // 登录成功后显示作家申请 + const authorContext = window.$authorApplicationContext; + if (authorContext && typeof authorContext.openApplicationModal === 'function') { + authorContext.openApplicationModal(() => { + // 申请成功后导航到作家专区 + router.push(targetRoute); + }); + } + }); + } + }, 0); + } else { + // 已登录但不是作家,直接显示作家申请 + setTimeout(() => { + const authorContext = window.$authorApplicationContext; + if (authorContext && typeof authorContext.openApplicationModal === 'function') { + authorContext.openApplicationModal(() => { + // 申请成功后导航到作家专区 + router.push(to.fullPath); + }); + } + }, 0); + } + + // 跳转到首页 + next({ path: '/' }); + return; } + + // 通过所有检查 + console.log('[Router] 通过所有权限检查'); + next(); }); export default router; \ No newline at end of file diff --git a/src/store/index.js b/src/store/index.js index d4590d8..f4bfca7 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -4,6 +4,7 @@ export const useMainStore = defineStore('main', { state: () => ({ user: null, isLoggedIn: false, + isAuthor: false, token: null, bookshelf: [] }), @@ -25,16 +26,19 @@ export const useMainStore = defineStore('main', { id: 'user_' + Date.now(), name: '用户' + loginData.phone.substring(loginData.phone.length - 4), phone: loginData.phone, - avatar: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png' + avatar: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png', + isAuthor: false }; this.user = userData; this.isLoggedIn = true; + this.isAuthor = userData.isAuthor; this.token = 'mock_token_' + Date.now(); // 保存到本地存储,使登录状态持久化 localStorage.setItem('user', JSON.stringify(userData)); localStorage.setItem('token', this.token); + localStorage.setItem('isAuthor', userData.isAuthor); return userData; } catch (error) { @@ -59,39 +63,72 @@ export const useMainStore = defineStore('main', { logout() { this.user = null; this.isLoggedIn = false; + this.isAuthor = false; this.token = null; // 清除本地存储 localStorage.removeItem('user'); localStorage.removeItem('token'); + localStorage.removeItem('isAuthor'); }, // 初始化,从本地存储恢复登录状态 initializeAuth() { + // 先直接从localStorage中获取各个状态 const userData = localStorage.getItem('user'); const token = localStorage.getItem('token'); + const isAuthor = localStorage.getItem('isAuthor') === 'true'; const bookshelf = localStorage.getItem('bookshelf'); + // 恢复用户数据和登录状态 if (userData && token) { try { this.user = JSON.parse(userData); this.token = token; this.isLoggedIn = true; + + // 明确设置作家状态,以localStorage为准 + this.isAuthor = isAuthor; + + // 确保用户对象中的isAuthor与状态一致 + if (this.user) { + this.user.isAuthor = isAuthor; + } + + // 输出调试信息 + console.log('[Store] Auth initialized:', { + isLoggedIn: this.isLoggedIn, + isAuthor: this.isAuthor, + user: this.user + }); } catch (e) { + console.error('[Store] Error parsing user data:', e); this.logout(); } } + // 恢复书架数据 if (bookshelf) { try { this.bookshelf = JSON.parse(bookshelf); } catch (e) { + console.error('[Store] Error parsing bookshelf:', e); this.bookshelf = []; localStorage.removeItem('bookshelf'); } } }, + // 设置为作家身份 + setAsAuthor() { + if (this.user) { + this.user.isAuthor = true; + this.isAuthor = true; + localStorage.setItem('user', JSON.stringify(this.user)); + localStorage.setItem('isAuthor', 'true'); + } + }, + // 添加书籍到书架 addToBookshelf(book) { // 检查是否已在书架中,避免重复添加 diff --git a/src/utils/debug.js b/src/utils/debug.js new file mode 100644 index 0000000..a4fc50e --- /dev/null +++ b/src/utils/debug.js @@ -0,0 +1,40 @@ +/** + * 调试工具函数 + */ + +// 打印当前用户状态 +export const logUserState = () => { + console.group('用户状态'); + + // 从localStorage获取状态 + const userData = localStorage.getItem('user'); + const token = localStorage.getItem('token'); + const isAuthor = localStorage.getItem('isAuthor'); + + console.log('localStorage状态:'); + console.log('- token:', token ? '已设置' : '未设置'); + console.log('- isAuthor:', isAuthor); + console.log('- user:', userData ? JSON.parse(userData) : null); + + // 从全局状态获取 + if (window.$pinia?.state?.value?.main) { + const storeState = window.$pinia.state.value.main; + console.log('Store状态:'); + console.log('- isLoggedIn:', storeState.isLoggedIn); + console.log('- isAuthor:', storeState.isAuthor); + console.log('- user:', storeState.user); + } else { + console.log('Store未初始化'); + } + + console.groupEnd(); +}; + +// 导出全局调试对象 +window.$debug = { + logUserState +}; + +export default { + logUserState +}; \ No newline at end of file diff --git a/src/views/author/AuthorCenter.vue b/src/views/author/AuthorCenter.vue new file mode 100644 index 0000000..b5d50e1 --- /dev/null +++ b/src/views/author/AuthorCenter.vue @@ -0,0 +1,124 @@ + + + + + \ No newline at end of file diff --git a/src/views/author/CreateWork.vue b/src/views/author/CreateWork.vue new file mode 100644 index 0000000..a562bbb --- /dev/null +++ b/src/views/author/CreateWork.vue @@ -0,0 +1,303 @@ + + + + + \ No newline at end of file diff --git a/src/views/author/WorkEdit.vue b/src/views/author/WorkEdit.vue new file mode 100644 index 0000000..16b4cea --- /dev/null +++ b/src/views/author/WorkEdit.vue @@ -0,0 +1,549 @@ + + + + + \ No newline at end of file diff --git a/src/views/author/WorkSetup.vue b/src/views/author/WorkSetup.vue new file mode 100644 index 0000000..b84762a --- /dev/null +++ b/src/views/author/WorkSetup.vue @@ -0,0 +1,502 @@ + + + + + \ No newline at end of file diff --git a/src/views/author/components/ReadersManagement.vue b/src/views/author/components/ReadersManagement.vue new file mode 100644 index 0000000..f3935a5 --- /dev/null +++ b/src/views/author/components/ReadersManagement.vue @@ -0,0 +1,161 @@ + + + + + \ No newline at end of file diff --git a/src/views/author/components/WorksManagement.vue b/src/views/author/components/WorksManagement.vue new file mode 100644 index 0000000..b092ec4 --- /dev/null +++ b/src/views/author/components/WorksManagement.vue @@ -0,0 +1,306 @@ + + + + + \ No newline at end of file diff --git a/src/views/book/index.vue b/src/views/book/index.vue index 4b22b2d..d9fbb10 100644 --- a/src/views/book/index.vue +++ b/src/views/book/index.vue @@ -38,7 +38,7 @@
- 互动打赏 + 互动打赏
+ +