封装message,统一处理http响应

This commit is contained in:
LittleBoy 2022-12-05 16:48:27 +08:00
parent 7bf0b5fb7f
commit 4f70b83f54
13 changed files with 204 additions and 20 deletions

View File

@ -5,7 +5,7 @@
"scripts": {
"start": "npm run dev",
"dev": "vite",
"build": "vue-tsc && vite build"
"build": "vite build"
},
"keywords": [],
"author": "",
@ -17,6 +17,7 @@
"vue-router": "^4.1.6"
},
"devDependencies": {
"@types/node": "^18.11.10",
"@vitejs/plugin-vue": "^3.2.0",
"typescript": "^4.9.3",
"vite": "^3.2.4",

View File

@ -1,16 +1,23 @@
<template>
<div>
<button @click="showMessage">显示消息</button>
<router-view/>
</div>
</template>
<script lang="ts">
import {ref} from "vue";
import message from "./components/message";
export default {
name: "App",
setup() {
return {}
return {
showMessage() {
message.info('test display')
}
}
}
}
</script>

View File

@ -13,7 +13,39 @@ body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 'Source Han Sans SC', 'Microsoft YaHei', 'Microsoft YaHei UI', "Helvetica Neue", sans-serif;
scroll-behavior: smooth;
}
.container{
.container {
max-width: 1200px;
margin: auto;
}
.message-wrapper {
box-sizing: border-box;
margin: 0;
padding: 0;
color: #000000d9;
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5715;
list-style: none;
font-feature-settings: "tnum";
position: fixed;
top: 8px;
left: 0;
z-index: 1010;
width: 100%;
pointer-events: none;
}
.message-notice {
padding: 8px;
text-align: center
}
.message-notice-content {
display: inline-block;
padding: 10px 16px;
background: #fff;
border-radius: 2px;
box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d;
pointer-events: all
}

View File

@ -0,0 +1,17 @@
<template>
<div class="message-notice">
<div class="message-notice-content">
{{ message }}
</div>
</div>
</template>
<script setup lang="ts">
defineProps<{
message: string
}>()
</script>
<style scoped>
</style>

View File

@ -0,0 +1,23 @@
import {createVNode, render} from 'vue'
import Toast from './Toast.vue'
export const toast = (message: string) => {
if (timer) clearTimeout(timer)
let div = document.querySelector('.message-wrapper');
if (!div) {
div = document.createElement('div');
div.classList.add("message-wrapper");
document.body.append(div);
}
// TODO 完善多消息同时共存 - 主要时添加新的容器
const messageNode = createVNode(Toast, {message})
render(messageNode, div)
// @ts-ignore
timer = setTimeout(() => {
div.removeChild(messageNode.el as Node)
}, 3000)
}
let timer = 0;
export default {
toast,
}

View File

@ -2,7 +2,7 @@ import {createApp} from 'vue';
import App from './App.vue'
import router from './router'
import {httpConfig} from "./util/http";
import './assets/app.css'
httpConfig.baseURL = "http://localhost:8080"

4
admin-fe/src/service/types.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
type AdminLoginModel = {
account: string
token: string
}

View File

@ -1,7 +1,18 @@
import {toast} from "../components/message";
export type HttpMethod = 'get' | 'post' | 'delete' | 'put'
export enum RequestDataContentType {
JSON = 'application/json;charset=UTF-8',
FORM = 'application/x-www-form-urlencoded;charset=UTF-8',
FORM_DATA = 'multipart/form-data;charset=UTF-8',
}
export const httpConfig = {
baseURL: ''
baseURL: '',
globalErrorHandler: {
1201: '请求参数不合法'
}
}
export type ResponseModel<T> = {
code: number
@ -12,27 +23,80 @@ export type ResponseModel<T> = {
class Http {
post<T>(url, data) {
return this.request<T>(url, 'post', data)
return this.request<T>(url, data)
}
request<T>(url: string, method: HttpMethod, data: any = null) {
return new Promise<ResponseModel<T>>((resolve, reject) => {
get<T>(url, data = null) {
return this.request<T>(url, data, 'get')
}
/**
* GET /xxxx?a=1&b=2
* NAME: value
* NAME2: value
* ....
*
* POSTDATA
*
*
* @param url
* @param method
* @param data
*/
request<T>(url: string, data: any = null, method: HttpMethod = 'post', type: 'normal' | 'form' = 'normal') {
return new Promise<T>((resolve, reject) => {
let contentType = RequestDataContentType.FORM;
if (data) {
if (type === 'form') {
const form = new FormData();
for (const key in data) {
form.append(key, data[key]);
}
data = form; // 将data有{} => formData
contentType = RequestDataContentType.FORM_DATA; // 由于使用formData
method = 'post';
} else if (method == 'post') {
// 将数据对象 转成json
data = JSON.stringify(data);
contentType = RequestDataContentType.JSON;
} else {
// 装对象转成 key=value&key=value
const params = [];
for (const key in data) {
params.push(`${key}=${data[key]}`);
}
data = params.join('&')
}
}
fetch(httpConfig.baseURL + url, {
method,
body: JSON.stringify(data),
body: data,
headers: {
'Content-Type': 'application/json;charset=utf-8'
'Content-Type': contentType
}
})
.then(res => res.json()) // 只要json的响应数据
.then((res: ResponseModel<T>) => {
if (res.code !== 0) {
const {code, data, message} = res;
if (code !== 0) {
const err = httpConfig.globalErrorHandler[code]
// 需要统一处理数据
if (code === 403) {
toast("登录凭证无效或者已过期")
return;
} else if (err) {
toast(httpConfig.globalErrorHandler[code])
return;
}
reject(Error(res.message))
return;
}
resolve(res.data)
}).catch(reject)
})
.catch(() => {
})
});
}
}

View File

@ -8,13 +8,14 @@
<input placeholder="请输入密码" type="text" v-model="data.password">
{{ data.password }}
</p>
<button type="submit">登录</button>
<div class="error">{{ err }}</div>
<button :disabled="loading" :type="loading?'button':'submit'">{{loading?'正在登录':'登录'}}</button>
</form>
</div>
</template>
<script lang="ts">
import {reactive} from "vue";
import {reactive, ref} from "vue";
import http from "../util/http";
export default {
@ -27,25 +28,36 @@ export default {
account: '',
password: ''
})
const loading = ref(false)
const err = ref()
function onLogin(e: Event) {
e.preventDefault()
http.post('/admin/user/login', data).then(result => {
loading.value = true
http.post<AdminLoginModel>('/admin/user/login', data).then(result => {
console.log(result)
}).catch(err => {
console.log(err)
}).catch(e => {
err.value = e.message
console.log(e)
}).finally(()=>{
loading.value = false
})
}
return {
data, onLogin
data, onLogin, err,loading
}
}
}
</script>
<style scoped>
.login-wrapper {
.error {
color: red;
}
.login-wrapper {
width: 500px;
margin: 50px auto;
}
</style>

13
admin-fe/tsconfig.json Normal file
View File

@ -0,0 +1,13 @@
{
"include": [
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue",
"vite.config.ts"
],
"exclude": [
"node_modules",
"dist",
"**/*.js"
]
}

View File

@ -1,6 +1,9 @@
import vue from '@vitejs/plugin-vue'
export default {
server: {
port: 10086
},
// 必须配置vue插件
plugins: [vue()]
}

View File

@ -17,6 +17,11 @@
resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.15.17.tgz#870daa61c257dfa0ee4f0ed71704d55e9af8e20c"
integrity sha512-IA1O7f7qxw2DX8oqTpugHElr926phs7Rq8ULXleBMk4go5K05BU0mI8BfCkWcYAvcmVaMc13bv5W3LIUlU6Y9w==
"@types/node@^18.11.10":
version "18.11.10"
resolved "https://registry.npmmirror.com/@types/node/-/node-18.11.10.tgz#4c64759f3c2343b7e6c4b9caf761c7a3a05cee34"
integrity sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==
"@vitejs/plugin-vue@^3.2.0":
version "3.2.0"
resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-3.2.0.tgz#a1484089dd85d6528f435743f84cdd0d215bbb54"

View File

@ -1,6 +1,7 @@
package me.xiaoyan.point.api.controller.admin;
import cn.dev33.satoken.stp.StpUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import me.xiaoyan.point.api.error.BizException;
import me.xiaoyan.point.api.pojo.UserInfo;
@ -33,8 +34,10 @@ public class UserAdminController {
userStoreMap.put("test", UserAdminInfo.create(2, "test", "123123"));
}
@SneakyThrows
@PostMapping("login")
public UserAdminInfo login(@Validated @RequestBody UserAdminInfo user) {
Thread.sleep(1);
// 判断是否存在账号
if (!userStoreMap.containsKey(user.getAccount())) {
throw BizException.create("账号不存在");