将多个页面中的uv-navbar替换为自定义的navbar组件,以保持导航栏风格一致。同时,添加了暗色主题支持,优化了主题切换功能,并更新了相关文档和样式文件。 主要变更: - 在多个页面中使用navbar组件替换uv-navbar - 添加暗色主题支持,优化主题切换功能 - 更新uni.scss和store.js,增加主题相关变量和状态管理 - 新增ThemeProvider组件和themeMode混入器 - 更新README.md和dark-mode-guide.md文档master
| @ -0,0 +1,28 @@ | |||||
| --- | |||||
| description: | |||||
| globs: | |||||
| alwaysApply: true | |||||
| --- | |||||
| 当前项目是uniapp开发微信小程序 | |||||
| 每次编写代码时,请你先查看我的整个目录,提供的哪些文件、组件、功能、API接口,了解我的代码风格,写出和我风格一致的代码 | |||||
| ## 项目规则 | |||||
| 1、将列表以及列表中的元素封装成组件,并使用组件的方式来组织项目。使他可以复用 | |||||
| 2、项目中使用到的功能优先去查看uni_modules里面的组件列表,若没有则自己封装。 | |||||
| ### 项目结构 | |||||
| - components 放主包组件 | |||||
| - pages 只放主页面 | |||||
| - pages_order 放其他页面 | |||||
| - components 放分包组件 | |||||
| css语法按照scss的嵌套写法 | |||||
| 白天/黑夜模式:白天模式保持当前的样式,黑夜模式颜色变暗 | |||||
| 主题色:#0A2463 | |||||
| @ -0,0 +1,156 @@ | |||||
| <template> | |||||
| <!-- 空模板,仅提供样式 --> | |||||
| </template> | |||||
| <script> | |||||
| export default { | |||||
| name: 'DarkModeStyles' | |||||
| } | |||||
| </script> | |||||
| <style lang="scss"> | |||||
| /* 通用暗色模式样式 */ | |||||
| .dark-mode { | |||||
| /* 文本容器 */ | |||||
| .text-container { | |||||
| color: $dark-text-color-secondary; | |||||
| .title { | |||||
| color: $dark-text-color-primary; | |||||
| } | |||||
| .subtitle { | |||||
| color: $dark-text-color-secondary; | |||||
| } | |||||
| .description, .content { | |||||
| color: $dark-text-color-secondary; | |||||
| } | |||||
| .hint, .caption { | |||||
| color: $dark-text-color-tertiary; | |||||
| } | |||||
| } | |||||
| /* 卡片组件 */ | |||||
| .card { | |||||
| background-color: $dark-bg-color-secondary; | |||||
| box-shadow: $dark-shadow; | |||||
| border-color: $dark-border-color; | |||||
| .card-title { | |||||
| color: $dark-text-color-primary; | |||||
| } | |||||
| .card-content { | |||||
| color: $dark-text-color-secondary; | |||||
| } | |||||
| .card-footer { | |||||
| border-top-color: $dark-border-color; | |||||
| } | |||||
| } | |||||
| /* 按钮组件 */ | |||||
| .btn { | |||||
| &.primary { | |||||
| background-color: $dark-accent-color; | |||||
| } | |||||
| &.secondary { | |||||
| background-color: $dark-bg-color-tertiary; | |||||
| color: $dark-text-color-primary; | |||||
| } | |||||
| &.outline { | |||||
| background-color: transparent; | |||||
| color: $dark-accent-color; | |||||
| border-color: $dark-accent-color; | |||||
| } | |||||
| } | |||||
| /* 导航栏 */ | |||||
| .navbar { | |||||
| background-color: $dark-bg-color-secondary; | |||||
| box-shadow: $dark-shadow; | |||||
| .navbar-title { | |||||
| color: $dark-text-color-primary; | |||||
| } | |||||
| .navbar-item { | |||||
| color: $dark-text-color-secondary; | |||||
| &.active { | |||||
| color: $dark-accent-color; | |||||
| } | |||||
| } | |||||
| } | |||||
| /* 表单元素 */ | |||||
| .form-item { | |||||
| .label { | |||||
| color: $dark-text-color-secondary; | |||||
| } | |||||
| .input { | |||||
| background-color: $dark-bg-color-tertiary; | |||||
| color: $dark-text-color-primary; | |||||
| border-color: $dark-border-color; | |||||
| &::placeholder { | |||||
| color: $dark-text-color-tertiary; | |||||
| } | |||||
| } | |||||
| .hint { | |||||
| color: $dark-text-color-tertiary; | |||||
| } | |||||
| } | |||||
| /* 列表组件 */ | |||||
| .list { | |||||
| .list-item { | |||||
| border-bottom-color: $dark-border-color; | |||||
| .list-title { | |||||
| color: $dark-text-color-primary; | |||||
| } | |||||
| .list-desc { | |||||
| color: $dark-text-color-secondary; | |||||
| } | |||||
| } | |||||
| } | |||||
| /* 分割线 */ | |||||
| .divider { | |||||
| background-color: $dark-border-color; | |||||
| } | |||||
| /* 标签 */ | |||||
| .tag { | |||||
| background-color: $dark-bg-color-tertiary; | |||||
| color: $dark-text-color-secondary; | |||||
| &.primary { | |||||
| background-color: rgba($dark-accent-color, 0.3); | |||||
| color: $dark-accent-color; | |||||
| } | |||||
| } | |||||
| /* 底部操作栏 */ | |||||
| .bottom-action-bar { | |||||
| background-color: $dark-bg-color-secondary; | |||||
| box-shadow: $dark-shadow; | |||||
| .action-btn { | |||||
| color: $dark-text-color-secondary; | |||||
| &.active { | |||||
| color: $dark-accent-color; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @ -0,0 +1,32 @@ | |||||
| <template> | |||||
| <view :class="{'dark-mode': isDarkMode}" class="theme-provider"> | |||||
| <slot></slot> | |||||
| </view> | |||||
| </template> | |||||
| <script> | |||||
| import { mapGetters, mapMutations } from 'vuex' | |||||
| export default { | |||||
| name: 'ThemeProvider', | |||||
| computed: { | |||||
| ...mapGetters(['isDarkMode', 'currentTheme']) | |||||
| }, | |||||
| methods: { | |||||
| ...mapMutations(['toggleThemeMode', 'setThemeMode']) | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style lang="scss"> | |||||
| .theme-provider { | |||||
| /* 确保占满容器,但不影响布局 */ | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| /* 适配应用到深色模式的样式 */ | |||||
| &.dark-mode { | |||||
| /* 这里不添加具体样式,由子元素通过对应的类应用样式 */ | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @ -0,0 +1,159 @@ | |||||
| # 暗色主题使用指南 | |||||
| 本文档介绍如何在项目中使用暗色主题功能。 | |||||
| ## 概述 | |||||
| 我们的应用支持白天/黑夜两种主题模式,用户可以根据自己的偏好切换。主题的状态保存在Vuex中,并且会持久化保存在本地存储中,以便下次打开应用时恢复用户的偏好设置。 | |||||
| ## 使用方法 | |||||
| ### 1. 在组件中使用主题混入器 | |||||
| 我们提供了一个方便的混入器,可以轻松地在任何组件中使用主题相关的功能: | |||||
| ```js | |||||
| // 1. 导入主题混入器 | |||||
| import themeMixin from '@/mixins/themeMode.js' | |||||
| export default { | |||||
| // 2. 在组件中注册混入器 | |||||
| mixins: [themeMixin], | |||||
| methods: { | |||||
| someMethod() { | |||||
| // 3. 直接使用混入器提供的属性和方法 | |||||
| console.log(this.isDarkMode) // 是否为暗色模式 | |||||
| console.log(this.currentTheme) // 当前主题模式:'light'或'dark' | |||||
| // 切换主题 | |||||
| this.toggleThemeMode() | |||||
| // 设置为特定主题 | |||||
| this.setThemeMode('dark') // 或 'light' | |||||
| } | |||||
| } | |||||
| } | |||||
| ``` | |||||
| ### 2. 为组件添加暗色主题样式 | |||||
| 我们在`uni.scss`中定义了一系列暗色主题相关的变量,您可以在组件的样式中使用这些变量: | |||||
| ```scss | |||||
| /* 示例组件的样式 */ | |||||
| .my-component { | |||||
| background-color: #fff; | |||||
| color: #333; | |||||
| /* 添加主题过渡效果 */ | |||||
| @extend .theme-transition; | |||||
| /* 定义暗色主题下的样式 */ | |||||
| .dark-mode & { | |||||
| background-color: $dark-bg-color; | |||||
| color: $dark-text-color-primary; | |||||
| } | |||||
| } | |||||
| ``` | |||||
| ### 3. 使用主题组件 | |||||
| 我们还提供了两个便捷的组件: | |||||
| #### ThemeProvider | |||||
| ThemeProvider是一个包装组件,它会自动传递当前的主题模式,并提供暗色主题的根容器: | |||||
| ```html | |||||
| <template> | |||||
| <ThemeProvider> | |||||
| <!-- 您的组件内容 --> | |||||
| <view class="my-content"> | |||||
| 这里的内容会自动响应主题变化 | |||||
| </view> | |||||
| </ThemeProvider> | |||||
| </template> | |||||
| <script> | |||||
| import ThemeProvider from '@/components/theme/ThemeProvider.vue' | |||||
| export default { | |||||
| components: { | |||||
| ThemeProvider | |||||
| } | |||||
| } | |||||
| </script> | |||||
| ``` | |||||
| #### DarkModeStyles | |||||
| DarkModeStyles是一个提供通用暗色主题样式的无渲染组件,可以导入到您的页面中使用: | |||||
| ```html | |||||
| <template> | |||||
| <view> | |||||
| <!-- 这里不会渲染任何内容,但会应用样式 --> | |||||
| <DarkModeStyles /> | |||||
| <!-- 您的组件内容 --> | |||||
| <view class="card"> | |||||
| <view class="card-title">标题</view> | |||||
| <view class="card-content">内容</view> | |||||
| </view> | |||||
| </view> | |||||
| </template> | |||||
| <script> | |||||
| import DarkModeStyles from '@/components/theme/DarkModeStyles.vue' | |||||
| export default { | |||||
| components: { | |||||
| DarkModeStyles | |||||
| } | |||||
| } | |||||
| </script> | |||||
| ``` | |||||
| ## 可用的主题变量 | |||||
| 以下是在`uni.scss`中定义的暗色主题相关变量,您可以在组件样式中使用: | |||||
| ```scss | |||||
| /* 背景色 */ | |||||
| $dark-bg-color: #1a1a1a; // 主背景色 | |||||
| $dark-bg-color-secondary: #222; // 次要背景色 | |||||
| $dark-bg-color-tertiary: #333; // 第三级背景色 | |||||
| /* 文本颜色 */ | |||||
| $dark-text-color-primary: #eee; // 主要文本颜色 | |||||
| $dark-text-color-secondary: #bbb; // 次要文本颜色 | |||||
| $dark-text-color-tertiary: #999; // 第三级文本颜色 | |||||
| /* 边框和阴影 */ | |||||
| $dark-border-color: #444; // 边框颜色 | |||||
| $dark-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.2); // 阴影 | |||||
| /* 强调色 */ | |||||
| $dark-accent-color: #4a90e2; // 强调色 | |||||
| /* 过渡动画时间 */ | |||||
| $theme-transition-duration: 0.3s; // 主题切换过渡时间 | |||||
| ``` | |||||
| ## 最佳实践 | |||||
| 1. 总是为可能受主题影响的元素添加`theme-transition`类,以确保主题切换时有平滑的过渡效果 | |||||
| 2. 使用预定义的变量而不是硬编码颜色值,以保持整个应用的视觉一致性 | |||||
| 3. 在设计组件时考虑两种主题模式,确保在暗色模式下内容仍然清晰可见 | |||||
| 4. 对于特定于组件的变量,可以基于全局主题变量派生: | |||||
| ```scss | |||||
| $my-component-bg: $dark-bg-color; | |||||
| $my-component-text: $dark-text-color-primary; | |||||
| ``` | |||||
| ## 注意事项 | |||||
| - 当用户切换主题时,会立即应用新的主题并保存到本地存储中 | |||||
| - 主题状态在整个应用中共享,任何组件都可以访问和修改它 | |||||
| - 对于复杂的组件,考虑分别定义亮色和暗色两套图标或图片资源 | |||||
| @ -0,0 +1,18 @@ | |||||
| import { mapGetters, mapMutations } from 'vuex' | |||||
| /** | |||||
| * 主题模式混合器 | |||||
| * 提供了isDarkMode和currentTheme计算属性,以及toggleThemeMode和setThemeMode方法 | |||||
| * 使用方法: | |||||
| * 1. 在组件中导入:import themeMixin from '@/mixins/themeMode.js' | |||||
| * 2. 在组件中注册:mixins: [themeMixin] | |||||
| * 3. 然后可以使用:this.isDarkMode 和 this.toggleThemeMode() 等 | |||||
| */ | |||||
| export default { | |||||
| computed: { | |||||
| ...mapGetters(['isDarkMode', 'currentTheme']) | |||||
| }, | |||||
| methods: { | |||||
| ...mapMutations(['toggleThemeMode', 'setThemeMode']) | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,141 @@ | |||||
| # 小说阅读相关组件 | |||||
| 本目录包含小说阅读页面使用的各种公共组件,所有组件都支持暗色主题模式。 | |||||
| ## 组件列表 | |||||
| ### 1. 订阅弹窗 (subscriptionPopup.vue) | |||||
| 中心弹出的订阅章节提示弹窗。 | |||||
| #### 使用方法 | |||||
| ```html | |||||
| <template> | |||||
| <subscriptionPopup ref="subscriptionPopup" @subscribe="handleSubscribe"/> | |||||
| </template> | |||||
| <script> | |||||
| import subscriptionPopup from 'pages_order/components/novel/subscriptionPopup.vue' | |||||
| export default { | |||||
| components: { | |||||
| subscriptionPopup | |||||
| }, | |||||
| methods: { | |||||
| showSubscriptionPopup() { | |||||
| this.$refs.subscriptionPopup.open() | |||||
| }, | |||||
| handleSubscribe() { | |||||
| // 处理订阅事件 | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| ``` | |||||
| ### 2. 支付弹窗 (paymentPopup.vue) | |||||
| 从底部弹出的支付方式选择弹窗。 | |||||
| #### 使用方法 | |||||
| ```html | |||||
| <template> | |||||
| <paymentPopup ref="paymentPopup" @subscribe="handleSubscribe" @close="handleClose"/> | |||||
| </template> | |||||
| <script> | |||||
| import paymentPopup from 'pages_order/components/novel/paymentPopup.vue' | |||||
| export default { | |||||
| components: { | |||||
| paymentPopup | |||||
| }, | |||||
| methods: { | |||||
| showPaymentPopup() { | |||||
| this.$refs.paymentPopup.open() | |||||
| }, | |||||
| handleSubscribe() { | |||||
| // 处理订阅事件 | |||||
| }, | |||||
| handleClose() { | |||||
| // 处理关闭事件 | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| ``` | |||||
| ### 3. 章节目录弹窗 (chapterPopup.vue) | |||||
| 显示小说章节列表的弹窗。 | |||||
| #### 使用方法 | |||||
| ```html | |||||
| <template> | |||||
| <chapterPopup ref="chapterPopup"/> | |||||
| </template> | |||||
| <script> | |||||
| import chapterPopup from 'pages_order/components/novel/chapterPopup.vue' | |||||
| export default { | |||||
| components: { | |||||
| chapterPopup | |||||
| }, | |||||
| methods: { | |||||
| showChapterList() { | |||||
| this.$refs.chapterPopup.open() | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| ``` | |||||
| ### 4. 评分弹窗 (novelVotePopup.vue) | |||||
| 用于评价小说的弹窗组件。 | |||||
| ## 暗色主题支持 | |||||
| 所有组件都支持暗色主题模式,当系统切换到暗色模式或用户手动切换时,组件会自动应用暗色样式。 | |||||
| ### 主题切换 | |||||
| 主题的状态保存在Vuex中,可以通过以下方式切换: | |||||
| ```js | |||||
| // 切换主题模式 | |||||
| this.$store.commit('toggleThemeMode') | |||||
| // 设置为特定模式 | |||||
| this.$store.commit('setThemeMode', 'dark') // 或 'light' | |||||
| // 获取当前主题模式 | |||||
| const isDarkMode = this.$store.getters.isDarkMode | |||||
| ``` | |||||
| ### 使用主题混入器 | |||||
| 为了方便在组件中使用主题相关功能,可以引入主题混入器: | |||||
| ```js | |||||
| import themeMixin from '@/mixins/themeMode.js' | |||||
| export default { | |||||
| mixins: [themeMixin], | |||||
| // 然后可以直接使用以下属性和方法 | |||||
| created() { | |||||
| console.log(this.isDarkMode) // 是否为暗色模式 | |||||
| console.log(this.currentTheme) // 'light' 或 'dark' | |||||
| // 切换主题 | |||||
| this.toggleThemeMode() | |||||
| } | |||||
| } | |||||
| ``` | |||||
| 详细的文档请参考 `doc/dark-mode-guide.md`。 | |||||
| @ -0,0 +1,159 @@ | |||||
| <template> | |||||
| <view class="subscription-popup" :class="{'dark-mode': isDarkMode}"> | |||||
| <uv-popup ref="popup" mode="center" :closeOnClickOverlay="true" :customStyle="popupStyle"> | |||||
| <view class="content theme-transition"> | |||||
| <view class="popup-title theme-transition">{{ title }}</view> | |||||
| <view class="popup-desc theme-transition">{{ description }}</view> | |||||
| <view class="popup-btns"> | |||||
| <button class="popup-btn theme-transition" @click="handleSubscribe">订阅本章</button> | |||||
| <button class="popup-btn popup-btn-video theme-transition">观看视频解锁</button> | |||||
| <button class="popup-btn popup-btn-batch theme-transition">批量订阅</button> | |||||
| </view> | |||||
| </view> | |||||
| </uv-popup> | |||||
| </view> | |||||
| </template> | |||||
| <script> | |||||
| import themeMixin from '@/mixins/themeMode.js' | |||||
| export default { | |||||
| name: 'subscriptionPopup', | |||||
| mixins: [themeMixin], | |||||
| props: { | |||||
| title: { | |||||
| type: String, | |||||
| default: '这是付费章节 需要订阅后才能阅读' | |||||
| }, | |||||
| description: { | |||||
| type: String, | |||||
| default: '订阅后可继续阅读本章内容' | |||||
| } | |||||
| }, | |||||
| data() { | |||||
| return { | |||||
| popupShown: false, // 是否已显示过 | |||||
| } | |||||
| }, | |||||
| computed: { | |||||
| popupStyle() { | |||||
| return { | |||||
| 'background': this.isDarkMode ? '#232323' : '#fff', | |||||
| 'padding': '48rpx 32rpx', | |||||
| 'text-align': 'center', | |||||
| 'border-radius': '24rpx', | |||||
| 'min-width': '500rpx' | |||||
| } | |||||
| } | |||||
| }, | |||||
| methods: { | |||||
| // 打开弹窗 | |||||
| open() { | |||||
| if (!this.popupShown) { | |||||
| this.$refs.popup.open(); | |||||
| this.popupShown = true; | |||||
| } | |||||
| }, | |||||
| // 关闭弹窗 | |||||
| close() { | |||||
| this.$refs.popup.close(); | |||||
| }, | |||||
| // 重置状态,允许再次显示 | |||||
| reset() { | |||||
| this.popupShown = false; | |||||
| }, | |||||
| // 处理订阅按钮点击 | |||||
| handleSubscribe() { | |||||
| this.$emit('subscribe'); | |||||
| this.close(); | |||||
| } | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style lang="scss" scoped> | |||||
| .subscription-popup { | |||||
| .content { | |||||
| .popup-title { | |||||
| font-size: 32rpx; | |||||
| font-weight: bold; | |||||
| color: #222; | |||||
| margin-bottom: 24rpx; | |||||
| word-wrap: break-word; | |||||
| white-space: normal; | |||||
| } | |||||
| .popup-desc { | |||||
| font-size: 26rpx; | |||||
| color: #999; | |||||
| margin-bottom: 40rpx; | |||||
| word-wrap: break-word; | |||||
| white-space: normal; | |||||
| } | |||||
| .popup-btns { | |||||
| display: flex; | |||||
| flex-wrap: wrap; | |||||
| justify-content: center; | |||||
| gap: 24rpx; | |||||
| .popup-btn { | |||||
| background: #ff9800; | |||||
| color: #fff; | |||||
| border-radius: 32rpx; | |||||
| font-size: 28rpx; | |||||
| padding: 0 32rpx; | |||||
| border: none; | |||||
| margin-bottom: 16rpx; | |||||
| word-break: keep-all; | |||||
| &.popup-btn-video { | |||||
| background: #fff3e0; | |||||
| color: #ff9800; | |||||
| border: 1px solid #ff9800; | |||||
| } | |||||
| &.popup-btn-batch { | |||||
| background: #fff; | |||||
| color: #ff9800; | |||||
| border: 1px solid #ff9800; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &.dark-mode { | |||||
| .content { | |||||
| .popup-title { | |||||
| color: $dark-text-color-primary; | |||||
| } | |||||
| .popup-desc { | |||||
| color: $dark-text-color-tertiary; | |||||
| } | |||||
| .popup-btns { | |||||
| .popup-btn { | |||||
| background: #ff9800; | |||||
| color: #fff; | |||||
| &.popup-btn-video { | |||||
| background: rgba(255, 152, 0, 0.1); | |||||
| color: #ff9800; | |||||
| border: 1px solid #ff9800; | |||||
| } | |||||
| &.popup-btn-batch { | |||||
| background: $dark-bg-color-secondary; | |||||
| color: #ff9800; | |||||
| border: 1px solid #ff9800; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| </style> | |||||
| @ -1,232 +1,250 @@ | |||||
| <template> | <template> | ||||
| <view class="comments-page"> | |||||
| <!-- 顶部导航栏 --> | |||||
| <navbar title="评论详情" :leftClick="true" @leftClick="goBack" /> | |||||
| <!-- 书本名称 --> | |||||
| <view class="book-title-area"> | |||||
| <view class="book-title-label">书本名称</view> | |||||
| <view class="book-title">《{{ bookTitle }}》</view> | |||||
| </view> | |||||
| <!-- 主评论卡片 --> | |||||
| <view class="comment-card"> | |||||
| <view class="comment-header"> | |||||
| <image class="avatar" :src="comment.avatar" mode="aspectFill" /> | |||||
| <view class="user-info"> | |||||
| <text class="username">{{ comment.username }}</text> | |||||
| <view class="comments-page"> | |||||
| <navbar title="评论详情" leftClick @leftClick="$utils.navigateBack" /> | |||||
| <!-- 书本名称 --> | |||||
| <view class="book-title-area"> | |||||
| <view class="book-title-label">书本名称</view> | |||||
| <view class="book-title">《{{ bookTitle }}》</view> | |||||
| </view> | </view> | ||||
| </view> | |||||
| <view class="comment-content">{{ comment.content }}</view> | |||||
| <view class="comment-footer"> | |||||
| <text class="comment-time">{{ comment.time }}</text> | |||||
| </view> | |||||
| </view> | |||||
| <!-- 全部评论列表 --> | |||||
| <view class="all-reply-area"> | |||||
| <view class="all-reply-header">全部评论·{{ comment.replyCount }}</view> | |||||
| <view class="reply-list"> | |||||
| <view class="reply-item" v-for="(item, idx) in replies" :key="idx"> | |||||
| <image class="reply-avatar" :src="item.avatar" mode="aspectFill" /> | |||||
| <view class="reply-main"> | |||||
| <view class="reply-username">{{ item.username }}</view> | |||||
| <view class="reply-content">{{ item.content }}</view> | |||||
| <view class="reply-time">{{ item.time }}</view> | |||||
| </view> | |||||
| <!-- 主评论卡片 --> | |||||
| <view class="comment-card"> | |||||
| <view class="comment-header"> | |||||
| <image class="avatar" :src="comment.avatar" mode="aspectFill" /> | |||||
| <view class="user-info"> | |||||
| <text class="username">{{ comment.username }}</text> | |||||
| </view> | |||||
| </view> | |||||
| <view class="comment-content">{{ comment.content }}</view> | |||||
| <view class="comment-footer"> | |||||
| <text class="comment-time">{{ comment.time }}</text> | |||||
| </view> | |||||
| </view> | |||||
| <!-- 全部评论列表 --> | |||||
| <view class="all-reply-area"> | |||||
| <view class="all-reply-header">全部评论·{{ comment.replyCount }}</view> | |||||
| <view class="reply-list"> | |||||
| <view class="reply-item" v-for="(item, idx) in replies" :key="idx"> | |||||
| <image class="reply-avatar" :src="item.avatar" mode="aspectFill" /> | |||||
| <view class="reply-main"> | |||||
| <view class="reply-username">{{ item.username }}</view> | |||||
| <view class="reply-content">{{ item.content }}</view> | |||||
| <view class="reply-time">{{ item.time }}</view> | |||||
| </view> | |||||
| </view> | |||||
| </view> | |||||
| </view> | |||||
| <!-- 底部回复按钮 --> | |||||
| <view class="reply-footer"> | |||||
| <button class="submit-btn" @click="goToRespond">回复评论</button> | |||||
| </view> | </view> | ||||
| </view> | |||||
| </view> | |||||
| <!-- 底部回复按钮 --> | |||||
| <view class="reply-footer"> | |||||
| <button class="submit-btn" @click="goToRespond">回复评论</button> | |||||
| </view> | </view> | ||||
| </view> | |||||
| </template> | </template> | ||||
| <script> | <script> | ||||
| import navbar from '@/components/base/navbar.vue' | import navbar from '@/components/base/navbar.vue' | ||||
| export default { | export default { | ||||
| components: { navbar }, | |||||
| data() { | |||||
| return { | |||||
| bookTitle: '这游戏也太真实了', | |||||
| comment: { | |||||
| avatar: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain', | |||||
| username: '方香橙', | |||||
| content: '我是本书的作者方香橙,这是一本甜文爽文哒!请放心入坑,五星好评!女主又美有个性可爱,绝对不圣母,不傻白!男主身心干净深情独宠媳妇儿一个人...', | |||||
| time: '2024.07.09', | |||||
| replyCount: 17 | |||||
| }, | |||||
| replies: [ | |||||
| { | |||||
| avatar: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain', | |||||
| username: '方香橙', | |||||
| content: '我是本书的作者方香橙,这是一本甜文爽文哒!请放心入坑,五星好评!女主又美有个性可爱,绝对不圣母,不傻白!男主身心干净深情独宠媳妇儿一个人...', | |||||
| time: '2024.07.09' | |||||
| }, | |||||
| { | |||||
| avatar: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain', | |||||
| username: '战斗世界', | |||||
| content: '这本书打破了我看甜文套路之前的观念女主真的太可爱太有趣和以往看过的一个NPC介绍有很大不同', | |||||
| time: '2024.07.09' | |||||
| components: { navbar }, | |||||
| data() { | |||||
| return { | |||||
| bookTitle: '这游戏也太真实了', | |||||
| comment: { | |||||
| avatar: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain', | |||||
| username: '方香橙', | |||||
| content: '我是本书的作者方香橙,这是一本甜文爽文哒!请放心入坑,五星好评!女主又美有个性可爱,绝对不圣母,不傻白!男主身心干净深情独宠媳妇儿一个人...', | |||||
| time: '2024.07.09', | |||||
| replyCount: 17 | |||||
| }, | |||||
| replies: [ | |||||
| { | |||||
| avatar: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain', | |||||
| username: '方香橙', | |||||
| content: '我是本书的作者方香橙,这是一本甜文爽文哒!请放心入坑,五星好评!女主又美有个性可爱,绝对不圣母,不傻白!男主身心干净深情独宠媳妇儿一个人...', | |||||
| time: '2024.07.09' | |||||
| }, | |||||
| { | |||||
| avatar: 'https://tse4-mm.cn.bing.net/th/id/OIP-C.iUyxJ_fxLjjX3kEBjteXWwAAAA?rs=1&pid=ImgDetMain', | |||||
| username: '战斗世界', | |||||
| content: '这本书打破了我看甜文套路之前的观念女主真的太可爱太有趣和以往看过的一个NPC介绍有很大不同', | |||||
| time: '2024.07.09' | |||||
| } | |||||
| ] | |||||
| } | } | ||||
| ] | |||||
| } | |||||
| }, | |||||
| methods: { | |||||
| goBack() { | |||||
| uni.navigateBack() | |||||
| }, | |||||
| goToRespond() { | |||||
| uni.navigateTo({ url: '/pages_order/novel/Respondcomments' }) | |||||
| }, | }, | ||||
| submitReply() { | |||||
| uni.showToast({ title: '功能开发中', icon: 'none' }) | |||||
| methods: { | |||||
| goToRespond() { | |||||
| uni.navigateTo({ url: '/pages_order/novel/Respondcomments' }) | |||||
| }, | |||||
| submitReply() { | |||||
| uni.showToast({ title: '功能开发中', icon: 'none' }) | |||||
| } | |||||
| } | } | ||||
| } | |||||
| } | } | ||||
| </script> | </script> | ||||
| <style scoped lang="scss"> | <style scoped lang="scss"> | ||||
| .comments-page { | .comments-page { | ||||
| min-height: 100vh; | |||||
| background: #f8f8f8; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| min-height: 100vh; | |||||
| background: #f8f8f8; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| } | } | ||||
| .book-title-area { | .book-title-area { | ||||
| background: #fff; | |||||
| margin: 24rpx 24rpx 0 24rpx; | |||||
| border-radius: 16rpx; | |||||
| padding: 24rpx 24rpx 0 24rpx; | |||||
| background: #fff; | |||||
| margin: 24rpx 24rpx 0 24rpx; | |||||
| border-radius: 16rpx; | |||||
| padding: 24rpx 24rpx 0 24rpx; | |||||
| } | } | ||||
| .book-title-label { | .book-title-label { | ||||
| color: #bdbdbd; | |||||
| font-size: 24rpx; | |||||
| margin-bottom: 4rpx; | |||||
| color: #bdbdbd; | |||||
| font-size: 24rpx; | |||||
| margin-bottom: 4rpx; | |||||
| } | } | ||||
| .book-title { | .book-title { | ||||
| font-size: 28rpx; | |||||
| color: #222; | |||||
| margin-bottom: 16rpx; | |||||
| border-bottom: 1px solid #ededed; | |||||
| padding-bottom: 8rpx; | |||||
| font-size: 28rpx; | |||||
| color: #222; | |||||
| margin-bottom: 16rpx; | |||||
| border-bottom: 1px solid #ededed; | |||||
| padding-bottom: 8rpx; | |||||
| } | } | ||||
| .comment-card { | .comment-card { | ||||
| background: #fff; | |||||
| margin: 0 24rpx 0 24rpx; | |||||
| border-radius: 16rpx; | |||||
| padding: 24rpx 24rpx 0 24rpx; | |||||
| box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03); | |||||
| margin-bottom: 0; | |||||
| background: #fff; | |||||
| margin: 0 24rpx 0 24rpx; | |||||
| border-radius: 16rpx; | |||||
| padding: 24rpx 24rpx 0 24rpx; | |||||
| box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03); | |||||
| margin-bottom: 0; | |||||
| } | } | ||||
| .comment-header { | .comment-header { | ||||
| display: flex; | |||||
| align-items: center; | |||||
| margin-bottom: 8rpx; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| margin-bottom: 8rpx; | |||||
| } | } | ||||
| .avatar { | .avatar { | ||||
| width: 56rpx; | |||||
| height: 56rpx; | |||||
| border-radius: 50%; | |||||
| margin-right: 16rpx; | |||||
| width: 56rpx; | |||||
| height: 56rpx; | |||||
| border-radius: 50%; | |||||
| margin-right: 16rpx; | |||||
| } | } | ||||
| .user-info { | .user-info { | ||||
| display: flex; | |||||
| flex-direction: column; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| } | } | ||||
| .username { | .username { | ||||
| font-size: 26rpx; | |||||
| color: #222; | |||||
| font-weight: 500; | |||||
| font-size: 26rpx; | |||||
| color: #222; | |||||
| font-weight: 500; | |||||
| } | } | ||||
| .comment-content { | .comment-content { | ||||
| font-size: 26rpx; | |||||
| color: #333; | |||||
| margin-bottom: 12rpx; | |||||
| font-size: 26rpx; | |||||
| color: #333; | |||||
| margin-bottom: 12rpx; | |||||
| } | } | ||||
| .comment-footer { | .comment-footer { | ||||
| display: flex; | |||||
| align-items: center; | |||||
| font-size: 22rpx; | |||||
| color: #bdbdbd; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| font-size: 22rpx; | |||||
| color: #bdbdbd; | |||||
| } | } | ||||
| .comment-time { | .comment-time { | ||||
| color: #bdbdbd; | |||||
| margin-top: 18rpx; | |||||
| color: #bdbdbd; | |||||
| margin-top: 18rpx; | |||||
| } | } | ||||
| .all-reply-area { | .all-reply-area { | ||||
| background: #fff; | |||||
| margin: 0 24rpx 0 24rpx; | |||||
| border-radius: 16rpx; | |||||
| padding: 24rpx 24rpx 16rpx 24rpx; | |||||
| margin-top: 24rpx; | |||||
| background: #fff; | |||||
| margin: 0 24rpx 0 24rpx; | |||||
| border-radius: 16rpx; | |||||
| padding: 24rpx 24rpx 16rpx 24rpx; | |||||
| margin-top: 24rpx; | |||||
| } | } | ||||
| .all-reply-header { | .all-reply-header { | ||||
| color: #222; | |||||
| font-size: 28rpx; | |||||
| font-weight: 500; | |||||
| margin-bottom: 16rpx; | |||||
| color: #222; | |||||
| font-size: 28rpx; | |||||
| font-weight: 500; | |||||
| margin-bottom: 16rpx; | |||||
| } | } | ||||
| .reply-list { | .reply-list { | ||||
| margin-top: 40rpx; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| gap: 50rpx; | |||||
| margin-top: 40rpx; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| gap: 50rpx; | |||||
| } | } | ||||
| .reply-item { | .reply-item { | ||||
| display: flex; | |||||
| align-items: flex-start; | |||||
| display: flex; | |||||
| align-items: flex-start; | |||||
| } | } | ||||
| .reply-avatar { | .reply-avatar { | ||||
| width: 44rpx; | |||||
| height: 44rpx; | |||||
| border-radius: 50%; | |||||
| margin-right: 16rpx; | |||||
| flex-shrink: 0; | |||||
| width: 44rpx; | |||||
| height: 44rpx; | |||||
| border-radius: 50%; | |||||
| margin-right: 16rpx; | |||||
| flex-shrink: 0; | |||||
| } | } | ||||
| .reply-main { | .reply-main { | ||||
| flex: 1; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| flex: 1; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| } | } | ||||
| .reply-username { | .reply-username { | ||||
| font-size: 24rpx; | |||||
| color: #222; | |||||
| font-weight: 500; | |||||
| margin-bottom: 4rpx; | |||||
| font-size: 24rpx; | |||||
| color: #222; | |||||
| font-weight: 500; | |||||
| margin-bottom: 4rpx; | |||||
| } | } | ||||
| .reply-content { | .reply-content { | ||||
| font-size: 24rpx; | |||||
| color: #333; | |||||
| margin-bottom: 6rpx; | |||||
| word-break: break-all; | |||||
| font-size: 24rpx; | |||||
| color: #333; | |||||
| margin-bottom: 6rpx; | |||||
| word-break: break-all; | |||||
| } | } | ||||
| .reply-time { | .reply-time { | ||||
| font-size: 20rpx; | |||||
| color: #bdbdbd; | |||||
| font-size: 20rpx; | |||||
| color: #bdbdbd; | |||||
| } | } | ||||
| .reply-footer { | .reply-footer { | ||||
| position: fixed; | |||||
| left: 0; | |||||
| right: 0; | |||||
| bottom: 90rpx; | |||||
| background: #fff; | |||||
| padding: 24rpx 32rpx 32rpx 32rpx; | |||||
| box-shadow: 0 -2rpx 12rpx rgba(0,0,0,0.03); | |||||
| z-index: 10; | |||||
| position: fixed; | |||||
| left: 0; | |||||
| right: 0; | |||||
| bottom: 90rpx; | |||||
| background: #fff; | |||||
| padding: 24rpx 32rpx 32rpx 32rpx; | |||||
| box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.03); | |||||
| z-index: 10; | |||||
| } | } | ||||
| .submit-btn { | .submit-btn { | ||||
| width: 100%; | |||||
| height: 80rpx; | |||||
| background: #0a225f; | |||||
| color: #fff; | |||||
| font-size: 30rpx; | |||||
| border-radius: 40rpx; | |||||
| font-weight: 500; | |||||
| letter-spacing: 2rpx; | |||||
| width: 100%; | |||||
| height: 80rpx; | |||||
| background: #0a225f; | |||||
| color: #fff; | |||||
| font-size: 30rpx; | |||||
| border-radius: 40rpx; | |||||
| font-weight: 500; | |||||
| letter-spacing: 2rpx; | |||||
| } | } | ||||
| </style> | </style> | ||||
| @ -1,126 +1,131 @@ | |||||
| <template> | <template> | ||||
| <!-- 申请成为作者页面 --> | |||||
| <view class="creator-page"> | |||||
| <uv-navbar title="请成为创作者" fixed placeholder> | |||||
| <template #left> | |||||
| <BackArrow :size="56" color="#333" /> | |||||
| </template> | |||||
| </uv-navbar> | |||||
| <view class="form-card"> | |||||
| <view class="form-item"> | |||||
| <text class="required">*</text> | |||||
| <text class="label">笔名</text> | |||||
| <input v-model="penName" placeholder="请输入" class="input" placeholder-class="input-placeholder" /> | |||||
| </view> | |||||
| <view class="form-item"> | |||||
| <text class="required">*</text> | |||||
| <text class="label">简介</text> | |||||
| <textarea v-model="intro" placeholder="请输入" class="textarea" placeholder-class="input-placeholder" /> | |||||
| </view> | |||||
| </view> | |||||
| <view class="footer-btn-area"> | |||||
| <button class="submit-btn" @click="submit">成为创作者</button> | |||||
| <!-- 申请成为作者页面 --> | |||||
| <view class="creator-page"> | |||||
| <navbar title="请成为创作者" leftClick @leftClick="$utils.navigateBack" /> | |||||
| <view class="form-card"> | |||||
| <view class="form-item"> | |||||
| <text class="required">*</text> | |||||
| <text class="label">笔名</text> | |||||
| <input v-model="penName" placeholder="请输入" class="input" placeholder-class="input-placeholder" /> | |||||
| </view> | |||||
| <view class="form-item"> | |||||
| <text class="required">*</text> | |||||
| <text class="label">简介</text> | |||||
| <textarea v-model="intro" placeholder="请输入" class="textarea" placeholder-class="input-placeholder" /> | |||||
| </view> | |||||
| </view> | |||||
| <view class="footer-btn-area"> | |||||
| <button class="submit-btn" @click="submit">成为创作者</button> | |||||
| </view> | |||||
| </view> | </view> | ||||
| </view> | |||||
| </template> | </template> | ||||
| <script> | <script> | ||||
| import BackArrow from './components/BackArrow.vue'; | |||||
| export default { | export default { | ||||
| components: { | |||||
| BackArrow, | |||||
| }, | |||||
| data() { | |||||
| return { | |||||
| penName: '', | |||||
| intro: '' | |||||
| components: { | |||||
| }, | |||||
| data() { | |||||
| return { | |||||
| penName: '', | |||||
| intro: '' | |||||
| } | |||||
| }, | |||||
| methods: { | |||||
| submit() { | |||||
| if (!this.penName) { | |||||
| uni.showToast({ title: '请输入笔名', icon: 'none' }) | |||||
| return | |||||
| } | |||||
| if (!this.intro) { | |||||
| uni.showToast({ title: '请输入简介', icon: 'none' }) | |||||
| return | |||||
| } | |||||
| // 这里可以添加提交逻辑 | |||||
| uni.showToast({ title: '申请成功', icon: 'success' }) | |||||
| setTimeout(() => { | |||||
| uni.navigateBack() | |||||
| }, 800) | |||||
| } | |||||
| } | } | ||||
| }, | |||||
| methods: { | |||||
| submit() { | |||||
| if (!this.penName) { | |||||
| uni.showToast({ title: '请输入笔名', icon: 'none' }) | |||||
| return | |||||
| } | |||||
| if (!this.intro) { | |||||
| uni.showToast({ title: '请输入简介', icon: 'none' }) | |||||
| return | |||||
| } | |||||
| // 这里可以添加提交逻辑 | |||||
| uni.showToast({ title: '申请成功', icon: 'success' }) | |||||
| setTimeout(() => { | |||||
| uni.navigateBack() | |||||
| }, 800) | |||||
| } | |||||
| } | |||||
| } | } | ||||
| </script> | </script> | ||||
| <style scoped lang="scss"> | <style scoped lang="scss"> | ||||
| .creator-page { | .creator-page { | ||||
| min-height: 100vh; | |||||
| background: #f7f8fa; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| min-height: 100vh; | |||||
| background: #f7f8fa; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| } | } | ||||
| .form-card { | .form-card { | ||||
| background: #fff; | |||||
| border-radius: 20rpx; | |||||
| margin: 40rpx 32rpx 0 32rpx; | |||||
| padding: 32rpx 24rpx; | |||||
| box-shadow: 0 4rpx 24rpx 0 rgba(0,0,0,0.04); | |||||
| background: #fff; | |||||
| border-radius: 20rpx; | |||||
| margin: 40rpx 32rpx 0 32rpx; | |||||
| padding: 32rpx 24rpx; | |||||
| box-shadow: 0 4rpx 24rpx 0 rgba(0, 0, 0, 0.04); | |||||
| } | } | ||||
| .form-item { | .form-item { | ||||
| display: flex; | |||||
| flex-direction: column; | |||||
| margin-bottom: 32rpx; | |||||
| position: relative; | |||||
| display: flex; | |||||
| flex-direction: column; | |||||
| margin-bottom: 32rpx; | |||||
| position: relative; | |||||
| } | } | ||||
| .required { | .required { | ||||
| color: #e23d3d; | |||||
| font-size: 28rpx; | |||||
| margin-right: 4rpx; | |||||
| color: #e23d3d; | |||||
| font-size: 28rpx; | |||||
| margin-right: 4rpx; | |||||
| } | } | ||||
| .label { | .label { | ||||
| font-size: 28rpx; | |||||
| color: #222; | |||||
| font-weight: bold; | |||||
| margin-bottom: 12rpx; | |||||
| font-size: 28rpx; | |||||
| color: #222; | |||||
| font-weight: bold; | |||||
| margin-bottom: 12rpx; | |||||
| } | } | ||||
| .input, .textarea { | |||||
| width: 100%; | |||||
| height: 80rpx; | |||||
| border: none; | |||||
| border-bottom: 1.5rpx solid #ececec; | |||||
| font-size: 28rpx; | |||||
| background: transparent; | |||||
| padding: 18rpx 0 12rpx 0; | |||||
| margin-bottom: 2rpx; | |||||
| .input, | |||||
| .textarea { | |||||
| width: 100%; | |||||
| height: 80rpx; | |||||
| border: none; | |||||
| border-bottom: 1.5rpx solid #ececec; | |||||
| font-size: 28rpx; | |||||
| background: transparent; | |||||
| padding: 18rpx 0 12rpx 0; | |||||
| margin-bottom: 2rpx; | |||||
| } | } | ||||
| .input-placeholder { | .input-placeholder { | ||||
| color: #d2d2d2; | |||||
| font-size: 26rpx; | |||||
| color: #d2d2d2; | |||||
| font-size: 26rpx; | |||||
| } | } | ||||
| .textarea { | .textarea { | ||||
| min-height: 120rpx; | |||||
| resize: none; | |||||
| min-height: 120rpx; | |||||
| resize: none; | |||||
| } | } | ||||
| .footer-btn-area { | .footer-btn-area { | ||||
| margin-top: auto; | |||||
| padding: 48rpx 32rpx 32rpx 32rpx; | |||||
| background: transparent; | |||||
| margin-bottom: 80rpx; | |||||
| margin-top: auto; | |||||
| padding: 48rpx 32rpx 32rpx 32rpx; | |||||
| background: transparent; | |||||
| margin-bottom: 80rpx; | |||||
| } | } | ||||
| .submit-btn { | .submit-btn { | ||||
| width: 100%; | |||||
| height: 88rpx; | |||||
| background: #0a2e6d; | |||||
| color: #fff; | |||||
| font-size: 32rpx; | |||||
| border-radius: 44rpx; | |||||
| font-weight: bold; | |||||
| letter-spacing: 2rpx; | |||||
| transition: background 0.2s; | |||||
| width: 100%; | |||||
| height: 88rpx; | |||||
| background: #0a2e6d; | |||||
| color: #fff; | |||||
| font-size: 32rpx; | |||||
| border-radius: 44rpx; | |||||
| font-weight: bold; | |||||
| letter-spacing: 2rpx; | |||||
| transition: background 0.2s; | |||||
| } | } | ||||
| </style> | </style> | ||||