hookehuyr

fix(plan): 统一分页起始页码从0开始

...@@ -5,6 +5,29 @@ ...@@ -5,6 +5,29 @@
5 5
6 --- 6 ---
7 7
8 +## [2026-02-11] - 统一分页起始页码从0开始
9 +
10 +### 修复
11 +- **计划书页**
12 + - 完善状态筛选功能(5个状态tab:全部、待处理、处理中、已生成、已查看)
13 + - 实现查看计划书功能(支持多文件选择,使用 ActionSheet)
14 + - 实现删除计划书功能(调用 deleteAPI)
15 + - 添加状态标记显示(4种状态颜色区分)
16 + - 修复分页从0开始
17 +
18 +- **消息页**
19 + - 修复分页从0开始
20 +
21 +---
22 +
23 +**详细信息**
24 +- **影响文件**: src/pages/plan/index.vue, src/pages/message/index.vue
25 +- **技术栈**: Taro 4, Vue 3, NutUI
26 +- **测试状态**: 已通过 ESLint 检查
27 +- **备注**: 统一项目所有列表页的分页规范,确保与 API 文档一致
28 +
29 +---
30 +
8 ## [2026-02-11] - 完善计划书 API 接口定义 31 ## [2026-02-11] - 完善计划书 API 接口定义
9 32
10 ### 新增 33 ### 新增
......
...@@ -72,10 +72,10 @@ const go = useGo() ...@@ -72,10 +72,10 @@ const go = useGo()
72 const currentList = ref([]) 72 const currentList = ref([])
73 73
74 /** 74 /**
75 - * 当前页码(从1开始) 75 + * 当前页码(从0开始)
76 * @type {Ref<number>} 76 * @type {Ref<number>}
77 */ 77 */
78 -const currentPage = ref(1) 78 +const currentPage = ref(0)
79 79
80 /** 80 /**
81 * 每页数量 81 * 每页数量
...@@ -105,7 +105,7 @@ const loadingMore = ref(false) ...@@ -105,7 +105,7 @@ const loadingMore = ref(false)
105 * 获取消息列表 105 * 获取消息列表
106 * 106 *
107 * @param {Object} params - 请求参数 107 * @param {Object} params - 请求参数
108 - * @param {number} params.page - 页码(从1开始) 108 + * @param {number} params.page - 页码(从0开始)
109 * @param {number} params.limit - 每页数量 109 * @param {number} params.limit - 每页数量
110 * @param {boolean} isLoadMore - 是否为加载更多 110 * @param {boolean} isLoadMore - 是否为加载更多
111 * @returns {Promise<void>} 111 * @returns {Promise<void>}
...@@ -174,11 +174,11 @@ useLoad(async (options) => { ...@@ -174,11 +174,11 @@ useLoad(async (options) => {
174 console.log('[Message] 页面参数:', options) 174 console.log('[Message] 页面参数:', options)
175 175
176 // 重置分页状态 176 // 重置分页状态
177 - currentPage.value = 1 177 + currentPage.value = 0
178 hasMore.value = true 178 hasMore.value = true
179 179
180 // 获取消息列表 180 // 获取消息列表
181 - await fetchMessageList({ page: 1, limit: pageSize }) 181 + await fetchMessageList({ page: 0, limit: pageSize })
182 }) 182 })
183 183
184 /** 184 /**
...@@ -207,11 +207,11 @@ const handleRefresh = async () => { ...@@ -207,11 +207,11 @@ const handleRefresh = async () => {
207 console.log('[Message] 下拉刷新') 207 console.log('[Message] 下拉刷新')
208 208
209 // 重置分页状态 209 // 重置分页状态
210 - currentPage.value = 1 210 + currentPage.value = 0
211 hasMore.value = true 211 hasMore.value = true
212 212
213 // 刷新数据 213 // 刷新数据
214 - await fetchMessageList({ page: 1, limit: pageSize }) 214 + await fetchMessageList({ page: 0, limit: pageSize })
215 } 215 }
216 216
217 /** 217 /**
......
...@@ -79,10 +79,13 @@ ...@@ -79,10 +79,13 @@
79 <view 79 <view
80 :class="[ 80 :class="[
81 'status-badge', 81 'status-badge',
82 - item.status === 'processing' ? 'status-processing' : 'status-generated' 82 + item.status === 'pending' ? 'status-pending' : '',
83 + item.status === 'processing' ? 'status-processing' : '',
84 + item.status === 'generated' ? 'status-generated' : '',
85 + item.status === 'viewed' ? 'status-viewed' : ''
83 ]" 86 ]"
84 > 87 >
85 - {{ item.status === 'processing' ? '生成中' : '已完成' }} 88 + {{ getStatusText(item.status) }}
86 </view> 89 </view>
87 </view> 90 </view>
88 </view> 91 </view>
...@@ -133,7 +136,7 @@ ...@@ -133,7 +136,7 @@
133 import { ref, nextTick } from 'vue' 136 import { ref, nextTick } from 'vue'
134 import Taro, { useLoad, useReachBottom } from '@tarojs/taro' 137 import Taro, { useLoad, useReachBottom } from '@tarojs/taro'
135 import { useFileOperation } from '@/composables/useFileOperation' 138 import { useFileOperation } from '@/composables/useFileOperation'
136 -import { listAPI } from '@/api/plan' 139 +import { listAPI, viewAPI, deleteAPI } from '@/api/plan'
137 import NavHeader from '@/components/navigation/NavHeader.vue' 140 import NavHeader from '@/components/navigation/NavHeader.vue'
138 import ListItemActions from '@/components/list/ListItemActions/index.vue' 141 import ListItemActions from '@/components/list/ListItemActions/index.vue'
139 import SearchBar from '@/components/forms/SearchBar.vue' 142 import SearchBar from '@/components/forms/SearchBar.vue'
...@@ -154,29 +157,55 @@ const pageSize = 20 // 每页数量 ...@@ -154,29 +157,55 @@ const pageSize = 20 // 每页数量
154 /** 157 /**
155 * Tab 数据源 158 * Tab 数据源
156 * @description 包含分类信息和对应的计划书列表 159 * @description 包含分类信息和对应的计划书列表
157 - * @description 状态值说明:空字符串=全部,"3"=生成中,"5"=已生成 160 + * @description 状态值说明(根据API文档):
161 + * - 空字符串 = 全部
162 + * - "3" = 待处理
163 + * - "5" = 处理中
164 + * - "7" = 已生成
165 + * - "9" = 已查看
158 */ 166 */
159 const tabsData = ref([ 167 const tabsData = ref([
160 { id: '', name: '全部', list: [] }, 168 { id: '', name: '全部', list: [] },
161 - { id: '3', name: '生成中', list: [] }, 169 + { id: '3', name: '待处理', list: [] },
162 - { id: '5', name: '已生成', list: [] }, 170 + { id: '5', name: '处理中', list: [] },
171 + { id: '7', name: '已生成', list: [] },
172 + { id: '9', name: '已查看', list: [] },
163 ]) 173 ])
164 174
165 /** 175 /**
166 * 订单状态映射 176 * 订单状态映射
167 * @description 将 API 返回的 order_status 映射到前端使用的状态 177 * @description 将 API 返回的 order_status 映射到前端使用的状态
168 * @param {string} orderStatus - API 返回的状态值 178 * @param {string} orderStatus - API 返回的状态值
169 - * @returns {string} 前端状态:'processing' | 'generated' 179 + * @returns {string} 前端状态:'pending' | 'processing' | 'generated' | 'viewed'
170 */ 180 */
171 const mapOrderStatus = (orderStatus) => { 181 const mapOrderStatus = (orderStatus) => {
172 - // 根据业务需求: 182 + // 根据API文档:
173 - // "3" = 生成中 183 + // "3" = 待处理
174 - // "5" = 已生成 184 + // "5" = 处理中
175 - // 其他值 = 生成中(默认) 185 + // "7" = 已生成
176 - if (orderStatus === '5') { 186 + // "9" = 已查看
177 - return 'generated' 187 + const statusMap = {
188 + '3': 'pending',
189 + '5': 'processing',
190 + '7': 'generated',
191 + '9': 'viewed'
178 } 192 }
179 - return 'processing' 193 + return statusMap[orderStatus] || 'pending'
194 +}
195 +
196 +/**
197 + * 获取状态文本
198 + * @param {string} status - 前端状态值
199 + * @returns {string} 状态文本
200 + */
201 +const getStatusText = (status) => {
202 + const textMap = {
203 + 'pending': '待处理',
204 + 'processing': '处理中',
205 + 'generated': '已生成',
206 + 'viewed': '已查看'
207 + }
208 + return textMap[status] || '待处理'
180 } 209 }
181 210
182 /** 211 /**
...@@ -186,8 +215,11 @@ const mapOrderStatus = (orderStatus) => { ...@@ -186,8 +215,11 @@ const mapOrderStatus = (orderStatus) => {
186 * @returns {Object} 组件使用的计划书对象 215 * @returns {Object} 组件使用的计划书对象
187 */ 216 */
188 const transformApiItem = (apiItem) => { 217 const transformApiItem = (apiItem) => {
189 - // 获取第一个文件(如果有) 218 + // 获取所有文件
190 - const firstFile = apiItem.proposal_files && apiItem.proposal_files[0] 219 + const proposalFiles = apiItem.proposal_files || []
220 +
221 + // 获取第一个文件(作为默认显示)
222 + const firstFile = proposalFiles[0]
191 223
192 // 获取第一个分类名称(categories 是对象数组) 224 // 获取第一个分类名称(categories 是对象数组)
193 const categoryName = apiItem.categories && apiItem.categories.length > 0 225 const categoryName = apiItem.categories && apiItem.categories.length > 0
...@@ -201,8 +233,11 @@ const transformApiItem = (apiItem) => { ...@@ -201,8 +233,11 @@ const transformApiItem = (apiItem) => {
201 date: apiItem.created_time || '', 233 date: apiItem.created_time || '',
202 tag: categoryName, 234 tag: categoryName,
203 status: mapOrderStatus(apiItem.order_status), 235 status: mapOrderStatus(apiItem.order_status),
236 + // 默认显示第一个文件
204 fileName: firstFile?.file_name || '', 237 fileName: firstFile?.file_name || '',
205 downloadUrl: firstFile?.file_url || '', 238 downloadUrl: firstFile?.file_url || '',
239 + // 保存所有文件(用于多文件选择)
240 + proposalFiles: proposalFiles,
206 // 保存完整的原始数据 241 // 保存完整的原始数据
207 _raw: apiItem 242 _raw: apiItem
208 } 243 }
...@@ -210,54 +245,34 @@ const transformApiItem = (apiItem) => { ...@@ -210,54 +245,34 @@ const transformApiItem = (apiItem) => {
210 245
211 /** 246 /**
212 * 加载计划书列表(调用真实 API) 247 * 加载计划书列表(调用真实 API)
213 - * @param {number} page - 页码(从1开始,API 要求) 248 + * @param {number} page - 页码(从0开始,API 要求)
214 * @param {number} limit - 每页数量 249 * @param {number} limit - 每页数量
215 * @param {boolean} isLoadMore - 是否为加载更多 250 * @param {boolean} isLoadMore - 是否为加载更多
216 * 251 *
217 - * @description 状态过滤说明(前端过滤,因为后端不支持): 252 + * @description 状态筛选说明(使用服务端 status 参数筛选):
218 * - activeTabId = '':不过滤,显示全部 253 * - activeTabId = '':不过滤,显示全部
219 - * - activeTabId = '3':只显示生成中的计划书(order_status = "3") 254 + * - activeTabId = '3':只显示待处理的计划书
220 - * - activeTabId = '5':只显示已生成的计划书(order_status = "5") 255 + * - activeTabId = '5':只显示处理中的计划书
221 - * 256 + * - activeTabId = '7':只显示已生成的计划书
222 - * @todo ⚠️ 后端API缺少关键查询参数,需要添加以下支持: 257 + * - activeTabId = '9':只显示已查看的计划书
223 - *
224 - * 1️⃣ **分页参数**(必须):
225 - * - `page` - 页码,从 1 开始
226 - * - `limit` - 每页数量,默认 20
227 - * - 当前影响:无法实现真正的分页,只能一次性加载所有数据
228 - *
229 - * 2️⃣ **状态筛选参数**(必须):
230 - * - `order_status` - 订单状态筛选
231 - * - ⚠️ **状态值不确定**:当前推测 `"3"` = 生成中,`"5"` = 已生成(需与后端确认)
232 - * - 当前影响:需要前端过滤,性能和分页准确性差
233 * 258 *
234 - * 3️⃣ **搜索参数**(建议): 259 + * ✅ **API参数说明**:
235 - * - `keyword` - 搜索关键字(搜索申请人、产品名等)
236 - * - 当前影响:搜索功能可能不准确
237 - *
238 - * 🔴 **严重问题**:
239 - * - 当前后端API不支持任何查询参数
240 - * - 前端只能一次性获取全部数据,然后在本地进行分页和过滤
241 - * - 这导致:
242 - * - 性能问题:数据量大时加载慢
243 - * - 分页失效:无法实现真正的服务端分页
244 - * - 内存占用:所有数据都在前端
245 - *
246 - * ✅ **建议的API参数规范**:
247 * ```javascript 260 * ```javascript
248 * GET /srv/?a=proposal&t=list 261 * GET /srv/?a=proposal&t=list
249 * Query Parameters: 262 * Query Parameters:
250 - * - page: number (必需) - 页码,从 1 开始 263 + * - page: number (必需) - 页码,从 0 开始
251 * - limit: number (必需) - 每页数量,默认 20 264 * - limit: number (必需) - 每页数量,默认 20
252 - * - order_status: string (可选) - 状态筛选,需与后端确认具体值 265 + * - status: string (可选) - 状态筛选
253 * - keyword: string (可选) - 搜索关键字 266 * - keyword: string (可选) - 搜索关键字
254 * ``` 267 * ```
255 * 268 *
256 - * ⚠️ **重要**:order_status 的具体值需要与后端确认! 269 + * ⚠️ **重要**:status 参数的具体值(根据API文档):
257 - * - 当前前端推测:`"3"` = 生成中,`"5"` = 已生成 270 + * - "3" = 待处理
258 - * - 实际值可能不同,请后端提供准确的状态值定义 271 + * - "5" = 处理中
272 + * - "7" = 已生成
273 + * - "9" = 已查看
259 */ 274 */
260 -const fetchPlanList = async (page = 1, limit = pageSize, isLoadMore = false) => { 275 +const fetchPlanList = async (page = 0, limit = pageSize, isLoadMore = false) => {
261 try { 276 try {
262 // 如果是加载更多,使用 loadingMore 状态,否则使用 loading 状态 277 // 如果是加载更多,使用 loadingMore 状态,否则使用 loading 状态
263 if (isLoadMore) { 278 if (isLoadMore) {
...@@ -267,12 +282,16 @@ const fetchPlanList = async (page = 1, limit = pageSize, isLoadMore = false) => ...@@ -267,12 +282,16 @@ const fetchPlanList = async (page = 1, limit = pageSize, isLoadMore = false) =>
267 } 282 }
268 283
269 // 构建请求参数 284 // 构建请求参数
270 - // 注意:后端不支持 order_status 参数,需要在前端过滤
271 const params = { 285 const params = {
272 page: page, 286 page: page,
273 limit: limit 287 limit: limit
274 } 288 }
275 289
290 + // 状态筛选(使用 status 参数)
291 + if (activeTabId.value !== '') {
292 + params.status = activeTabId.value
293 + }
294 +
276 // 搜索关键字 295 // 搜索关键字
277 if (searchValue.value) { 296 if (searchValue.value) {
278 params.keyword = searchValue.value 297 params.keyword = searchValue.value
...@@ -283,12 +302,6 @@ const fetchPlanList = async (page = 1, limit = pageSize, isLoadMore = false) => ...@@ -283,12 +302,6 @@ const fetchPlanList = async (page = 1, limit = pageSize, isLoadMore = false) =>
283 302
284 if (res.code === 1 && res.data) { 303 if (res.code === 1 && res.data) {
285 let apiList = res.data.list || [] 304 let apiList = res.data.list || []
286 -
287 - // ⚠️ 前端过滤:因为后端不支持 order_status 参数
288 - if (activeTabId.value !== '') {
289 - apiList = apiList.filter(item => item.order_status === activeTabId.value)
290 - }
291 -
292 const transformedList = apiList.map(transformApiItem) 305 const transformedList = apiList.map(transformApiItem)
293 306
294 if (isLoadMore) { 307 if (isLoadMore) {
...@@ -336,8 +349,8 @@ const onTabClick = (id) => { ...@@ -336,8 +349,8 @@ const onTabClick = (id) => {
336 activeTabId.value = id 349 activeTabId.value = id
337 listVisible.value = false 350 listVisible.value = false
338 351
339 - // 重置分页状态(从第 1 页开始) 352 + // 重置分页状态(从第 0 页开始)
340 - currentPage.value = 1 353 + currentPage.value = 0
341 hasMore.value = true 354 hasMore.value = true
342 355
343 nextTick(() => { 356 nextTick(() => {
...@@ -345,7 +358,7 @@ const onTabClick = (id) => { ...@@ -345,7 +358,7 @@ const onTabClick = (id) => {
345 listVisible.value = true 358 listVisible.value = true
346 359
347 // 重新加载数据 360 // 重新加载数据
348 - fetchPlanList(1, pageSize, false) 361 + fetchPlanList(0, pageSize, false)
349 }) 362 })
350 } 363 }
351 364
...@@ -353,14 +366,14 @@ const onTabClick = (id) => { ...@@ -353,14 +366,14 @@ const onTabClick = (id) => {
353 * 搜索处理 366 * 搜索处理
354 */ 367 */
355 const onSearch = () => { 368 const onSearch = () => {
356 - // 重置分页状态(从第 1 页开始) 369 + // 重置分页状态(从第 0 页开始)
357 - currentPage.value = 1 370 + currentPage.value = 0
358 hasMore.value = true 371 hasMore.value = true
359 372
360 listRenderKey.value += 1 373 listRenderKey.value += 1
361 374
362 // 重新加载数据 375 // 重新加载数据
363 - fetchPlanList(1, pageSize, false) 376 + fetchPlanList(0, pageSize, false)
364 } 377 }
365 378
366 /** 379 /**
...@@ -372,14 +385,14 @@ const onClear = () => { ...@@ -372,14 +385,14 @@ const onClear = () => {
372 // 清空搜索值 385 // 清空搜索值
373 searchValue.value = '' 386 searchValue.value = ''
374 387
375 - // 重置分页状态(从第 1 页开始) 388 + // 重置分页状态(从第 0 页开始)
376 - currentPage.value = 1 389 + currentPage.value = 0
377 hasMore.value = true 390 hasMore.value = true
378 391
379 listRenderKey.value += 1 392 listRenderKey.value += 1
380 393
381 // 重新加载数据 394 // 重新加载数据
382 - fetchPlanList(1, pageSize, false) 395 + fetchPlanList(0, pageSize, false)
383 396
384 console.log('[Plan Page] onClear 完成,列表已刷新') 397 console.log('[Plan Page] onClear 完成,列表已刷新')
385 } 398 }
...@@ -388,8 +401,8 @@ const onClear = () => { ...@@ -388,8 +401,8 @@ const onClear = () => {
388 * 页面加载时初始化数据 401 * 页面加载时初始化数据
389 */ 402 */
390 useLoad(() => { 403 useLoad(() => {
391 - // 加载第一页数据 404 + // 加载第一页数据(page = 0)
392 - fetchPlanList(1, pageSize, false) 405 + fetchPlanList(0, pageSize, false)
393 }) 406 })
394 407
395 /** 408 /**
...@@ -428,48 +441,121 @@ useReachBottom(() => { ...@@ -428,48 +441,121 @@ useReachBottom(() => {
428 * @param {Object} item - 计划书对象 441 * @param {Object} item - 计划书对象
429 */ 442 */
430 const onView = async (item) => { 443 const onView = async (item) => {
431 - // 检查是否已生成 444 + // 检查是否已生成(只有"已生成"或"已查看"状态才能查看)
432 - if (item.status === 'processing') { 445 + if (item.status === 'pending' || item.status === 'processing') {
446 + Taro.showToast({
447 + title: '计划书尚未生成,请稍后',
448 + icon: 'none'
449 + })
450 + return
451 + }
452 +
453 + // 检查是否有计划书文件
454 + if (!item.proposalFiles || item.proposalFiles.length === 0) {
433 Taro.showToast({ 455 Taro.showToast({
434 - title: '计划书生成中,请稍后', 456 + title: '暂无可查看的计划书',
435 icon: 'none' 457 icon: 'none'
436 }) 458 })
437 return 459 return
438 } 460 }
439 461
462 + /**
463 + * 处理文件查看
464 + * @param {Object} file - 文件对象
465 + */
466 + const handleFileView = async (file) => {
467 + try {
468 + // 调用 viewAPI 标记为已查看
469 + const viewRes = await viewAPI({ i: item.id })
470 +
471 + if (viewRes.code === 1) {
472 + // 更新本地状态为"已查看"
473 + item.status = 'viewed'
474 +
475 + // 显示提示
476 + Taro.showToast({
477 + title: '已标记为查看',
478 + icon: 'success',
479 + duration: 1000
480 + })
481 + }
482 + } catch (error) {
483 + console.error('标记查看失败:', error)
484 + // 即使标记失败,也继续查看文档
485 + }
486 +
440 // 使用 useFileOperation 查看文档 487 // 使用 useFileOperation 查看文档
441 await viewFile({ 488 await viewFile({
442 - downloadUrl: item.downloadUrl, 489 + downloadUrl: file.file_url,
443 - fileName: item.fileName 490 + fileName: file.file_name
491 + })
492 + }
493 +
494 + // 如果只有一个文件,直接查看
495 + if (item.proposalFiles.length === 1) {
496 + await handleFileView(item.proposalFiles[0])
497 + return
498 + }
499 +
500 + // 如果有多个文件,显示选择列表
501 + const fileList = item.proposalFiles.map((file, index) => ({
502 + text: file.file_name || `计划书 ${index + 1}`,
503 + file: file
504 + }))
505 +
506 + // 使用 Taro.showActionSheet 显示文件选择列表
507 + Taro.showActionSheet({
508 + itemList: fileList.map(f => f.text),
509 + success: async (res) => {
510 + const selectedIndex = res.tapIndex
511 + if (selectedIndex !== undefined && selectedIndex >= 0) {
512 + const selectedFile = fileList[selectedIndex].file
513 + await handleFileView(selectedFile)
514 + }
515 + }
444 }) 516 })
445 } 517 }
446 518
447 /** 519 /**
448 * 删除计划书 520 * 删除计划书
449 - * @description 注意:当前后端可能未提供删除接口,暂时只做前端提示 521 + * @description 调用删除API删除计划书
450 */ 522 */
451 -const onDelete = (item) => { 523 +const onDelete = async (item) => {
452 Taro.showModal({ 524 Taro.showModal({
453 - title: '提示', 525 + title: '确认删除',
454 - content: '删除功能需要后端支持,是否继续?', 526 + content: `确定要删除"${item.title}"吗?`,
455 - success: (res) => { 527 + success: async (res) => {
456 if (res.confirm) { 528 if (res.confirm) {
457 - // TODO: 调用删除 API 529 + try {
458 - // const res = await deletePlanAPI({ id: item.id }) 530 + // 调用删除API
459 - // if (res.code === 1) { 531 + const deleteRes = await deleteAPI({ i: item.id })
460 - // Taro.showToast({ title: '已删除', icon: 'success' })
461 - // // 重新加载列表
462 - // currentPage.value = 1
463 - // fetchPlanList(1, pageSize, false)
464 - // }
465 532
533 + if (deleteRes.code === 1) {
466 Taro.showToast({ 534 Taro.showToast({
467 - title: '删除功能待实现', 535 + title: '删除成功',
468 - icon: 'none', 536 + icon: 'success',
469 - duration: 2000 537 + duration: 1500
538 + })
539 +
540 + // 重新加载列表
541 + currentPage.value = 0
542 + hasMore.value = true
543 + await fetchPlanList(0, pageSize, false)
544 + } else {
545 + Taro.showToast({
546 + title: deleteRes.msg || '删除失败',
547 + icon: 'none'
548 + })
549 + }
550 + } catch (error) {
551 + console.error('删除失败:', error)
552 + Taro.showToast({
553 + title: '网络异常,请重试',
554 + icon: 'none'
470 }) 555 })
471 } 556 }
472 } 557 }
558 + }
473 }) 559 })
474 } 560 }
475 </script> 561 </script>
...@@ -585,13 +671,23 @@ const onDelete = (item) => { ...@@ -585,13 +671,23 @@ const onDelete = (item) => {
585 white-space: nowrap; 671 white-space: nowrap;
586 } 672 }
587 673
588 -.status-processing { 674 +.status-pending {
589 background-color: #FEF3C7; 675 background-color: #FEF3C7;
590 color: #D97706; 676 color: #D97706;
591 } 677 }
592 678
679 +.status-processing {
680 + background-color: #DBEAFE;
681 + color: #2563EB;
682 +}
683 +
593 .status-generated { 684 .status-generated {
594 background-color: #D1FAE5; 685 background-color: #D1FAE5;
595 color: #059669; 686 color: #059669;
596 } 687 }
688 +
689 +.status-viewed {
690 + background-color: #E5E7EB;
691 + color: #6B7280;
692 +}
597 </style> 693 </style>
......