feat: add number library routing and cdr location support

This commit is contained in:
hectorzhao
2026-06-24 11:39:32 +08:00
parent 5fa1bd35e9
commit 7057fd3c42
63 changed files with 6361 additions and 566 deletions
+88 -1
View File
@@ -1,6 +1,18 @@
const API_BASE = (import.meta.env.VITE_LISGLOSIPS_API_BASE || '/api/v2').replace(/\/$/, '');
const ACCESS_TOKEN_KEY = 'lisglosips.accessToken';
export function getAccessToken() {
return window.localStorage.getItem(ACCESS_TOKEN_KEY);
}
export function setAccessToken(token) {
if (token) {
window.localStorage.setItem(ACCESS_TOKEN_KEY, token);
} else {
window.localStorage.removeItem(ACCESS_TOKEN_KEY);
}
}
export class ApiError extends Error {
constructor(message, { status, code } = {}) {
super(message);
@@ -55,12 +67,39 @@ function idempotencyKey(scope) {
return `${scope}:${random}`;
}
function queryString(params = {}) {
const entries = Object.entries(params).filter(([, value]) => value !== undefined && value !== null && value !== '' && value !== 'all');
return entries.length ? `?${new URLSearchParams(Object.fromEntries(entries))}` : '';
}
export const api = {
captcha: () => request('/auth/captcha'),
login: async (body) => {
const response = await request('/auth/login', { method: 'POST', body: jsonBody(body) });
setAccessToken(response.accessToken);
return response;
},
refresh: async () => {
const response = await request('/auth/refresh', { method: 'POST' });
setAccessToken(response.accessToken);
return response;
},
logout: async () => {
try {
await request('/auth/logout', { method: 'POST' });
} finally {
setAccessToken('');
}
},
dashboardSummary: () => request('/dashboard/summary'),
dashboardTrends: (params = { hours: 24, bucketMinutes: 60 }) => request(`/dashboard/trends?${new URLSearchParams(params)}`),
activeCalls: () => request('/active-calls'),
hangupActiveCall: (id) => request(`/active-calls/${encodeURIComponent(id)}/hangup`, { method: 'POST' }),
cdrs: (params = {}) => request(`/cdrs${queryString({ take: 100, ...params })}`),
customers: () => request('/customers'),
createCustomer: (body) => request('/customers', { method: 'POST', body: jsonBody(body) }),
updateCustomer: (id, body) => request(`/customers/${encodeURIComponent(id)}`, { method: 'PATCH', body: jsonBody(body) }),
deleteCustomer: (id) => request(`/customers/${encodeURIComponent(id)}`, { method: 'DELETE' }),
rechargeCustomer: (id, body) =>
request(`/customers/${encodeURIComponent(id)}/recharges`, {
method: 'POST',
@@ -69,20 +108,68 @@ export const api = {
vendors: () => request('/vendors'),
createVendor: (body) => request('/vendors', { method: 'POST', body: jsonBody(body) }),
updateVendor: (id, body) => request(`/vendors/${encodeURIComponent(id)}`, { method: 'PATCH', body: jsonBody(body) }),
deleteVendor: (id) => request(`/vendors/${encodeURIComponent(id)}`, { method: 'DELETE' }),
rechargeVendor: (id, body) =>
request(`/vendors/${encodeURIComponent(id)}/recharges`, {
method: 'POST',
body: jsonBody({ ...body, idempotencyKey: idempotencyKey('vendor-recharge') }),
}),
customerGateways: () => request('/customer-gateways'),
enableCustomerGateway: (id) => request(`/customer-gateways/${encodeURIComponent(id)}/enable`, { method: 'POST' }),
disableCustomerGateway: (id) => request(`/customer-gateways/${encodeURIComponent(id)}/disable`, { method: 'POST' }),
deleteCustomerGateway: (id) => request(`/customer-gateways/${encodeURIComponent(id)}`, { method: 'DELETE' }),
vendorGateways: () => request('/vendor-gateways'),
enableVendorGateway: (id) => request(`/vendor-gateways/${encodeURIComponent(id)}/enable`, { method: 'POST' }),
disableVendorGateway: (id) => request(`/vendor-gateways/${encodeURIComponent(id)}/disable`, { method: 'POST' }),
deleteVendorGateway: (id) => request(`/vendor-gateways/${encodeURIComponent(id)}`, { method: 'DELETE' }),
landingLineGroups: () => request('/landing-line-groups'),
deleteLandingLineGroup: (id) => request(`/landing-line-groups/${encodeURIComponent(id)}`, { method: 'DELETE' }),
recharges: () => request('/recharges?take=100'),
users: () => request('/users'),
deleteUser: (id) => request(`/users/${encodeURIComponent(id)}`, { method: 'DELETE' }),
roles: () => request('/roles'),
deleteRole: (id) => request(`/roles/${encodeURIComponent(id)}`, { method: 'DELETE' }),
auditLogs: () => request('/audit-logs?take=100'),
numberLibraryCities: (params = {}) => request(`/number-library/cities${queryString({ take: 100, ...params })}`),
importNumberLibraryCities: (items) => request('/number-library/cities/import', { method: 'POST', body: jsonBody({ items }) }),
numberLibraryPhoneSegments: (params = {}) => request(`/number-library/phone-segments${queryString({ take: 100, ...params })}`),
importNumberLibraryPhoneSegments: (items) => request('/number-library/phone-segments/import', { method: 'POST', body: jsonBody({ items }) }),
numberLibraryAreaCodes: (params = {}) => request(`/number-library/area-codes${queryString({ take: 100, ...params })}`),
importNumberLibraryAreaCodes: (items) => request('/number-library/area-codes/import', { method: 'POST', body: jsonBody({ items }) }),
numberLibraryCarrierPrefixRules: (params = {}) => request(`/number-library/carrier-prefix-rules${queryString({ take: 100, ...params })}`),
importNumberLibraryCarrierPrefixRules: (items) => request('/number-library/carrier-prefix-rules/import', { method: 'POST', body: jsonBody({ items }) }),
};
export function explainApiError(error) {
if (error instanceof ApiError && error.code === 'AUTH_CAPTCHA_INVALID') {
return '验证码错误或已过期,请重新输入。';
}
if (error instanceof ApiError && error.code === 'AUTH_INVALID_CREDENTIALS') {
return '用户名、密码或验证码不正确。';
}
if (error instanceof ApiError && error.code === 'CUSTOMER_HAS_GATEWAYS') {
return '该客户仍有关联客户网关,不能删除。';
}
if (error instanceof ApiError && error.code === 'VENDOR_HAS_GATEWAYS') {
return '该供应商仍有关联落地网关,不能删除。';
}
if (error instanceof ApiError && error.code === 'LINE_GROUP_IN_USE') {
return '该落地线路组仍被客户网关使用,不能删除。';
}
if (error instanceof ApiError && error.code === 'VENDOR_GATEWAY_IN_LINE_GROUP') {
return '该落地网关仍被落地线路组引用,不能删除。';
}
if (error instanceof ApiError && error.code === 'ROLE_HAS_USERS') {
return '该角色仍有关联用户,不能删除。';
}
if (error instanceof ApiError && error.code === 'BUILT_IN_ROLE_PROTECTED') {
return '系统内置角色受保护,不能删除或修改关键权限。';
}
if (error instanceof ApiError && (error.status === 401 || error.status === 403)) {
return 'API 已启用鉴权,请先通过登录接口获取会话或在同源环境使用有效 Cookie。';
return '登录状态已失效,请重新登录。';
}
if (error instanceof ApiError && error.status === 429) {
return '登录尝试过于频繁,请稍后再试。';
}
return error instanceof Error ? error.message : '请求失败,请稍后重试。';
}