diff --git a/internal/api/handler/app.go b/internal/api/handler/app.go index f3d6f96..0771f0a 100644 --- a/internal/api/handler/app.go +++ b/internal/api/handler/app.go @@ -16,7 +16,17 @@ func NewAppHandler(service *app.AppService) *AppHandler { } func (h *AppHandler) List(c *gin.Context) { - apps, err := h.service.List() + params := make(map[string]any) + + // 从查询参数中获取过滤条件 + if name := c.Query("name"); name != "" { + params["name"] = name + } + if description := c.Query("description"); description != "" { + params["description"] = description + } + + apps, err := h.service.List(params) if err != nil { response.FailServer(c, err.Error()) return diff --git a/internal/app/service.go b/internal/app/service.go index 35b3c6e..5ac8063 100644 --- a/internal/app/service.go +++ b/internal/app/service.go @@ -1,6 +1,10 @@ package app -import "gorm.io/gorm" +import ( + "nebula/pkg/util" + + "gorm.io/gorm" +) type AppService struct { db *gorm.DB @@ -10,9 +14,19 @@ func NewService(db *gorm.DB) *AppService { return &AppService{db: db} } -func (s *AppService) List() ([]App, error) { +func (s *AppService) List(params map[string]any) ([]App, error) { var apps []App - err := s.db.Find(&apps).Error + query := s.db + + // 只处理name和description参数,使用模糊查询 + if name, ok := params["name"]; ok { + query = query.Where("name LIKE ?", "%"+name.(string)+"%") + } + if description, ok := params["description"]; ok { + query = query.Where("description LIKE ?", "%"+description.(string)+"%") + } + + err := query.Order("updated_at desc").Find(&apps).Error return apps, err } @@ -26,6 +40,30 @@ func (s *AppService) Get(id string) (*App, error) { } func (s *AppService) Create(app App) error { + // 生成唯一的短ID作为应用ID + var appExists App + var err error + var id string + + for { + // 生成短ID + id = util.GenerateShortID() + + // 检查ID是否已存在 + err = s.db.First(&appExists, "id = ?", id).Error + if err != nil { + // 如果错误是记录未找到,说明ID可用 + if err == gorm.ErrRecordNotFound { + break + } + // 其他错误直接返回 + return err + } + // 如果ID已存在,继续循环生成新ID + } + + // 使用生成的唯一ID + app.ID = id return s.db.Create(&app).Error } diff --git a/pkg/util/identity.go b/pkg/util/identity.go new file mode 100644 index 0000000..2fd2bc5 --- /dev/null +++ b/pkg/util/identity.go @@ -0,0 +1,21 @@ +package util + +import ( + "math/rand" +) + +const ( + // 短ID字符集(纯数字) + shortIDChars = "0123456789" + // 短ID长度 + shortIDLength = 6 +) + +// GenerateShortID 生成短ID +func GenerateShortID() string { + b := make([]byte, shortIDLength) + for i := range b { + b[i] = shortIDChars[rand.Intn(len(shortIDChars))] + } + return string(b) +} diff --git a/web/src/App.vue b/web/src/App.vue index 7402409..1b6b86d 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -1,9 +1,12 @@ - @@ -11,20 +14,31 @@ import { zhCN, dateZhCN, - NConfigProvider + NConfigProvider, + NMessageProvider, + NDialogProvider } from 'naive-ui' - - diff --git a/web/src/api/app/index.ts b/web/src/api/app/index.ts new file mode 100644 index 0000000..b36a18b --- /dev/null +++ b/web/src/api/app/index.ts @@ -0,0 +1,48 @@ +import type { ApiResponse, App } from '@/types/api' +import { apiClient } from '@/api/client' + +export interface GetAppsParams { + name?: string + description?: string +} + +export interface CreateAppRequest { + name: string + description?: string +} + +export interface UpdateAppRequest { + name?: string + description?: string +} + +export const appApi = { + async getApps(params?: GetAppsParams): Promise> { + const queryString = params ? '?' + new URLSearchParams(params as Record).toString() : '' + return apiClient.request(`/apps${queryString}`) + }, + + async getApp(id: string): Promise> { + return apiClient.request(`/apps/${id}`) + }, + + async createApp(data: CreateAppRequest): Promise> { + return apiClient.request('/apps', { + method: 'POST', + body: JSON.stringify(data), + }) + }, + + async updateApp(id: string, data: UpdateAppRequest): Promise> { + return apiClient.request(`/apps/${id}`, { + method: 'PUT', + body: JSON.stringify(data), + }) + }, + + async deleteApp(id: string): Promise> { + return apiClient.request<{ message: string }>(`/apps/${id}`, { + method: 'DELETE', + }) + }, +} \ No newline at end of file diff --git a/web/src/api/auth/index.ts b/web/src/api/auth/index.ts new file mode 100644 index 0000000..7ef6f0b --- /dev/null +++ b/web/src/api/auth/index.ts @@ -0,0 +1,22 @@ +import type { ApiResponse, LoginRequest, LoginResponse, User } from '@/types/api' +import { apiClient } from '@/api/client' + +export const authApi = { + async login(data: LoginRequest): Promise> { + return apiClient.request('/auth/login', { + method: 'POST', + body: JSON.stringify(data), + }) + }, + + async getProfile(): Promise> { + return apiClient.request('/auth/profile') + }, + + async refreshToken(refreshToken: string): Promise> { + return apiClient.request<{ accessToken: string; refreshToken: string; expiresIn: number }>('/auth/refresh', { + method: 'POST', + body: JSON.stringify({ refreshToken }), + }) + }, +} \ No newline at end of file diff --git a/web/src/api/client.ts b/web/src/api/client.ts new file mode 100644 index 0000000..0acdb60 --- /dev/null +++ b/web/src/api/client.ts @@ -0,0 +1,29 @@ +import type { ApiResponse } from '@/types/api' + +const API_BASE = '/api' + +export class ApiClient { + private getAuthHeader(): HeadersInit { + const token = localStorage.getItem('accessToken') + return token ? { Authorization: `Bearer ${token}` } : {} + } + + async request(url: string, options: RequestInit = {}): Promise> { + const response = await fetch(`${API_BASE}${url}`, { + ...options, + headers: { + 'Content-Type': 'application/json', + ...this.getAuthHeader(), + ...options.headers, + }, + }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + return response.json() + } +} + +export const apiClient = new ApiClient() \ No newline at end of file diff --git a/web/src/api/index.ts b/web/src/api/index.ts new file mode 100644 index 0000000..ef96652 --- /dev/null +++ b/web/src/api/index.ts @@ -0,0 +1,3 @@ +export * from './auth' +export * from './app' +export * from './client' diff --git a/web/src/router/index.ts b/web/src/router/index.ts index 440d6c3..da5daa4 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -20,24 +20,27 @@ const router = createRouter({ name: 'home', component: () => import('@/view/home/index.vue'), }, + { + path: '/apps', + name: 'apps', + component: () => import('@/view/app/index.vue'), + } ], }, ], }) // 路由守卫 -router.beforeEach((to, from, next) => { +router.beforeEach((to, _from) => { const authStore = useAuthStore() const isAuthenticated = authStore.isAuthenticated() if (to.meta.requiresAuth && !isAuthenticated) { // 需要登录但未登录,跳转到登录页 - next('/login') + return '/login' } else if (to.path === '/login' && isAuthenticated) { // 已登录访问登录页,跳转到首页 - next('/') - } else { - next() + return '/' } }) diff --git a/web/src/types/api.ts b/web/src/types/api.ts index 5267e7b..6989423 100644 --- a/web/src/types/api.ts +++ b/web/src/types/api.ts @@ -29,3 +29,11 @@ export interface LoginResponse { user: User tokens: TokenPair } + +export interface App { + id: string + name: string + description: string + createdAt: string + updatedAt: string +} diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts deleted file mode 100644 index 23ad771..0000000 --- a/web/src/utils/api.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { ApiResponse, LoginRequest, LoginResponse } from '@/types/api' - -const API_BASE = '/api' - -class ApiClient { - private getAuthHeader(): HeadersInit { - const token = localStorage.getItem('accessToken') - return token ? { Authorization: `Bearer ${token}` } : {} - } - - async request(url: string, options: RequestInit = {}): Promise> { - const response = await fetch(`${API_BASE}${url}`, { - ...options, - headers: { - 'Content-Type': 'application/json', - ...this.getAuthHeader(), - ...options.headers, - }, - }) - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } - - return response.json() - } - - // 认证相关 - async login(data: LoginRequest): Promise> { - return this.request('/auth/login', { - method: 'POST', - body: JSON.stringify(data), - }) - } - - async getProfile(): Promise> { - return this.request('/auth/profile') - } - - async refreshToken(refreshToken: string): Promise> { - return this.request('/auth/refresh', { - method: 'POST', - body: JSON.stringify({ refreshToken }), - }) - } -} - -export const api = new ApiClient() diff --git a/web/src/view/app/index.vue b/web/src/view/app/index.vue new file mode 100644 index 0000000..ed88a91 --- /dev/null +++ b/web/src/view/app/index.vue @@ -0,0 +1,280 @@ + + + + + diff --git a/web/src/view/auth/login.vue b/web/src/view/auth/login.vue index dc470da..6982252 100644 --- a/web/src/view/auth/login.vue +++ b/web/src/view/auth/login.vue @@ -2,47 +2,62 @@
-
-
+
+
-
- +
-
@@ -124,20 +135,24 @@