Compare commits

..

5 Commits
main ... dev

Author SHA1 Message Date
Kelvin 00397ab3cb update
3 years ago
Kelvin defaa50315 update
3 years ago
kelvin ba3db59a89 update
3 years ago
Kelvin 3525532a8d update
3 years ago
Kelvin 117b20430c update
3 years ago

@ -1 +1 @@
VITE_API_BASE_URL= 'http://localhost:8080'
VITE_API_BASE_URL= 'http://localhost:8080/'

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

@ -0,0 +1,60 @@
import axios from 'axios';
export interface QueryParams {
pageNum: number;
pageSize: number;
}
export interface ModelData {
modelName: string;
modelPrice: number;
remark: string;
}
export interface IndexData {
serverUrl: string;
userKey: string;
serverUrl2: string;
userBalance: number;
userBalanceMonth: number;
modelList: ModelData[];
pushUrl: string;
}
export interface OrderData {
orderNo : string;
paymentPrice : number;
isPay : string;
createTime : string;
paymentWay : string;
}
export interface DataTable {
total: number;
rows: any[];
}
export interface PayRreturn {
payUrl: string;
aoid: string;
}
// 获取首页信息
export function getCertIndex() {
return axios.get<IndexData>('/cert/index');
}
// 用户订单列表
export function currentUserListOrder(query: QueryParams) {
return axios.get<DataTable>('/cert/order/user/list');
}
// 支付
export function payment(newOrder: OrderData) {
return axios.post<PayRreturn>('/order/payment', newOrder);
}
// 支付状态刷新
export function refreshStatus(orderNo: string) {
return axios.post(`/order/payment/${orderNo}`);
}

@ -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,

