feat:发送请求

main
向文可 4 years ago
parent 63ad76379f
commit a263a36692

@ -0,0 +1,2 @@
VITE_BASE_URL=/api
VITE_REQUEST_TIMEOUT=20000

@ -2,12 +2,25 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<link href="/src/styles/loading.less" rel="stylesheet" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<div id="app">
<div class="loading-container">
<div class="dot-wrapper">
<span class="dot">
<i></i>
<i></i>
<i></i>
<i></i>
</span>
</div>
<h1>加载中</h1>
</div>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

@ -6,14 +6,13 @@
:button="config.button"
:message="config.message"
>
<LayoutDefault />
<router-view />
</el-config-provider>
</template>
<script setup>
import LayoutDefault from '@/layouts/default.vue';
import zh from 'element-plus/lib/locale/lang/zh-cn';
const route = useRoute();
const config = reactive({
locale: zh,
size: 'small',

@ -0,0 +1,39 @@
import request from '@/utils/request';
// 获取验证码
export function sendSmsCode(params) {
return request({
url: '/uaa/sms/sendSms',
method: 'get',
params,
});
}
// 登录
export async function login(data) {
return request({
url: '/uaa/sso/appManageLogin',
method: 'post',
data,
});
}
// 获取用户信息
export function getUserInfo() {
return request({
url: '/uc/user/v1/info/token',
method: 'get',
});
}
// 获取权限列表
export function getPermission() {
return request({
url: '/u-admin/uc/ucPermission/listUserMenu',
method: 'get',
});
}
// 退出登录
export function logout() {
return request({
url: '/uaa/sso/v1/logout',
method: 'get',
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

@ -5,7 +5,6 @@
declare module 'vue' {
export interface GlobalComponents {
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBacktop: typeof import('element-plus/es')['ElBacktop']
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
ElButton: typeof import('element-plus/es')['ElButton']
@ -15,10 +14,12 @@ declare module 'vue' {
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElMenuItemGroup: typeof import('element-plus/es')['ElMenuItemGroup']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
ElTabPane: typeof import('element-plus/es')['ElTabPane']

@ -0,0 +1,4 @@
export default {
baseURL: import.meta.env.VITE_BASE_URL,
requestTimeout: import.meta.env.VITE_REQUEST_TIMEOUT,
};

@ -11,6 +11,8 @@
<script setup>
import routes from '@/router/route.json';
const router = useRouter();
router.getRoutes();
const handleRoutes = computed(() => routes);
const handleClick = () => {};
</script>

@ -1,22 +1,32 @@
<template>
<div class="layout-profile">
<el-avatar />
<el-dropdown size="middle">
<el-avatar :src="userInfo.avatar" />
<el-dropdown size="middle" @command="handleCommand">
<span class="el-dropdown-link">
<span>管理员</span>
<span>{{ userInfo.username }}</span>
<XIcon class="el-icon--right" name="ArrowDown" />
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>个人中心</el-dropdown-item>
<el-dropdown-item>退出登录</el-dropdown-item>
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>
<script setup></script>
<script setup>
const store = useStore();
const userInfo = computed(() => store.state.local.userInfo);
const handleCommand = (command) => {
switch (command) {
case 'logout':
store.dispatch('auth/logout');
break;
}
};
</script>
<style lang="less" scoped>
.layout-profile {

@ -34,6 +34,7 @@
{ path: '/', name: 'route4', meta: { title: '菜单4', icon: 'home-fill' } },
{ path: '/', name: 'route5', meta: { title: '菜单5', icon: 'home-fill' } },
]);
const handleClick = () => {};
</script>
<style lang="less" scoped>

@ -5,10 +5,13 @@ import '@/icons';
const app = createApp(App);
import store from '@/store';
app.use(store);
import router from '@/router';
app.use(router);
import store from '@/store';
app.use(store);
import usePlugins from '@/plugins';
usePlugins(app);
app.mount('#app');

@ -0,0 +1,6 @@
import { ElMessage } from 'element-plus/es/components/message/index';
import 'element-plus/es/components/message/style/css';
export default (app) => {
app.use(ElMessage);
};
export { ElMessage };

@ -0,0 +1,6 @@
export default (app) => {
Object.entries(import.meta.globEager('./*.js')).forEach((entry) => {
entry[1].default?.(app);
console.info('[plugins] loaded ' + entry[0].split('/').pop().split('.').reverse().slice(1).reverse().join());
});
};

@ -2,9 +2,700 @@ import { createRouter, createWebHistory } from 'vue-router';
// 静态路由
const routes = [
{
path: '/login',
name: 'Login',
component: () => import('@/views/global/login.vue'),
meta: {
layout: false,
global: true,
},
},
{
path: '/',
component: () => import('@/views/index.vue'),
name: 'App',
redirect: { name: 'Home' },
component: () => import('@/layouts/default.vue'),
children: [
{
path: '/home',
name: 'Home',
component: () => import('@/views/global/home.vue'),
meta: {
title: '首页',
icon: 'home-line',
},
},
{
path: '/user',
name: 'UserManagement',
component: () => import('@/views/global/home.vue'),
meta: {
title: '用户管理',
icon: 'folder-user-fill',
},
children: [
{
path: 'consult',
name: 'ConsultManagement',
meta: {
title: '咨询管理',
icon: 'kakao-talk-fill',
},
},
{
path: 'course',
name: 'CourseConsultManagement',
meta: {
title: '咨询课程',
icon: 'chat-smile-2-fill',
},
},
{
path: 'account',
name: 'AccountManagement',
meta: {
title: '账号课程',
icon: 'account-box-fill',
},
},
{
path: 'feedback',
name: 'FeedbackManagement',
meta: {
title: '意见反馈',
icon: 'feedback-fill',
},
},
],
},
{
path: '/system',
name: 'SystemManagement',
component: () => import('@/views/global/home.vue'),
meta: {
title: '系统管理',
icon: 'settings-3-fill',
},
children: [
{
path: 'log',
name: 'LogManagement',
meta: {
title: '日志管理',
icon: 'file-chart-fill',
},
},
{
path: 'app',
name: 'AppManagement',
meta: {
title: 'APP包管理',
icon: 'app-store-fill',
},
},
{
path: 'notify',
name: 'NotifyManagement',
meta: {
title: '通知管理',
icon: 'alarm-warning-fill',
},
},
{
path: 'file',
name: 'FileManagement',
meta: {
title: '文件中台',
icon: 'file-copy-fill',
},
},
],
},
{
path: '/personal',
name: 'Personal',
component: () => import('@/views/global/home.vue'),
meta: {
title: '个人面板',
icon: 'user-2-fill',
},
children: [
{
path: 'answer/:tab?',
name: 'MineAnswer',
meta: {
title: '我的答疑',
icon: 'question-answer-fill',
},
},
{
path: 'file',
name: 'MineFile',
meta: {
title: '我的文件',
icon: 'file-3-line',
},
},
{
path: 'data',
name: 'MineData',
meta: {
title: '数据面板',
icon: 'file-chart-line',
},
},
],
},
{
path: '/operation',
name: 'Operation',
component: () => import('@/views/global/home.vue'),
meta: {
title: '运营管理',
icon: 'briefcase-4-fill',
},
children: [
{
path: 'banner',
name: 'BannerManagement',
meta: {
title: '轮播图',
icon: 'image-fill',
},
},
{
path: 'activity',
name: 'ActivityManagement',
component: () => import('@/views/global/home.vue'),
meta: {
title: '活动管理',
icon: 'gift-2-fill',
},
children: [
{
path: 'course',
name: 'CourseActivity',
meta: {
title: '课程活动',
icon: 'git-repository-line',
},
},
{
path: 'poster',
name: 'PosterShare',
meta: {
title: '海报分享',
icon: 'dashboard-fill',
},
},
{
path: 'punch',
name: 'PunchActivity',
meta: {
title: '打卡活动',
icon: 'fingerprint-line',
},
},
],
},
{
path: 'distribution',
name: 'DistributionManagement',
meta: {
title: '分销管理',
icon: 'mind-map',
},
},
{
path: 'integral',
name: 'IntegralManagement',
meta: {
title: '积分管理',
icon: 'bubble-chart-fill',
},
},
{
path: 'news',
name: 'GoodNewsManagement',
meta: {
title: '喜报管理',
icon: 'newspaper-fill',
},
},
{
path: 'lottery',
name: 'LotteryActivity',
meta: {
title: '抽奖活动',
icon: 'star-smile-fill',
},
},
{
path: 'promotion',
name: 'promotionActivity',
meta: {
title: '弹窗活动配置',
icon: 'e-bike-2-fill',
},
},
{
path: 'promotion/:id',
name: 'promotionActivityDetail',
meta: {
title: '弹窗活动详情',
icon: 'e-bike-2-fill',
hidden: true,
},
},
{
path: 'landing',
name: 'landingActivity',
meta: {
title: '落地页列表',
icon: 'arrow-left-right-fill',
},
},
{
path: 'landing/:id',
name: 'landingActivityDetail',
meta: {
title: '落地页详情',
icon: 'arrow-left-right-fill',
hidden: true,
},
},
{
path: 'supernatant',
name: 'supernatantActivity',
meta: {
title: '浮窗配置',
icon: 'bring-forward',
},
},
{
path: 'supernatant/:id',
name: 'supernatantActivityDetail',
meta: {
title: '浮窗详情',
icon: 'bring-forward',
hidden: true,
},
},
{
path: 'marketing',
name: 'MarketingActivities',
meta: {
title: '咨询组管理',
icon: 'bar-chart-fill',
},
},
{
path: 'marketing/edit/:id?',
name: 'ActivityEdit',
meta: {
hidden: true,
title: '营销活动-编辑',
icon: 'bar-chart-fill',
},
},
{
path: 'marketing/visitor/:id?',
name: 'MarketingLogs',
meta: {
hidden: true,
title: '营销活动-数据',
icon: 'bar-chart-fill',
},
},
],
},
{
path: '/finance',
name: 'Finance',
component: () => import('@/views/global/home.vue'),
meta: {
title: '财务管理',
icon: 'money-cny-circle-fill',
},
children: [
{
path: 'order',
name: 'OrderManagement',
meta: {
title: '订单管理',
icon: 'auction-fill',
},
},
{
path: 'refund',
name: 'RefundManagement',
meta: {
title: '退款管理',
icon: 'refund-2-fill',
},
},
{
path: 'reconciliation',
name: 'FinanceReconciliation',
meta: {
title: '财务对账',
icon: 'money-dollar-box-fill',
},
},
],
},
{
path: '/education',
name: 'Education',
component: () => import('@/views/global/home.vue'),
meta: {
title: '教务教学',
icon: 'book-open-fill',
},
children: [
{
path: 'teacher',
name: 'TeacherManagement',
meta: {
title: '老师管理',
icon: 'user-shared-fill',
},
},
{
path: 'student',
name: 'StudentManagement',
meta: {
title: '学生管理',
icon: 'user-received-fill',
},
children: [
{
path: 'list',
name: 'StudentManagementList',
meta: {
title: '学生列表',
icon: 'user-received-line',
},
},
{
path: 'grade',
name: 'GradeManagement',
meta: {
title: '年级管理',
icon: 'account-pin-box-fill',
},
},
{
path: 'class',
name: 'ClassManagement',
meta: {
title: '班级管理',
icon: 'account-pin-circle-fill',
},
},
],
},
{
path: 'admission',
name: 'AdmissionManagement',
component: () => import('@/views/global/home.vue'),
meta: {
title: '入学',
icon: 'award-fill',
},
children: [
{
path: 'evaluation',
name: 'AdmissionEvaluation',
meta: {
title: '入学评测',
icon: 'file-paper-2-fill',
},
},
{
path: 'way',
name: 'StudyWay',
meta: {
title: '学习路线',
icon: 'send-plane-fill',
},
},
],
},
{
path: 'answer',
name: 'Answer',
component: () => import('@/views/global/home.vue'),
meta: {
title: '答疑',
icon: 'question-answer-fill',
},
children: [
{
path: 'management',
name: 'AnswerManagement',
component: () => import('@/views/global/home.vue'),
meta: {
title: '答疑管理',
icon: 'question-answer-fill',
},
children: [
{
path: 'list',
name: 'AnswerManagementList',
meta: {
title: '答疑管理列表',
icon: 'question-answer-line',
},
},
{
path: 'invalid',
name: 'InvalidQuestionManagement',
meta: {
title: '无效追问管理',
icon: 'questionnaire-fill',
},
},
{
path: 'lock',
name: 'UserLockManagement',
meta: {
title: '用户锁定管理',
icon: 'lock-fill',
},
},
],
},
{
path: 'assign/:questionId',
name: 'AssignAnswerTeacher',
meta: {
title: '指派老师',
icon: 'account-box-fill',
hidden: true,
activeMenu: '/education/answer/management',
},
},
{
path: 'teacher',
name: 'AnswerTeacher',
component: () => import('@/views/global/home.vue'),
meta: {
title: '答疑老师管理',
icon: 'book-line',
},
children: [
{
path: 'summary',
name: 'AnswerTeacherSummary',
meta: {
title: '答疑老师统计',
icon: 'book-2-line',
},
},
{
path: 'management',
name: 'AnswerTeacherManagement',
meta: {
title: '答疑老师管理',
icon: 'book-3-line',
},
},
{
path: 'bind/:teacher?',
name: 'AnswerTeacherUpdateBind',
meta: {
title: '编辑课程绑定',
icon: 'book-fill',
hidden: true,
activeMenu: '/education/answer/teacher/management',
},
},
{
path: 'course',
name: 'AnswerTeacherBindCourse',
meta: {
title: '绑定课程管理',
icon: 'book-fill',
},
},
],
},
],
},
{
path: 'question',
name: 'QuestionManagement',
meta: {
title: '题库管理',
icon: 'brush-fill',
},
},
{
path: 'note',
name: 'NoteManagement',
meta: {
title: '笔记管理',
icon: 'sticky-note-2-fill',
},
},
{
path: 'research',
name: 'CourseResearch',
component: () => import('@/views/global/home.vue'),
meta: {
title: '课研更新',
icon: 'contacts-book-upload-fill',
},
children: [
{
path: 'plan',
name: 'CourseResearchPlan',
meta: {
title: '课研计划',
icon: 'contacts-book-upload-line',
},
},
{
path: 'log',
name: 'CourseResearchLog',
meta: {
title: '课研更新日志',
icon: 'file-copy-2-line',
},
},
],
},
{
path: 'live',
name: 'LiveManagement',
meta: {
title: '直播管理',
icon: 'live-fill',
},
},
{
path: 'material',
name: 'MaterialManagement',
meta: {
title: '老师资料管理',
icon: 'database-2-fill',
},
},
{
path: 'shift',
name: 'ShiftTable',
meta: {
title: '排班表',
icon: 'calendar-check-line',
},
},
{
path: 'course',
name: 'CourseTable',
meta: {
title: '课程表管理',
icon: 'calendar-todo-fill',
},
},
],
},
{
path: '/course',
name: 'Course',
component: () => import('@/views/global/home.vue'),
meta: {
title: '课程管理',
icon: 'checkbox-multiple-fill',
},
children: [
{
path: 'management',
name: 'CourseManagement',
meta: {
title: '课程列表',
icon: 'book-2-fill',
},
},
{
path: 'category',
name: 'CourseCategory',
meta: {
title: '课程分类',
icon: 'book-read-fill',
},
},
{
path: 'system',
name: 'SystemCourseManagement',
meta: {
title: '体系课',
icon: 'book-3-fill',
},
},
{
path: 'authorize',
name: 'CourseAuthorize',
meta: {
title: '课程授权',
icon: 'book-fill',
},
},
{
path: 'package',
name: 'CoursePackage',
meta: {
title: '课程包',
icon: 'archive-drawer-fill',
},
},
{
path: 'live',
name: 'LiveCourse',
meta: {
title: '直播课',
icon: 'live-fill',
},
},
],
},
{
path: '/audit',
name: 'Audit',
component: () => import('@/views/global/home.vue'),
meta: {
title: '审核管理',
icon: 'checkbox-multiple-fill',
},
children: [
{
path: 'course',
name: 'CourseAudit',
meta: {
title: '课程审核',
icon: 'book-fill',
},
},
{
path: 'answer',
name: 'AnswerCourseAudit',
meta: {
title: '答疑课程审核',
icon: 'book-2-fill',
},
},
{
path: 'admission',
name: 'AdmissionAudit',
meta: {
title: '入学审核',
icon: 'book-3-fill',
},
},
],
},
],
},
];
@ -19,4 +710,25 @@ const router = createRouter({
routes,
});
import store from '@/store';
router.onError((error, to) => {
console.info('[router] error', error, to);
});
router.beforeEach(async (to, from, next) => {
if (!from.matched.length) {
store.loadCache();
}
if (to.meta.global) {
next();
} else {
if (!store.state.auth.userInfo) {
await store.dispatch('auth/getUserInfo');
}
if (!store.state.auth.permission.length) {
await store.dispatch('auth/getPermission');
}
next();
}
});
export default router;

@ -18,4 +18,36 @@ const store = createStore({
strict: process.env.NODE_ENV !== 'production',
modules,
});
const storeCache = () => {
localStorage.setItem('storeCache', JSON.stringify(store.state.local));
// sessionStorage.setItem('storeCache', JSON.stringify(store.state));
console.info('[store] store cache');
};
const loadCache = () => {
let storeCache = sessionStorage.getItem('storeCache');
if (storeCache) {
let init = _.cloneDeep(store.state);
storeCache = Object.assign(init, JSON.parse(storeCache));
sessionStorage.removeItem('storeCache');
} else {
storeCache = { ...store.state };
}
let localCache = localStorage.getItem('storeCache');
if (localCache) {
storeCache.local = _.cloneDeep(store.state.local);
Object.assign(storeCache.local, JSON.parse(localCache));
}
store.replaceState(storeCache);
console.info('[store] load cache');
};
window._open = window.open;
window.open = function () {
storeCache();
window._open.apply(window, arguments);
};
window.addEventListener('beforeunload', storeCache);
store.storeCache = storeCache;
store.loadCache = loadCache;
export default store;

@ -0,0 +1,79 @@
import { getUserInfo, getPermission } from '@/api/auth';
import { ElMessage } from '@/plugins/element-plus';
const sms = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, 1000);
});
};
const login = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
token: 'eyJhbGciOiJSUzI1NiJ9.eyJ1aWQiOjEzMjI1LCJwaG9uZSI6IjEzMjAyMDkwNjAxIiwibG9naW5UeXBlIjo1LCJ1c2VyTm8iOiIxMzIyNSIsIm5pY2tuYW1lIjoi5b-D5LmL5omA5ZCRIiwiZGVwdElkIjpudWxsLCJleHAiOjE2Nzg5NzI5NjksInVzZXJuYW1lIjoiTUNBLTMzYTkifQ.bDgxrGWUYW1iGLQidoj3q4lBXTlI0zKMBslIjN5LuvyLE4O8LPfFFYh33nAlevKuey_tqYQSWn0Cvm81Ywuksn70lGyy5-BEBbLUQCXaFKaWXujc2FzWFIvPHzAXfF16aUzyKkCppR5tfLDQz5cimLHAQBw24Ar69UCMiYJjN8E',
});
}, 1000);
});
};
import router from '@/router';
const state = () => ({
userInfo: null,
permission: [],
});
const getters = {};
const mutations = {
setUserInfo: (state, data) => (state.userInfo = data),
setPermission: (state, data) => (state.permission = data),
};
const actions = {
sms: async ({ commit }, data) => {
let res = await sms(data);
if (res) {
ElMessage.success('验证码已发送');
commit('local/sendMessage', {}, { root: true });
} else {
ElMessage.error('发送验证码失败');
}
return res;
},
login: async ({ commit }, data) => {
let res = await login(data);
if (res) {
ElMessage.success('登陆成功');
commit('local/setToken', res.token, { root: true });
router.push('/');
} else {
ElMessage.error('登陆失败');
}
return res;
},
logout: ({ commit }) => {
commit('local/setToken', null, { root: true });
router.push({ name: 'Login' });
},
getPermission: async ({ commit }) => {
let res = await getPermission();
if (res) {
commit('setPermission', res);
} else {
ElMessage.error('加载权限信息失败');
}
return res;
},
getUserInfo: async ({ commit }) => {
let res = await getUserInfo();
if (res) {
commit('setUserInfo', res);
} else {
ElMessage.error('加载用户信息失败');
}
return res;
},
};
export default {
state,
getters,
mutations,
actions,
};

