feat: optimize email filtering with frontend-only search (#787)

* feat: optimize email filtering with frontend-only search

- Remove backend keyword parameter from mail APIs (breaking change)
- Implement frontend filtering on current page (20-100 items)
- Add message_id database index for UPDATE performance
- Support desktop and mobile responsive layouts
- Update API documentation and CHANGELOG

BREAKING CHANGE: /admin/mails and /user_api/mails no longer accept keyword parameter

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: restore Mail ID query input in Index.vue

- Keep showMailIdQuery UI input for querying specific mail by ID
- Triggered when URL contains mail_id parameter

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Dream Hunter
2025-12-15 02:55:50 +08:00
committed by GitHub
parent 1836f931ee
commit e5f62d4713
17 changed files with 489 additions and 546 deletions

View File

@@ -40,7 +40,7 @@
"devDependencies": {
"@vicons/fa": "^0.13.0",
"@vicons/material": "^0.13.0",
"@vitejs/plugin-vue": "^6.0.2",
"@vitejs/plugin-vue": "^6.0.3",
"unplugin-auto-import": "^20.3.0",
"unplugin-vue-components": "^30.0.0",
"vite": "^7.2.7",

182
frontend/pnpm-lock.yaml generated
View File

@@ -12,8 +12,8 @@ importers:
specifier: ^5.0.1
version: 5.0.1
'@simplewebauthn/browser':
specifier: 13.2.2
version: 13.2.2
specifier: 10.0.0
version: 10.0.0
'@unhead/vue':
specifier: ^2.0.19
version: 2.0.19(vue@3.5.25(typescript@5.4.5))
@@ -64,8 +64,8 @@ importers:
specifier: ^0.13.0
version: 0.13.0
'@vitejs/plugin-vue':
specifier: ^6.0.2
version: 6.0.2(vite@7.2.7(terser@5.44.1))(vue@3.5.25(typescript@5.4.5))
specifier: ^6.0.3
version: 6.0.3(vite@7.2.7(terser@5.44.1))(vue@3.5.25(typescript@5.4.5))
unplugin-auto-import:
specifier: ^20.3.0
version: 20.3.0(@vueuse/core@14.1.0(vue@3.5.25(typescript@5.4.5)))
@@ -1129,17 +1129,17 @@ packages:
'@juggle/resize-observer@3.4.0':
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
'@poppinss/colors@4.1.5':
resolution: {integrity: sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==}
'@poppinss/colors@4.1.6':
resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==}
'@poppinss/dumper@0.6.5':
resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==}
'@poppinss/exception@1.2.2':
resolution: {integrity: sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==}
'@poppinss/exception@1.2.3':
resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==}
'@rolldown/pluginutils@1.0.0-beta.50':
resolution: {integrity: sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==}
'@rolldown/pluginutils@1.0.0-beta.53':
resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==}
'@rollup/plugin-babel@5.3.1':
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
@@ -1309,8 +1309,12 @@ packages:
cpu: [x64]
os: [win32]
'@simplewebauthn/browser@13.2.2':
resolution: {integrity: sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==}
'@simplewebauthn/browser@10.0.0':
resolution: {integrity: sha512-hG0JMZD+LiLUbpQcAjS4d+t4gbprE/dLYop/CkE01ugU/9sKXflxV5s0DRjdz3uNMFecatRfb4ZLG3XvF8m5zg==}
'@simplewebauthn/types@10.0.0':
resolution: {integrity: sha512-SFXke7xkgPRowY2E+8djKbdEznTVnD5R6GO7GPTthpHrokLvNKw8C3lFZypTxLI7KkCfGPfhtqB3d7OVGGa9jQ==}
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
'@sindresorhus/is@7.1.1':
resolution: {integrity: sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ==}
@@ -1322,68 +1326,68 @@ packages:
'@surma/rollup-plugin-off-main-thread@2.2.3':
resolution: {integrity: sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==}
'@swc/core-darwin-arm64@1.15.3':
resolution: {integrity: sha512-AXfeQn0CvcQ4cndlIshETx6jrAM45oeUrK8YeEY6oUZU/qzz0Id0CyvlEywxkWVC81Ajpd8TQQ1fW5yx6zQWkQ==}
'@swc/core-darwin-arm64@1.15.4':
resolution: {integrity: sha512-NU/Of+ShFGG/i0lXKsF6GaGeTBNsr9iD8uUzdXxFfGbEjTeuKNXc5CWn3/Uo4Gr4LMAGD3hsRwG2Jq5iBDMalw==}
engines: {node: '>=10'}
cpu: [arm64]
os: [darwin]
'@swc/core-darwin-x64@1.15.3':
resolution: {integrity: sha512-p68OeCz1ui+MZYG4wmfJGvcsAcFYb6Sl25H9TxWl+GkBgmNimIiRdnypK9nBGlqMZAcxngNPtnG3kEMNnvoJ2A==}
'@swc/core-darwin-x64@1.15.4':
resolution: {integrity: sha512-9oWYMZHiEfHLqjjRGrXL17I8HdAOpWK/Rps34RKQ74O+eliygi1Iyq1TDUzYqUXcNvqN2K5fHgoMLRIni41ClQ==}
engines: {node: '>=10'}
cpu: [x64]
os: [darwin]
'@swc/core-linux-arm-gnueabihf@1.15.3':
resolution: {integrity: sha512-Nuj5iF4JteFgwrai97mUX+xUOl+rQRHqTvnvHMATL/l9xE6/TJfPBpd3hk/PVpClMXG3Uvk1MxUFOEzM1JrMYg==}
'@swc/core-linux-arm-gnueabihf@1.15.4':
resolution: {integrity: sha512-I1dPxXli3N1Vr71JXogUTLcspM5ICgCYaA16RE+JKchj3XKKmxLlYjwAHAA4lh/Cy486ikzACaG6pIBcegoGkg==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux]
'@swc/core-linux-arm64-gnu@1.15.3':
resolution: {integrity: sha512-2Nc/s8jE6mW2EjXWxO/lyQuLKShcmTrym2LRf5Ayp3ICEMX6HwFqB1EzDhwoMa2DcUgmnZIalesq2lG3krrUNw==}
'@swc/core-linux-arm64-gnu@1.15.4':
resolution: {integrity: sha512-iGpuS/2PDZ68ioAlhkxiN5M4+pB9uDJolTKk4mZ0JM29uFf9YIkiyk7Bbr2y1QtmD82rF0tDHhoG9jtnV8mZMg==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
'@swc/core-linux-arm64-musl@1.15.3':
resolution: {integrity: sha512-j4SJniZ/qaZ5g8op+p1G9K1z22s/EYGg1UXIb3+Cg4nsxEpF5uSIGEE4mHUfA70L0BR9wKT2QF/zv3vkhfpX4g==}
'@swc/core-linux-arm64-musl@1.15.4':
resolution: {integrity: sha512-Ly95wc+VXDhl08pjAoPUhVu5vNbuPMbURknRZa5QOZuiizJ6DkaSI0/zsEc26PpC6HTc4prNLY3ARVwZ7j/IJQ==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
'@swc/core-linux-x64-gnu@1.15.3':
resolution: {integrity: sha512-aKttAZnz8YB1VJwPQZtyU8Uk0BfMP63iDMkvjhJzRZVgySmqt/apWSdnoIcZlUoGheBrcqbMC17GGUmur7OT5A==}
'@swc/core-linux-x64-gnu@1.15.4':
resolution: {integrity: sha512-7pIG0BnaMn4zTpHeColPwyrWoTY9Drr+ISZQIgYHUKh3oaPtNCrXb289ScGbPPPjLsSfcGTeOy2pXmNczMC+yg==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
'@swc/core-linux-x64-musl@1.15.3':
resolution: {integrity: sha512-oe8FctPu1gnUsdtGJRO2rvOUIkkIIaHqsO9xxN0bTR7dFTlPTGi2Fhk1tnvXeyAvCPxLIcwD8phzKg6wLv9yug==}
'@swc/core-linux-x64-musl@1.15.4':
resolution: {integrity: sha512-oaqTV25V9H+PpSkvTcK25q6Q56FvXc6d2xBu486dv9LAPCHWgeAworE8WpBLV26g8rubcN5nGhO5HwSunXA7Ww==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
'@swc/core-win32-arm64-msvc@1.15.3':
resolution: {integrity: sha512-L9AjzP2ZQ/Xh58e0lTRMLvEDrcJpR7GwZqAtIeNLcTK7JVE+QineSyHp0kLkO1rttCHyCy0U74kDTj0dRz6raA==}
'@swc/core-win32-arm64-msvc@1.15.4':
resolution: {integrity: sha512-VcPuUJw27YbGo1HcOaAriI50dpM3ZZeDW3x2cMnJW6vtkeyzUFk1TADmTwFax0Fn+yicCxhaWjnFE3eAzGAxIQ==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
'@swc/core-win32-ia32-msvc@1.15.3':
resolution: {integrity: sha512-B8UtogMzErUPDWUoKONSVBdsgKYd58rRyv2sHJWKOIMCHfZ22FVXICR4O/VwIYtlnZ7ahERcjayBHDlBZpR0aw==}
'@swc/core-win32-ia32-msvc@1.15.4':
resolution: {integrity: sha512-dREjghAZEuKAK9nQzJETAiCSihSpAVS6Vk9+y2ElaoeTj68tNB1txV/m1RTPPD/+Kgbz6ITPNyXRWxPdkP5aXw==}
engines: {node: '>=10'}
cpu: [ia32]
os: [win32]
'@swc/core-win32-x64-msvc@1.15.3':
resolution: {integrity: sha512-SpZKMR9QBTecHeqpzJdYEfgw30Oo8b/Xl6rjSzBt1g0ZsXyy60KLXrp6IagQyfTYqNYE/caDvwtF2FPn7pomog==}
'@swc/core-win32-x64-msvc@1.15.4':
resolution: {integrity: sha512-o/odIBuQkoxKbRweJWOMI9LeRSOenFKN2zgPeaaNQ/cyuVk2r6DCAobKMOodvDdZWlMn6N1xJrldeCRSTZIgiQ==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
'@swc/core@1.15.3':
resolution: {integrity: sha512-Qd8eBPkUFL4eAONgGjycZXj1jFCBW8Fd+xF0PzdTlBCWQIV1xnUT7B93wUANtW3KGjl3TRcOyxwSx/u/jyKw/Q==}
'@swc/core@1.15.4':
resolution: {integrity: sha512-fH81BPo6EiJ7BUb6Qa5SY/NLWIRVambqU3740g0XPFPEz5KFPnzRYpR6zodQNOcEb9XUtZzRO1Y0WyIJP7iBxQ==}
engines: {node: '>=10'}
peerDependencies:
'@swc/helpers': '>=0.5.17'
@@ -1397,8 +1401,8 @@ packages:
'@swc/types@0.1.25':
resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
'@swc/wasm@1.15.3':
resolution: {integrity: sha512-NrjGmAplk+v4wokIaLxp1oLoCMVqdQcWoBXopQg57QqyPRcJXLKe+kg5ehhW6z8XaU4Bu5cRkDxUTDY5P0Zy9Q==}
'@swc/wasm@1.15.4':
resolution: {integrity: sha512-waPWVrijKwM8qpUEJGpnQRDlpNE4nQyRmPxjjbJ+5dpmua132o1Nd9f5KQQIgVQqAKSKshKMHskIec8ussNsUg==}
'@transloadit/prettier-bytes@0.0.7':
resolution: {integrity: sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==}
@@ -1458,11 +1462,11 @@ packages:
'@vicons/material@0.13.0':
resolution: {integrity: sha512-lKVxFNprM+CaBkUH3gt6VjIeiMsKQl2zARQMwTCZruQl2vRHzyeZiKeCflWS99CEfv2JzX/6y697smxlzyxcVw==}
'@vitejs/plugin-vue@6.0.2':
resolution: {integrity: sha512-iHmwV3QcVGGvSC1BG5bZ4z6iwa1SOpAPWmnjOErd4Ske+lZua5K9TtAVdx0gMBClJ28DViCbSmZitjWZsWO3LA==}
'@vitejs/plugin-vue@6.0.3':
resolution: {integrity: sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==}
engines: {node: ^20.19.0 || >=22.12.0}
peerDependencies:
vite: ^5.0.0 || ^6.0.0 || ^7.0.0
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
vue: ^3.2.25
'@vue/compiler-core@3.5.25':
@@ -1680,8 +1684,8 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
baseline-browser-mapping@2.9.6:
resolution: {integrity: sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==}
baseline-browser-mapping@2.9.7:
resolution: {integrity: sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==}
hasBin: true
blake3-wasm@2.1.5:
@@ -1871,8 +1875,8 @@ packages:
error-stack-parser-es@1.0.5:
resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==}
es-abstract@1.24.0:
resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==}
es-abstract@1.24.1:
resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==}
engines: {node: '>= 0.4'}
es-define-property@1.0.1:
@@ -2844,8 +2848,8 @@ packages:
resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==}
engines: {node: '>=4'}
unimport@5.5.0:
resolution: {integrity: sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg==}
unimport@5.6.0:
resolution: {integrity: sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A==}
engines: {node: '>=18.12.0'}
unique-string@2.0.0:
@@ -4132,19 +4136,19 @@ snapshots:
'@juggle/resize-observer@3.4.0': {}
'@poppinss/colors@4.1.5':
'@poppinss/colors@4.1.6':
dependencies:
kleur: 4.1.5
'@poppinss/dumper@0.6.5':
dependencies:
'@poppinss/colors': 4.1.5
'@poppinss/colors': 4.1.6
'@sindresorhus/is': 7.1.1
supports-color: 10.2.2
'@poppinss/exception@1.2.2': {}
'@poppinss/exception@1.2.3': {}
'@rolldown/pluginutils@1.0.0-beta.50': {}
'@rolldown/pluginutils@1.0.0-beta.53': {}
'@rollup/plugin-babel@5.3.1(@babel/core@7.28.5)(rollup@2.79.2)':
dependencies:
@@ -4264,7 +4268,11 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.53.3':
optional: true
'@simplewebauthn/browser@13.2.2': {}
'@simplewebauthn/browser@10.0.0':
dependencies:
'@simplewebauthn/types': 10.0.0
'@simplewebauthn/types@10.0.0': {}
'@sindresorhus/is@7.1.1': {}
@@ -4277,51 +4285,51 @@ snapshots:
magic-string: 0.25.9
string.prototype.matchall: 4.0.12
'@swc/core-darwin-arm64@1.15.3':
'@swc/core-darwin-arm64@1.15.4':
optional: true
'@swc/core-darwin-x64@1.15.3':
'@swc/core-darwin-x64@1.15.4':
optional: true
'@swc/core-linux-arm-gnueabihf@1.15.3':
'@swc/core-linux-arm-gnueabihf@1.15.4':
optional: true
'@swc/core-linux-arm64-gnu@1.15.3':
'@swc/core-linux-arm64-gnu@1.15.4':
optional: true
'@swc/core-linux-arm64-musl@1.15.3':
'@swc/core-linux-arm64-musl@1.15.4':
optional: true
'@swc/core-linux-x64-gnu@1.15.3':
'@swc/core-linux-x64-gnu@1.15.4':
optional: true
'@swc/core-linux-x64-musl@1.15.3':
'@swc/core-linux-x64-musl@1.15.4':
optional: true
'@swc/core-win32-arm64-msvc@1.15.3':
'@swc/core-win32-arm64-msvc@1.15.4':
optional: true
'@swc/core-win32-ia32-msvc@1.15.3':
'@swc/core-win32-ia32-msvc@1.15.4':
optional: true
'@swc/core-win32-x64-msvc@1.15.3':
'@swc/core-win32-x64-msvc@1.15.4':
optional: true
'@swc/core@1.15.3':
'@swc/core@1.15.4':
dependencies:
'@swc/counter': 0.1.3
'@swc/types': 0.1.25
optionalDependencies:
'@swc/core-darwin-arm64': 1.15.3
'@swc/core-darwin-x64': 1.15.3
'@swc/core-linux-arm-gnueabihf': 1.15.3
'@swc/core-linux-arm64-gnu': 1.15.3
'@swc/core-linux-arm64-musl': 1.15.3
'@swc/core-linux-x64-gnu': 1.15.3
'@swc/core-linux-x64-musl': 1.15.3
'@swc/core-win32-arm64-msvc': 1.15.3
'@swc/core-win32-ia32-msvc': 1.15.3
'@swc/core-win32-x64-msvc': 1.15.3
'@swc/core-darwin-arm64': 1.15.4
'@swc/core-darwin-x64': 1.15.4
'@swc/core-linux-arm-gnueabihf': 1.15.4
'@swc/core-linux-arm64-gnu': 1.15.4
'@swc/core-linux-arm64-musl': 1.15.4
'@swc/core-linux-x64-gnu': 1.15.4
'@swc/core-linux-x64-musl': 1.15.4
'@swc/core-win32-arm64-msvc': 1.15.4
'@swc/core-win32-ia32-msvc': 1.15.4
'@swc/core-win32-x64-msvc': 1.15.4
'@swc/counter@0.1.3': {}
@@ -4329,7 +4337,7 @@ snapshots:
dependencies:
'@swc/counter': 0.1.3
'@swc/wasm@1.15.3': {}
'@swc/wasm@1.15.4': {}
'@transloadit/prettier-bytes@0.0.7': {}
@@ -4392,9 +4400,9 @@ snapshots:
'@vicons/material@0.13.0': {}
'@vitejs/plugin-vue@6.0.2(vite@7.2.7(terser@5.44.1))(vue@3.5.25(typescript@5.4.5))':
'@vitejs/plugin-vue@6.0.3(vite@7.2.7(terser@5.44.1))(vue@3.5.25(typescript@5.4.5))':
dependencies:
'@rolldown/pluginutils': 1.0.0-beta.50
'@rolldown/pluginutils': 1.0.0-beta.53
vite: 7.2.7(terser@5.44.1)
vue: 3.5.25(typescript@5.4.5)
@@ -4608,7 +4616,7 @@ snapshots:
array-buffer-byte-length: 1.0.2
call-bind: 1.0.8
define-properties: 1.2.1
es-abstract: 1.24.0
es-abstract: 1.24.1
es-errors: 1.3.0
get-intrinsic: 1.3.0
is-array-buffer: 3.0.5
@@ -4661,7 +4669,7 @@ snapshots:
balanced-match@1.0.2: {}
baseline-browser-mapping@2.9.6: {}
baseline-browser-mapping@2.9.7: {}
blake3-wasm@2.1.5: {}
@@ -4671,7 +4679,7 @@ snapshots:
browserslist@4.28.1:
dependencies:
baseline-browser-mapping: 2.9.6
baseline-browser-mapping: 2.9.7
caniuse-lite: 1.0.30001760
electron-to-chromium: 1.5.267
node-releases: 2.0.27
@@ -4844,7 +4852,7 @@ snapshots:
error-stack-parser-es@1.0.5: {}
es-abstract@1.24.0:
es-abstract@1.24.1:
dependencies:
array-buffer-byte-length: 1.0.2
arraybuffer.prototype.slice: 1.0.4
@@ -5581,7 +5589,7 @@ snapshots:
dependencies:
call-bind: 1.0.8
define-properties: 1.2.1
es-abstract: 1.24.0
es-abstract: 1.24.1
es-errors: 1.3.0
es-object-atoms: 1.1.1
get-intrinsic: 1.3.0
@@ -5845,7 +5853,7 @@ snapshots:
call-bind: 1.0.8
call-bound: 1.0.4
define-properties: 1.2.1
es-abstract: 1.24.0
es-abstract: 1.24.1
es-errors: 1.3.0
es-object-atoms: 1.1.1
get-intrinsic: 1.3.0
@@ -5862,7 +5870,7 @@ snapshots:
call-bound: 1.0.4
define-data-property: 1.1.4
define-properties: 1.2.1
es-abstract: 1.24.0
es-abstract: 1.24.1
es-object-atoms: 1.1.1
has-property-descriptors: 1.0.2
@@ -6011,7 +6019,7 @@ snapshots:
unicode-property-aliases-ecmascript@2.2.0: {}
unimport@5.5.0:
unimport@5.6.0:
dependencies:
acorn: 8.15.0
escape-string-regexp: 5.0.0
@@ -6039,7 +6047,7 @@ snapshots:
local-pkg: 1.1.2
magic-string: 0.30.21
picomatch: 4.0.3
unimport: 5.5.0
unimport: 5.6.0
unplugin: 2.3.11
unplugin-utils: 0.3.1
optionalDependencies:
@@ -6104,8 +6112,8 @@ snapshots:
vite-plugin-top-level-await@1.6.0(rollup@2.79.2)(vite@7.2.7(terser@5.44.1)):
dependencies:
'@rollup/plugin-virtual': 3.0.2(rollup@2.79.2)
'@swc/core': 1.15.3
'@swc/wasm': 1.15.3
'@swc/core': 1.15.4
'@swc/wasm': 1.15.4
uuid: 10.0.0
vite: 7.2.7(terser@5.44.1)
transitivePeerDependencies:
@@ -6382,12 +6390,12 @@ snapshots:
youch-core@0.3.3:
dependencies:
'@poppinss/exception': 1.2.2
'@poppinss/exception': 1.2.3
error-stack-parser-es: 1.0.5
youch@4.1.0-beta.10:
dependencies:
'@poppinss/colors': 4.1.5
'@poppinss/colors': 4.1.6
'@poppinss/dumper': 0.6.5
'@speed-highlight/core': 1.2.12
cookie: 1.1.1

View File

@@ -49,20 +49,44 @@ const props = defineProps({
default: (mail_id, filename, blob) => { },
required: false
},
showFilterInput: {
type: Boolean,
default: false,
required: false
},
})
const localFilterKeyword = ref('')
const {
isDark, mailboxSplitSize, indexTab, loading, useUTCDate,
autoRefresh, configAutoRefreshInterval, sendMailModel
} = useGlobalState()
const autoRefreshInterval = ref(configAutoRefreshInterval.value)
const data = ref([])
const rawData = ref([])
const timer = ref(null)
const count = ref(0)
const page = ref(1)
const pageSize = ref(20)
// Computed property for filtered data (only filter current page)
const data = computed(() => {
if (!localFilterKeyword.value || localFilterKeyword.value.trim() === '') {
return rawData.value;
}
const keyword = localFilterKeyword.value.toLowerCase();
return rawData.value.filter(mail => {
// Search in subject, text, message fields
const searchFields = [
mail.subject || '',
mail.text || '',
mail.message || ''
].map(field => field.toLowerCase());
return searchFields.some(field => field.includes(keyword));
});
})
const canGoPrevMail = computed(() => {
if (!curMail.value) return false
const currentIndex = data.value.findIndex(mail => mail.id === curMail.value.id)
@@ -136,6 +160,8 @@ const { t } = useI18n({
unselectAll: 'Unselect All',
prevMail: 'Previous',
nextMail: 'Next',
keywordQueryTip: 'Filter current page',
query: 'Query',
},
zh: {
success: '成功',
@@ -158,6 +184,8 @@ const { t } = useI18n({
unselectAll: '取消全选',
prevMail: '上一封',
nextMail: '下一封',
keywordQueryTip: '过滤当前页',
query: '查询',
}
}
});
@@ -197,7 +225,7 @@ const refresh = async () => {
pageSize.value, (page.value - 1) * pageSize.value
);
loading.value = true;
data.value = await Promise.all(results.map(async (item) => {
rawData.value = await Promise.all(results.map(async (item) => {
item.checked = false;
return await processItem(item);
}));
@@ -370,7 +398,7 @@ onBeforeUnmount(() => {
<div>
<div v-if="!isMobile" class="left">
<div style="margin-bottom: 10px;">
<n-space v-if="multiActionMode">
<n-space v-if="multiActionMode" align="center">
<n-button @click="multiActionModeClick(false)" tertiary>
{{ t('cancelMultiAction') }}
</n-button>
@@ -393,7 +421,7 @@ onBeforeUnmount(() => {
{{ t('downloadMail') }}
</n-button>
</n-space>
<n-space v-else>
<n-space v-else align="center">
<n-button @click="multiActionModeClick(true)" type="primary" tertiary>
{{ t('multiAction') }}
</n-button>
@@ -410,6 +438,9 @@ onBeforeUnmount(() => {
<n-button @click="backFirstPageAndRefresh" type="primary" tertiary>
{{ t('refresh') }}
</n-button>
<n-input v-if="showFilterInput" v-model:value="localFilterKeyword"
:placeholder="t('keywordQueryTip')" style="width: 200px; display: flex; align-items: center;"
clearable />
</n-space>
</div>
<n-split class="left" direction="horizontal" :max="0.75" :min="0.25" :default-size="mailboxSplitSize"
@@ -482,10 +513,8 @@ onBeforeUnmount(() => {
</n-split>
</div>
<div class="left" v-else>
<n-space justify="center">
<div style="display: inline-block;">
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" simple size="small" />
</div>
<n-space justify="space-around" align="center" :wrap="false" style="display: flex; align-items: center;">
<n-pagination v-model:page="page" v-model:page-size="pageSize" :item-count="count" simple size="small" />
<n-switch v-model:value="autoRefresh" size="small" :round="false">
<template #checked>
{{ t('refreshAfter', { msg: autoRefreshInterval }) }}
@@ -498,6 +527,10 @@ onBeforeUnmount(() => {
{{ t('refresh') }}
</n-button>
</n-space>
<div v-if="showFilterInput" style="padding: 0 10px; margin-top: 8px;">
<n-input v-model:value="localFilterKeyword"
:placeholder="t('keywordQueryTip')" size="small" clearable />
</div>
<div style="overflow: auto; height: 80vh;">
<n-list hoverable clickable>
<n-list-item v-for="row in data" v-bind:key="row.id" @click="() => clickRow(row)">

View File

@@ -158,7 +158,7 @@ onMounted(() => {
</div>
<MailBox :key="mailBoxKey" :showEMailTo="false" :showReply="true" :showSaveS3="openSettings.isS3Enabled"
:saveToS3="saveToS3" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"
:fetchMailData="fetchMailData" :deleteMail="deleteMail" />
:fetchMailData="fetchMailData" :deleteMail="deleteMail" :showFilterInput="true" />
</n-tab-pane>
<n-tab-pane name="sendbox" :tab="t('sendbox')">
<SendBox :fetchMailData="fetchSenboxData" :enableUserDeleteEmail="openSettings.enableUserDeleteEmail"

View File

@@ -12,23 +12,19 @@ const { t } = useI18n({
messages: {
en: {
addressQueryTip: 'Leave blank to query all addresses',
keywordQueryTip: 'Leave blank to not query by keyword',
query: 'Query',
},
zh: {
addressQueryTip: '留空查询所有地址',
keywordQueryTip: '留空不按关键字查询',
query: '查询',
}
}
});
const mailBoxKey = ref("")
const mailKeyword = ref("")
const queryMail = () => {
adminMailTabAddress.value = adminMailTabAddress.value.trim();
mailKeyword.value = mailKeyword.value.trim();
mailBoxKey.value = Date.now();
}
@@ -38,7 +34,6 @@ const fetchMailData = async (limit, offset) => {
+ `?limit=${limit}`
+ `&offset=${offset}`
+ (adminMailTabAddress.value ? `&address=${adminMailTabAddress.value}` : '')
+ (mailKeyword.value ? `&keyword=${mailKeyword.value}` : '')
);
}
@@ -51,14 +46,13 @@ const deleteMail = async (curMailId) => {
<div style="margin-top: 10px;">
<n-input-group>
<n-input v-model:value="adminMailTabAddress" :placeholder="t('addressQueryTip')"
@keydown.enter="queryMail" />
<n-input v-model:value="mailKeyword" :placeholder="t('keywordQueryTip')" @keydown.enter="queryMail" />
@keydown.enter="queryMail" clearable />
<n-button @click="queryMail" type="primary" tertiary>
{{ t('query') }}
</n-button>
</n-input-group>
<div style="margin-top: 10px;"></div>
<MailBox :key="mailBoxKey" :enableUserDeleteEmail="true" :fetchMailData="fetchMailData"
:deleteMail="deleteMail" />
:deleteMail="deleteMail" :showFilterInput="true" />
</div>
</template>

View File

@@ -10,12 +10,10 @@ const { t } = useI18n({
messages: {
en: {
addressQueryTip: 'Leave blank to query all addresses',
keywordQueryTip: 'Leave blank to not query by keyword',
query: 'Query',
},
zh: {
addressQueryTip: '留空查询所有地址',
keywordQueryTip: '留空不按关键字查询',
query: '查询',
}
}
@@ -23,12 +21,10 @@ const { t } = useI18n({
const mailBoxKey = ref("")
const addressFilter = ref();
const mailKeyword = ref("")
const addressFilterOptions = ref([]);
const queryMail = () => {
addressFilter.value = addressFilter.value ? addressFilter.value.trim() : addressFilter.value;
mailKeyword.value = mailKeyword.value.trim();
mailBoxKey.value = Date.now();
}
@@ -38,7 +34,6 @@ const fetchMailData = async (limit, offset) => {
+ `?limit=${limit}`
+ `&offset=${offset}`
+ (addressFilter.value ? `&address=${addressFilter.value}` : '')
+ (mailKeyword.value ? `&keyword=${mailKeyword.value}` : '')
);
}
@@ -77,13 +72,12 @@ onMounted(() => {
<n-input-group>
<n-select v-model:value="addressFilter" :options="addressFilterOptions" clearable
:placeholder="t('addressQueryTip')" />
<n-input v-model:value="mailKeyword" :placeholder="t('keywordQueryTip')" @keydown.enter="queryMail" />
<n-button @click="queryMail" type="primary" tertiary>
{{ t('query') }}
</n-button>
</n-input-group>
<div style="margin-top: 10px;"></div>
<MailBox :key="mailBoxKey" :enableUserDeleteEmail="true" :fetchMailData="fetchMailData"
:deleteMail="deleteMail" />
:deleteMail="deleteMail" :showFilterInput="true" />
</div>
</template>