提交 09ed5c22 authored 作者: yanyalin's avatar yanyalin

页面调整

上级 96121ad7
{
"globals": {
"Component": true,
"ComponentPublicInstance": true,
"ComputedRef": true,
"EffectScope": true,
"ExtractDefaultPropTypes": true,
"ExtractPropTypes": true,
"ExtractPublicPropTypes": true,
"InjectionKey": true,
"PropType": true,
"Ref": true,
"VNode": true,
"WritableComputedRef": true,
"acceptHMRUpdate": true,
"computed": true,
"createApp": true,
"createPinia": true,
"customRef": true,
"defineAsyncComponent": true,
"defineComponent": true,
"defineStore": true,
"effectScope": true,
"getActivePinia": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"h": true,
"inject": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"mapActions": true,
"mapGetters": true,
"mapState": true,
"mapStores": true,
"mapWritableState": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeRouteLeave": true,
"onBeforeRouteUpdate": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onDeactivated": true,
"onErrorCaptured": true,
"onMounted": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onUnmounted": true,
"onUpdated": true,
"provide": true,
"reactive": true,
"readonly": true,
"ref": true,
"resolveComponent": true,
"setActivePinia": true,
"setMapStoreSuffix": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"storeToRefs": true,
"toRaw": true,
"toRef": true,
"toRefs": true,
"toValue": true,
"triggerRef": true,
"unref": true,
"useAttrs": true,
"useCssModule": true,
"useCssVars": true,
"useLink": true,
"useMouse": true,
"useMyFetch": true,
"useRoute": true,
"useRouter": true,
"useSlots": true,
"watch": true,
"watchEffect": true,
"watchPostEffect": true,
"watchSyncEffect": true
}
}
......@@ -7,7 +7,8 @@ module.exports = {
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
'@vue/eslint-config-prettier/skip-formatting',
"./.eslintrc-auto-import.json",
],
parserOptions: {
ecmaVersion: 'latest'
......
......@@ -18,6 +18,7 @@
"@logicflow/core": "~1.2.28",
"@logicflow/extension": "~1.2.28",
"@vueuse/core": "^11.0.3",
"ant-design-vue": "^4.2.3",
"axios": "^1.7.7",
"codemirror": "^6.0.1",
"cropperjs": "^1.6.2",
......@@ -37,6 +38,7 @@
"nprogress": "^0.2.0",
"pinia": "^2.1.6",
"pinyin-pro": "^3.18.2",
"qs": "^6.13.0",
"screenfull": "^6.0.2",
"use-element-plus-theme": "^0.0.5",
"vue": "^3.5.4",
......@@ -53,17 +55,24 @@
"@types/lodash": "^4.17.7",
"@types/node": "^18.17.5",
"@types/nprogress": "^0.2.0",
"@vitejs/plugin-vue": "^4.3.1",
"@types/qs": "^6.9.15",
"@vitejs/plugin-vue": "^5.1.3",
"@vitejs/plugin-vue-jsx": "^4.0.1",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^11.0.3",
"@vue/test-utils": "^2.4.1",
"@vue/tsconfig": "^0.4.0",
"autoprefixer": "^10.4.20",
"eslint": "~8.57.0",
"eslint-plugin-vue": "^9.16.1",
"jsdom": "^22.1.0",
"postcss": "^8.4.45",
"prettier": "^3.0.0",
"sass": "^1.66.1",
"tailwindcss": "^3.4.11",
"typescript": "~5.6.2",
"unplugin-auto-import": "^0.18.3",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.4.4",
"vite-plugin-vue-devtools": "^7.4.5",
"vitest": "^0.34.2",
......
差异被折叠。
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
<template>
<router-view />
</template>
\ No newline at end of file
</template>
<script setup lang="ts">
import { useFavicon, useTitle } from "@vueuse/core";
import { getConfigInfo } from '@/utils/api'
const title = useTitle();
getConfigInfo("PRODUCT-NAME").then(res => {
title.value = res;
});
const icon = useFavicon();
getConfigInfo("ICON").then(res => {
icon.value = "data:image/x-icon;base64," + res;
});
const logoImage = ref<string>("");
getConfigInfo("LOGO").then(res => {
logoImage.value = "data:image/jpg;base64," + res;
});
provide("logoImage", logoImage);
</script>
<style>
@tailwind base;
@tailwind components;
@tailwind utilities;
</style>
\ No newline at end of file
......@@ -80,15 +80,15 @@ interface ResetCurrentUserPasswordRequest {
/**
* 验证码
*/
code: string
password: string
/**
*密码
*/
password: string
new_password: string
/**
* 确认密码
*/
re_password: string
repeat_new_password: string
}
interface ResetPasswordRequest {
......
<template>
<div class="login-form-container">
<div class="login-title">
<div class="logo text-center">
<LogoFull height="45px" />
</div>
<div class="sub-title text-center" v-if="subTitle">
<el-text type="info">{{ subTitle }}</el-text>
</div>
<div class="text-[28px] text-center mb-8">
{{ subTitle }}
</div>
<el-card class="login-card">
<slot></slot>
......@@ -18,18 +13,11 @@ defineOptions({ name: 'LoginContainer' })
defineProps({
title: String,
subTitle: String
})
});
</script>
<style lang="scss" scoped>
.login-form-container {
width: 480px;
.login-title {
margin-bottom: 32px;
.sub-title {
font-size: 16px;
}
}
.login-card {
border-radius: 8px;
padding: 18px;
......
<template>
<div class="login-warp flex-center">
<div class="login-container w-full h-full">
<el-row class="container w-full h-full">
<el-col :xs="0" :sm="0" :md="10" :lg="10" :xl="10" class="left-container">
<div class="login-image" :style="{ backgroundImage: `url(${loginImage})` }"></div>
<el-row class="w-full h-full">
<el-col :xs="0" :sm="0" :md="10" :lg="10" :xl="14" class="left-container">
<div class="login-image"><img class="w-full h-full block" src="@/assets/login.png" alt="" /></div>
</el-col>
<el-col :xs="24" :sm="24" :md="14" :lg="14" :xl="14" class="right-container flex-center">
<el-col :xs="24" :sm="24" :md="14" :lg="14" :xl="10" class="right-container flex-center">
<slot></slot>
</el-col>
</el-row>
......@@ -13,42 +13,13 @@
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { getThemeImg } from '@/utils/theme'
import useStore from '@/stores'
import { request } from '@/request'
defineOptions({ name: 'LoginLayout' })
const { user } = useStore()
const fileURL = computed(() => {
if (user.themeInfo?.loginImage) {
if (typeof user.themeInfo?.loginImage === 'string') {
return user.themeInfo?.loginImage
} else {
return URL.createObjectURL(user.themeInfo?.loginImage)
}
} else {
return ''
}
})
const loginImage = computed(() => {
if (user.themeInfo?.loginImage) {
return `${fileURL.value}`
} else {
return new URL(`../../assets/theme/${getThemeImg(user.themeInfo?.theme)}.jpg`, import.meta.url)
.href
}
})
</script>
<style lang="scss" scoped>
.login-warp {
height: 100vh;
.login-image {
background-repeat: no-repeat;
background-position: center;
background-size: cover;
width: 100%;
height: 100%;
}
......
<template>
<el-dialog v-model="resetPasswordDialog" :title="$t('layout.topbar.avatar.resetPassword')">
<el-form
class="reset-password-form mb-24"
ref="resetPasswordFormRef"
:model="resetPasswordForm"
:rules="rules"
>
<p class="mb-8 lighter">{{ $t("layout.topbar.avatar.dialog.newPassword") }}</p>
<el-form-item prop="password" style="margin-bottom: 8px">
<el-dialog v-model="resetPasswordDialog" title="修改密码">
<el-form ref="resetPasswordFormRef" :model="resetPasswordForm" :rules="rules">
<el-form-item prop="password">
<el-input
type="password"
class="input-item"
v-model="resetPasswordForm.password"
:placeholder="$t('layout.topbar.avatar.dialog.enterPassword')"
placeholder="请输入密码"
show-password
>
</el-input>
/>
</el-form-item>
<el-form-item prop="re_password">
<el-form-item prop="new_password">
<el-input
type="password"
class="input-item"
v-model="resetPasswordForm.re_password"
:placeholder="$t('layout.topbar.avatar.dialog.confirmPassword')"
v-model="resetPasswordForm.new_password"
placeholder="请输入密码"
show-password
>
</el-input>
</el-form-item>
<p class="mb-8 lighter">{{ $t("layout.topbar.avatar.dialog.useEmail") }}</p>
<el-form-item style="margin-bottom: 8px">
<el-form-item prop="repeat_new_password">
<el-input
type="password"
class="input-item"
:disabled="true"
v-bind:modelValue="user.userInfo?.email"
:placeholder="$t('layout.topbar.avatar.dialog.enterEmail')"
v-model="resetPasswordForm.repeat_new_password"
placeholder="请输入确认密码"
show-password
>
</el-input>
</el-form-item>
<el-form-item prop="code">
<div class="flex-between w-full">
<el-input class="code-input" v-model="resetPasswordForm.code" :placeholder="$t('layout.topbar.avatar.dialog.enterVerificationCode')">
</el-input>
<el-button
:disabled="isDisabled"
class="send-email-button ml-8"
@click="sendEmail"
:loading="loading"
>
{{ isDisabled ? $t('layout.topbar.avatar.dialog.resend', { time }) : $t('layout.topbar.avatar.dialog.getVerificationCode') }}
</el-button>
</div>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="resetPasswordDialog = false">{{ $t('layout.topbar.avatar.dialog.cancel') }}</el-button>
<el-button type="primary" @click="resetPassword"> {{ $t('layout.topbar.avatar.dialog.save') }} </el-button>
<el-button @click="resetPasswordDialog = false">取消</el-button>
<el-button type="primary" @click="resetPassword">保存</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { ResetCurrentUserPasswordRequest } from '@/api/type/user'
import type { FormInstance, FormRules } from 'element-plus'
import { MsgSuccess } from '@/utils/message'
import UserApi from '@/api/user'
import useStore from '@/stores'
import { useRouter } from 'vue-router'
import { t } from '@/locales'
import { editPassword } from "@/utils/api";
const router = useRouter()
const { user } = useStore()
const resetPasswordDialog = ref<boolean>(false)
const resetPasswordForm = ref<ResetCurrentUserPasswordRequest>({
code: '',
password: '',
re_password: ''
new_password: '',
repeat_new_password: ''
})
const resetPasswordFormRef = ref<FormInstance>()
const loading = ref<boolean>(false)
const isDisabled = ref<boolean>(false)
const time = ref<number>(60)
const rules = ref<FormRules<ResetCurrentUserPasswordRequest>>({
// @ts-ignore
code: [{ required: true, message: t('layout.topbar.avatar.dialog.enterVerificationCode'), trigger: 'blur' }],
password: [
password: [{ required: true, message: "请输入旧密码", trigger: 'blur' }],
new_password: [
{
required: true,
message: t('layout.topbar.avatar.dialog.enterPassword'),
message: '请输入新密码',
trigger: 'blur'
},
{
min: 6,
max: 20,
message: t('layout.topbar.avatar.dialog.passwordLength'),
message: '长度在6~20',
trigger: 'blur'
}
],
re_password: [
repeat_new_password: [
{
required: true,
message: t('layout.topbar.avatar.dialog.confirmPassword'),
message: '请确认新密码',
trigger: 'blur'
},
{
min: 6,
max: 20,
message: t('layout.topbar.avatar.dialog.passwordLength'),
message: '长度在6~20',
trigger: 'blur'
},
{
validator: (rule, value, callback) => {
if (resetPasswordForm.value.password != resetPasswordForm.value.re_password) {
callback(new Error(t('layout.topbar.avatar.dialog.passwordMismatch')))
if (resetPasswordForm.value.new_password != resetPasswordForm.value.repeat_new_password) {
callback(new Error('两次密码不一致'))
} else {
callback()
}
......@@ -126,49 +95,24 @@ const rules = ref<FormRules<ResetCurrentUserPasswordRequest>>({
}
]
})
/**
* 发送验证码
*/
const sendEmail = () => {
UserApi.sendEmailToCurrent(loading).then(() => {
MsgSuccess(t('verificationCodeSentSuccess'))
isDisabled.value = true
handleTimeChange()
})
}
const handleTimeChange = () => {
if (time.value <= 0) {
isDisabled.value = false
time.value = 60
} else {
setTimeout(() => {
time.value--
handleTimeChange()
}, 1000)
}
}
const open = () => {
resetPasswordForm.value = {
code: '',
password: '',
re_password: ''
new_password: '',
repeat_new_password: ''
}
resetPasswordDialog.value = true
resetPasswordFormRef.value?.resetFields()
}
const resetPassword = () => {
resetPasswordFormRef.value
?.validate()
.then(() => {
return UserApi.resetCurrentUserPassword(resetPasswordForm.value)
})
.then(() => {
return user.logout()
})
.then(() => {
router.push({ name: 'login' })
resetPasswordFormRef.value?.validate().then(() => {
editPassword(resetPasswordForm.value).then(res => {
if (res) {
sessionStorage.clear();
router.push("/login")
}
})
})
}
const close = () => {
......@@ -176,7 +120,4 @@ const close = () => {
}
defineExpose({ open, close })
</script>
<style lang="scss" scoped></style>
......@@ -4,7 +4,7 @@
<AppAvatar>
<img src="@/assets/user-icon.svg" style="width: 54%" alt=""/>
</AppAvatar>
<span class="ml-8">{{ user.userInfo?.username }}</span>
<span class="ml-4">{{ user.name }}</span>
<el-icon class="el-icon--right">
<CaretBottom/>
</el-icon>
......@@ -12,88 +12,32 @@
<template #dropdown>
<el-dropdown-menu class="avatar-dropdown">
<div class="userInfo">
<p class="bold mb-4" style="font-size: 14px">{{ user.userInfo?.username }}</p>
<p>
<el-text type="info">
{{ user.userInfo?.email }}
</el-text>
</p>
</div>
<el-dropdown-item class="border-t p-8" @click="openResetPassword">
{{ $t('layout.topbar.avatar.resetPassword') }}
</el-dropdown-item>
<div v-hasPermission="new ComplexPermission([], ['x-pack'], 'OR')">
<el-dropdown-item class="border-t p-8" @click="openAPIKeyDialog">
{{ $t('layout.topbar.avatar.apiKey') }}
</el-dropdown-item>
</div>
<el-dropdown-item class="border-t" @click="openAbout">
{{ $t('layout.topbar.avatar.about') }}
<el-dropdown-item @click="openResetPassword">
重置密码
</el-dropdown-item>
<el-dropdown-item class="border-t" @click="logout">
{{ $t('layout.topbar.avatar.logout') }}
退出
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<ResetPassword ref="resetPasswordRef"></ResetPassword>
<AboutDialog ref="AboutDialogRef"></AboutDialog>
<APIKeyDialog :user-id="user.userInfo?.id" ref="APIKeyDialogRef"/>
<UserPwdDialog ref="UserPwdDialogRef"/>
</template>
<script setup lang="ts">
import {ref, onMounted} from 'vue'
import useStore from '@/stores'
import {useRouter} from 'vue-router'
import ResetPassword from './ResetPassword.vue'
import AboutDialog from './AboutDialog.vue'
import UserPwdDialog from '@/views/user-manage/component/UserPwdDialog.vue'
import APIKeyDialog from './APIKeyDialog.vue'
import {ComplexPermission} from '@/utils/permission/type'
const {user} = useStore()
import { layout } from "@/utils/api";
const router = useRouter()
const UserPwdDialogRef = ref()
const AboutDialogRef = ref()
const APIKeyDialogRef = ref()
const resetPasswordRef = ref<InstanceType<typeof ResetPassword>>()
const openAbout = () => {
AboutDialogRef.value?.open()
}
function openAPIKeyDialog() {
APIKeyDialogRef.value.open()
}
const openResetPassword = () => {
resetPasswordRef.value?.open()
}
const logout = () => {
user.logout().then(() => {
layout().then(() => {
router.push({name: 'login'})
})
}
onMounted(() => {
if (user.userInfo?.is_edit_password) {
UserPwdDialogRef.value.open(user.userInfo)
}
})
const user = JSON.parse(sessionStorage.getItem("userInfo"));
</script>
<style lang="scss" scoped>
.avatar-dropdown {
min-width: 210px;
.userInfo {
padding: 12px 11px;
}
:deep(.el-dropdown-menu__item) {
padding: 12px 11px;
}
}
</style>
......@@ -3,47 +3,11 @@
<div class="top-bar-container border-b flex-between">
<div class="flex-center h-full">
<div class="app-title-container cursor" @click="router.push('/')">
<div class="logo flex-center">
<LogoFull />
</div>
<img :src="logo" class="w-[180px] mt-[8px]" alt="" />
</div>
<TopMenu></TopMenu>
</div>
<div class="flex-center avatar">
<el-button
v-if="!user.isEnterprise()"
link
type="primary"
@click="toUrl('https://maxkb.cn/pricing.html')"
class="mr-8"
>
<AppIcon iconName="app-pricing" class="mr-8" style="font-size: 20px"></AppIcon>
购买专业版
</el-button>
<el-tooltip effect="dark" :content="$t('layout.topbar.github')" placement="top">
<AppIcon
iconName="app-github"
class="cursor color-secondary mr-8 ml-8"
style="font-size: 20px"
@click="toUrl('https://github.com/1Panel-dev/MaxKB')"
></AppIcon>
</el-tooltip>
<el-tooltip effect="dark" :content="$t('layout.topbar.wiki')" placement="top">
<AppIcon
iconName="app-reading"
class="cursor color-secondary mr-8 ml-8"
style="font-size: 20px"
@click="toUrl('https://maxkb.cn/docs/')"
></AppIcon>
</el-tooltip>
<el-tooltip effect="dark" :content="$t('layout.topbar.forum')" placement="top">
<AppIcon
iconName="app-help"
class="cursor color-secondary mr-16 ml-8"
style="font-size: 20px"
@click="toUrl('https://bbs.fit2cloud.com/c/mk/11')"
></AppIcon>
</el-tooltip>
<el-dropdown v-if="false" trigger="click" type="primary">
<template #dropdown>
<el-dropdown-menu>
......@@ -71,22 +35,19 @@
import TopMenu from './top-menu/index.vue'
import Avatar from './avatar/index.vue'
import { useRouter } from 'vue-router'
import { langList } from '@/locales/index'
import { langList } from '@/locales'
import { useLocale } from '@/locales/useLocale'
import useStore from '@/stores'
const { user } = useStore()
const router = useRouter()
const { changeLocale } = useLocale()
const changeLang = (lang: string) => {
changeLocale(lang)
}
function toUrl(url: string) {
window.open(url, '_blank')
}
const logo: string = inject("logoImage");
</script>
<style lang="scss">
<style scoped lang="scss">
.top-bar-container {
height: var(--app-header-height);
box-sizing: border-box;
......
......@@ -2,7 +2,6 @@
<div class="top-menu-container flex align-center h-full">
<MenuItem
:menu="menu"
v-hasPermission="menu.meta?.permission"
v-for="(menu, index) in topMenuList"
:key="index"
>
......@@ -10,14 +9,10 @@
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { getChildRouteListByPathAndName } from '@/router/index'
import { getChildRouteListByPathAndName } from '@/router'
import MenuItem from './MenuItem.vue'
const topMenuList = computed(() => {
return getChildRouteListByPathAndName('/', 'home')
})
</script>
<style lang="scss" scoped>
</style>
});
</script>
\ No newline at end of file
......@@ -58,5 +58,4 @@ app.use(ElementPlus, {
app.use(router)
app.use(i18n)
app.use(Components)
app.mount('#app')
export { app }
app.mount('#app');
\ No newline at end of file
......@@ -14,6 +14,7 @@ const router = createRouter({
})
// 路由前置拦截器
/*
router.beforeEach(
async (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
if (to.name === '404') {
......@@ -41,6 +42,7 @@ router.beforeEach(
next()
}
)
*/
export const getChildRouteListByPathAndName = (path: any, name?: RouteRecordName | any) => {
return getChildRouteList(routes, path, name)
......
import Layout from '@/layout/layout-template/DetailLayout.vue'
const applicationRouter = {
path: '/application',
name: 'application',
meta: { title: '应用', permission: 'APPLICATION:READ' },
path: '/',
name: 'app',
meta: { title: '应用' },
redirect: '/application',
component: () => import('@/layout/layout-template/AppLayout.vue'),
children: [
......
import type { RouteRecordRaw } from 'vue-router'
import { Role } from '@/utils/permission/type'
import type { RouteRecordRaw } from 'vue-router';
const modules: any = import.meta.glob('./modules/*.ts', { eager: true })
const rolesRoutes: RouteRecordRaw[] = [...Object.keys(modules).map((key) => modules[key].default)]
const rolesRoutes: RouteRecordRaw[] = [...Object.keys(modules).map((key) => modules[key].default)];
export const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'home',
redirect: '/application',
children: [...rolesRoutes]
children: [rolesRoutes[0]]
},
// 高级编排
{
path: '/application/:id/workflow',
......@@ -32,16 +29,6 @@ export const routes: Array<RouteRecordRaw> = [
component: () => import('@/views/login/index.vue')
},
{
path: '/register',
name: 'register',
component: () => import('@/views/login/register/index.vue')
},
{
path: '/forgot_password',
name: 'forgot_password',
component: () => import('@/views/login/forgot-password/index.vue')
},
{
path: '/reset_password/:code/:email',
name: 'reset_password',
component: () => import('@/views/login/reset-password/index.vue')
......
......@@ -117,8 +117,8 @@ const useUserStore = defineStore({
})
},
async login(username: string, password: string) {
return UserApi.login({ username, password }).then((ok) => {
async login(email: string, password: string) {
return UserApi.login({ email, password }).then((ok) => {
this.token = ok.data
localStorage.setItem('token', ok.data)
return this.profile()
......
差异被折叠。
import axios from "axios";
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from "axios";
import { ElNotification } from 'element-plus';
import router from "@/router";
let bool = false;
function layout(text?: string) {
!bool && ElNotification.error({
title: "通知",
message: text || "登录超时",
});
!bool && router.push("/login");
bool = true
const timout = setTimeout(() => {
bool = false
clearTimeout(timout);
}, 4500)
}
const regex = /^2([0-9]{2})$/; //判断数字大于等于200,小于300
interface ISettings {
prompt?: boolean; //报错是否全局提示
}
type jsonData = Record<string, any>;
class Config {
private instance: AxiosInstance;
private settings: ISettings;
constructor(
requeseConfig,
settings = { prompt: true },
) {
this.instance = axios.create(requeseConfig);
this.settings = settings;
// 全局请求拦截
this.instance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
if (config.baseURL === "/console/api") {
config.headers["Authorization"] = "Bearer " + sessionStorage.getItem("token");
} else if (config.baseURL === "/api") {
config.headers["Authorization"] = "Bearer " + sessionStorage.getItem("console-token");
}
return config;
},
(error) => {
return error;
},
);
// 全局响应拦截
this.instance.interceptors.response.use(
(res) => {
if (res.data.status === 401) {
layout();
return;
}
return res;
},
(error) => {
if (error?.response) {
switch (error.response.status) {
case 400:
this.settings?.prompt &&
ElNotification.error({
title: "通知",
message: error.response.data.message,
});
break;
case 401:
layout(error.response.data.message);
break;
case 403:
this.settings?.prompt &&
ElNotification.error({
title: "通知",
message: error.response.data.message,
});
break;
case 404:
this.settings?.prompt &&
ElNotification.error({
title: "通知",
message: error.response.data.message,
});
break;
case 405:
this.settings?.prompt &&
ElNotification.error({
title: "通知",
message:
"请求方法未允许 " + error.response.data.message,
});
break;
case 408:
this.settings?.prompt &&
ElNotification.error({
title: "通知",
message: error.response.data.message,
});
break;
case 413:
this.settings?.prompt &&
ElNotification.error({
title: "通知",
message: error.response.data.message,
});
break;
case 409:
this.settings?.prompt &&
ElNotification.error({
title: "通知",
message: error.response.data.message,
});
break;
case 500:
this.settings?.prompt &&
ElNotification.error({
title: "通知",
message: error.response.data.message || error.response.data.statusText,
});
break;
case 501:
this.settings?.prompt &&
ElNotification.error({
title: "通知",
message: "网络未实现",
});
break;
case 502:
this.settings?.prompt &&
ElNotification.error({
title: "服务异常",
message: "服务重启中...",
});
break;
case 503:
this.settings?.prompt &&
ElNotification.error({
title: "服务异常",
message: "服务重启中...",
});
break;
case 504:
this.settings?.prompt &&
ElNotification.error({
title: "通知",
message: "网络超时",
});
break;
case 505:
this.settings?.prompt &&
ElNotification.error({
title: "通知",
message: "http版本不支持该请求",
});
break;
default:
this.settings?.prompt &&
ElNotification.error({
title: "通知",
message: `连接错误`,
});
}
}
return error?.response?.data || error;
},
);
}
get(url: string, params?: jsonData, headers?: jsonData) {
return new Promise<any>((resolve, reject) => {
this.instance
.get(url, { params, headers })
.then((res) => {
if (regex.test(res.status as any as string)) {
resolve(res.data);
} else {
reject(res)
}
})
.catch((err) => {
reject(err);
});
});
}
post(url: string, data?: any, params?: jsonData, headers?: jsonData) {
return new Promise<any>((resolve, reject) => {
this.instance
.post(url, data, { params, headers })
.then((res) => {
if (regex.test(res.status as any as string)) {
resolve(res.data);
} else {
reject(res)
}
})
.catch((err) => {
reject(err);
});
});
}
delete(url: string, config?: AxiosRequestConfig) {
return new Promise<any>((resolve, reject) => {
this.instance
.delete(url, config)
.then((res) => {
if (regex.test(res.status as any as string)) {
resolve(res.data);
} else {
reject(res)
}
})
.catch((err) => {
reject(err);
});
});
}
put(url: string, data: any, params?: jsonData, headers?: jsonData) {
return new Promise<any>((resolve, reject) => {
this.instance
.put(url, data, { params, headers })
.then((res) => {
if (regex.test(res.status as any as string)) {
resolve(res.data);
} else {
reject(res)
}
})
.catch((err) => {
reject(err);
});
});
}
patch(url: string, data?: any, params?: jsonData, headers?: jsonData) {
return new Promise<any>((resolve, reject) => {
this.instance
.patch(url, data, { params, headers })
.then((res) => {
if (regex.test(res.status as any as string)) {
resolve(res.data);
} else {
reject(res)
}
})
.catch((err) => {
reject(err);
});
});
}
requestConfig(config: AxiosRequestConfig) {
return new Promise<any>((resolve, reject) => {
this.instance
.request(config)
.then((res) => {
if (regex.test(res.status as any as string)) {
resolve(res.data);
} else {
reject(res)
}
})
.catch((err) => {
reject(err);
});
});
}
postForm(url: string, data: jsonData, params?: jsonData, headers?: jsonData) {
return new Promise<any>((resolve, reject) => {
this.instance
.postForm(url, data, { params, headers })
.then((res) => {
if (regex.test(res.status as any as string)) {
resolve(res.data);
} else {
reject(res)
}
})
.catch((err) => {
reject(err);
});
});
}
}
export default Config;
import Config from "./config";
export const request = new Config({baseURL: "/console/api"});
export const api = new Config({baseURL: "/api"});
export const noPrompt = new Config({baseURL: "/console/api"}, { prompt: false });
<template>
<login-layout>
<LoginContainer subTitle="欢迎使用 MaxKB 智能知识库">
<h2 class="mb-24">忘记密码</h2>
<el-form
class="register-form"
ref="resetPasswordFormRef"
:model="CheckEmailForm"
:rules="rules"
>
<div class="mb-24">
<el-form-item prop="email">
<el-input
size="large"
class="input-item"
v-model="CheckEmailForm.email"
placeholder="请输入邮箱"
>
</el-input>
</el-form-item>
</div>
<div class="mb-24">
<el-form-item prop="code">
<div class="flex-between w-full">
<el-input
size="large"
class="code-input"
v-model="CheckEmailForm.code"
placeholder="请输入验证码"
>
</el-input>
<el-button
:disabled="isDisabled"
size="large"
class="send-email-button ml-12"
@click="sendEmail"
:loading="loading"
>
{{ isDisabled ? `重新发送(${time}s)` : '获取验证码' }}</el-button
>
</div>
</el-form-item>
</div>
</el-form>
<el-button size="large" type="primary" class="w-full" @click="checkCode">立即验证</el-button>
<div class="operate-container mt-12">
<el-button
class="register"
@click="router.push('/login')"
link
type="primary"
icon="ArrowLeft"
>
返回登录
</el-button>
</div>
</LoginContainer>
</login-layout>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CheckCodeRequest } from '@/api/type/user'
import { useRouter } from 'vue-router'
import type { FormInstance, FormRules } from 'element-plus'
import UserApi from '@/api/user'
import { MsgSuccess } from '@/utils/message'
const router = useRouter()
const CheckEmailForm = ref<CheckCodeRequest>({
email: '',
code: '',
type: 'reset_password'
})
const resetPasswordFormRef = ref<FormInstance>()
const rules = ref<FormRules<CheckCodeRequest>>({
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{
validator: (rule, value, callback) => {
const emailRegExp = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/
if (!emailRegExp.test(value) && value != '') {
callback(new Error('请输入有效邮箱格式!'))
} else {
callback()
}
},
trigger: 'blur'
}
],
code: [{ required: true, message: '请输入验证码' }]
})
const loading = ref<boolean>(false)
const isDisabled = ref<boolean>(false)
const time = ref<number>(60)
const checkCode = () => {
resetPasswordFormRef.value
?.validate()
.then(() => UserApi.checkCode(CheckEmailForm.value, loading))
.then(() => router.push({ name: 'reset_password', params: CheckEmailForm.value }))
}
/**
* 发送验证码
*/
const sendEmail = () => {
resetPasswordFormRef.value?.validateField('email', (v: boolean) => {
if (v) {
UserApi.sendEmit(CheckEmailForm.value.email, 'reset_password', loading).then(() => {
MsgSuccess('发送验证码成功')
isDisabled.value = true
handleTimeChange()
})
}
})
}
const handleTimeChange = () => {
if (time.value <= 0) {
isDisabled.value = false
time.value = 60
} else {
setTimeout(() => {
time.value--
handleTimeChange()
}, 1000)
}
}
</script>
<style lang="scss" scoped></style>
<template>
<login-layout v-if="user.isEnterprise() ? user.themeInfo : true" v-loading="loading">
<LoginContainer :subTitle="user.themeInfo?.slogan || '欢迎使用 MaxKB 智能知识库'">
<LoginContainer subTitle="欢迎使用 快际新云 智能知识库">
<el-form
class="login-form"
:rules="rules"
......@@ -8,45 +8,28 @@
ref="loginFormRef"
@keyup.enter="login"
>
<div class="mb-24">
<el-form-item prop="username">
<el-input
size="large"
class="input-item"
v-model="loginForm.email"
placeholder="请输入用户名"
>
</el-input>
</el-form-item>
</div>
<div class="mb-24">
<el-form-item prop="password">
<el-input
type="password"
size="large"
class="input-item"
v-model="loginForm.password"
placeholder="请输入密码"
show-password
>
</el-input>
</el-form-item>
</div>
<el-form-item prop="username" class="mb-[30px]">
<el-input
size="large"
class="input-item"
v-model="loginForm.email"
placeholder="请输入用户名"
>
</el-input>
</el-form-item>
<el-form-item prop="password" class="mb-[30px]">
<el-input
type="password"
size="large"
class="input-item"
v-model="loginForm.password"
placeholder="请输入密码"
show-password
>
</el-input>
</el-form-item>
</el-form>
<el-button size="large" type="primary" class="w-full" @click="login">登录</el-button>
<div class="operate-container flex-between mt-12">
<!-- <el-button class="register" @click="router.push('/register')" link type="primary">
注册
</el-button> -->
<el-button
class="forgot-password"
@click="router.push('/forgot_password')"
link
type="primary"
>
忘记密码?
</el-button>
</div>
</LoginContainer>
</login-layout>
</template>
......@@ -56,6 +39,7 @@ import type {LoginRequest} from '@/api/type/user'
import {useRouter} from 'vue-router'
import type {FormInstance, FormRules} from 'element-plus'
import useStore from '@/stores'
import { loginPort, userInfo } from '@/utils/api'
const loading = ref<boolean>(false)
const {user} = useStore()
......@@ -86,12 +70,18 @@ const loginFormRef = ref<FormInstance>()
const login = () => {
loginFormRef.value?.validate().then(() => {
loading.value = true
user
.login(loginForm.value.email, loginForm.value.password)
.then(() => {
router.push({name: 'home'})
})
.finally(() => (loading.value = false))
loginPort(loginForm.value).then((res) => {
res && userInfo().then(res => {
sessionStorage.setItem("userInfo", JSON.stringify(res));
const timer = setTimeout(() => {
router.push({name: 'home'});
loading.value = false;
clearTimeout(timer);
}, 300)
})
}).catch(() => {
loading.value = false;
})
})
}
......
<template>
<login-layout>
<LoginContainer subTitle="欢迎使用 MaxKB 智能知识库">
<h2 class="mb-24">用户注册</h2>
<el-form class="register-form" :model="registerForm" :rules="rules" ref="registerFormRef">
<div class="mb-24">
<el-form-item prop="username">
<el-input
size="large"
class="input-item"
v-model="registerForm.username"
placeholder="请输入用户名"
>
</el-input>
</el-form-item>
</div>
<div class="mb-24">
<el-form-item prop="password">
<el-input
type="password"
size="large"
class="input-item"
v-model="registerForm.password"
placeholder="请输入密码"
show-password
>
</el-input>
</el-form-item>
</div>
<div class="mb-24">
<el-form-item prop="re_password">
<el-input
type="password"
size="large"
class="input-item"
v-model="registerForm.re_password"
placeholder="请输入确认密码"
show-password
>
</el-input>
</el-form-item>
</div>
<div class="mb-24">
<el-form-item prop="email">
<el-input
size="large"
class="input-item"
v-model="registerForm.email"
placeholder="请输入邮箱"
>
</el-input>
</el-form-item>
</div>
<div class="mb-24">
<el-form-item prop="code">
<div class="flex-between w-full">
<el-input
size="large"
class="code-input"
v-model="registerForm.code"
placeholder="请输入验证码"
>
</el-input>
<el-button
:disabled="isDisabled"
size="large"
class="send-email-button ml-12"
@click="sendEmail"
:loading="sendEmailLoading"
>
{{ isDisabled ? `重新发送(${time}s)` : '获取验证码' }}</el-button
>
</div>
</el-form-item>
</div>
</el-form>
<el-button size="large" type="primary" class="w-full" @click="register">注册</el-button>
<div class="operate-container mt-12">
<el-button
class="register"
@click="router.push('/login')"
link
type="primary"
icon="ArrowLeft"
>
返回登录
</el-button>
</div>
</LoginContainer>
</login-layout>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { RegisterRequest } from '@/api/type/user'
import { useRouter } from 'vue-router'
import UserApi from '@/api/user'
import { MsgSuccess } from '@/utils/message'
import type { FormInstance, FormRules } from 'element-plus'
const router = useRouter()
const registerForm = ref<RegisterRequest>({
username: '',
password: '',
re_password: '',
email: '',
code: ''
})
const rules = ref<FormRules<RegisterRequest>>({
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur'
},
{
min: 6,
max: 20,
message: '长度在 6 到 20 个字符',
trigger: 'blur'
}
],
password: [
{
required: true,
message: '请输入密码',
trigger: 'blur'
},
{
min: 6,
max: 20,
message: '长度在 6 到 20 个字符',
trigger: 'blur'
}
],
re_password: [
{
required: true,
message: '请输入确认密码',
trigger: 'blur'
},
{
min: 6,
max: 20,
message: '长度在 6 到 20 个字符',
trigger: 'blur'
},
{
validator: (rule, value, callback) => {
if (registerForm.value.password != registerForm.value.re_password) {
callback(new Error('密码不一致'))
} else {
callback()
}
},
trigger: 'blur'
}
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{
validator: (rule, value, callback) => {
const emailRegExp = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/
if (!emailRegExp.test(value) && value != '') {
callback(new Error('请输入有效邮箱格式!'))
} else {
callback()
}
},
trigger: 'blur'
}
],
code: [{ required: true, message: '请输入验证码' }]
})
const registerFormRef = ref<FormInstance>()
const register = () => {
registerFormRef.value
?.validate()
.then(() => {
return UserApi.register(registerForm.value)
})
.then(() => {
router.push('login')
})
}
const sendEmailLoading = ref<boolean>(false)
const isDisabled = ref<boolean>(false)
const time = ref<number>(60)
/**
* 发送验证码
*/
const sendEmail = () => {
registerFormRef.value?.validateField('email', (v: boolean) => {
if (v) {
UserApi.sendEmit(registerForm.value.email, 'register', sendEmailLoading).then(() => {
MsgSuccess('发送验证码成功')
isDisabled.value = true
handleTimeChange()
})
}
})
}
const handleTimeChange = () => {
if (time.value <= 0) {
isDisabled.value = false
time.value = 60
} else {
setTimeout(() => {
time.value--
handleTimeChange()
}, 1000)
}
}
</script>
<style lang="scss" scoped></style>
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{vue,js,jsx,tsx}"],
corePlugins: {
preflight: false,
},
theme: {
screens: {
sm: "640px",
md: "768px",
lg: "1024px",
xl: "1280px",
"2xl": "1536px",
},
colors: {
primary: "#0c599d",
danger: "#ff0000",
white: "#ffffff",
success: "#67C23A",
warning: "#E6A23C",
info: "#909399",
blue: "#409EFF"
},
extend: {},
},
plugins: [],
}
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "./auto-imports.d.ts"],
"exclude": ["src/**/__tests__/*", "node_modules", "public"],
"compilerOptions": {
"composite": true,
"moduleResolution": "node",
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": false,
"jsx": "preserve",
"importHelpers": true,
"experimentalDecorators": true,
"strictFunctionTypes": false,
"skipLibCheck": true,
"esModuleInterop": true,
"isolatedModules": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"allowJs": false,
"resolveJsonModule": true,
"lib": [
"ESNext",
"DOM"
],
"baseUrl": ".",
"target": "esnext", // 使用ES最新语法
"module": "esnext", // 使用ES模块语法
"paths": {
"@/*": ["./src/*"]
"@/*": ["./src/*"],
}
}
}
......@@ -2,27 +2,77 @@ import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from "@vitejs/plugin-vue-jsx";
import vueDevTools from "vite-plugin-vue-devtools";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => {
return {
plugins: [vue(), vueDevTools()],
plugins: [
vue(),
vueDevTools(),
vueJsx(),
AutoImport({
include: [
/\.[j]sx?$/, // .ts, .tsx, .js, .jsx
/\.vue$/,
/\.vue\?vue/, // .vue
/\.md$/, // .md
],
// 全局引入插件
imports: [
// presets
"vue",
"vue-router",
"pinia",
// custom
{
"@vueuse/core": [
// named imports
"useMouse", // import { useMouse } from '@vueuse/core',
// alias
["useFetch", "useMyFetch"], // import { useFetch as useMyFetch } from '@vueuse/core',
],
},
],
eslintrc: {
enabled: false,
filepath: './.eslintrc-auto-import.json',
globalsPropValue: true,
},
resolvers: [],
}),
Components({
resolvers: [],
}),
],
server: {
port: 8917,
cors: true,
proxy: {
"/console": {
target: "https://klm-service-dev.apps.iytcloud.com/",
changeOrigin: true,
secure: false,
},
"/api": {
target: "http://192.168.121.203:18080",
target: "https://klm-service-dev.apps.iytcloud.com/",
changeOrigin: true,
secure: false,
},
"/workspaces": {
target: "https://klm-service-dev.apps.iytcloud.com/",
changeOrigin: true,
secure: false,
}
},
},
resolve: {
extensions: ['.vue', '.ts', '.js', '.json', '.tsx', '.jsx'],
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
"@": fileURLToPath(new URL("./src", import.meta.url)),
}
},
esbuild: {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论