fzzj před 10 měsíci
rodič
revize
e108df3e67

+ 31
- 3
RuoYi-App/App.vue Zobrazit soubor

7
   import config from './config'
7
   import config from './config'
8
   import { getToken } from '@/utils/auth'
8
   import { getToken } from '@/utils/auth'
9
 import { useThemeStore } from '@/stores/theme'
9
 import { useThemeStore } from '@/stores/theme'
10
-import { onLaunch } from '@dcloudio/uni-app'
10
+import { onLaunch, onShow } from '@dcloudio/uni-app'
11
+//import { useThemeStore } from '@/stores/theme'
12
+import { useUserStore } from '@/stores/user'
13
+import { useVipStore } from '@/stores/vip'
14
+
11
 // 初始化主题
15
 // 初始化主题
12
 onLaunch(() => {
16
 onLaunch(() => {
13
   const themeStore = useThemeStore()
17
   const themeStore = useThemeStore()
14
   themeStore.initTheme()
18
   themeStore.initTheme()
15
 })
19
 })
16
-
20
+// 每次启动时加载用户和VIP状态
21
+onShow(async () => {
22
+  const userStore = useUserStore()
23
+  const vipStore = useVipStore()
24
+  
25
+  // 检查登录状态
26
+  if (uni.getStorageSync('token')) {
27
+    // 加载用户信息
28
+    await userStore.initUser()
29
+    
30
+    // 加载VIP状态
31
+    if (userStore.userId) {
32
+      await vipStore.loadVipStatus(userStore.userId)
33
+    }
34
+  }
35
+})
17
   export default {
36
   export default {
18
     onLaunch: function() {
37
     onLaunch: function() {
19
       this.initApp()
38
       this.initApp()
58
   --bg-color: #f8f9fa;
77
   --bg-color: #f8f9fa;
59
   --text-color: #333;
78
   --text-color: #333;
60
   --card-bg: #ffffff;
79
   --card-bg: #ffffff;
61
-  
80
+  --header-bg: #ffffff;
62
   /* 确保所有页面继承主题 */
81
   /* 确保所有页面继承主题 */
63
   background-color: var(--bg-color);
82
   background-color: var(--bg-color);
64
   color: var(--text-color);
83
   color: var(--text-color);
70
   --bg-color: #e6f7ff;
89
   --bg-color: #e6f7ff;
71
   --text-color: #1a3353;
90
   --text-color: #1a3353;
72
   --card-bg: #d0e8ff;
91
   --card-bg: #d0e8ff;
92
+  --header-bg: #2a5caa;
73
 }
93
 }
74
 
94
 
75
 /* 深色模式主题变量覆盖 */
95
 /* 深色模式主题变量覆盖 */
78
   --bg-color: #1a1a1a;
98
   --bg-color: #1a1a1a;
79
   --text-color: #e6e6e6;
99
   --text-color: #e6e6e6;
80
   --card-bg: #2a2a2a;
100
   --card-bg: #2a2a2a;
101
+  --header-bg: #1a1a1a;
81
 }
102
 }
82
 
103
 
83
 /* 公共组件样式 */
104
 /* 公共组件样式 */
141
   padding: 10px 20px;
162
   padding: 10px 20px;
142
   font-weight: bold;
163
   font-weight: bold;
143
 }
164
 }
165
+/* 全局样式 */
166
+page {
167
+  background-color: var(--bg-color);
168
+  color: var(--text-color);
169
+  font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
170
+}
171
+
144
 </style>
172
 </style>

+ 33
- 1
RuoYi-App/components/NovelReader.vue Zobrazit soubor

96
 import { useThemeStore } from '@/stores/theme'
96
 import { useThemeStore } from '@/stores/theme'
97
 import { useReadingProgress } from '@/composables/useReadingProgress'
97
 import { useReadingProgress } from '@/composables/useReadingProgress'
98
 import { cleanNovelContent, paginateContent } from '@/utils/contentUtils'
98
 import { cleanNovelContent, paginateContent } from '@/utils/contentUtils'
99
+// 导入清洗工具
100
+import { contentCleaner } from '@/utils/contentCleaner'
101
+//import { useAdManager } from '@/utils/adManager'
102
+import { useVipStore } from '@/stores/vip'
99
 
103
 
