Kelvin 3 years ago
parent 117b20430c
commit 3525532a8d

@ -0,0 +1,2 @@
{
}

@ -40,7 +40,7 @@ axios.interceptors.response.use(
(response: AxiosResponse<HttpResponse>) => {
const res = response.data;
// if the custom code is not 20000, it is judged as an error.
if (res.code !== 20000) {
if (res.code !== 200) {
Message.error({
content: res.msg || 'Error',
duration: 5 * 1000,

@ -9,21 +9,51 @@ export interface LoginData {
uuid: string;
}
export interface RegisterData {
username: string;
password: string;
email: string;
code: string;
uuid: string;
inviteCode: string;
}
export interface LoginRes {
token: string;
}
export interface CaptchaData {
captchaEnabled: boolean;
img: string;
uuid: string;
}
export function login(data: LoginData) {
return axios.post<LoginRes>('/login', data);
}
export function register(data: RegisterData) {
return axios.post<LoginRes>('/register', data);
}
export function logout() {
return axios.post<LoginRes>('/api/user/logout');
return axios.post<LoginRes>('/logout');
}
export function getUserInfo() {
return axios.post<UserState>('/api/user/info');
return axios.get<UserState>('/getInfo');
}
export function getMenuList() {
return axios.post<RouteRecordNormalized[]>('/api/user/menu');
}
// 获取验证码
export function getCodeImg() {
return axios.get<CaptchaData>('/captchaImage2');
}
// 获取邮箱验证码
export function getEmailCode(email: string) {
return axios.get<CaptchaData>(`/captchaMail?email=${email}`);
}

@ -23,7 +23,7 @@
<Menu v-if="topMenu" />
</div>
<ul class="right-side">
<li>
<!-- <li>
<a-tooltip :content="$t('settings.search')">
<a-button class="nav-btn" type="outline" :shape="'circle'">
<template #icon>
@ -31,8 +31,8 @@
</template>
</a-button>
</a-tooltip>
</li>
<li>
</li> -->
<!-- <li>
<a-tooltip :content="$t('settings.language')">
<a-button
class="nav-btn"
@ -60,7 +60,7 @@
</a-doption>
</template>
</a-dropdown>
</li>
</li> -->
<li>
<a-tooltip
:content="
@ -82,7 +82,7 @@
</a-button>
</a-tooltip>
</li>
<li>
<!-- <li>
<a-tooltip :content="$t('settings.navbar.alerts')">
<div class="message-box-trigger">
<a-badge :count="9" dot>
@ -108,7 +108,7 @@
<message-box />
</template>
</a-popover>
</li>
</li> -->
<li>
<a-tooltip
:content="
@ -150,10 +150,10 @@
:size="32"
:style="{ marginRight: '8px', cursor: 'pointer' }"
>
<img alt="avatar" :src="avatar" />
<img alt="avatar" src="//lf1-xgcdn-tos.pstatp.com/obj/vcloud/vadmin/start.8e0e4855ee346a46ccff8ff3e24db27b.png" />
</a-avatar>
<template #content>
<a-doption>
<!-- <a-doption>
<a-space @click="switchRoles">
<icon-tag />
<span>
@ -168,7 +168,7 @@
{{ $t('messageBox.userCenter') }}
</span>
</a-space>
</a-doption>
</a-doption> -->
<a-doption>
<a-space @click="$router.push({ name: 'Setting' })">
<icon-settings />
@ -201,7 +201,7 @@
import useLocale from '@/hooks/locale';
import useUser from '@/hooks/user';
import Menu from '@/components/menu/index.vue';
import MessageBox from '../message-box/index.vue';
// import MessageBox from '../message-box/index.vue';
const appStore = useAppStore();
const userStore = useUserStore();

@ -44,6 +44,8 @@ export default {
'menu.faq': '常见问题',
'navbar.docs': '文档中心',
'navbar.action.locale': '切换为中文',
'menu.pay': '帐户充值 ',
...localeSettings,
...localeMessageBox,
...localeLogin,

@ -1,10 +0,0 @@
export default {
path: 'https://arco.design',
name: 'arcoWebsite',
meta: {
locale: 'menu.arcoWebsite',
icon: 'icon-link',
requiresAuth: true,
order: 8,
},
};

@ -30,9 +30,27 @@ const DASHBOARD: AppRouteRecordRaw = {
meta: {
locale: 'menu.dashboard.monitor',
requiresAuth: true,
roles: ['admin'],
roles: ['*'],
},
},
{
path: 'main',
name: 'Main',
component: () => import('@/views/api2gpt/main/index.vue'),
meta: {
locale: 'menu.dashboard.workplace',
requiresAuth: true,
}
},
{
path: 'pay',
name: 'Pay',
component: () => import('@/views/api2gpt/pay/index.vue'),
meta: {
locale: 'menu.pay',
requiresAuth: true,
},
}
],
};

@ -1,19 +1,11 @@
export type RoleType = '' | '*' | 'admin' | 'user';
export interface UserState {
name?: string;
avatar?: string;
job?: string;
organization?: string;
location?: string;
email?: string;
introduction?: string;
personalWebsite?: string;
jobName?: string;
organizationName?: string;
locationName?: string;
phone?: string;
registrationDate?: string;
accountId?: string;
certification?: number;
role: RoleType;
userId: number;
userName: string;
nickName: string;
email: string;
phonenumber: string;
sex: string;
avatar: string;
password: string;
}

@ -22,4 +22,14 @@ export const regexUrl = new RegExp(
'i'
);
// 定义获取 cookie 的函数
export function getCookie(name: string) {
const regexp = new RegExp(`(^| )${name}=([^;]*)(;|$)`)
const result = document.cookie.match(regexp)
if (result) {
return result[2]
}
return ''
}
export default null;

@ -0,0 +1,61 @@
<template>
<div class="container">
Main1111
</div>
</template>
<script lang="ts" setup>
</script>
<script lang="ts">
export default {
name: 'Main2',
};
</script>
<style scoped lang="less">
.container {
padding: 0 20px 20px 20px;
}
.content {
display: flex;
margin-top: 12px;
&-left {
flex: 1;
margin-right: 16px;
overflow: hidden;
// background-color: var(--color-bg-2);
:deep(.arco-tabs-nav-tab) {
margin-left: 16px;
}
}
&-right {
width: 332px;
}
.tab-pane-wrapper {
padding: 0 16px 16px 16px;
}
}
</style>
<style lang="less" scoped>
.mobile {
.content {
display: block;
&-left {
margin-right: 0;
margin-bottom: 16px;
}
&-right {
width: 100%;
}
}
}
</style>

@ -0,0 +1,61 @@
<template>
<div class="container">
pay
</div>
</template>
<script lang="ts" setup>
</script>
<script lang="ts">
export default {
name: 'Pay',
};
</script>
<style scoped lang="less">
.container {
padding: 0 20px 20px 20px;
}
.content {
display: flex;
margin-top: 12px;
&-left {
flex: 1;
margin-right: 16px;
overflow: hidden;
// background-color: var(--color-bg-2);
:deep(.arco-tabs-nav-tab) {
margin-left: 16px;
}
}
&-right {
width: 332px;
}
.tab-pane-wrapper {
padding: 0 16px 16px 16px;
}
}
</style>
<style lang="less" scoped>
.mobile {
.content {
display: block;
&-left {
margin-right: 0;
margin-bottom: 16px;
}
&-right {
width: 100%;
}
}
}
</style>

@ -16,7 +16,7 @@
const userStore = useUserStore();
const userInfo = computed(() => {
return {
name: userStore.name,
name: userStore.userName,
};
});
</script>

@ -41,21 +41,42 @@
</template>
</a-input-password>
</a-form-item>
<a-form-item
field="code"
:rules="[{ required: true, message: $t('login.form.code.errMsg') }]"
:validate-trigger="['change', 'blur']"
hide-label
v-if="captchaEnabled"
>
<a-input
v-model="userInfo.code"
:placeholder="$t('login.form.code.placeholder')"
allow-clear
>
<template #prefix>
<icon-dice />
</template>
<template #append>
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
</template>
</a-input>
</a-form-item>
<a-space :size="16" direction="vertical">
<div class="login-form-password-actions">
<a-checkbox
checked="rememberPassword"
:model-value="loginConfig.rememberPassword"
@change="setRememberPassword as any"
v-if="false"
>
{{ $t('login.form.rememberPassword') }}
</a-checkbox>
<a-link>{{ $t('login.form.forgetPassword') }}</a-link>
<a-link v-if="false">{{ $t('login.form.forgetPassword') }}</a-link>
</div>
<a-button type="primary" html-type="submit" long :loading="loading">
{{ $t('login.form.login') }}
</a-button>
<a-button type="text" long class="login-form-register-btn">
<a-button type="text" long class="login-form-register-btn" href="/login?type=register">
{{ $t('login.form.register') }}
</a-button>
</a-space>
@ -73,25 +94,40 @@
import { useUserStore } from '@/store';
import useLoading from '@/hooks/loading';
import type { LoginData } from '@/api/user';
import { getCodeImg } from '@/api/user';
const router = useRouter();
const { t } = useI18n();
const errorMessage = ref('');
const { loading, setLoading } = useLoading();
const userStore = useUserStore();
const codeUrl = ref("");
//
const captchaEnabled = ref(true);
const loginConfig = useStorage('login-config', {
rememberPassword: true,
username: 'admin', //
password: 'admin', // demo default value
rememberPassword: false,
username: '',
password: '',
code: '',
uuid: ''
});
const userInfo = reactive({
username: loginConfig.value.username,
password: loginConfig.value.password,
code: '111',
uuid: '222'
code: '',
uuid: ''
});
const getCode = async () => {
const res = await getCodeImg();
captchaEnabled.value = res.data.captchaEnabled === undefined ? true : res.data.captchaEnabled;
if (captchaEnabled.value) {
codeUrl.value = `data:image/gif;base64,${res.data.img}`;
userInfo.uuid = res.data.uuid;
}
};
const handleSubmit = async ({
errors,
values,
@ -114,12 +150,13 @@
Message.success(t('login.form.login.success'));
const { rememberPassword } = loginConfig.value;
const { username, password } = values;
//
// The actual production environment requires encrypted storage.
loginConfig.value.username = rememberPassword ? username : '';
loginConfig.value.password = rememberPassword ? password : '';
} catch (err) {
errorMessage.value = (err as Error).message;
if (captchaEnabled.value) {
getCode();
}
} finally {
setLoading(false);
}
@ -128,9 +165,16 @@
const setRememberPassword = (value: boolean) => {
loginConfig.value.rememberPassword = value;
};
getCode();
</script>
<style lang="less" scoped>
.login-code-img {
height: 40px;
padding-left: 12px;
}
.login-form {
&-wrapper {
width: 320px;

@ -0,0 +1,216 @@
<template>
<div class="login-form-wrapper">
<div class="login-form-title">{{ $t('register.form.title') }}</div>
<div class="login-form-sub-title">{{ $t('register.form.title') }}</div>
<div class="login-form-error-msg">{{ errorMessage }}</div>
<a-form
ref="registerForm"
:model="userInfo"
class="login-form"
layout="vertical"
@submit="handleSubmit"
>
<a-form-item
field="username"
:rules="[{ required: true, message: $t('register.form.userName.errMsg') }]"
:validate-trigger="['change', 'blur']"
hide-label
>
<a-input
v-model="userInfo.username"
:placeholder="$t('register.form.userName.placeholder')"
>
<template #prefix>
<icon-user />
</template>
</a-input>
</a-form-item>
<a-form-item
field="password"
:rules="[{ required: true, message: $t('register.form.password.errMsg') }]"
:validate-trigger="['change', 'blur']"
hide-label
>
<a-input-password
v-model="userInfo.password"
:placeholder="$t('register.form.password.placeholder')"
allow-clear
>
<template #prefix>
<icon-lock />
</template>
</a-input-password>
</a-form-item>
<a-form-item
field="confirmPassword"
:rules="[{ required: true, message: $t('register.form.confirmPassword.errMsg') }]"
:validate-trigger="['change', 'blur']"
hide-label
>
<a-input-password
v-model="userInfo.confirmPassword"
:placeholder="$t('register.form.confirmPassword.placeholder')"
allow-clear
>
<template #prefix>
<icon-lock />
</template>
</a-input-password>
</a-form-item>
<a-form-item
field="email"
:rules="[{ required: true, message: $t('register.form.email.errMsg') }]"
:validate-trigger="['change', 'blur']"
hide-label
>
<a-input
v-model="userInfo.email"
:placeholder="$t('register.form.email.placeholder')"
>
<template #prefix>
<icon-email />
</template>
</a-input>
</a-form-item>
<a-form-item
field="code"
:rules="[{ required: true, message: $t('register.form.code.errMsg') }]"
:validate-trigger="['change', 'blur']"
hide-label
>
<a-input
v-model="userInfo.code"
:placeholder="$t('register.form.code.placeholder')"
>
<template #prefix>
<icon-dice />
</template>
<template #append>
<a-button @click="getCode"></a-button>
</template>
</a-input>
</a-form-item>
<a-space :size="16" direction="vertical">
<a-button type="primary" html-type="submit" long :loading="loading">
{{ $t('login.form.register') }}
</a-button>
<a-button type="text" long class="login-form-register-btn" href="/login">
{{ $t('login.form.login') }}
</a-button>
</a-space>
</a-form>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import { ValidatedError } from '@arco-design/web-vue/es/form/interface';
import { useI18n } from 'vue-i18n';
import useLoading from '@/hooks/loading';
import type { RegisterData } from '@/api/user';
import { getEmailCode, register } from '@/api/user';
import { getCookie } from '@/utils/index';
const router = useRouter();
const { t } = useI18n();
const errorMessage = ref('');
const { loading, setLoading } = useLoading();
const inviteCode = getCookie('inviteCode')
const userInfo = reactive({
username: '',
password: '',
confirmPassword: '',
email: '',
code: '',
uuid: ''
});
const getCode = async () => {
const emailRegex = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/;
if(userInfo.email === '' || !emailRegex.test(userInfo.email)){
Message.error(t('register.form.email.error'));
} else {
const res = await getEmailCode(userInfo.email);
userInfo.uuid = res.data.uuid;
}
};
const handleSubmit = async ({
errors,
values,
}: {
errors: Record<string, ValidatedError> | undefined;
values: Record<string, any>;
}) => {
if (loading.value) return;
if (userInfo.password !== userInfo.confirmPassword) {
Message.error(t('register.form.password.nomatch.error'));
return;
}
if(userInfo.password.length < 6){
Message.error('密码长度不能小于6位');
return;
}
if (!errors) {
setLoading(true);
try {
values.inviteCode = inviteCode
const req = await register(values as RegisterData);
Message.success(`${userInfo.email} 注册成功`);
setTimeout(function(){
window.location.href = "/login";
}, 1500);
} catch (err) {
errorMessage.value = (err as Error).message;
} finally {
setLoading(false);
}
}
};
</script>
<style lang="less" scoped>
.login-code-img {
height: 40px;
padding-left: 12px;
}
.login-form {
&-wrapper {
width: 320px;
}
&-title {
color: var(--color-text-1);
font-weight: 500;
font-size: 24px;
line-height: 32px;
}
&-sub-title {
color: var(--color-text-3);
font-size: 16px;
line-height: 24px;
}
&-error-msg {
height: 32px;
color: rgb(var(--red-6));
line-height: 32px;
}
&-password-actions {
display: flex;
justify-content: space-between;
}
&-register-btn {
color: var(--color-text-3) !important;
}
}
</style>

@ -5,12 +5,13 @@
alt="logo"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/dfdba5317c0c20ce20e64fac803d52bc.svg~tplv-49unhts6dw-image.image"
/>
<div class="logo-text">Arco Design Pro</div>
<div class="logo-text">Api2Gpt</div>
</div>
<LoginBanner />
<div class="content">
<div class="content-inner">
<LoginForm />
<LoginForm v-if="type!='register'"/>
<RegisterForm v-if="type=='register'"/>
</div>
<div class="footer">
<Footer />
@ -20,9 +21,19 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { useRoute } from "vue-router";
import Footer from '@/components/footer/index.vue';
import LoginBanner from './components/banner.vue';
import LoginForm from './components/login-form.vue';
import RegisterForm from './components/register-form.vue';
const type = ref("");
const route = useRoute(); //
if(route.query.type){
type.value = route.query.type as string;
}
</script>
<style lang="less" scoped>

@ -1,11 +1,13 @@
export default {
'login.form.title': '登录 Arco Design Pro',
'login.form.title': '登录 Api2Gpt',
'login.form.userName.errMsg': '用户名不能为空',
'login.form.password.errMsg': '密码不能为空',
'login.form.login.errMsg': '登录出错,轻刷新重试',
'login.form.code.errMsg': '验证码不能为空',
'login.form.login.errMsg': '登录出错,请刷新重试',
'login.form.login.success': '欢迎使用',
'login.form.userName.placeholder': '用户名admin',
'login.form.password.placeholder': '密码admin',
'login.form.userName.placeholder': '用户名:',
'login.form.password.placeholder': '密码:',
'login.form.code.placeholder': '验证码:',
'login.form.rememberPassword': '记住密码',
'login.form.forgetPassword': '忘记密码',
'login.form.login': '登录',
@ -16,4 +18,18 @@ export default {
'login.banner.subSlogan2': '国际化,路由配置,状态管理应有尽有',
'login.banner.slogan3': '接入可视化增强工具AUX',
'login.banner.subSlogan3': '实现灵活的区块式开发',
'register.form.title': '注册 Api2Gpt',
'register.form.userName.errMsg': '用户名不能为空',
'register.form.password.errMsg': '密码不能为空',
'register.form.confirmPassword.errMsg': '确认密码不能为空',
'register.form.email.errMsg': '邮箱不能为空',
'register.form.code.errMsg': '验证码不能为空',
'register.form.userName.placeholder': '用户名:',
'register.form.password.placeholder': '密码:',
'register.form.confirmPassword.placeholder': '确认密码:',
'register.form.email.placeholder': '邮箱:',
'register.form.code.placeholder': '验证码:',
'register.form.email.error': '邮箱格式不正确',
'register.form.email.success': '邮箱验证码发送成功',
'register.form.password.nomatch.error': '两次输入密码不一致',
};

Loading…
Cancel
Save