From 000e6a23cf0a43f7c2e68ac885604577a9d17fe4 Mon Sep 17 00:00:00 2001
From: dai0v0 <dai.0v0@qq.com>
Date: Tue, 28 Jan 2025 02:02:59 +0800
Subject: [PATCH] feat: enhance student list sorting and filtering

- Implement advanced birthday sorting
- Refactor data processing logic in App.vue for more efficient filtering and sorting
---
 src/App.vue                           | 73 +++++++++++++--------------
 src/assets/requestUtils/dateFormat.ts | 57 ++++++++++++++++++++-
 src/assets/utils/search.ts            |  3 +-
 3 files changed, 93 insertions(+), 40 deletions(-)

diff --git a/src/App.vue b/src/App.vue
index ddb7103..64f5325 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -119,10 +119,11 @@ import { RouterLink, RouterView } from 'vue-router'
 import i18n from '@/locales/i18n'
 import { baseStudent, studentInfo } from '@/assets/requestUtils/interface'
 import { getStudents, getSchoolIcon } from '@/assets/requestUtils/request'
+import { birthday_sort, SupportedLanguage } from '@/assets/requestUtils/dateFormat'
 import { download } from '@/assets/imgUtils/download'
 import { store } from '@/assets/storeUtils/store'
 import { talkHistory } from '@/assets/storeUtils/talkHistory'
-import { search, debounce } from '@/assets/utils/search'
+import { debounce, search } from '@/assets/utils/search'
 import Popper from 'vue3-popper'
 
 store.getData()
@@ -139,10 +140,10 @@ const dataDisplay = ref<studentInfo[]>(database.value)
 const showPopper = ref<boolean>(false)
 const filter_condition = ref(
     {
-        sort_type: '',                    // 排序类型 名字 生日 学校 社团
-        sort_asc: true,                   // 排序顺序 true 升序 false 降序
-        filter_star: 0,                   // 稀有度   0 1 2 3
-        filter_released: true,            // 已实装 true false
+        sort_type: '',          // 排序类型 名字 生日 学校 社团
+        sort_asc: true,         // 排序顺序 true 升序 false 降序
+        filter_star: 0,         // 稀有度   0 1 2 3
+        filter_released: true,  // 已实装 true false
     }
 )
 const filter_condition_copy = ref(filter_condition.value)
