基础框架搭建
This commit is contained in:
parent
0fe6b2c393
commit
dd1137ba60
42
src/api/ApiCommon.ts
Normal file
42
src/api/ApiCommon.ts
Normal file
@ -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<T = unknown> {
|
||||
records: T[];
|
||||
/**
|
||||
* 记录总数
|
||||
*/
|
||||
total: number;
|
||||
/**
|
||||
* 第几页
|
||||
*/
|
||||
current: number;
|
||||
/**
|
||||
* 每页数量
|
||||
*/
|
||||
size: number;
|
||||
}
|
63
src/api/UserApi.ts
Normal file
63
src/api/UserApi.ts
Normal file
@ -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<User> {
|
||||
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<string> {
|
||||
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<PageDto<User>> {
|
||||
const response = await api.get(`${UserUriBase}/paging`, {
|
||||
params: params,
|
||||
});
|
||||
return response.data;
|
||||
}
|
@ -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<unknown, unknown>) {
|
||||
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<unknown, unknown>): 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
|
||||
|
9
src/configs/TokenManage.ts
Normal file
9
src/configs/TokenManage.ts
Normal file
@ -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);
|
||||
}
|
11
src/configs/UrlManage.ts
Normal file
11
src/configs/UrlManage.ts
Normal file
@ -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`;
|
||||
}
|
68
src/pages/UserLogin.vue
Normal file
68
src/pages/UserLogin.vue
Normal file
@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<q-layout>
|
||||
<q-page-container>
|
||||
<q-page class="flex bg-image flex-center">
|
||||
<q-card
|
||||
v-bind:style="$q.screen.lt.sm ? { width: '80%' } : { width: '30%' }"
|
||||
style="min-width: 350px"
|
||||
>
|
||||
<q-card-section>
|
||||
<q-avatar size="100px" class="absolute-center shadow-10">
|
||||
<img src="icons/favicon-96x96.png" />
|
||||
</q-avatar>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="text-center q-pt-lg">
|
||||
<div class="col text-h6 ellipsis">登录</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<q-form @submit="doLogin" class="q-gutter-md">
|
||||
<q-input
|
||||
filled
|
||||
v-model="loginInfo.account"
|
||||
label="Username"
|
||||
lazy-rules
|
||||
/>
|
||||
|
||||
<q-input
|
||||
type="password"
|
||||
filled
|
||||
v-model="loginInfo.password"
|
||||
label="Password"
|
||||
lazy-rules
|
||||
/>
|
||||
|
||||
<div class="text-right">
|
||||
<q-btn flat label="注册" to="/register" color="primary" />
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<q-btn label="Login" type="submit" color="primary" />
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue';
|
||||
|
||||
const loginInfo = reactive({
|
||||
account: '',
|
||||
password: '',
|
||||
});
|
||||
|
||||
function doLogin() {
|
||||
console.log(loginInfo);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.bg-image {
|
||||
background-image: linear-gradient(135deg, #5481fd 0%, #0e02b1 80%);
|
||||
}
|
||||
</style>
|
@ -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
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user