✨ feat: 完成修改用户名功能
This commit is contained in:
parent
364dc4560f
commit
4f3dc11101
@ -1,3 +1,4 @@
|
||||
import { REQUEST_BASE_URL } from '@/env';
|
||||
import {
|
||||
userInfoRespSchema,
|
||||
userInfoRespSchemaNullable,
|
||||
@ -13,7 +14,7 @@ import { ElMessage } from 'element-plus';
|
||||
import type { z } from 'zod';
|
||||
|
||||
export const axiosInstance = axios.create({
|
||||
baseURL: import.meta.env.VITE_REQUEST_BASE_URL,
|
||||
baseURL: REQUEST_BASE_URL,
|
||||
});
|
||||
// 自动添加token到请求中.
|
||||
axiosInstance.interceptors.request.use((config) => {
|
||||
@ -32,11 +33,8 @@ axiosInstance.interceptors.response.use((response) => {
|
||||
return response;
|
||||
});
|
||||
export type RawResp = Record<string, unknown>;
|
||||
const errorMessage = (
|
||||
description: string = '请求错误',
|
||||
code: string | number | undefined,
|
||||
msg: string,
|
||||
) => `${description}:(${code})${msg}`;
|
||||
const errorMessage = (description: string, code: string | number | undefined, msg: string) =>
|
||||
`${description}:(${code})${msg}`;
|
||||
|
||||
interface RequestOptions extends Override<AxiosRequestConfig, { method?: Method }, 'url'> {
|
||||
errorDescription?: string;
|
||||
@ -44,7 +42,7 @@ interface RequestOptions extends Override<AxiosRequestConfig, { method?: Method
|
||||
export function parseRawResp<T extends AnyRespSchema>(
|
||||
schema: T,
|
||||
raw: RawResp | undefined,
|
||||
errorDescription?: string,
|
||||
errorDescription: string,
|
||||
): SucceedRespOf<z.infer<T>> | undefined {
|
||||
if (!raw) return;
|
||||
const resp = schema.parse(raw);
|
||||
@ -59,14 +57,16 @@ export async function request<T extends AnyRespSchema>(
|
||||
schema: T,
|
||||
options: RequestOptions = {},
|
||||
) {
|
||||
const { errorDescription = '请求错误' } = options;
|
||||
return parseRawResp(
|
||||
schema,
|
||||
await axiosInstance
|
||||
.request<RawResp>(Object.assign(options, { url }))
|
||||
.then((r) => r.data)
|
||||
.catch((err: AxiosError): undefined => {
|
||||
ElMessage.error(errorMessage(options.errorDescription, err.code, err.message));
|
||||
ElMessage.error(errorMessage(errorDescription, err.code, err.message));
|
||||
}),
|
||||
errorDescription,
|
||||
);
|
||||
}
|
||||
export async function getUserInfo(): Promise<SucceedUserInfoResp>;
|
||||
|
@ -41,7 +41,7 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="verifyCode">
|
||||
<el-form-item v-if="!DEV" prop="verifyCode">
|
||||
<verify-input v-model="loginFormData" :disabled="logining" />
|
||||
</el-form-item>
|
||||
<el-form-item style="margin-bottom: 0">
|
||||
@ -105,7 +105,7 @@
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="verifyCode">
|
||||
<el-form-item v-if="!DEV" prop="verifyCode">
|
||||
<verify-input v-model="registerFormData" :disabled="registering" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@ -151,9 +151,12 @@ export interface LoginRegisterDialogProps {
|
||||
handleLogin: (params: LoginParams) => Promise<boolean>;
|
||||
handleRegister: (params: RegisterParams) => Promise<boolean>;
|
||||
}
|
||||
export const usernameLength = 3;
|
||||
export const passwordLength = usernameLength;
|
||||
</script>
|
||||
<script lang="ts" setup>
|
||||
import VerifyInput, { type VerifyImagePath } from '@/components/app/VerifyInput.vue';
|
||||
import { DEV } from '@/env';
|
||||
import { useMediaStore } from '@/stores/media';
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
|
||||
import { storeToRefs } from 'pinia';
|
||||
@ -174,18 +177,17 @@ watch(show, (show) => {
|
||||
loginFormRef.value?.resetFields();
|
||||
registerFormRef.value?.resetFields();
|
||||
});
|
||||
const inputLength = 3;
|
||||
const loginFormRules = reactive<FormRules<typeof loginFormData>>({
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名' },
|
||||
{ min: inputLength, message: `用户名长度不能小于${inputLength}位` },
|
||||
{ min: usernameLength, message: `用户名长度不能小于${usernameLength}位` },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码' },
|
||||
{ min: inputLength, message: `密码长度不能小于${inputLength}位` },
|
||||
{ min: passwordLength, message: `密码长度不能小于${passwordLength}位` },
|
||||
],
|
||||
verifyCode: [
|
||||
{ required: true, message: '请输入验证码' },
|
||||
{ required: !DEV, message: '请输入验证码' },
|
||||
{ pattern: /^[0-9A-Za-z]{4}$/, message: '验证码不符合格式' },
|
||||
],
|
||||
});
|
||||
@ -196,18 +198,19 @@ async function submitLoginForm() {
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
let key: string;
|
||||
if (typeof loginFormData.verifyImage === 'string') {
|
||||
ElMessage.error('请获取验证码');
|
||||
return;
|
||||
if (!DEV) {
|
||||
ElMessage.error('请获取验证码');
|
||||
return;
|
||||
}
|
||||
key = '';
|
||||
} else {
|
||||
({ key } = loginFormData.verifyImage);
|
||||
}
|
||||
logining.value = true;
|
||||
try {
|
||||
const {
|
||||
username,
|
||||
password,
|
||||
verifyImage: { key },
|
||||
verifyCode: code,
|
||||
} = loginFormData;
|
||||
const { username, password, verifyCode: code } = loginFormData;
|
||||
if (!(await handleLogin({ username, password, key, code }))) return;
|
||||
ElMessage.success('登录成功');
|
||||
show.value = false;
|
||||
@ -235,8 +238,8 @@ const registerFormRules = reactive<FormRules<typeof registerFormData>>({
|
||||
callback('请输入密码');
|
||||
} else if (value !== registerFormData.password) {
|
||||
callback('密码不一致');
|
||||
} else if (value.length < inputLength) {
|
||||
callback(`密码长度不能小于${inputLength}位`);
|
||||
} else if (value.length < passwordLength) {
|
||||
callback(`密码长度不能小于${passwordLength}位`);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
@ -251,18 +254,19 @@ async function submitRegisterForm() {
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
let key: string;
|
||||
if (typeof registerFormData.verifyImage === 'string') {
|
||||
ElMessage.error('请输入验证码');
|
||||
return;
|
||||
if (!DEV) {
|
||||
ElMessage.error('请获取验证码');
|
||||
return;
|
||||
}
|
||||
key = '';
|
||||
} else {
|
||||
({ key } = registerFormData.verifyImage);
|
||||
}
|
||||
registering.value = true;
|
||||
try {
|
||||
const {
|
||||
username,
|
||||
password,
|
||||
verifyCode: code,
|
||||
verifyImage: { key },
|
||||
} = registerFormData;
|
||||
const { username, password, verifyCode: code } = registerFormData;
|
||||
if (!(await handleRegister({ username, password, code, key, auth: 1 }))) return;
|
||||
ElMessage.success('注册成功');
|
||||
show.value = false;
|
||||
|
4
src/env.ts
Normal file
4
src/env.ts
Normal file
@ -0,0 +1,4 @@
|
||||
// export const DEV = import.meta.env.DEV;
|
||||
export const DEV = false;
|
||||
export const REQUEST_BASE_URL = import.meta.env.VITE_REQUEST_BASE_URL;
|
||||
export const WEBSOCKET_BASE_URL = import.meta.env.VITE_WEBSOCKET_BASE_URL;
|
@ -14,10 +14,7 @@
|
||||
v-loading="loading"
|
||||
:element-loading-text="loadingText"
|
||||
class="gobang-list-page-main flex-col"
|
||||
:class="[
|
||||
!rooms.length ? 'justify-center items-center' : '',
|
||||
{ '!flex': !rooms.length },
|
||||
]"
|
||||
:class="[!rooms.length ? 'justify-center items-center' : '', { '!flex': !rooms.length }]"
|
||||
>
|
||||
<template v-if="rooms.length || !loading">
|
||||
<template v-if="!rooms.length">
|
||||
@ -153,10 +150,7 @@ export function useGobangSocket(
|
||||
) {
|
||||
return useGameSocket<Request, Resp>()(
|
||||
Object.assign(options, {
|
||||
url:
|
||||
import.meta.env.VITE_DEBUG === 'true'
|
||||
? `ws://172.16.114.84:58080/chess/${useUserStore().token}`
|
||||
: `wss://wzpmc.cn:18080/chess/${useUserStore().token}`,
|
||||
url: `${WEBSOCKET_BASE_URL}/chess/${useUserStore().token}`,
|
||||
relations,
|
||||
}),
|
||||
);
|
||||
@ -171,6 +165,7 @@ const playerCountMap: Record<RoomState, string> = {
|
||||
<script lang="ts" setup>
|
||||
import CreateGobangRoomDialog from '@/components/gobang/CreateGobangRoomDialog.vue';
|
||||
import GobangHeader from '@/components/gobang/GobangHeader.vue';
|
||||
import { WEBSOCKET_BASE_URL } from '@/env';
|
||||
import router from '@/router';
|
||||
import type { UserInfo } from '@/schemas/response';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
|
@ -2,38 +2,60 @@
|
||||
<div>
|
||||
<h4 class="p">编辑资料</h4>
|
||||
<el-divider />
|
||||
<div class="p flex flex-col items-center gap-10px">
|
||||
<el-avatar :size="100" :src="getAvatarURL(userStore.userInfo!.avatar)" />
|
||||
<el-upload
|
||||
:show-file-list="false"
|
||||
:http-request="upload"
|
||||
@success="onUploadSuccess"
|
||||
@error="onUploadError"
|
||||
>
|
||||
<div class="p outer">
|
||||
<el-avatar class="self-center" :size="100" :src="getAvatarURL(userStore.userInfo!.avatar)" />
|
||||
<el-upload class="self-center" :show-file-list="false" :http-request="upload">
|
||||
<template #trigger>
|
||||
<el-link type="primary" :icon="Upload" :underline="false" :limit="1">上传头像</el-link>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
<el-divider />
|
||||
<div class="p outer">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
class="flex flex-col items-stretch"
|
||||
hide-required-asterisk
|
||||
@submit.prevent
|
||||
:model="data"
|
||||
:rules="rules"
|
||||
>
|
||||
<el-form-item prop="username" label="用户名">
|
||||
<el-input v-model="data.username" />
|
||||
</el-form-item>
|
||||
<el-form-item class="self-center">
|
||||
<el-button type="primary" native-type="submit" @click="submit" :loading="submitting">
|
||||
保存设置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.p {
|
||||
padding: 0 var(--main-h-padding);
|
||||
}
|
||||
.outer {
|
||||
@apply flex flex-col gap-10px;
|
||||
}
|
||||
.el-form {
|
||||
padding: 0 100px;
|
||||
}
|
||||
</style>
|
||||
<script lang="ts">
|
||||
import { getAvatarURL, request } from '@/api';
|
||||
import { usernameLength } from '@/components/app/LoginRegisterDialog.vue';
|
||||
import { ordinarySchema, uploadAvatarRespSchema } from '@/schemas/response';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import { Upload } from '@element-plus/icons-vue';
|
||||
import {
|
||||
ElMessage,
|
||||
type UploadInstance,
|
||||
type UploadProps,
|
||||
type FormInstance,
|
||||
type FormRules,
|
||||
type UploadRequestHandler,
|
||||
} from 'element-plus';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { defineComponent, reactive, ref } from 'vue';
|
||||
import type { RouteLocationNormalized } from 'vue-router';
|
||||
function validate({ params: { id } }: RouteLocationNormalized) {
|
||||
if (!useUserStore().isSelf(id as string)) {
|
||||
@ -48,29 +70,79 @@ export default defineComponent({
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
const userStore = useUserStore();
|
||||
const uploadRef = ref<UploadInstance>();
|
||||
const upload: UploadRequestHandler = async ({ file }) => {
|
||||
if (!file.type.startsWith('image')) {
|
||||
ElMessage.error('只能上传图片');
|
||||
return;
|
||||
}
|
||||
if (file.size > 1024 * 1024) {
|
||||
throw new Error('文件大小不能超过1MB');
|
||||
ElMessage.error('文件大小不能超过1MB');
|
||||
return;
|
||||
}
|
||||
const formdata = new FormData();
|
||||
formdata.append('file', file);
|
||||
const uploadResp = await request('/api/user/avatar', uploadAvatarRespSchema, {
|
||||
method: 'post',
|
||||
data: formdata,
|
||||
errorDescription: '上传头像失败',
|
||||
});
|
||||
if (!uploadResp) return;
|
||||
const changeResp = await request('/api/user/avatar', ordinarySchema, {
|
||||
method: 'put',
|
||||
params: { code: uploadResp.data },
|
||||
errorDescription: '上传头像失败',
|
||||
});
|
||||
if (!changeResp) return;
|
||||
await userStore.updateSelfUserInfo();
|
||||
ElMessage.success('头像上传成功');
|
||||
};
|
||||
const onUploadSuccess: UploadProps['onSuccess'] = (...args) => {
|
||||
console.log(args);
|
||||
};
|
||||
const onUploadError: UploadProps['onError'] = (err) => {
|
||||
ElMessage.error(err.message);
|
||||
};
|
||||
const formRef = ref<FormInstance>();
|
||||
const data = reactive({
|
||||
username: userStore.userInfo!.name,
|
||||
});
|
||||
const rules = reactive<FormRules<typeof data>>({
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名' },
|
||||
{ min: usernameLength, message: `用户名长度不能小于${usernameLength}位` },
|
||||
],
|
||||
});
|
||||
const submitting = ref(false);
|
||||
async function submit() {
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
submitting.value = true;
|
||||
const { username } = data;
|
||||
let changed = false,
|
||||
allSucceed = true;
|
||||
try {
|
||||
if (username !== userStore.userInfo!.name) {
|
||||
const resp = await request('/api/user/rename', ordinarySchema, {
|
||||
method: 'put',
|
||||
data: {
|
||||
id: userStore.userInfo!.id,
|
||||
newName: username,
|
||||
},
|
||||
errorDescription: '修改用户名失败',
|
||||
});
|
||||
if (resp) {
|
||||
changed = true;
|
||||
} else {
|
||||
allSucceed = false;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (changed) {
|
||||
await userStore.updateSelfUserInfo();
|
||||
if (allSucceed) {
|
||||
ElMessage.success('修改成功');
|
||||
}
|
||||
} else if (allSucceed) {
|
||||
ElMessage.info('没有要修改的信息');
|
||||
}
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -40,7 +40,7 @@
|
||||
</el-button>
|
||||
</el-card>
|
||||
<div class="flex gap-10px">
|
||||
<el-card class="aside w-200px">
|
||||
<el-card class="aside w-200px self-start">
|
||||
<el-menu class="menu" router :default-active="$route.fullPath">
|
||||
<h4 class="mb-5px text-center">个人中心</h4>
|
||||
<el-divider />
|
||||
@ -49,7 +49,6 @@
|
||||
<el-divider />
|
||||
<el-menu-item :index="`/user/${$route.params.id}/edit`">编辑信息</el-menu-item>
|
||||
</template>
|
||||
<el-divider />
|
||||
</el-menu>
|
||||
</el-card>
|
||||
<el-card class="main flex-1">
|
||||
|
Loading…
x
Reference in New Issue
Block a user