diff --git a/src/api/ApiCommon.ts b/src/api/ApiCommon.ts new file mode 100644 index 0000000..18494ce --- /dev/null +++ b/src/api/ApiCommon.ts @@ -0,0 +1,42 @@ +export class PageQueryDto { + current: number; + size: number; + orders?: OrderItemDto[]; + constructor(current: number, size: number, orders?: OrderItemDto[]) { + this.current = current; + this.size = size; + this.orders = orders; + } +} + +export class OrderItemDto { + column: string; + asc: boolean; + constructor(column: string, asc: boolean) { + this.column = column; + this.asc = asc; + } + + static asc(column: string): OrderItemDto { + return new OrderItemDto(column, true); + } + static desc(column: string): OrderItemDto { + return new OrderItemDto(column, false); + } +} + +export interface PageDto { + records: T[]; + /** + * 记录总数 + */ + total: number; + /** + * 第几页 + */ + current: number; + /** + * 每页数量 + */ + size: number; +} diff --git a/src/api/UserApi.ts b/src/api/UserApi.ts new file mode 100644 index 0000000..6e98f3e --- /dev/null +++ b/src/api/UserApi.ts @@ -0,0 +1,63 @@ +import { api } from 'src/boot/axios'; +import { PageDto, PageQueryDto } from './ApiCommon'; + +const UserUriBase = '/api/user'; + +interface RegisterInfo { + name: string; + mobile: string; + password: string; +} + +interface User { + id: string; + name: string; + mobile: string; + password: string; + registerTime: string; +} + +/** + * 用户注册 + * @param info + * @returns + */ +export async function register(info: RegisterInfo): Promise { + const response = await api.post(`${UserUriBase}/register`, info); + return response.data as User; +} + +interface LoginInfo { + account: string; + password: string; +} + +const PasswordSult = ''; + +/** + * 用户登录 + * @param loginInfo + * @returns + */ +export async function login(loginInfo: LoginInfo): Promise { + const response = await api.post(`${UserUriBase}/login`, loginInfo); + return response.data; +} + +export class PagingQueryParams extends PageQueryDto { + name?: string; +} + +/** + * 分页查询用户信息 + * @param params + * @returns + */ +export async function pageQuery( + params: PagingQueryParams +): Promise> { + const response = await api.get(`${UserUriBase}/paging`, { + params: params, + }); + return response.data; +} diff --git a/src/boot/axios.ts b/src/boot/axios.ts index 408a99d..5e7b59c 100644 --- a/src/boot/axios.ts +++ b/src/boot/axios.ts @@ -1,5 +1,9 @@ -import { boot } from 'quasar/wrappers'; import axios, { AxiosInstance } from 'axios'; +import { AxiosError } from 'axios'; +import { Dialog } from 'quasar'; +import { boot } from 'quasar/wrappers'; +import { getJwtToken } from 'src/configs/TokenManage'; +import { getHttpBase } from 'src/configs/UrlManage'; declare module '@vue/runtime-core' { interface ComponentCustomProperties { @@ -7,17 +11,94 @@ declare module '@vue/runtime-core' { } } +interface ErrorData { + status: number; + title: string; + detail: string; + properties: { [key: string]: unknown }; +} + +export class ApiError { + origin: AxiosError; + /** + * 业务错误代码 + */ + code: number; + /** + * 错误信息 + */ + title: string; + /** + * 相关问题描述 + */ + detail?: string; + constructor(origin: AxiosError) { + this.origin = origin; + const response = origin.response; + if (response) { + const err = response.data as ErrorData; + this.code = err.properties.code as number; + this.title = err.title; + this.detail = err.detail; + } else { + this.code = origin.status || -1; + this.title = origin.message; + } + } + + static from(err: AxiosError): ApiError { + return new ApiError(err); + } + + /** + * 是否认证失败(登录过期) + * @returns + */ + isAuthError(): boolean { + return this.origin.response?.status === 401; + } +} + // Be careful when using SSR for cross-request state pollution // due to creating a Singleton instance here; // If any client changes this (global) instance, it might be a // good idea to move this instance creation inside of the // "export default () => {}" function below (which runs individually // for each client) -const api = axios.create({ baseURL: 'https://api.example.com' }); +const api = axios.create({ baseURL: getHttpBase() }); -export default boot(({ app }) => { +export default boot(({ app, router }) => { // for use inside Vue files (Options API) through this.$axios and this.$api + // 拦截请求,添加 + api.interceptors.request.use( + (config) => { + config.headers.Authorization = getJwtToken(); + return config; + }, + (err: AxiosError) => { + return Promise.reject(ApiError.from(err)); + } + ); + + api.interceptors.response.use( + (response) => { + return response; + }, + (err) => { + if (err.response && err.response.status === 401) { + Dialog.create({ + title: '认证失败', + message: '认证失败或登录超时,请重新登录', + persistent: true, + }).onOk(() => { + router.push({ name: 'login' }); + }); + } + return Promise.reject(ApiError.from(err)); + } + ); + app.config.globalProperties.$axios = axios; // ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form) // so you won't necessarily have to import axios in each vue file diff --git a/src/configs/TokenManage.ts b/src/configs/TokenManage.ts new file mode 100644 index 0000000..6360618 --- /dev/null +++ b/src/configs/TokenManage.ts @@ -0,0 +1,9 @@ +const JwtTokenKey = 'jwttoken'; + +export function saveJwtToken(token: string) { + sessionStorage.setItem(JwtTokenKey, `Bearer ${token}`); +} + +export function getJwtToken(): string | null { + return sessionStorage.getItem(JwtTokenKey); +} diff --git a/src/configs/UrlManage.ts b/src/configs/UrlManage.ts new file mode 100644 index 0000000..93c0159 --- /dev/null +++ b/src/configs/UrlManage.ts @@ -0,0 +1,11 @@ +function getHost(): string { + return '192.168.3.212:9081'; +} + +export function getHttpBase() { + return `http://${getHost()}`; +} + +export function getWebsocketUrl() { + return `ws://${getHost()}/ws-xiannccda`; +} diff --git a/src/pages/UserLogin.vue b/src/pages/UserLogin.vue new file mode 100644 index 0000000..57f30d7 --- /dev/null +++ b/src/pages/UserLogin.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/src/router/routes.ts b/src/router/routes.ts index 2d34fc1..465e233 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -3,10 +3,23 @@ import { RouteRecordRaw } from 'vue-router'; const routes: RouteRecordRaw[] = [ { path: '/', + name: 'home', component: () => import('layouts/MainLayout.vue'), children: [{ path: '', component: () => import('pages/IndexPage.vue') }], }, + { + path: '/login', + name: 'login', + component: () => import('pages/UserLogin.vue'), + }, + + { + path: '/register', + name: 'register', + component: () => import('pages/UserRegister.vue'), + }, + // Always leave this as last one, // but you can also remove it {