mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-27 19:29:04 +08:00
initial commit
This commit is contained in:
30
src/views/sys/error-log/DetailModal.vue
Normal file
30
src/views/sys/error-log/DetailModal.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<script lang="tsx">
|
||||
import { defineComponent, PropOptions } from 'compatible-vue';
|
||||
import { BasicModal } from '@/components/modal/index';
|
||||
import { ErrorInfo } from '@/store/modules/error';
|
||||
import { Description, useDescription } from '@/components/description/index';
|
||||
import { getDescSchema } from './data';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ErrorLogDetailModal',
|
||||
props: {
|
||||
info: {
|
||||
type: Object,
|
||||
default: null,
|
||||
} as PropOptions<ErrorInfo>,
|
||||
},
|
||||
setup(props, { listeners }) {
|
||||
const [register] = useDescription({
|
||||
column: 2,
|
||||
schema: getDescSchema(),
|
||||
});
|
||||
return () => {
|
||||
return (
|
||||
<BasicModal width={800} title="错误详情" on={listeners}>
|
||||
<Description data={props.info} onRegister={register} />
|
||||
</BasicModal>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
66
src/views/sys/error-log/data.tsx
Normal file
66
src/views/sys/error-log/data.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Tag } from 'ant-design-vue';
|
||||
import { BasicColumn } from '@/components/table/index';
|
||||
import { ErrorTypeEnum } from '@/store/modules/error';
|
||||
import { DescItem } from '@/components/description/index';
|
||||
|
||||
export function getColumns(): BasicColumn[] {
|
||||
return [
|
||||
{
|
||||
dataIndex: 'type',
|
||||
title: '类型',
|
||||
width: 80,
|
||||
customRender: (text: string) => {
|
||||
const color =
|
||||
text === ErrorTypeEnum.VUE
|
||||
? 'green'
|
||||
: text === ErrorTypeEnum.RESOURCE
|
||||
? 'cyan'
|
||||
: text === ErrorTypeEnum.PROMISE
|
||||
? 'blue'
|
||||
: ErrorTypeEnum.AJAX
|
||||
? 'red'
|
||||
: 'purple';
|
||||
return <Tag color={color}>{text}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
dataIndex: 'url',
|
||||
title: '地址',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
dataIndex: 'time',
|
||||
title: '时间',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
dataIndex: 'file',
|
||||
title: '文件',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: 'Name',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
dataIndex: 'message',
|
||||
title: '错误信息',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
dataIndex: 'stack',
|
||||
title: 'stack信息',
|
||||
width: 300,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function getDescSchema(): DescItem[] {
|
||||
return getColumns().map((column) => {
|
||||
return {
|
||||
field: column.dataIndex!,
|
||||
label: column.title,
|
||||
};
|
||||
});
|
||||
}
|
108
src/views/sys/error-log/index.vue
Normal file
108
src/views/sys/error-log/index.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<script lang="tsx">
|
||||
import { defineComponent, watch, ref, unref } from 'compatible-vue';
|
||||
|
||||
import DetailModal from './DetailModal.vue';
|
||||
import { useModal } from '@/components/modal/index';
|
||||
|
||||
import { useDesign } from '@/hooks/core/useDesign';
|
||||
|
||||
import { BasicTable, useTable } from '@/components/table/index';
|
||||
|
||||
import { errorStore, ErrorInfo } from '@/store/modules/error';
|
||||
|
||||
import { fireErrorApi } from '@/api/demo/error';
|
||||
|
||||
import { getColumns } from './data';
|
||||
|
||||
const { prefixCls } = useDesign('error-handle');
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ErrorHandler',
|
||||
setup() {
|
||||
const rowInfoRef = ref<ErrorInfo>();
|
||||
const imgListRef = ref<string[]>([]);
|
||||
const [register, { setTableData }] = useTable({
|
||||
titleHelpMessage: '只在`/src/settings/projectSetting.ts` 内的useErrorHandle=true时生效!',
|
||||
title: '错误日志列表',
|
||||
columns: getColumns(),
|
||||
actionColumn: {
|
||||
width: 80,
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
customRender: (text: string, recoed: ErrorInfo) => {
|
||||
return (
|
||||
<a-button type="link" size="small" onClick={handleDetail.bind(null, recoed)}>
|
||||
详情
|
||||
</a-button>
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const [registerModal, { openModal }] = useModal();
|
||||
watch(
|
||||
() => errorStore.getErrorInfoState,
|
||||
(list: any[]) => {
|
||||
setTableData(list);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
// 查看详情
|
||||
function handleDetail(row: ErrorInfo) {
|
||||
rowInfoRef.value = row;
|
||||
openModal({
|
||||
visible: true,
|
||||
});
|
||||
}
|
||||
|
||||
function fireVueError() {
|
||||
throw new Error('fire vue error!');
|
||||
}
|
||||
|
||||
function fireResourceError() {
|
||||
imgListRef.value.push(`${new Date().getTime()}.png`);
|
||||
}
|
||||
|
||||
async function fireAjaxError() {
|
||||
await fireErrorApi();
|
||||
}
|
||||
|
||||
return () => (
|
||||
<div class={[prefixCls, 'p-4']}>
|
||||
{unref(imgListRef).map((src) => {
|
||||
return <img src={src} key={src} class="hidden" />;
|
||||
})}
|
||||
|
||||
<DetailModal info={unref(rowInfoRef)} onRegister={registerModal} />
|
||||
|
||||
<BasicTable onRegister={register} class={`${prefixCls}-table`}>
|
||||
<template slot="toolbar">
|
||||
<a-button onClick={fireVueError} type="primary">
|
||||
点击触发vue错误
|
||||
</a-button>
|
||||
<a-button onClick={fireResourceError} type="primary">
|
||||
点击触发resource错误
|
||||
</a-button>
|
||||
<a-button onClick={fireAjaxError} type="primary">
|
||||
点击触发ajax错误
|
||||
</a-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
@import (reference) '~@design';
|
||||
@prefix-cls: ~'@{namespace}-error-handle';
|
||||
|
||||
.@{prefix-cls} {
|
||||
&-table {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
</style>
|
125
src/views/sys/exception/Exception.tsx
Normal file
125
src/views/sys/exception/Exception.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
import type { PropType } from 'vue';
|
||||
|
||||
import { Result, Button } from 'ant-design-vue';
|
||||
import { defineComponent, ref, computed, unref } from 'vue';
|
||||
|
||||
import { ExceptionEnum } from '/@/enums/exceptionEnum';
|
||||
|
||||
import netWorkImg from '/@/assets/images/exception/net-work.png';
|
||||
import error404 from '/@/assets/images/exception/404.png';
|
||||
import error500 from '/@/assets/images/exception/500.png';
|
||||
import notDataImg from '/@/assets/images/no-data.png';
|
||||
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { useGo, useRedo } from '/@/hooks/web/usePage';
|
||||
import { PageEnum } from '/@/enums/pageEnum';
|
||||
|
||||
interface MapValue {
|
||||
title: string;
|
||||
subTitle: string;
|
||||
btnText?: string;
|
||||
icon?: string;
|
||||
handler?: Fn;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ErrorPage',
|
||||
props: {
|
||||
// 状态码
|
||||
status: {
|
||||
type: Number as PropType<number>,
|
||||
default: ExceptionEnum.PAGE_NOT_FOUND,
|
||||
},
|
||||
|
||||
title: {
|
||||
type: String as PropType<string>,
|
||||
},
|
||||
|
||||
subTitle: {
|
||||
type: String as PropType<string>,
|
||||
},
|
||||
|
||||
full: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const statusMapRef = ref(new Map<string | number, MapValue>());
|
||||
const { query } = useRoute();
|
||||
const go = useGo();
|
||||
const redo = useRedo();
|
||||
const getStatus = computed(() => {
|
||||
const { status: routeStatus } = query;
|
||||
const { status } = props;
|
||||
return Number(routeStatus) || status;
|
||||
});
|
||||
|
||||
const getMapValue = computed(
|
||||
(): MapValue => {
|
||||
return unref(statusMapRef).get(unref(getStatus)) as MapValue;
|
||||
}
|
||||
);
|
||||
|
||||
unref(statusMapRef).set(ExceptionEnum.PAGE_NOT_FOUND, {
|
||||
title: '404',
|
||||
subTitle: '抱歉,您访问的页面不存在!',
|
||||
btnText: props.full ? '返回登录' : '返回首页',
|
||||
handler: () => (props.full ? go(PageEnum.BASE_LOGIN) : go()),
|
||||
icon: error404,
|
||||
});
|
||||
|
||||
unref(statusMapRef).set(ExceptionEnum.ERROR, {
|
||||
title: '500',
|
||||
subTitle: '抱歉,服务器出现异常!',
|
||||
btnText: '返回首页',
|
||||
handler: () => go(),
|
||||
icon: error500,
|
||||
});
|
||||
|
||||
unref(statusMapRef).set(ExceptionEnum.PAGE_NOT_DATA, {
|
||||
title: '当前页面无数据',
|
||||
subTitle: '',
|
||||
btnText: '刷新',
|
||||
handler: () => redo(),
|
||||
icon: notDataImg,
|
||||
});
|
||||
|
||||
unref(statusMapRef).set(ExceptionEnum.NET_WORK_ERROR, {
|
||||
title: '网络错误',
|
||||
subTitle: '抱歉,您的网络连接已断开,请检查您的网络!',
|
||||
btnText: '刷新',
|
||||
handler: () => redo(),
|
||||
icon: netWorkImg,
|
||||
});
|
||||
|
||||
unref(statusMapRef).set(ExceptionEnum.PAGE_TIMEOUT, {
|
||||
title: '页面加载失败',
|
||||
subTitle: '抱歉,您的页面加载出错或者过久未响应,请检查您的网络!',
|
||||
btnText: '刷新',
|
||||
handler: () => redo(),
|
||||
icon: netWorkImg,
|
||||
});
|
||||
return () => {
|
||||
const { title, subTitle, btnText, icon, handler } = unref(getMapValue) || {};
|
||||
return (
|
||||
<Result
|
||||
class="flex items-center flex-col"
|
||||
title={props.title || title}
|
||||
sub-title={props.subTitle || subTitle}
|
||||
>
|
||||
{{
|
||||
extra: () =>
|
||||
btnText && (
|
||||
<Button type="primary" onClick={handler}>
|
||||
{() => btnText}
|
||||
</Button>
|
||||
),
|
||||
icon: () => icon && <img src={icon} />,
|
||||
}}
|
||||
</Result>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
17
src/views/sys/exception/LoadTimeOut.vue
Normal file
17
src/views/sys/exception/LoadTimeOut.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<Exception :status="ExceptionEnum.PAGE_TIMEOUT" />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { Exception } from '/@/views/sys/exception';
|
||||
|
||||
import { ExceptionEnum } from '/@/enums/exceptionEnum';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LoadTimeout',
|
||||
components: { Exception },
|
||||
setup() {
|
||||
return { ExceptionEnum };
|
||||
},
|
||||
});
|
||||
</script>
|
2
src/views/sys/exception/index.ts
Normal file
2
src/views/sys/exception/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as Exception } from './Exception';
|
||||
export { default as LoadTimeOut } from './LoadTimeOut.vue';
|
9
src/views/sys/iframe/FrameBlank.vue
Normal file
9
src/views/sys/iframe/FrameBlank.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
export default defineComponent({
|
||||
name: 'FrameBlank',
|
||||
});
|
||||
</script>
|
114
src/views/sys/iframe/index.vue
Normal file
114
src/views/sys/iframe/index.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div class="iframe-page" :style="getWrapStyle">
|
||||
<Spin :spinning="loading" size="large" :style="getWrapStyle">
|
||||
<div class="iframe-page__mask" v-show="menuStore.getDragStartState" />
|
||||
<iframe :src="frameSrc" class="iframe-page__main" ref="frameRef" />
|
||||
</Spin>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, ref, unref, onMounted, nextTick, computed } from 'vue';
|
||||
import { Spin } from 'ant-design-vue';
|
||||
|
||||
import { getViewportOffset } from '/@/utils/domUtils';
|
||||
import { useWindowSizeFn } from '/@/hooks/event/useWindowSize';
|
||||
import { menuStore } from '/@/store/modules/menu';
|
||||
export default defineComponent({
|
||||
name: 'IFrame',
|
||||
components: { Spin },
|
||||
props: {
|
||||
frameSrc: {
|
||||
type: String as PropType<string>,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const loadingRef = ref(false);
|
||||
const topRef = ref(50);
|
||||
const heightRef = ref(window.innerHeight);
|
||||
const frameRef = ref<HTMLElement | null>(null);
|
||||
|
||||
function calcHeight() {
|
||||
const iframe = unref(frameRef);
|
||||
if (!iframe) {
|
||||
return;
|
||||
}
|
||||
let { top } = getViewportOffset(iframe);
|
||||
top += 20;
|
||||
topRef.value = top;
|
||||
heightRef.value = window.innerHeight - top;
|
||||
const clientHeight = document.documentElement.clientHeight - top;
|
||||
iframe.style.height = `${clientHeight}px`;
|
||||
}
|
||||
|
||||
useWindowSizeFn(calcHeight, 150, { immediate: true });
|
||||
|
||||
function hideLoading() {
|
||||
loadingRef.value = false;
|
||||
calcHeight();
|
||||
}
|
||||
|
||||
function init() {
|
||||
nextTick(() => {
|
||||
const iframe = unref(frameRef);
|
||||
if (!iframe) {
|
||||
return;
|
||||
}
|
||||
if ((iframe as any).attachEvent) {
|
||||
(iframe as any).attachEvent('onload', () => {
|
||||
hideLoading();
|
||||
});
|
||||
} else {
|
||||
iframe.onload = () => {
|
||||
hideLoading();
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
onMounted(() => {
|
||||
loadingRef.value = true;
|
||||
init();
|
||||
});
|
||||
return {
|
||||
getWrapStyle: computed(() => {
|
||||
return {
|
||||
height: `${unref(heightRef)}px`,
|
||||
};
|
||||
}),
|
||||
loading: loadingRef,
|
||||
frameRef,
|
||||
menuStore,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.iframe-page {
|
||||
.ant-spin-nested-loading {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
.ant-spin-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&__mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
border: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
</style>
|
149
src/views/sys/lock/index.vue
Normal file
149
src/views/sys/lock/index.vue
Normal file
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div class="lock-page">
|
||||
<div class="lock-page__entry">
|
||||
<div class="lock-page__header">
|
||||
<img src="../../../assets/images/header.jpg" class="lock-page__header-img" />
|
||||
<p class="lock-page__header-name">{{ realName }}</p>
|
||||
</div>
|
||||
<BasicForm @register="register" v-if="!getIsNotPwd" />
|
||||
<Alert v-if="errMsgRef" type="error" message="锁屏密码错误" banner />
|
||||
<div class="lock-page__footer">
|
||||
<a-button type="default" class="mt-2 mr-2" @click="goLogin" v-if="!getIsNotPwd">
|
||||
返回登录
|
||||
</a-button>
|
||||
<a-button type="primary" class="mt-2" @click="unLock(!getIsNotPwd)" :loading="loadingRef">
|
||||
进入系统
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
// 组件相关
|
||||
import { defineComponent, ref, computed } from 'vue';
|
||||
import { Alert } from 'ant-design-vue';
|
||||
// hook
|
||||
import { BasicForm, useForm } from '/@/components/Form';
|
||||
|
||||
import { userStore } from '/@/store/modules/user';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
export default defineComponent({
|
||||
name: 'LockPage',
|
||||
components: { Alert, BasicForm },
|
||||
|
||||
setup() {
|
||||
// 获取配置文件
|
||||
// 样式前缀
|
||||
const loadingRef = ref(false);
|
||||
const errMsgRef = ref(false);
|
||||
const [register, { validateFields }] = useForm({
|
||||
showActionButtonGroup: false,
|
||||
schemas: [
|
||||
{
|
||||
field: 'password',
|
||||
label: '',
|
||||
component: 'InputPassword',
|
||||
componentProps: {
|
||||
style: { width: '100%' },
|
||||
placeholder: '请输入锁屏密码或者用户密码',
|
||||
},
|
||||
rules: [{ required: true }],
|
||||
},
|
||||
],
|
||||
});
|
||||
const realName = computed(() => {
|
||||
const { realName } = userStore.getUserInfoState || {};
|
||||
return realName;
|
||||
});
|
||||
/**
|
||||
* @description: unLock
|
||||
*/
|
||||
async function unLock(valid = true) {
|
||||
let password = '';
|
||||
if (valid) {
|
||||
try {
|
||||
const values = (await validateFields()) as any;
|
||||
password = values.password;
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
loadingRef.value = true;
|
||||
const res = await appStore.unLockAction({ password, valid });
|
||||
errMsgRef.value = !res;
|
||||
} finally {
|
||||
loadingRef.value = false;
|
||||
}
|
||||
}
|
||||
function goLogin() {
|
||||
userStore.loginOut(true);
|
||||
appStore.resetLockInfo();
|
||||
}
|
||||
const getIsNotPwd = computed(() => {
|
||||
if (!appStore.getLockInfo) {
|
||||
return true;
|
||||
}
|
||||
return appStore.getLockInfo.pwd === undefined;
|
||||
});
|
||||
// 账号密码登录
|
||||
return {
|
||||
register,
|
||||
getIsNotPwd,
|
||||
goLogin,
|
||||
realName,
|
||||
unLock,
|
||||
errMsgRef,
|
||||
loadingRef,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import (reference) '../../../design/index.less';
|
||||
|
||||
.lock-page {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 999999;
|
||||
display: flex;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: url(../../../assets/images/lock-page.jpg) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
&__entry {
|
||||
position: relative;
|
||||
width: 400px;
|
||||
height: 260px;
|
||||
padding: 80px 50px 0 50px;
|
||||
margin-right: 50px;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
position: absolute;
|
||||
top: -35px;
|
||||
left: calc(50% - 45px);
|
||||
width: auto;
|
||||
text-align: center;
|
||||
|
||||
&-img {
|
||||
width: 70px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
&-name {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
137
src/views/sys/login/Login.vue
Normal file
137
src/views/sys/login/Login.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div class="login h-screen relative">
|
||||
<div class="login-mask h-full hidden lg:block" />
|
||||
<div
|
||||
class="h-full absolute right-0 top-0 w-full lg:w-2/5 xl:w-1/3 flex justify-center items-center"
|
||||
>
|
||||
<div class="login-form bg-white w-full rounded-sm border-solid bg-clip-padding mx-6 xl:mx-14">
|
||||
<div class="w-full h-full border border-gray-600 px-2 py-10 rounded-sm">
|
||||
<header class="flex justify-center items-center">
|
||||
<img src="/@/assets/images/logo.png" class="w-12 mr-4 inline-block" />
|
||||
<h1 class="text-2xl text-center text-primary tracking-wide">Vben Admin 2.0</h1>
|
||||
</header>
|
||||
|
||||
<a-form class="w-4/5 mx-auto mt-10" :model="formData" :rules="formRules" ref="formRef">
|
||||
<a-form-item name="account">
|
||||
<a-input size="large" v-model:value="formData.account" placeholder="vben" />
|
||||
</a-form-item>
|
||||
<a-form-item name="password">
|
||||
<a-input-password
|
||||
autofocus="autofocus"
|
||||
size="large"
|
||||
visibilityToggle
|
||||
v-model:value="formData.password"
|
||||
placeholder="123456"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item name="verify" v-if="openLoginVerify">
|
||||
<BasicDragVerify v-model:value="formData.verify" ref="verifyRef" />
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="large"
|
||||
class="rounded-sm"
|
||||
block
|
||||
@click="login"
|
||||
:loading="formState.loading"
|
||||
>登录</a-button
|
||||
>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref, unref, toRaw, computed } from 'vue';
|
||||
import { BasicDragVerify, DragVerifyActionType } from '/@/components/Verify/index';
|
||||
import { userStore } from '/@/store/modules/user';
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
export default defineComponent({
|
||||
components: { BasicDragVerify },
|
||||
setup() {
|
||||
const { notification } = useMessage();
|
||||
const formRef = ref<any>(null);
|
||||
const verifyRef = ref<RefInstanceType<DragVerifyActionType>>(null);
|
||||
|
||||
const openLoginVerifyRef = computed(() => appStore.getProjectConfig.openLoginVerify);
|
||||
|
||||
const formData = reactive({
|
||||
account: '',
|
||||
password: '',
|
||||
verify: undefined,
|
||||
});
|
||||
const formState = reactive({
|
||||
loading: false,
|
||||
});
|
||||
|
||||
const formRules = reactive({
|
||||
account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
verify: unref(openLoginVerifyRef) ? [{ required: true, message: '请通过验证码校验' }] : [],
|
||||
});
|
||||
|
||||
function resetVerify() {
|
||||
const verify = unref(verifyRef);
|
||||
if (!verify) return;
|
||||
formData.verify && verify.$.resume();
|
||||
formData.verify = undefined;
|
||||
}
|
||||
|
||||
async function handleLogin() {
|
||||
const form = unref(formRef);
|
||||
if (!form) return;
|
||||
formState.loading = true;
|
||||
try {
|
||||
const data = await form.validate();
|
||||
const userInfo = await userStore.login(
|
||||
toRaw({
|
||||
password: data.password,
|
||||
username: data.account,
|
||||
})
|
||||
);
|
||||
if (userInfo) {
|
||||
notification.success({
|
||||
message: '登录成功',
|
||||
description: `欢迎回来: ${userInfo.realName}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
} finally {
|
||||
resetVerify();
|
||||
formState.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
formRef,
|
||||
verifyRef,
|
||||
formData,
|
||||
formState,
|
||||
formRules,
|
||||
login: handleLogin,
|
||||
openLoginVerify: openLoginVerifyRef,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.login {
|
||||
background: url(../../../assets/images/login/login-bg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
|
||||
&-mask {
|
||||
background: url(../../../assets/images/login/login-in.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
&-form {
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
border-width: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
32
src/views/sys/redirect/index.vue
Normal file
32
src/views/sys/redirect/index.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, onBeforeMount, unref } from 'vue';
|
||||
|
||||
import { appStore } from '/@/store/modules/app';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
export default defineComponent({
|
||||
name: 'Redirect',
|
||||
setup() {
|
||||
const { currentRoute, replace } = useRouter();
|
||||
onBeforeMount(() => {
|
||||
const { params, query } = unref(currentRoute);
|
||||
const { path } = params;
|
||||
const _path = Array.isArray(path) ? path.join('/') : path;
|
||||
replace({
|
||||
path: '/' + _path,
|
||||
query,
|
||||
});
|
||||
const { openRouterTransition, openPageLoading } = appStore.getProjectConfig;
|
||||
if (openRouterTransition && openPageLoading) {
|
||||
setTimeout(() => {
|
||||
appStore.setPageLoadingAction(false);
|
||||
}, 0);
|
||||
}
|
||||
});
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
Reference in New Issue
Block a user