@ -0,0 +1,16 @@
const state = () => ({
lastSendMessageTime: 0,
token: null,
});
const getters = {};
const mutations = {
sendMessage: (state) => (state.lastSendMessageTime = new Date().getTime()),
setToken: (state, data) => (state.token = data),
};
const actions = {};
export default {
state,
getters,
mutations,
actions,
};

@ -10,4 +10,5 @@ body,
padding: 0;
transition: all 0.1s cubic-bezier(0.645, 0.045, 0.355, 1);
box-sizing: border-box;
word-break: break-all;
}

@ -0,0 +1,75 @@
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 90vh;
min-height: 90vh;
h1 {
font-size: 28px;
font-weight: bolder;
}
.dot-wrapper {
display: flex;
align-items: center;
justify-content: center;
padding: 98px;
.dot {
position: relative;
box-sizing: border-box;
display: inline-block;
width: 64px;
height: 64px;
font-size: 64px;
transform: rotate(45deg);
animation: rotate 1.2s infinite linear;
i {
position: absolute;
display: block;
width: 28px;
height: 28px;
background-color: #1890ff;
border-radius: 100%;
opacity: 0.3;
transform: scale(0.75);
transform-origin: 50% 50%;
animation: spin 1s infinite linear alternate;
&:nth-child(1) {
top: 0;
left: 0;
}
&:nth-child(2) {
top: 0;
right: 0;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
&:nth-child(3) {
right: 0;
bottom: 0;
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
&:nth-child(4) {
bottom: 0;
left: 0;
-webkit-animation-delay: 1.2s;
animation-delay: 1.2s;
}
}
}
}
}
@keyframes rotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@keyframes spin {
to {
opacity: 1;
}
}

@ -0,0 +1,74 @@
import config from '@/configs';
import store from '@/store';
import qs from 'qs';
import { ElMessage } from '@/plugins/element-plus';
const handleResponse = async ({ config, headers, data, status }) => {
if (
['application/octet-stream', 'application/zip'].indexOf(headers['content-type']) !== -1 ||
config['down'] === 'file'
) {
return data;
}
let code = data.code || status;
console.info('[api]', code, config.method, config.url, data.data);
if (code !== 200) {
ElMessage.error(data.msg || '服务器异常');
switch (code) {
case 500:
case 501:
break;
case 524:
case 525:
case 50008:
case 50012:
case 50014:
store.dispatch('auth/logout');
break;
case 404:
case 9999:
console.warn('接口9999', config);
break;
default:
}
}
return data?.data;
};
const instance = axios.create({
baseURL: config.baseURL,
timeout: config.requestTimeout,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
});
instance.interceptors.request.use(
(config) => {
const token = store.state.local.token;
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
if (config.data && config.headers['Content-Type'] === 'application/x-www-form-urlencoded;charset=UTF-8') {
config.data = qs.stringify(config.data);
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
instance.interceptors.response.use(
(response) => handleResponse(response),
(error) => {
if (!error.response) {
ElMessage.error('服务器无响应');
return null;
} else {
return handleResponse(error.response);
}
}
);
export default instance;

@ -1,5 +1,7 @@
<template>
<div class="container">
<h1>TOKEN:{{ token }}</h1>
<h2>{{ userInfo }}</h2>
<h1>
<XIcon name="app-store" size="30" />
<span>马士兵严选</span>
@ -11,11 +13,15 @@
<el-button type="danger" @click="handleClear"></el-button>
<br />
<el-date-picker></el-date-picker>
{{ form }}
<el-input v-model="form.msg" />
</div>
</template>
<script setup>
const store = useStore();
const token = computed(() => store.state.local.token);
const userInfo = computed(() => store.state.auth.userInfo);
const count = computed(() => store.state.demo.count);
const doubleCount = computed(() => store.getters['demo/doubleCount']);
const handleAdd = () => {
@ -24,6 +30,7 @@
const handleClear = () => {
store.dispatch('demo/clear');
};
const form = reactive({ msg: '123' });
</script>
<style lang="less">

@ -0,0 +1,131 @@
<template>
<div class="mask">
<div class="box">
<div class="title">登录</div>
<el-form ref="refsForm" class="content" :model="form" :rules="rules" size="large">
<el-form-item prop="phone">
<el-input v-model="form.phone" class="ghost" placeholder="请输入手机号码" />
</el-form-item>
<el-form-item prop="password">
<el-input v-model="form.password" type="password" class="ghost" placeholder="请输入登录密码" />
</el-form-item>
<el-form-item prop="verifyCode">
<div class="flex">
<el-input v-model="form.verifyCode" class="ghost" placeholder="请输入验证码" />
<el-button class="ghost" :disabled="waitTime > 0" @click="handleSms" :loading="loading">
{{ waitTime ? waitTime + 'S' : '发送验证码' }}
</el-button>
</div>
</el-form-item>
<el-button class="block" @click="handleLogin" :loading="loading">立即登录</el-button>
</el-form>
</div>
</div>
</template>
<script setup>
const { proxy } = getCurrentInstance();
const store = useStore();
const refsForm = ref(null);
const loading = ref(false);
const form = reactive({
phone: '',
password: '',
verifyCode: '',
});
const lastTime = computed(() => store.state.local.lastSendMessageTime);
const waitTime = ref(60);
const sendStep = ref(60);
const rules = reactive({
phone: [{ required: true, message: '请输入手机号码' }],
password: [{ required: true, message: '请输入登录密码' }],
verifyCode: [{ required: true, message: '请输入验证码' }],
});
waitTime.value = Math.max(0, unref(sendStep) - Math.ceil((new Date().getTime() - unref(lastTime)) / 1000));
setInterval(() => {
waitTime.value = Math.max(0, unref(sendStep) - Math.ceil((new Date().getTime() - unref(lastTime)) / 1000));
}, 1000);
const handleSms = async () => {
if (form.phone) {
loading.value = true;
await store.dispatch('auth/sms', { phone: form.phone, type: 1 });
loading.value = false;
} else {
proxy.$message.warning('请输入手机号码');
}
};
const handleLogin = async () => {
loading.value = true;
try {
await unref(refsForm).validate();
await store.dispatch('auth/login', form);
} catch (e) {
console.info('取消登录', e);
}
loading.value = false;
};
</script>
<style lang="less" scoped>
.mask {
width: 100%;
height: 100vh;
background: #000 url('~/global/login-bgp.png') center center / 100% 100% no-repeat;
display: flex;
justify-content: center;
align-items: center;
color: @color-white;
.box {
width: 420px;
padding: 30px;
background: #1a2229;
border-radius: 10px;
.title {
margin-bottom: 20px;
font-size: 20px;
text-align: center;
}
.content {
.flex {
width: 100%;
display: flex;
+ .flex {
margin-top: 10px;
}
.el-button {
width: 30%;
margin-left: 10px;
color: #fff;
}
}
:deep(.el-input) {
input {
color: #fff;
}
}
:deep(.ghost) {
background: none;
* {
background: none;
}
}
.block {
font-size: 16px;
font-weight: normal;
color: #ffffff;
width: 100%;
height: 40px;
text-align: center;
line-height: 40px;
background: linear-gradient(0deg, #fb3a4e, #ff6272);
border-radius: 4px;
border: none;
cursor: pointer;
}
}
}
}
</style>

@ -13,7 +13,23 @@ import removeConsole from 'vite-plugin-remove-console';
import legacy from '@vitejs/plugin-legacy';
export default ({ command, mode }: ConfigEnv): UserConfigExport => {
console.info('command', command);
console.info('mode', mode);
return {
server: {
host: '0.0.0.0',
port: 3000,
open: false,
proxy: {
'/api': {
target: 'http://39.103.236.147/api', // 测试地址
// target: 'http://121.89.192.107:7771', // 预发地址
// target: 'https://edu.mashibing.com/api/', // 生产环境
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
resolve: {
alias: [
{

Loading…
Cancel
Save