Ember常用服务示例 #
一、认证服务 #
1.1 Session服务 #
javascript
// app/services/session.js
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
const TOKEN_KEY = 'auth_token';
const USER_KEY = 'current_user';
export default class SessionService extends Service {
@tracked isAuthenticated = false;
@tracked currentUser = null;
@tracked token = null;
@tracked isLoading = false;
constructor() {
super(...arguments);
this.restore();
}
restore() {
const token = localStorage.getItem(TOKEN_KEY);
const userJson = localStorage.getItem(USER_KEY);
if (token && userJson) {
this.token = token;
this.currentUser = JSON.parse(userJson);
this.isAuthenticated = true;
}
}
@action
async login(email, password) {
this.isLoading = true;
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || '登录失败');
}
const data = await response.json();
this.token = data.token;
this.currentUser = data.user;
this.isAuthenticated = true;
localStorage.setItem(TOKEN_KEY, data.token);
localStorage.setItem(USER_KEY, JSON.stringify(data.user));
return data;
} finally {
this.isLoading = false;
}
}
@action
async logout() {
try {
await fetch('/api/auth/logout', {
method: 'POST',
headers: this.headers,
});
} finally {
this.clearSession();
}
}
clearSession() {
this.token = null;
this.currentUser = null;
this.isAuthenticated = false;
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(USER_KEY);
}
get headers() {
const headers = {
'Content-Type': 'application/json',
};
if (this.token) {
headers.Authorization = `Bearer ${this.token}`;
}
return headers;
}
get isAdmin() {
return this.currentUser?.role === 'admin';
}
}
1.2 使用认证服务 #
javascript
// app/routes/application.js
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
export default class ApplicationRoute extends Route {
@service session;
beforeModel() {
if (this.session.isAuthenticated) {
this.session.restore();
}
}
}
javascript
// app/routes/admin.js
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
export default class AdminRoute extends Route {
@service session;
@service router;
beforeModel(transition) {
if (!this.session.isAuthenticated) {
this.session.set('attemptedTransition', transition);
this.router.transitionTo('login');
} else if (!this.session.isAdmin) {
this.router.transitionTo('forbidden');
}
}
}
二、主题服务 #
2.1 Theme服务 #
javascript
// app/services/theme.js
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
const THEME_KEY = 'app_theme';
export default class ThemeService extends Service {
@tracked current = 'light';
themes = ['light', 'dark', 'auto'];
constructor() {
super(...arguments);
this.loadTheme();
this.watchSystemTheme();
}
loadTheme() {
const stored = localStorage.getItem(THEME_KEY);
if (stored && this.themes.includes(stored)) {
this.current = stored;
}
this.applyTheme();
}
watchSystemTheme() {
if (window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', (e) => {
if (this.current === 'auto') {
this.applyTheme();
}
});
}
}
@action
setTheme(theme) {
if (this.themes.includes(theme)) {
this.current = theme;
localStorage.setItem(THEME_KEY, theme);
this.applyTheme();
}
}
@action
toggle() {
const newTheme = this.current === 'light' ? 'dark' : 'light';
this.setTheme(newTheme);
}
applyTheme() {
const effectiveTheme = this.effectiveTheme;
document.documentElement.setAttribute('data-theme', effectiveTheme);
}
get effectiveTheme() {
if (this.current === 'auto') {
return this.systemTheme;
}
return this.current;
}
get systemTheme() {
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
}
get isDark() {
return this.effectiveTheme === 'dark';
}
}
2.2 使用主题服务 #
handlebars
{{! app/components/theme-toggle.hbs}}
<button {{on "click" this.theme.toggle}}>
{{if this.theme.isDark "🌙" "☀️"}}
</button>
<select {{on "change" this.handleChange}}>
{{#each this.theme.themes as |theme|}}
<option value={{theme}} selected={{eq this.theme.current theme}}>
{{theme}}
</option>
{{/each}}
</select>
三、通知服务 #
3.1 Notification服务 #
javascript
// app/services/notifications.js
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { later, cancel } from '@ember/runloop';
export default class NotificationsService extends Service {
@tracked items = [];
defaultTimeout = 5000;
@action
add(message, options = {}) {
const notification = {
id: Date.now(),
message,
type: options.type || 'info',
timeout: options.timeout ?? this.defaultTimeout,
dismissible: options.dismissible ?? true,
timestamp: new Date(),
};
this.items = [...this.items, notification];
if (notification.timeout > 0) {
this.scheduleDismiss(notification);
}
return notification;
}
scheduleDismiss(notification) {
later(this, () => {
this.remove(notification.id);
}, notification.timeout);
}
@action
remove(id) {
this.items = this.items.filter((n) => n.id !== id);
}
@action
clear() {
this.items = [];
}
success(message, options = {}) {
return this.add(message, { ...options, type: 'success' });
}
error(message, options = {}) {
return this.add(message, { ...options, type: 'error' });
}
warning(message, options = {}) {
return this.add(message, { ...options, type: 'warning' });
}
info(message, options = {}) {
return this.add(message, { ...options, type: 'info' });
}
}
3.2 使用通知服务 #
javascript
// app/controllers/posts/new.js
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
export default class PostsNewController extends Controller {
@service notifications;
@service router;
@action
async savePost(post) {
try {
await post.save();
this.notifications.success('文章保存成功');
this.router.transitionTo('posts.show', post.id);
} catch (error) {
this.notifications.error('保存失败:' + error.message);
}
}
}
四、本地存储服务 #
4.1 Storage服务 #
javascript
// app/services/storage.js
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
export default class StorageService extends Service {
@tracked data = {};
constructor() {
super(...arguments);
this.load();
}
load() {
try {
const stored = localStorage.getItem('app_data');
if (stored) {
this.data = JSON.parse(stored);
}
} catch (e) {
console.error('Failed to load from storage:', e);
}
}
save() {
try {
localStorage.setItem('app_data', JSON.stringify(this.data));
} catch (e) {
console.error('Failed to save to storage:', e);
}
}
get(key, defaultValue = null) {
return this.data[key] ?? defaultValue;
}
set(key, value) {
this.data = { ...this.data, [key]: value };
this.save();
}
remove(key) {
const { [key]: _, ...rest } = this.data;
this.data = rest;
this.save();
}
clear() {
this.data = {};
localStorage.removeItem('app_data');
}
}
五、API服务 #
5.1 API服务 #
javascript
// app/services/api.js
import Service from '@ember/service';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
export default class ApiService extends Service {
@service session;
@service notifications;
baseUrl = '/api';
async request(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
const config = {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
};
if (this.session.isAuthenticated) {
config.headers.Authorization = `Bearer ${this.session.token}`;
}
if (options.body && typeof options.body === 'object') {
config.body = JSON.stringify(options.body);
}
try {
const response = await fetch(url, config);
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || '请求失败');
}
return await response.json();
} catch (error) {
this.notifications.error(error.message);
throw error;
}
}
get(endpoint, params = {}) {
const query = new URLSearchParams(params).toString();
const url = query ? `${endpoint}?${query}` : endpoint;
return this.request(url, { method: 'GET' });
}
post(endpoint, body = {}) {
return this.request(endpoint, { method: 'POST', body });
}
put(endpoint, body = {}) {
return this.request(endpoint, { method: 'PUT', body });
}
delete(endpoint) {
return this.request(endpoint, { method: 'DELETE' });
}
}
六、总结 #
常用服务模式:
| 服务 | 用途 |
|---|---|
| Session | 用户认证 |
| Theme | 主题管理 |
| Notifications | 消息通知 |
| Storage | 本地存储 |
| API | API请求 |
服务是Ember应用中管理共享状态和功能的最佳方式。
最后更新:2026-03-28