@ -5,23 +5,55 @@ import { UserState } from '@/store/modules/user/types';
export interface LoginData {
username: string;
password: string;
code: string;
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>('/api/user/login', data);
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}`);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

@ -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,9 @@ export default {
'menu.faq': '常见问题',
'navbar.docs': '文档中心',
'navbar.action.locale': '切换为中文',
'menu.pay': '帐户充值 ',
'menu.model': '模型定价',
...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,36 @@ 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: 'model',
name: 'Model',
component: () => import('@/views/api2gpt/model/index.vue'),
meta: {
locale: 'menu.model',
requiresAuth: true,
}
},
{
path: 'pay',
name: 'Pay',
component: () => import('@/views/api2gpt/pay/index.vue'),
meta: {
locale: 'menu.pay',
requiresAuth: true,
},
}
],
};

@ -12,6 +12,7 @@ import useAppStore from '../app';
const useUserStore = defineStore('user', {
state: (): UserState => ({
userName: undefined,
name: undefined,
avatar: undefined,
job: undefined,
@ -45,6 +46,7 @@ const useUserStore = defineStore('user', {
},
// Set user's information
setInfo(partial: Partial<UserState>) {
console.log('setInfo', partial);
this.$patch(partial);
},
@ -56,7 +58,6 @@ const useUserStore = defineStore('user', {
// Get user's information
async info() {
const res = await getUserInfo();
this.setInfo(res.data);
},

@ -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,35 @@
<template>
<a-col class="banner">
<a-col :span="8">
<a-typography-title :heading="5" style="margin-top: 0">
{{ $t('workplace.welcome') }} {{ userInfo.name }}
</a-typography-title>
</a-col>
<a-divider class="panel-border" />
</a-col>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { useUserStore } from '@/store';
const userStore = useUserStore();
const userInfo = computed(() => {
return {
name: userStore.userName,
};
});
</script>
<style scoped lang="less">
.banner {
width: 100%;
padding: 20px 20px 0 20px;
background-color: var(--color-bg-2);
border-radius: 4px 4px 0 0;
}
:deep(.arco-icon-home) {
margin-right: 6px;
}
</style>

@ -0,0 +1,139 @@
<template>
<a-grid :cols="24" :row-gap="16" class="panel">
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
>
<a-space>
<a-avatar :size="54" class="col-avatar">
<img
alt="avatar"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/288b89194e657603ff40db39e8072640.svg~tplv-49unhts6dw-image.image"
/>
</a-avatar>
<a-statistic
title="帐户余额"
:value="indexData?.userBalance"
:precision="2"
:value-from="0"
animation
show-group-separator
>
<template #suffix>
<span class="unit"></span>
</template>
</a-statistic>
</a-space>
</a-grid-item>
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
>
<a-space>
<a-avatar :size="54" class="col-avatar">
<img
alt="avatar"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/c8b36e26d2b9bb5dbf9b74dd6d7345af.svg~tplv-49unhts6dw-image.image"
/>
</a-avatar>
<a-statistic
title="本月消费"
:value="indexData?.userBalanceMonth"
:precision="2"
:value-from="0"
animation
show-group-separator
>
<template #suffix>
<span class="unit"></span>
</template>
</a-statistic>
</a-space>
</a-grid-item>
<a-grid-item :span="24">
<a-divider class="panel-border" />
</a-grid-item>
</a-grid>
<a-card class="general-card" title="推荐计划">
<a-space direction="vertical" fill :size="10">
<a-input-search :model-value="indexData?.pushUrl" readonly button-text="" search-button @search="handleCopy(indexData?.pushUrl)">
<template #prepend>
推荐链接
</template>
</a-input-search>
</a-space>
</a-card>
<a-card class="general-card" title="服务信息">
<a-space direction="vertical" fill>
<a-input-search :model-value="indexData?.serverUrl" readonly button-text="" search-button @search="handleCopy(indexData?.pushUrl)">
<template #prepend>
Api Server
</template>
</a-input-search>
<a-input-search :model-value="indexData?.userKey" readonly button-text="" search-button @search="handleCopy(indexData?.pushUrl)">
<template #prepend>
Api Key
</template>
</a-input-search>
<a-input-password :model-value="indexData?.serverUrl2" readonly button-text="" search-button @search="handleCopy(indexData?.pushUrl)">
<template #prepend>
Api Server备用
</template>
</a-input-password>
</a-space>
</a-card>
</template>
<script lang="ts" setup>
import { Message } from '@arco-design/web-vue';
function handleCopy(id: string){
//
const text = id;
// input
const input = document.createElement("input");
input.value = text;
document.body.appendChild(input);
//
input.select();
document.execCommand("copy");
// input
document.body.removeChild(input);
Message.success("复制成功");
}
</script>
<script lang="ts">
export default {
name: 'DataPanel',
props: {
indexData: {}
}
};
</script>
<style lang="less" scoped>
.arco-grid.panel {
margin-bottom: 0;
padding: 16px 20px 0 20px;
}
.panel-col {
padding-left: 43px;
border-right: 1px solid rgb(var(--gray-2));
}
.col-avatar {
margin-right: 12px;
background-color: var(--color-fill-2);
}
.up-icon {
color: rgb(var(--red-6));
}
.unit {
margin-left: 8px;
color: rgb(var(--gray-8));
font-size: 12px;
}
:deep(.panel-border) {
margin: 4px 0 0 0;
}
</style>

@ -0,0 +1,130 @@
<template>
<div class="container">
<div class="left-side">
<div class="panel">
<Banner />
<DataPanel :indexData="indexData"/>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { getCertIndex } from '@/api/cert'
import Banner from './components/banner.vue';
import DataPanel from './components/data-panel.vue';
const modelList = ref([]);
const serverUrl = ref("");
const serverUrl2 = ref("");
const userKey = ref("");
const userBalance = ref(0);
const userBalanceMonth = ref(0);
const pushUrl = ref("");
const indexData = ref({});
const Init = async () => {
const res = await getCertIndex()
serverUrl.value = res.data.serverUrl;
indexData.value = res.data;
}
Init();
</script>
<script lang="ts">
export default {
name: 'Main2',
components: {
'DataPanel': DataPanel
},
};
</script>
<style lang="less" scoped>
.container {
background-color: var(--color-fill-2);
padding: 16px 20px;
padding-bottom: 0;
display: flex;
}
.left-side {
flex: 1;
overflow: auto;
}
.right-side {
width: 280px;
margin-left: 16px;
}
.panel {
background-color: var(--color-bg-2);
border-radius: 4px;
overflow: auto;
}
:deep(.panel-border) {
margin-bottom: 0;
border-bottom: 1px solid rgb(var(--gray-2));
}
.moduler-wrap {
border-radius: 4px;
background-color: var(--color-bg-2);
:deep(.text) {
font-size: 12px;
text-align: center;
color: rgb(var(--gray-8));
}
:deep(.wrapper) {
margin-bottom: 8px;
text-align: center;
cursor: pointer;
&:last-child {
.text {
margin-bottom: 0;
}
}
&:hover {
.icon {
color: rgb(var(--arcoblue-6));
background-color: #e8f3ff;
}
.text {
color: rgb(var(--arcoblue-6));
}
}
}
:deep(.icon) {
display: inline-block;
width: 32px;
height: 32px;
margin-bottom: 4px;
color: rgb(var(--dark-gray-1));
line-height: 32px;
font-size: 16px;
text-align: center;
background-color: rgb(var(--gray-1));
border-radius: 4px;
}
}
</style>
<style lang="less" scoped>
// responsive
.mobile {
.container {
display: block;
}
.right-side {
// display: none;
width: 100%;
margin-left: 0;
margin-top: 16px;
}
}
</style>

@ -0,0 +1,86 @@
<template>
<div class="container">
<a-spin style="width: 100%">
<a-card
class="general-card"
:header-style="{ paddingBottom: '0' }"
:body-style="{ padding: '17px 20px 21px 20px' }"
>
<template #title>
模型列表与定价
</template>
<a-table
:data="modelList"
:pagination="false"
:bordered="false"
:scroll="{ x: '100%', y: '264px' }"
>
<template #columns>
<a-table-column title="模型名称" data-index="modelName"></a-table-column>
<a-table-column title="提问价格" data-index="modelPrice">
<template #cell="{ record }">
{{NumFilter4(record.modelPrice*1000)}}/k
</template>
</a-table-column>
<a-table-column title="回答价格" data-index="modelPrice">
<template #cell="{ record }">
{{NumFilter4(record.modelPrice*1000)}}/k
</template>
</a-table-column>
<a-table-column title="备注" data-index="remark"></a-table-column>
</template>
</a-table>
</a-card>
</a-spin>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { getCertIndex } from '@/api/cert'
import type { ModelData } from '@/api/cert'
const modelList = ref();
const Init = async () => {
const res = await getCertIndex()
modelList.value = res.data.modelList as ModelData[];
}
function NumFilter4 (value: number) {
//
return parseFloat(`${value}`).toFixed(4)
}
Init();
</script>
<script lang="ts">
export default {
name: 'Model',
};
</script>
<style scoped lang="less">
.container {
background-color: var(--color-fill-2);
padding: 16px 20px;
padding-bottom: 0;
display: flex;
}
.general-card {
min-height: 395px;
}
:deep(.arco-table-tr) {
height: 44px;
.arco-typography {
margin-bottom: 0;
}
}
.increases-cell {
display: flex;
align-items: center;
span {
margin-right: 4px;
}
}
</style>

@ -0,0 +1,185 @@
<template>
<div class="container">
<a-spin style="width: 100%">
<a-row >
<a-col :span="6">
<a-card :style="{ marginBottom: '20px' }">
<div
:style="{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}"
>
<span>充值
<a-input-number v-model="paymentPrice" :default-value="10" :step="1" :precision="1" mode="button" :min="0.1" :max="100" class="input-demo" /></span>
<a-button type="primary" @click="handlePayment" :loading="loading">
<template #icon>
<icon-alipay-circle />
</template>
<!-- Use the default slot to avoid extra spaces -->
<template #default>充值</template>
</a-button>
</div>
</a-card>
<a-alert type="success">
<template #title>
友情提醒
</template>
第一次充值建议先小额进行试用充值最高100元
</a-alert>
</a-col>
<a-col :span="12">
<a-alert type="warning">
<template #title>
充值说明
</template>
1. 充值后不支持退款建议先小额充值进行测试平台不限制最低充值金额请按需充值<br/>
2. 定价页面没写的模型和接口即不支持不是所有的接口都支持有特殊需求的朋友要注意<br/>
3. 支付宝充值到账一般为几秒钟到几分钟但是偶尔也会遇到延迟比较高的情况如果遇到支付状态长时间没有变化请点击手动刷新更新状态即可<br/>
4. 商务订制等大需求请联系客服<br/>
</a-alert>
</a-col>
</a-row>
<a-card
class="general-card"
:header-style="{ paddingBottom: '0' }"
:body-style="{ padding: '17px 20px 21px 20px' }"
:style="{ marginTop: '20px' }"
>
<template #title>
充值历史
</template>
<template #extra>
<a-button @click="flash">
<template #icon>
<icon-refresh />
</template>
刷新
</a-button>
</template>
<a-table :columns="columns" :data="data" :pagination="pages"></a-table>
</a-card>
</a-spin>
</div>
<a-modal :visible="open" @ok="open = false" @cancel="open = false" okText="确定" cancelText="" unmountOnClose>
<template #title>
请使用支付宝扫码充值
</template>
<div>
<center>等待用户扫码中</center>
<center>
<img :src="'https://xorpay.com/qr?data='+payUrl" alt="" width="300" />
</center>
<center>
<img src="@/assets/images/alipay-logo.jpg" alt="" width="120" />
</center>
<center>
<span>扫码后如支付完成点击确认按钮即可查看已完成订单</span>
</center>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { currentUserListOrder,payment } from '@/api/cert'
import type { OrderData } from '@/api/cert'
import { Message } from '@arco-design/web-vue';
import useLoading from '@/hooks/loading';
const { loading, setLoading } = useLoading();
const paymentPrice = ref(10);
const open = ref(false);
const payUrl = ref('');
const aoid = ref('');
const columns = ref([
{
title: '订单号',
dataIndex: 'orderNo',
},
{
title: '金额',
dataIndex: 'paymentPrice',
},
{
title: '是否支付',
dataIndex: 'isPay',
},
{
title: '支付方式',
dataIndex: 'paymentWay',
},
{
title: '创建时间',
dataIndex: 'createTime',
},
]);
const data = ref();
const pages = ref({
pageNum: 1,
pageSize: 10
})
const Init = async () => {
const res = await currentUserListOrder(pages.value)
data.value = res.data.rows as OrderData[];
}
const flash = async () => {
await Init();
Message.success('刷新成功');
}
//
const handlePayment = async () => {
if (loading.value) return;
setLoading(true);
const order = {
paymentPrice: paymentPrice.value,
paymentWay: "alipay"
} as OrderData;
try {
const res = await payment(order);
payUrl.value = res.data.info.qr;
aoid.value = res.data.aoid;
open.value = true;
} catch (err) {
Message.error((err as Error).message);
} finally {
setLoading(false);
}
}
Init()
</script>
<script lang="ts">
export default {
name: 'Pay',
};
</script>
<style scoped lang="less">
.container {
background-color: var(--color-fill-2);
padding: 16px 20px;
padding-bottom: 0;
display: flex;
}
.general-card {
min-height: 395px;
}
:deep(.arco-table-tr) {
height: 44px;
.arco-typography {
margin-bottom: 0;
}
}
.increases-cell {
display: flex;
align-items: center;
span {
margin-right: 4px;
}
}
</style>

@ -15,8 +15,9 @@
const userStore = useUserStore();
const userInfo = computed(() => {
console.log(userStore);
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,23 +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: '',
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,
@ -112,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);
}
@ -126,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': '两次输入密码不一致',
};

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save