@@ -161,40 +162,36 @@ const popperConfirm = () => {
 const searchText = ref<string>('')
 const searchSchool = ref<string>('')
 
-const dataFilter = () => {
-    dataDisplay.value = database.value.filter((item) => {
-        if (filter_condition.value.filter_star > 0 && item.Star !== filter_condition.value.filter_star) return false
-        if (item.Released !== filter_condition.value.filter_released) return false
-        return true
-    })
-}
-const dataSort = () => {
-    if (filter_condition.value.sort_type === '') {
-        if (!filter_condition.value.sort_asc) dataDisplay.value = dataDisplay.value.reverse()
-        return
-    }
-    dataDisplay.value = dataDisplay.value.sort((a, b) => {
-        // TODO: if sort_type == "Birthday" 
-        // student who has birthday today(or in 3 days) will be at the top, and her school will be show as a birthday cake icon
-        const aValue = a[filter_condition.value.sort_type as keyof studentInfo] as string
-        const bValue = b[filter_condition.value.sort_type as keyof studentInfo] as string
-        return filter_condition.value.sort_asc ?
-            aValue.localeCompare(bValue) :
-            bValue.localeCompare(aValue)
-    })
-}
-const dataSearch = debounce(() => {
-    dataDisplay.value = search(
-        dataDisplay.value,
-        searchText.value,
-        searchSchool.value
-    )
+const processData = debounce(() => {
+    dataDisplay.value = database.value
+        // filter
+        .filter(item => {
+            if (filter_condition.value.filter_star > 0 && item.Star !== filter_condition.value.filter_star) return false
+            if (item.Released !== filter_condition.value.filter_released) return false
+            return true
+        })
+        // search
+        .filter(item => search([item], searchText.value, searchSchool.value).length > 0)
+        // sort
+        .sort((a, b) => {
+            if (filter_condition.value.sort_type === '') {
+                return filter_condition.value.sort_asc ? 0 : -1
+            }
+
+            if (filter_condition.value.sort_type === 'Birthday') {
+                return filter_condition.value.sort_asc ?
+                    birthday_sort(a, b, store.language as SupportedLanguage) :
+                    birthday_sort(b, a, store.language as SupportedLanguage)
+            }
+
+            const aValue = a[filter_condition.value.sort_type as keyof studentInfo] as string
+            const bValue = b[filter_condition.value.sort_type as keyof studentInfo] as string
+            return filter_condition.value.sort_asc ?
+                aValue.localeCompare(bValue) :
+                bValue.localeCompare(aValue)
+        })
 }, 300) // 防抖
-const processData = () => {
-    dataFilter()
-    dataSearch()
-    dataSort()
-}
+
 processData()
 watch(filter_condition, () => {
     processData()
diff --git a/src/assets/requestUtils/dateFormat.ts b/src/assets/requestUtils/dateFormat.ts
index 0d6e55a..483ee2b 100644
--- a/src/assets/requestUtils/dateFormat.ts
+++ b/src/assets/requestUtils/dateFormat.ts
@@ -17,7 +17,7 @@ const getOrdinalSuffix = (day: number): string => {
 }
 
 export const dateFormat = (birthday: string, lng: SupportedLanguage) => {
-    if (!birthday) return '???'
+    if (!birthday || !/^\d+\/\d+$/.test(birthday)) return '???'
     const TOOL = {
         zh: (month: number, day: number) => `${month}月${day}日`,
         tw: (month: number, day: number) => `${month}月${day}日`,
@@ -29,3 +29,58 @@ export const dateFormat = (birthday: string, lng: SupportedLanguage) => {
     const [month, day] = birthday.split('/').map(Number)
     return TOOL[lng](month, day)
 }
+
+export const dateFormatReverse = (date: string, lng: SupportedLanguage): string => {
+    if (!date) return ''
+
+    const parseDate = {
+        zh: (str: string) => {
+            const nums = str.match(/\d+/g)?.map(Number)
+            return nums?.length === 2 ? nums : null
+        },
+        tw: (str: string) => parseDate.zh(str),
+        jp: (str: string) => parseDate.zh(str),
+        kr: (str: string) => parseDate.zh(str),
+        en: (str: string) => {
+            const monthName = MONTHS_EN.find(m => str.toLowerCase().includes(m.toLowerCase()))
+            if (!monthName) return null
+            const month = MONTHS_EN.indexOf(monthName) + 1
+            const day = parseInt(str.match(/\d+/)?.[0] || '')
+            return [month, day]
+        }
+    }
+
+    const [month, day] = parseDate[lng](date) || []
+
+    if (!month || !day || month < 1 || month > 12 || day < 1 || day > 31) {
+        return ''
+    }
+
+    return `${month}/${day}`
+}
+
+export const birthday_sort = (a: any, b: any, lng: SupportedLanguage) => {
+    // 将 MM/DD 格式转换为今年的日期以进行比较
+    const today = new Date()
+    const currentYear = today.getFullYear()
+    
+    const getDateFromBirthday = (birthday: string) => {
+        birthday = dateFormatReverse(birthday, lng)
+        if (!birthday) {
+            const yesterday = new Date()
+            yesterday.setDate(yesterday.getDate() - 1)
+            return yesterday
+        }
+        const [month, day] = birthday.split('/').map(Number)
+        return new Date(currentYear, month - 1, day - 1)
+    }
+
+    const aDate = getDateFromBirthday(a.Birthday)
+    const bDate = getDateFromBirthday(b.Birthday)
+    
+    // 如果日期已经过了,使用明年的日期
+    if (aDate < today) aDate.setFullYear(currentYear + 1)
+    if (bDate < today) bDate.setFullYear(currentYear + 1)
+
+    return aDate.getTime() - bDate.getTime()
+}
\ No newline at end of file
diff --git a/src/assets/utils/search.ts b/src/assets/utils/search.ts
index 5ff97a3..3574fd0 100644
--- a/src/assets/utils/search.ts
+++ b/src/assets/utils/search.ts
@@ -19,7 +19,8 @@ const search = (data: studentInfo[], key: string, filter: string) => {
     if (!processedKey && !processedFilter) return data
     
     return data.filter(item => {
-        if (!processedKey) return processString(item.School).includes(processedFilter)
+        if (processedFilter && !processString(item.School).includes(processedFilter)) return false
+        if (!processedKey) return true
 
         // 检查名字(简体、繁体、拼音)
         const name = processString(item.Name)