build(deps): 添加smooth-scroll插件、axios封装

This commit is contained in:
Soybean
2021-05-29 03:02:15 +08:00
parent afd4d04110
commit 82411cc5eb
29 changed files with 504 additions and 24 deletions

View File

@@ -1,19 +1,6 @@
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Hello Vue 3 + TypeScript + Vite" />
<div></div>
</template>
<script lang="ts" setup>
import HelloWorld from './components/HelloWorld.vue';
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
<script lang="ts" setup></script>
<style></style>

View File

@@ -1,7 +1,10 @@
import { createApp } from 'vue';
import App from './App.vue';
import { setupElementPlus } from './plugins';
import { setupSmoothScroll, setupElementPlus } from './plugins';
import 'virtual:windi.css';
import './styles/css/global.css';
setupSmoothScroll();
const app = createApp(App);
setupElementPlus(app);

View File

@@ -1,5 +1,7 @@
import type { App } from 'vue';
import 'element-plus/lib/theme-chalk/base.css';
import lang from 'element-plus/lib/locale/lang/zh-cn';
import 'dayjs/locale/zh-cn';
import {
// ElAlert,
// ElAside,
@@ -26,7 +28,7 @@ import {
ElCollapseTransition,
// ElColorPicker,
// ElContainer,
// ElDatePicker,
ElDatePicker,
// ElDialog,
// ElDivider,
// ElDrawer,
@@ -81,10 +83,11 @@ import {
// ElTree,
// ElUpload,
// ElInfiniteScroll,
ElLoading
ElLoading,
// ElMessage
// ElMessageBox,
// ElNotification
locale
} from 'element-plus';
const components = [
@@ -113,7 +116,7 @@ const components = [
ElCollapseTransition,
// ElColorPicker,
// ElContainer,
// ElDatePicker,
ElDatePicker,
// ElDialog,
// ElDivider,
// ElDrawer,
@@ -179,6 +182,8 @@ const plugins = [
/** 引入element-plus UI组件 */
export default function setupElementPlus(app: App<Element>) {
/** 国际化 */
locale(lang);
components.forEach(component => {
app.component(component.name, component);
});

View File

@@ -1,4 +1,5 @@
import setupSmoothScroll from './smooth-scroll';
import setupElementPlus from './element-plus';
import NProgress from './nprogress';
export { setupElementPlus, NProgress };
export { setupSmoothScroll, setupElementPlus, NProgress };

View File

@@ -0,0 +1,6 @@
import smoothscroll from 'smoothscroll-polyfill';
/** 平滑滚动插件(兼容主流浏览器) */
export default function setupSmoothScroll() {
smoothscroll.polyfill();
}

0
src/service/api/auth.ts Normal file
View File

0
src/service/index.ts Normal file
View File

View File

View File

@@ -0,0 +1,8 @@
/** 请求超时时间 */
export const REQUEST_TIMEOUT = 15 * 1000;
/** 请求头的content-type类型 */
export enum ContentType {
json = 'application/json',
formUrlEncoded = 'application/x-www-form-urlencoded'
}

View File

@@ -0,0 +1,77 @@
import { ElMessage } from 'element-plus';
const ERROR_STATUS = {
400: '400: 请求出现语法错误',
401: '401: 用户未授权~',
403: '403: 服务器拒绝访问~',
404: '404: 请求的资源不存在~',
405: '405: 请求方法未允许~',
408: '408: 网络请求超时~',
500: '500: 服务器内部错误~',
501: '501: 服务器未实现请求功能~',
502: '502: 错误网关~',
503: '503: 服务不可用~',
504: '504: 网关超时~',
505: '505: http版本不支持该请求~'
};
type ErrorStatus = 400 | 401 | 403 | 404 | 405 | 408 | 500 | 501 | 502 | 503 | 504 | 505;
/** 错误信息显示时间 */
export const errorDuration = 3000 / 1000;
/**
* 网络请求错误状态处理
* @param error - 错误
*/
export function errorHandler(error: any) {
if (error.response) {
const status = error.response.status as ErrorStatus;
ElMessage.error(ERROR_STATUS[status]);
return;
}
if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) {
ElMessage.error('网络连接超时~');
return;
}
if (!window.navigator.onLine || error.message === 'Network Error') {
ElMessage.error('网络不可用~');
return;
}
ElMessage.error('未知错误~');
}
/**
* 连续的请求错误依此显示
* @param duration - 上一次弹出错误消息到下一次的时间(ms)
*/
export function continuousErrorHandler(duration: number) {
let errorStacks: string[] = [];
function pushError(id: string) {
errorStacks.push(id);
}
function removeError(id: string) {
errorStacks = errorStacks.filter(item => item !== id);
}
function handleError(id: string, callback: Function) {
callback();
setTimeout(() => {
removeError(id);
}, duration);
}
function handleContinuousError(callback: Function) {
const id = Date.now().toString(36);
const { length } = errorStacks;
if (length > 0) {
pushError(id);
setTimeout(() => {
handleError(id, callback);
}, duration * length);
} else {
pushError(id);
handleError(id, callback);
}
}
return handleContinuousError;
}

View File

@@ -0,0 +1,11 @@
import { createRequest } from './request';
import { REQUEST_TIMEOUT, ContentType } from './config';
export { handleResponse } from './request';
export { ContentType };
// emoss-admin
export const adminRequest = createRequest({
baseURL: import.meta.env.VITE_HTTP_URL_EMOSS_ADMIN as string,
timeout: REQUEST_TIMEOUT
});

View File

@@ -0,0 +1,77 @@
import axios from 'axios';
import qs from 'qs';
import { getStorageToken } from '@/utils';
import { ElMessage } from 'element-plus';
import type { AxiosRequestConfig, AxiosInstance } from 'axios';
import { errorHandler } from './errorHandler';
export interface StatusConfig {
/** 表明请求状态的属性key */
statusKey: string;
/** 请求信息的属性key */
msgKey: string;
/** 成功状态的状态码 */
successCode: string | number;
}
/**
* 封装axios请求类
* @author Soybean(曹理斌) 2021-03-13
* @class CustomAxiosInstance
*/
export default class CustomAxiosInstance {
instance: AxiosInstance;
constructor(
axiosConfig: AxiosRequestConfig,
statusConfig: StatusConfig = {
statusKey: 'code',
msgKey: 'message',
successCode: 200
}
) {
this.instance = axios.create(axiosConfig);
this.setInterceptor(statusConfig);
}
/** 设置请求拦截器 */
setInterceptor(statusConfig: StatusConfig) {
this.instance.interceptors.request.use(
config => {
const handleConfig = { ...config };
// content-type为application/x-www-form-urlencoded类型的data参数需要序列化
if (handleConfig.headers['Content-Type'] === 'application/x-www-form-urlencoded') {
handleConfig.data = qs.stringify(handleConfig.data);
}
// 设置token
handleConfig.headers.Authorization = getStorageToken();
return handleConfig;
},
error => {
errorHandler(error);
return Promise.reject(error);
}
);
this.instance.interceptors.response.use(
response => {
const { status, data } = response;
const { statusKey, msgKey, successCode } = statusConfig;
if (status === 200 || status < 300 || status === 304) {
if (data[statusKey] === successCode) {
return Promise.resolve(data.data);
}
ElMessage.error(data[msgKey]);
return Promise.reject(data[msgKey]);
}
const error = { response };
errorHandler(error);
return Promise.reject(error);
},
error => {
errorHandler(error);
return Promise.reject(error);
}
);
}
}

View File

@@ -0,0 +1,66 @@
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios';
import CustomAxiosInstance from './instance';
import type { StatusConfig } from './instance';
type ResponseSuccess = [null, any];
type ResponseFail = [any, null];
/**
* 封装各个请求方法及结果处理的类
* @author Soybean(曹理斌) 2021-03-15
* @class Request
*/
class Request {
instance: AxiosInstance;
constructor(instance: AxiosInstance) {
this.instance = instance;
}
static successHandler(response: AxiosResponse) {
const result: ResponseSuccess = [null, response];
return result;
}
static failHandler(error: any) {
const result: ResponseFail = [error, null];
return result;
}
get(url: string, config?: AxiosRequestConfig) {
return this.instance.get(url, config).then(Request.successHandler).catch(Request.failHandler);
}
post(url: string, data?: any, config?: AxiosRequestConfig) {
return this.instance.post(url, data, config).then(Request.successHandler).catch(Request.failHandler);
}
put(url: string, data?: any, config?: AxiosRequestConfig) {
return this.instance.put(url, data, config).then(Request.successHandler).catch(Request.failHandler);
}
delete(url: string, config?: AxiosRequestConfig) {
return this.instance.delete(url, config).then(Request.successHandler).catch(Request.failHandler);
}
}
export function createRequest(axiosConfig: AxiosRequestConfig, statusConfig?: StatusConfig) {
const customInstance = new CustomAxiosInstance(axiosConfig, statusConfig);
const request = new Request(customInstance.instance);
return request;
}
/**
* 对请求的结果数据进行格式化的处理
* @param handleFunc - 处理函数
* @param errors - 接收多个请求的错误
* @param datas - 接收多个请求的数据
*/
export function handleResponse<T>(handleFunc: Function, errors: any[], datas: any[]) {
let handleData = null;
if (errors.every(error => !error)) {
handleData = handleFunc(...datas);
}
const resError = errors.find(error => Boolean(error));
return [resError, handleData] as [any, T];
}

View File

10
src/store/index.ts Normal file
View File

@@ -0,0 +1,10 @@
import type { App } from 'vue';
import { createAuthStore, useAuthStore } from './modules/auth';
import { createAsideStore, useAsideStore } from './modules/aside';
export function createStore(app: App) {
createAuthStore(app);
createAsideStore(app);
}
export { useAuthStore, useAsideStore };

View File

@@ -0,0 +1,33 @@
import { inject, reactive } from 'vue';
import type { App, InjectionKey } from 'vue';
interface AsideState {
collapse: boolean;
}
interface AsideStore {
/** aside状态 */
asideState: AsideState;
/** 切换collapse */
toggle: () => void;
}
const injectKey: InjectionKey<AsideStore> = Symbol('aside-store');
export function createAsideStore(app: App) {
const state = reactive<AsideState>({
collapse: false
});
function toggle() {
state.collapse = !state.collapse;
}
const provideData: AsideStore = {
asideState: state,
toggle
};
app.provide(injectKey, provideData);
}
export function useAsideStore() {
return inject(injectKey)!;
}

View File

@@ -0,0 +1,44 @@
import { computed, inject, reactive } from 'vue';
import type { ComputedRef, App, InjectionKey } from 'vue';
interface UserInfo {
userId: string;
userName: string;
userPhone: string;
}
interface AuthState {
token: string;
userInfo: UserInfo;
}
interface AuthStore {
/** auth状态 */
authState: AuthState;
/** 是否登录 */
isLogin: ComputedRef<boolean>;
}
const injectKey: InjectionKey<AuthStore> = Symbol('auth-store');
export function createAuthStore(app: App) {
const state = reactive<AuthState>({
token: '',
userInfo: {
userId: '',
userName: '',
userPhone: ''
}
});
const isLogin = computed(() => Boolean(state.token));
const provideData: AuthStore = {
authState: state,
isLogin
};
app.provide(injectKey, provideData);
}
export function useAuthStore() {
return inject(injectKey)!;
}

23
src/styles/css/global.css Normal file
View File

@@ -0,0 +1,23 @@
@import './scrollbar.css';
html,
body,
#app {
width: 100%;
height: 100%;
}
html {
min-width: 1320px;
min-height: 650px;
font-size: 16px;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-size: 14px;
color: rgba(0, 0, 0, 0.65);
}
svg {
display: inline-block;
}

View File

@@ -0,0 +1,20 @@
/*---滚动条默认显示样式--*/
::-webkit-scrollbar-thumb {
background-color: #d9d9d9;
border-radius: 8px;
}
/*---鼠标点击滚动条显示样式--*/
::-webkit-scrollbar-thumb:hover {
background-color: #d9d9d9;
border-radius: 4px;
}
/*---滚动条大小--*/
::-webkit-scrollbar {
width: 8px;
height: 10px;
}
/*---滚动框背景样式--*/
::-webkit-scrollbar-track-piece {
background-color: rgba(0, 0, 0, 0);
border-radius: 0;
}

View File

@@ -0,0 +1,18 @@
@mixin scrollbar($size:8px, $color:#d9d9d9) {
&::-webkit-scrollbar-thumb {
background-color: $color;
border-radius: $size;
}
&::-webkit-scrollbar-thumb:hover {
background-color: $color;
border-radius: $size;
}
&::-webkit-scrollbar {
width: $size;
height: $size;
}
&::-webkit-scrollbar-track-piece {
background-color: rgba(0, 0, 0, 0);
border-radius: 0;
}
}

5
src/utils/auth/index.ts Normal file
View File

@@ -0,0 +1,5 @@
export function getStorageToken() {
return '';
}
export function getStorageUserInfo() {}

1
src/utils/index.ts Normal file
View File

@@ -0,0 +1 @@
export { getStorageToken, getStorageUserInfo } from './auth';