100
 const props = defineProps({
104
 const props = defineProps({
101
   chapterId: {
105
   chapterId: {
114
 
118
 
115
 const emit = defineEmits(['back', 'prev', 'next', 'show-chapters'])
119
 const emit = defineEmits(['back', 'prev', 'next', 'show-chapters'])
116
 
120
 
121
+const vipStore = useVipStore()
122
+const { showRewardAd, showFeedAd } = useAdManager()
123
+
117
 const userStore = useUserStore()
124
 const userStore = useUserStore()
118
 const themeStore = useThemeStore()
125
 const themeStore = useThemeStore()
119
-const { showRewardAd } = useAdManager()
126
+//const { showRewardAd } = useAdManager()
120
 const { saveProgress, loadProgress } = useReadingProgress()
127
 const { saveProgress, loadProgress } = useReadingProgress()
121
 
128
 
122
 // 阅读状态
129
 // 阅读状态
183
   currentPage.value = e.detail.current
190
   currentPage.value = e.detail.current
184
   saveReadingPosition()
191
   saveReadingPosition()
185
   
192
   
193
+  // VIP用户跳过广告
194
+  if (vipStore.hasPrivilege('免广告')) return
195
+  
186
   // 每5页触发广告
196
   // 每5页触发广告
187
   if (currentPage.value % 5 === 0) {
197
   if (currentPage.value % 5 === 0) {
188
     showRewardAd(props.chapterId)
198
     showRewardAd(props.chapterId)
189
   }
199
   }
190
 }
200
 }
191
 
201
 
202
+
192
 // 滚动事件处理
203
 // 滚动事件处理
193
 const onScroll = (e) => {
204
 const onScroll = (e) => {
194
   lastScrollPosition.value = e.detail.scrollTop
205
   lastScrollPosition.value = e.detail.scrollTop
270
 
281
 
271
 // 初始化
282
 // 初始化
272
 onMounted(() => {
283
 onMounted(() => {
284
+  // VIP用户不显示广告
285
+  if (vipStore.hasPrivilege('免广告')) return 
286
+    // 显示底部广告
287
+    feedAd.value = showFeedAd()
288
+	
273
   fetchChapterContent()
289
   fetchChapterContent()
274
   
290
   
275
   // 设置广告单元ID
291
   // 设置广告单元ID
288
     showBottomAd.value = !isVip
304
     showBottomAd.value = !isVip
289
   })
305
   })
290
 })
306
 })
307
+
308
+
309
+
310
+// 在获取章节内容后清洗
311
+const fetchChapterContent = async () => {
312
+  const res = await uni.request({
313
+    url: `https://php-backend.aiyadianzi.ltd/chapter/${props.chapterId}`,
314
+    method: 'GET'
315
+  })
316
+  
317
+  // 清洗内容
318
+  chapterContent.value = contentCleaner.clean(res.data.content)
319
+  
320
+  // 分页处理
321
+  paginatedContent.value = paginateContent(chapterContent.value)
322
+}
291
 </script>
323
 </script>
292
 
324
 
293
 <style scoped>
325
 <style scoped>

+ 224
- 106
RuoYi-App/pages/welfare/index.vue Zobrazit soubor

1
 <template>
1
 <template>
2
-  <view class="welfare-page">
2
+  <scroll-view scroll-y class="welfare-page">
3
+    <!-- 用户信息栏 -->
4
+    <view class="user-header">
5
+      <image :src="userAvatar" class="avatar" />
6
+      <view class="info">
7
+        <text class="name">{{ userName }}</text>
8
+        <text class="coins">金币: {{ coins }}</text>
9
+      </view>
10
+      <view class="vip-tag" v-if="isVIP">
11
+        <uni-icons type="crown" size="16" color="#ffc53d"></uni-icons>
12
+        <text>VIP会员</text>
13
+      </view>
14
+    </view>
3
     <!-- 签到区域 -->
15
     <!-- 签到区域 -->
4
     <view class="signin-section">
16
     <view class="signin-section">
5
-      <text class="title">每日签到</text>
6
-      <view class="signin-grid">
17
+      <view class="sign-header">
18
+        <text class="title">每日签到</text>
19
+        <text class="subtitle">已连续签到 {{ signedDays }} 天</text>
20
+      </view>
21
+      
22
+      <view class="sign-calendar">
7
         <view 
23
         <view 
8
-          v-for="(day, index) in 7" 
9
-          :key="day" 
10
-          :class="['day', { signed: index < signedDays, today: index === currentDay }]"
11
-          @click="signIn(index)"
24
+          v-for="day in 7" 
25
+          :key="day"
26
+          :class="[
27
+            'sign-day',
28
+            { 
29
+              'signed': day <= signedDays,
30
+              'today': day === currentDay && !todaySigned,
31
+              'active': day === currentDay && todaySigned
32
+            }
33
+          ]"
34
+          @click="handleSign(day)"
12
         >
35
         >
13
-          <text>第{{ day }}天</text>
14
-          <text class="reward">+{{ calculateReward(index) }}金币</text>
36
+          <text class="day-label">第{{ day }}天</text>
37
+          <text class="reward">+{{ getReward(day) }}金币</text>
15
         </view>
38
         </view>
16
       </view>
39
       </view>
17
       <text class="tip">已连续签到 {{ signedDays }} 天</text>
40
       <text class="tip">已连续签到 {{ signedDays }} 天</text>
18
     </view>
41
     </view>
19
     
42
     
43
+    <!-- 合作任务 -->
44
+    <PartnerTask />
45
+    
20
     <!-- 任务中心 -->
46
     <!-- 任务中心 -->
21
-    <view class="tasks-section">
22
-      <text class="title">每日任务</text>
47
+    <view class="task-center">
48
+      <view class="section-header">
49
+        <text class="title">每日任务</text>
50
+        <text class="more">查看更多</text>
51
+      </view>
52
+      
23
       <view class="task-list">
53
       <view class="task-list">
24
-        <view v-for="task in dailyTasks" :key="task.id" class="task-item">
54
+        <view 
55
+          v-for="task in dailyTasks"
56
+          :key="task.id"
57
+          class="task-item"
58
+        >
25
           <view class="task-info">
59
           <view class="task-info">
26
-            <text class="task-name">{{ task.name }}</text>
27
-            <text class="task-reward">+{{ task.reward }}金币</text>
60
+            <uni-icons :type="task.icon" size="20" :color="task.completed ? '#52c41a' : '#666'"></uni-icons>
61
+            <text class="name">{{ task.name }}</text>
28
           </view>
62
           </view>
29
           <button 
63
           <button 
30
-            :class="['task-btn', { disabled: task.completed }]"
64
+            class="action-btn"
65
+            :class="{ completed: task.completed }"
31
             @click="completeTask(task)"
66
             @click="completeTask(task)"
32
           >
67
           >
33
-            {{ task.completed ? '已完成' : '去完成' }}
68
+            {{ task.completed ? '已完成' : '+'+task.reward+'金币' }}
34
           </button>
69
           </button>
35
         </view>
70
         </view>
36
       </view>
71
       </view>
51
         </view>
86
         </view>
52
       </view>
87
       </view>
53
     </view>
88
     </view>
54
-  </view>
89
+  </scroll-view>
55
 </template>
90
 </template>
56
 
91
 
57
 <script setup>
92
 <script setup>
58
-import { ref } from 'vue'
93
+import { ref, onMounted } from 'vue'
94
+import { useSignSystem } from '@/utils/signUtils'
95
+import PartnerTask from '@/components/PartnerTask.vue'
96
+import { useUserStore } from '@/stores/user'
97
+
98
+const userStore = useUserStore()
99
+const { signedDays, todaySigned, signRewards, loadSignStatus, doSign } = useSignSystem()
59
 
100
 
60
-const signedDays = ref(3)
61
-const currentDay = ref(3) // 0-6 表示周一到周日
101
+// 用户数据
102
+const userName = ref('哎呀用户')
103
+const userAvatar = ref('/static/avatar/default.png')
104
+const coins = ref(350)
105
+const isVIP = ref(true)
62
 
106
 
63
 // 每日任务
107
 // 每日任务
64
 const dailyTasks = ref([
108
 const dailyTasks = ref([
65
-  { id: 1, name: '阅读30分钟', reward: 50, completed: false },
66
-  { id: 2, name: '分享给好友', reward: 30, completed: true },
67
-  { id: 3, name: '评论本章节', reward: 20, completed: false },
68
-  { id: 4, name: '完善个人资料', reward: 10, completed: false }
109
+  { id: 1, name: '阅读30分钟', icon: 'eye', reward: 50, completed: false },
110
+  { id: 2, name: '分享给好友', icon: 'redo', reward: 30, completed: true },
111
+  { id: 3, name: '评论本章节', icon: 'chat', reward: 20, completed: false },
112
+  { id: 4, name: '完善个人资料', icon: 'person', reward: 10, completed: false }
69
 ])
113
 ])
114
+// 当前星期几(0-6)
115
+const currentDay = ref(new Date().getDay() + 1)
70
 
116
 
117
+// 获取每日奖励
118
+const getReward = (day) => {
119
+  const reward = signRewards.value.find(r => r.day === day)
120
+  return reward ? reward.reward : 10 + day * 5
121
+}
71
 // 合作平台
122
 // 合作平台
72
 const partners = ref([
123
 const partners = ref([
73
   { id: 1, name: '百度地图', icon: '/static/partners/baidu.png', url: 'https://map.baidu.com' },
124
   { id: 1, name: '百度地图', icon: '/static/partners/baidu.png', url: 'https://map.baidu.com' },
76
   { id: 4, name: '饿了么', icon: '/static/partners/eleme.png', url: 'https://www.ele.me' }
127
   { id: 4, name: '饿了么', icon: '/static/partners/eleme.png', url: 'https://www.ele.me' }
77
 ])
128
 ])
78
 
129
 
79
-// 签到逻辑
80
-const signIn = (dayIndex) => {
81
-  if (dayIndex !== currentDay.value) return
82
-  if (signedDays.value > currentDay.value) return
130
+// 处理签到
131
+const handleSign = async (day) => {
132
+  if (day !== currentDay.value) {
133
+    uni.showToast({ title: '请先完成今日签到', icon: 'none' })
134
+    return
135
+  }
83
   
136
   
84
-  signedDays.value++
85
-  uni.showToast({ title: `签到成功!获得${calculateReward(dayIndex)}金币` })
86
-}
87
-
88
-// 计算签到奖励
89
-const calculateReward = (day) => {
90
-  const baseReward = 10
91
-  return baseReward * (day + 1)
137
+  if (todaySigned.value) {
138
+    uni.showToast({ title: '今日已签到', icon: 'none' })
139
+    return
140
+  }
141
+  
142
+  const reward = await doSign()
143
+  if (reward) {
144
+    coins.value += reward
145
+  }
92
 }
146
 }
93
 
147
 
94
 // 完成任务
148
 // 完成任务
95
 const completeTask = (task) => {
149
 const completeTask = (task) => {
96
   if (task.completed) return
150
   if (task.completed) return
97
-  
98
   task.completed = true
151
   task.completed = true
99
-  uni.showToast({ title: `任务完成!获得${task.reward}金币` })
152
+  coins.value += task.reward
153
+  uni.showToast({ title: `任务完成!获得${task.reward}金币`, icon: 'success' })
100
 }
154
 }
101
-
155
+// 初始化
156
+onMounted(async () => {
157
+  await loadSignStatus()
158
+  
159
+  // 加载用户信息
160
+  if (userStore.userInfo) {
161
+    userName.value = userStore.userInfo.nickname || '哎呀用户'
162
+    userAvatar.value = userStore.userInfo.avatar || '/static/avatar/default.png'
163
+    coins.value = userStore.userInfo.coins || 0
164
+    isVIP.value = userStore.isVIP
165
+  }
166
+})
102
 // 跳转合作平台
167
 // 跳转合作平台
103
 const goPartnerTask = (partner) => {
168
 const goPartnerTask = (partner) => {
104
   uni.navigateTo({
169
   uni.navigateTo({
109
 
174
 
110
 <style scoped>
175
 <style scoped>
111
 .welfare-page {
176
 .welfare-page {
177
+  height: 100vh;
178
+  background-color: var(--bg-color);
112
   padding: 20px;
179
   padding: 20px;
180
+  box-sizing: border-box;
113
 }
181
 }
114
 
182
 
115
-.title {
183
+.user-header {
184
+  display: flex;
185
+  align-items: center;
186
+  margin-bottom: 25px;
187
+}
188
+
189
+.avatar {
190
+  width: 60px;
191
+  height: 60px;
192
+  border-radius: 50%;
193
+  margin-right: 15px;
194
+}
195
+
196
+.info {
197
+  flex: 1;
198
+}
199
+
200
+.name {
116
   font-size: 18px;
201
   font-size: 18px;
117
   font-weight: bold;
202
   font-weight: bold;
118
-  margin-bottom: 15px;
119
   display: block;
203
   display: block;
120
 }
204
 }
121
 
205
 
122
-.signin-section {
123
-  margin-bottom: 25px;
206
+.coins {
207
+  font-size: 14px;
208
+  color: #faad14;
209
+  display: block;
210
+  margin-top: 5px;
211
+}
212
+
213
+.vip-tag {
214
+  background: linear-gradient(135deg, #ffd666 0%, #ffc53d 100%);
215
+  color: #1a3353;
216
+  padding: 4px 10px;
217
+  border-radius: 15px;
218
+  font-size: 12px;
219
+  display: flex;
220
+  align-items: center;
221
+}
222
+
223
+.sign-card {
224
+  background-color: var(--card-bg);
225
+  border-radius: 16px;
226
+  padding: 20px;
227
+  margin-bottom: 20px;
228
+}
229
+
230
+.sign-header {
231
+  display: flex;
232
+  justify-content: space-between;
233
+  align-items: center;
234
+  margin-bottom: 15px;
235
+}
236
+
237
+.title {
238
+  font-size: 18px;
239
+  font-weight: bold;
124
 }
240
 }
125
 
241
 
126
-.signin-grid {
242
+.subtitle {
243
+  font-size: 14px;
244
+  color: #666;
245
+}
246
+
247
+.sign-calendar {
127
   display: grid;
248
   display: grid;
128
   grid-template-columns: repeat(7, 1fr);
249
   grid-template-columns: repeat(7, 1fr);
129
-  gap: 5px;
250
+  gap: 8px;
130
 }
251
 }
131
 
252
 
132
-.day {
253
+.sign-day {
133
   border: 1px solid #eee;
254
   border: 1px solid #eee;
134
   border-radius: 8px;
255
   border-radius: 8px;
135
-  padding: 8px 5px;
256
+  padding: 10px 5px;
136
   text-align: center;
257
   text-align: center;
137
-  font-size: 12px;
138
-  
139
-  &.signed {
140
-    background-color: #e6fffb;
141
-    border-color: #36cfc9;
142
-  }
143
-  
144
-  &.today {
145
-    background-color: #fff7e6;
146
-    border-color: #ffc53d;
147
-  }
258
+  background-color: #fafafa;
148
 }
259
 }
149
 
260
 
150
-.reward {
151
-  display: block;
152
-  color: #faad14;
153
-  font-weight: bold;
261
+.sign-day.signed {
262
+  background-color: #e6fffb;
263
+  border-color: #36cfc9;
264
+}
265
+
266
+.sign-day.today {
267
+  background-color: #fff7e6;
268
+  border-color: #ffc53d;
154
 }
269
 }
155
 
270
 
156
-.tip {
271
+.sign-day.active {
272
+  background-color: #fff1b8;
273
+  border-color: #ffc53d;
274
+}
275
+
276
+.day-label {
277
+  font-size: 13px;
157
   display: block;
278
   display: block;
158
-  margin-top: 10px;
159
-  color: #8c8c8c;
279
+}
280
+
281
+.reward {
160
   font-size: 12px;
282
   font-size: 12px;
283
+  color: #faad14;
284
+  font-weight: bold;
285
+  display: block;
286
+  margin-top: 3px;
161
 }
287
 }
162
 
288
 
163
-.task-list {
164
-  border-top: 1px solid #f0f0f0;
289
+.task-center {
290
+  background-color: var(--card-bg);
291
+  border-radius: 16px;
292
+  padding: 20px;
165
 }
293
 }
166
 
294
 
167
-.task-item {
295
+.section-header {
168
   display: flex;
296
   display: flex;
169
   justify-content: space-between;
297
   justify-content: space-between;
170
   align-items: center;
298
   align-items: center;
171
-  padding: 12px 0;
172
-  border-bottom: 1px solid #f0f0f0;
173
-}
174
-
175
-.task-info {
176
-  flex: 1;
299
+  margin-bottom: 15px;
177
 }
300
 }
178
 
301
 
179
-.task-name {
180
-  display: block;
302
+.more {
181
   font-size: 14px;
303
   font-size: 14px;
304
+  color: var(--primary-color);
182
 }
305
 }
183
 
306
 
184
-.task-reward {
185
-  font-size: 12px;
186
-  color: #faad14;
187
-}
188
-
189
-.task-btn {
190
-  margin: 0;
191
-  padding: 0 10px;
192
-  height: 28px;
193
-  line-height: 28px;
194
-  font-size: 12px;
195
-  background-color: #1890ff;
196
-  color: white;
197
-  
198
-  &.disabled {
199
-    background-color: #bfbfbf;
200
-  }
307
+.task-list {
308
+  border-top: 1px solid #f0f0f0;
201
 }
309
 }
202
 
310
 
203
-.partner-grid {
204
-  display: grid;
205
-  grid-template-columns: repeat(4, 1fr);
206
-  gap: 15px;
311
+.task-item {
312
+  display: flex;
313
+  justify-content: space-between;
314
+  align-items: center;
315
+  padding: 15px 0;
316
+  border-bottom: 1px solid #f0f0f0;
207
 }
317
 }
208
 
318
 
209
-.partner-item {
319
+.task-info {
210
   display: flex;
320
   display: flex;
211
-  flex-direction: column;
212
   align-items: center;
321
   align-items: center;
213
 }
322
 }
214
 
323
 
215
-.partner-icon {
216
-  width: 50px;
217
-  height: 50px;
218
-  border-radius: 10px;
219
-  margin-bottom: 5px;
324
+.name {
325
+  font-size: 15px;
326
+  margin-left: 10px;
220
 }
327
 }
221
 
328
 
222
-.partner-name {
223
-  font-size: 12px;
329
+.action-btn {
330
+  background-color: var(--primary-color);
331
+  color: white;
332
+  border: none;
333
+  border-radius: 16px;
334
+  height: 30px;
335
+  line-height: 30px;
336
+  padding: 0 12px;
337
+  font-size: 13px;
338
+}
339
+
340
+.action-btn.completed {
341
+  background-color: #bfbfbf;
224
 }
342
 }
225
 </style>
343
 </style>

+ 1
- 0
RuoYi-App/utils/adManager.js Zobrazit soubor

1
 import { ref } from 'vue'
1
 import { ref } from 'vue'
2
 import { useUserStore } from '@/stores/user'
2
 import { useUserStore } from '@/stores/user'
3
+import { useVipStore } from '@/stores/vip'
3
 
4
 
4
 // 广告平台配置
5
 // 广告平台配置
5
 const AD_PLATFORMS = {
6
 const AD_PLATFORMS = {

+ 95
- 0
RuoYi-App/utils/contentCleaner.js Zobrazit soubor

1
+/**
2
+ * 小说内容清洗工具
3
+ * 移除原始网站信息、广告、干扰元素
4
+ */
5
+export const contentCleaner = {
6
+  // 主清洗函数
7
+  clean(content) {
8
+    if (!content) return ''
9
+    
10
+    // 执行清洗流水线
11
+    return this.pipeline(content, [
12
+      this.removeWebsiteInfo,
13
+      this.removeAds,
14
+      this.removeHtmlTags,
15
+      this.normalizeSpaces,
16
+      this.fixParagraphs,
17
+      this.removeSpecialChars
18
+    ])
19
+  },
20
+  
21
+  // 清洗流水线
22
+  pipeline(content, processors) {
23
+    return processors.reduce((result, processor) => {
24
+      return processor(result)
25
+    }, content)
26
+  },
27
+  
28
+  // 移除网站信息
29
+  removeWebsiteInfo(content) {
30
+    const patterns = [
31
+      /最新网址[::]?\s*[a-z0-9.-]+/gi,
32
+      /www\.[a-z0-9]+\.[a-z]{2,}/gi,
33
+      /请?收藏本站:https?:\/\/[^\s]+/gi,
34
+      /请?记住本站域名:\w+\.\w+/gi,
35
+      /(电脑版|手机版)?访问:m?\.\w+\.\w+/gi,
36
+      /【[^】]{0,10}更新快[^】]{0,10}】/g
37
+    ]
38
+    
39
+    return patterns.reduce((result, pattern) => {
40
+      return result.replace(pattern, '')
41
+    }, content)
42
+  },
43
+  
44
+  // 移除广告文本
45
+  removeAds(content) {
46
+    const adKeywords = [
47
+      '广告', '推广', '推荐票', '月票', 'QQ群', '微信号',
48
+      'VIP章节', '正版订阅', '盗版', '笔趣阁', '天才一秒记住',
49
+      '本站地址', '手机用户'
50
+    ]
51
+    
52
+    const adPattern = new RegExp(
53
+      `[【((]?(${adKeywords.join('|')})[^))】]*[))】]?`,
54
+      'gi'
55
+    )
56
+    
57
+    return content.replace(adPattern, '')
58
+  },
59
+  
60
+  // 移除HTML标签
61
+  removeHtmlTags(content) {
62
+    return content
63
+      .replace(/<br\s*\/?>/g, '\n') // 保留换行
64
+      .replace(/&nbsp;|&#160;/g, ' ') // 替换空格实体
65
+      .replace(/<[^>]+>/g, '') // 移除所有HTML标签
66
+  },
67
+  
68
+  // 标准化空格
69
+  normalizeSpaces(content) {
70
+    return content
71
+      .replace(/\s+/g, ' ') // 合并连续空格
72
+      .replace(/^\s+|\s+$/g, '') // 移除首尾空格
73
+  },
74
+  
75
+  // 修复段落格式
76
+  fixParagraphs(content) {
77
+    return content
78
+      .split('\n')
79
+      .map(line => line.trim())
80
+      .filter(line => line.length > 0)
81
+      .join('\n\n')
82
+  },
83
+  
84
+  // 移除特殊字符
85
+  removeSpecialChars(content) {
86
+    const specialChars = [
87
+      '\u200b', // 零宽空格
88
+      '\ufeff', // BOM头
89
+      '\u202a', '\u202b', '\u202c', '\u202d', '\u202e' // 方向控制符
90
+    ]
91
+    
92
+    const pattern = new RegExp(`[${specialChars.join('')}]`, 'g')
93
+    return content.replace(pattern, '')
94
+  }
95
+}

+ 74
- 0
RuoYi-App/utils/signUtils.js Zobrazit soubor

1
+import { ref } from 'vue'
2
+import { request } from '@/utils/request'
3
+
4
+// 签到状态管理
5
+export function useSignSystem() {
6
+  const signedDays = ref(0)
7
+  const todaySigned = ref(false)
8
+  const signRewards = ref([])
9
+  
10
+  // 加载签到状态
11
+  const loadSignStatus = async () => {
12
+    try {
13
+      const res = await request({
14
+        url: '/sign/status',
15
+        method: 'GET',
16
+        headers: { 'Authorization': `Bearer ${uni.getStorageSync('token')}` }
17
+      })
18
+      
19
+      signedDays.value = res.data.signedDays || 0
20
+      todaySigned.value = res.data.todaySigned || false
21
+      signRewards.value = res.data.rewards || generateDefaultRewards()
22
+      return true
23
+    } catch (err) {
24
+      console.error('签到状态加载失败', err)
25
+      return false
26
+    }
27
+  }
28
+  
29
+  // 执行签到
30
+  const doSign = async () => {
31
+    if (todaySigned.value) return
32
+    
33
+    try {
34
+      uni.showLoading({ title: '签到中...' })
35
+      const res = await request({
36
+        url: '/sign/do',
37
+        method: 'POST',
38
+        headers: { 'Authorization': `Bearer ${uni.getStorageSync('token')}` }
39
+      })
40
+      
41
+      if (res.data.success) {
42
+        signedDays.value++
43
+        todaySigned.value = true
44
+        uni.showToast({ title: `签到成功!获得${res.data.reward}金币`, icon: 'success' })
45
+        return res.data.reward
46
+      }
47
+    } catch (err) {
48
+      uni.showToast({ title: '签到失败', icon: 'none' })
49
+    } finally {
50
+      uni.hideLoading()
51
+    }
52
+  }
53
+  
54
+  // 生成默认奖励规则
55
+  const generateDefaultRewards = () => {
56
+    return [
57
+      { day: 1, reward: 10 },
58
+      { day: 2, reward: 15 },
59
+      { day: 3, reward: 20 },
60
+      { day: 4, reward: 25 },
61
+      { day: 5, reward: 30 },
62
+      { day: 6, reward: 40 },
63
+      { day: 7, reward: 50, extra: '双倍金币卡' }
64
+    ]
65
+  }
66
+  
67
+  return {
68
+    signedDays,
69
+    todaySigned,
70
+    signRewards,
71
+    loadSignStatus,
72
+    doSign
73
+  }
74
+}

Načítá se…
Zrušit
Uložit