feat(webui): add Tailwind build system and refactor frontend architecture

- Replace Tailwind CDN with local build (PostCSS + autoprefixer + daisyui)

- Add CSS build scripts with automatic prepare hook on npm install

- Create account-actions.js service layer with unified response format

- Extend ErrorHandler.withLoading() for automatic loading state management

- Add skeleton screens for initial load, silent refresh for subsequent updates

- Implement loading animations for async operations (buttons, modals)

- Improve empty states and add ARIA labels for accessibility

- Abstract component styles using @apply (buttons, badges, inputs)

- Add JSDoc documentation for Dashboard modules

- Update README and CLAUDE.md with development guidelines
This commit is contained in:
Wha1eChai
2026-01-11 02:11:35 +08:00
parent ee6d222e4d
commit a56bc06cc1
22 changed files with 2730 additions and 499 deletions

View File

@@ -105,3 +105,41 @@ window.ErrorHandler.apiCall = async function(apiCall, successMessage = null, err
return result;
};
/**
* Execute an async function with automatic loading state management
* @param {Function} asyncFn - Async function to execute
* @param {object} context - Component context (this) that contains the loading state
* @param {string} loadingKey - Name of the loading state property (default: 'loading')
* @param {object} options - Additional options (same as safeAsync)
* @returns {Promise<any>} Result of the function or undefined on error
*
* @example
* // In your Alpine component:
* async refreshAccount(email) {
* return await window.ErrorHandler.withLoading(async () => {
* const response = await window.utils.request(`/api/accounts/${email}/refresh`, { method: 'POST' });
* this.$store.global.showToast('Account refreshed', 'success');
* return response;
* }, this, 'refreshing');
* }
*
* // In HTML:
* // <button @click="refreshAccount(email)" :disabled="refreshing">
* // <i class="fas fa-sync-alt" :class="{ 'fa-spin': refreshing }"></i>
* // Refresh
* // </button>
*/
window.ErrorHandler.withLoading = async function(asyncFn, context, loadingKey = 'loading', options = {}) {
// Set loading state to true
context[loadingKey] = true;
try {
// Execute the async function with error handling
const result = await window.ErrorHandler.safeAsync(asyncFn, options.errorMessage, options);
return result;
} finally {
// Always reset loading state, even if there was an error
context[loadingKey] = false;
}
};