Compare commits

...

2 Commits

8 changed files with 618 additions and 24 deletions

View File

@ -93,6 +93,9 @@ const install = (Vue, vm) => {
// 发送消息
let SendMessageApi = (params = {}) => vm.$u.post('api/ChatAI/CreateChat', params);
// 获取图形验证码
let GetCaptcha = (params = {}) => vm.$u.get('api/Login/GetCaptcha', params);
// 将各个定义的接口名称统一放进对象挂载到vm.$u.api(因为vm就是this也即this.$u.api)下
vm.$u.api = {
LoginApp,
@ -132,7 +135,8 @@ const install = (Vue, vm) => {
getVerifyInfo,
getInfoByCard,
updateCard,
SendMessageApi
SendMessageApi,
GetCaptcha
};
}

View File

@ -66,6 +66,14 @@
"navigationStyle": "custom"
}
},
{
"path": "pages/home/userInfo/index",
"style": {
"navigationBarTitleText": "个人信息",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/login/login/login",
"style": {

View File

@ -0,0 +1,330 @@
<template>
<view class="userInfo-container">
<view class="header">
<div class="header-left">
<u-icon
class="header-left-icon"
name="arrow-left"
@click="handleLeftClick"
></u-icon>
</div>
<text class="header-title">个人信息</text>
<div></div>
</view>
<view class="user-info-content">
<!-- 第一个卡片头像 -->
<view class="info-card">
<view class="info-item avatar-item">
<text class="item-label">头像</text>
<view class="item-content avatar-content">
<image
class="avatar-image"
:src="
userInfo.avatar || '/static/common/images/avatar_default.png'
"
mode="aspectFill"
@click="chooseAvatar"
></image>
</view>
</view>
</view>
<!-- 第二个卡片姓名和性别 -->
<view class="info-card">
<view class="info-item">
<text class="item-label">姓名</text>
<view class="item-content">
<text>{{ userInfo.name }}</text>
</view>
</view>
<view class="info-item">
<text class="item-label">性别</text>
<view class="item-content" @click="openPicker('gender')">
<text class="placeholder-text" v-if="!userInfo.gender"
>请选择性别</text
>
<text v-else>{{ userInfo.gender }}</text>
<text class="arrow-icon">></text>
</view>
</view>
</view>
<!-- 第三个卡片归属地高考年份生源高中意向学院专业 -->
<view class="info-card">
<view class="info-item">
<text class="item-label">归属地</text>
<view class="item-content" @click="openPicker('region')">
<text class="placeholder-text" v-if="!userInfo.region"
>请选择归属地</text
>
<text v-else>{{ userInfo.region }}</text>
<text class="arrow-icon">></text>
</view>
</view>
<view class="info-item">
<text class="item-label">高考年份</text>
<view class="item-content" @click="openPicker('examYear')">
<text class="placeholder-text" v-if="!userInfo.examYear"
>请选择高考年份</text
>
<text v-else>{{ userInfo.examYear }}</text>
<text class="arrow-icon">></text>
</view>
</view>
<view class="info-item">
<text class="item-label">生源高中</text>
<view class="item-content">
<input
class="item-input"
v-model="userInfo.highSchool"
placeholder="请输入生源高中"
placeholder-style="color: #CCCCCC;"
/>
</view>
</view>
<view class="info-item">
<text class="item-label">意向学院专业</text>
<view class="item-content" @click="openPicker('major')">
<text class="placeholder-text" v-if="!userInfo.major"
>请选择意向学院专业</text
>
<text v-else>{{ userInfo.major }}</text>
<text class="arrow-icon">></text>
</view>
</view>
</view>
</view>
<div class="user-info-footer">
<u-button class="user-info-footer-button" @click="handleSave">
保存
</u-button>
</div>
<!-- 选择器弹窗 -->
<u-picker
v-model="showPicker"
:columns="pickerColumns"
@confirm="onPickerConfirm"
@cancel="showPicker = false"
></u-picker>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
userInfo: {
avatar: "/static/common/images/avatar_default.png",
name: "杨翼",
gender: "",
region: "",
examYear: "",
highSchool: "",
major: "",
},
showPicker: false,
currentPicker: "",
pickerColumns: [],
pickerData: {
gender: ["男", "女"],
region: ["北京", "上海", "广州", "深圳", "江西", "湖南"],
examYear: ["2023", "2024", "2025", "2026"],
major: [
"计算机科学与技术",
"软件工程",
"人工智能",
"电子信息工程",
"机械工程",
],
},
};
},
mounted() {},
onLoad(options) {
if (options.userId) {
this.userId = options.userId;
this.getUserInfo();
}
},
methods: {
handleLeftClick() {
uni.navigateBack();
},
getUserInfo() {
//
console.log("获取用户ID为", this.userId, "的信息");
//
},
chooseAvatar() {
uni.chooseImage({
count: 1,
sizeType: ["compressed"],
sourceType: ["album", "camera"],
success: (res) => {
this.userInfo.avatar = res.tempFilePaths[0];
//
},
});
},
openPicker(type) {
this.currentPicker = type;
this.pickerColumns = [this.pickerData[type]];
this.showPicker = true;
},
onPickerConfirm(e) {
const value = e.value[0];
switch (this.currentPicker) {
case "gender":
this.userInfo.gender = value;
break;
case "region":
this.userInfo.region = value;
break;
case "examYear":
this.userInfo.examYear = value;
break;
case "major":
this.userInfo.major = value;
break;
}
this.showPicker = false;
},
},
};
</script>
<style lang="scss" scoped>
.userInfo-container {
min-height: 100vh;
padding-bottom: calc(112rpx + env(safe-area-inset-bottom));
padding: 32rpx;
padding-top: calc(88rpx + 40rpx);
background-image: url(/static//common/images/images_bg.png);
background-repeat: no-repeat;
background-size: 100% 100%;
background-position: center;
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
// background-color: #ffffff;
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30rpx;
z-index: 99;
.header-left {
font-size: 36rpx;
}
.header-title {
font-size: 36rpx;
font-weight: 500;
color: #333333;
}
}
.user-info-content {
.info-card {
background-color: #ffffff;
border-radius: 20rpx;
padding: 0 32rpx;
margin-bottom: 32rpx;
.info-item {
height: 110rpx;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1rpx solid #f2f2f2;
&:last-child {
border-bottom: none;
}
.item-label {
font-family: PingFang SC;
font-size: 28rpx;
color: #333333;
}
.item-content {
flex: 1;
display: flex;
justify-content: flex-end;
align-items: center;
font-size: 28rpx;
color: #333333;
.item-input {
text-align: right;
width: 100%;
font-size: 28rpx;
}
.placeholder-text {
color: #cccccc;
}
.arrow-icon {
margin-left: 10rpx;
color: #cccccc;
}
}
&.avatar-item {
.avatar-content {
display: flex;
align-items: center;
}
.avatar-image {
width: 68rpx;
height: 68rpx;
border-radius: 50%;
}
}
}
}
}
.user-info-footer {
position: fixed;
bottom: 32rpx;
left: 0;
right: 0;
padding: 0 32rpx;
.user-info-footer-button {
width: 100%;
height: 85rpx;
background: #4f6aff;
color: #ffffff;
border-radius: 16rpx;
opacity: 0.9;
}
}
}
/* 响应式布局 - PC端样式 */
@media screen and (min-width: 768px) {
.userInfo-container {
.user-info-content {
max-width: 1200rpx;
margin: 0 auto;
}
}
}
</style>

View File

@ -37,40 +37,98 @@
<view class="form-content">
<view class="form-item">
<text class="form-label">手机号</text>
<text class="form-label"
><image
class="image_icon"
src="/static/common/images/icon_phoneNumber.png"
mode="scaleToFill"
/>
手机号</text
>
<view class="input-wrapper">
<input
type="text"
class="form-input"
placeholder="请输入手机号"
v-model="phone"
@blur="validatePhone"
/>
</view>
<text v-if="errors.phone" class="error-tip">{{ errors.phone }}</text>
</view>
<view class="form-item" v-if="isTeacher && loginType === 'psd'">
<text class="form-label">密码</text>
<text class="form-label"
><image
class="image_icon"
src="/static/common/images/icon_password.png"
mode="scaleToFill"
/></text
>
<view class="input-wrapper">
<input
type="text"
class="form-input"
placeholder="请输入密码"
v-model="password"
@blur="validatePassword"
/>
</view>
<text v-if="errors.password" class="error-tip">{{
errors.password
}}</text>
</view>
<view class="form-item" v-if="loginType === 'code'">
<text class="form-label">验证码</text>
<text class="form-label">
<image
class="image_icon"
src="/static/common/images/icon_verificationCode.png"
mode="scaleToFill"
/>
图形验证码</text
>
<view class="input-wrapper">
<input
type="text"
class="form-input"
placeholder="请输入验证码"
v-model="captcha"
@blur="validateCaptcha"
/>
<view class="captcha-img" @click="refreshCaptcha">
<image
v-if="captchaUrl"
:src="captchaUrl"
mode="widthFix"
></image>
</view>
</view>
<text v-if="errors.captcha" class="error-tip">{{
errors.captcha
}}</text>
</view>
<view class="form-item" v-if="loginType === 'code'">
<text class="form-label">
<image
class="image_icon"
src="/static/common/images/icon_verificationCode.png"
mode="scaleToFill"
/>
验证码</text
>
<view class="input-wrapper">
<input
type="text"
class="form-input"
placeholder="请输入验证码"
v-model="code"
@blur="validateCode"
/>
<text class="get-code-btn" @click="getCode">获取验证码</text>
<text class="get-code-btn" @click="getCode">{{ codeText }}</text>
</view>
<text v-if="errors.code" class="error-tip">{{ errors.code }}</text>
</view>
</view>
@ -85,22 +143,196 @@
export default {
data() {
return {
isTeacher: true, //
isTeacher: false, //
phone: "",
password: "",
code: "",
captcha: "", //
captchaId: "", // ID
captchaUrl: "", // URL使
loginType: "code", //
codeText: "获取验证码",
countdown: 60,
timer: null,
errors: {
phone: "",
password: "",
code: "",
captcha: "",
},
};
},
mounted() {
this.refreshCaptcha();
},
beforeDestroy() {
if (this.timer) {
clearInterval(this.timer);
}
},
methods: {
switchLoginType(type) {
this.loginType = type;
this.clearErrors();
},
//
refreshCaptcha() {
//
this.$u.api.GetCaptcha().then((res) => {
console.log("获取图形验证码...", res);
this.captchaId = res.captchaId;
this.captchaUrl = "data:image/png;base64," + res.imageBase64;
});
},
//
getCode() {
//
if (!this.validatePhone()) {
return;
}
if (this.loginType === "code" && !this.validateCaptcha()) {
return;
}
if (this.codeText !== "获取验证码") {
return;
}
//
this.startCountdown();
// API
uni.showToast({
title: "验证码已发送",
icon: "success",
});
},
//
startCountdown() {
this.countdown = 60;
this.codeText = `${this.countdown}秒后重新获取`;
this.timer = setInterval(() => {
this.countdown--;
this.codeText = `${this.countdown}秒后重新获取`;
if (this.countdown <= 0) {
clearInterval(this.timer);
this.codeText = "获取验证码";
}
}, 1000);
},
//
validatePhone() {
if (!this.phone) {
this.errors.phone = "请输入手机号";
return false;
}
if (!/^1[3-9]\d{9}$/.test(this.phone)) {
this.errors.phone = "手机号格式不正确";
return false;
}
this.errors.phone = "";
return true;
},
//
validatePassword() {
if (!this.password) {
this.errors.password = "请输入密码";
return false;
}
// if (this.password.length < 6) {
// this.errors.password = "6";
// return false;
// }
this.errors.password = "";
return true;
},
//
validateCaptcha() {
if (!this.captcha) {
this.errors.captcha = "请输入图形验证码";
return false;
}
// if (this.captcha.length < 4) {
// this.errors.captcha = "";
// return false;
// }
//
this.errors.captcha = "";
return true;
},
//
validateCode() {
if (!this.code) {
this.errors.code = "请输入验证码";
return false;
}
if (this.code.length < 4) {
this.errors.code = "验证码错误请重新输入";
return false;
}
this.errors.code = "";
return true;
},
//
clearErrors() {
this.errors = {
phone: "",
password: "",
code: "",
captcha: "",
};
},
//
login() {
//
let isValid = true;
//
if (!this.validatePhone()) {
isValid = false;
}
//
if (this.loginType === "psd") {
if (!this.validatePassword()) {
isValid = false;
}
} else {
if (!this.validateCaptcha()) {
isValid = false;
}
if (!this.validateCode()) {
isValid = false;
}
}
if (!isValid) {
return;
}
//
uni.showLoading({
title: "登录中...",
});
},
},
};
@ -116,7 +348,7 @@ export default {
flex-direction: column;
box-sizing: border-box;
background-image: url(/static//common/images/images_bg.png);
background-image: url(/static/common/images/images_bg.png);
background-repeat: no-repeat;
background-size: 100% 100%;
background-position: 50%;
@ -130,7 +362,7 @@ export default {
.logo-container {
display: flex;
align-items: center;
margin-bottom: 20rpx;
margin-bottom: 32rpx;
.logo-icon {
width: 48rpx;
@ -217,23 +449,18 @@ export default {
.form-label {
display: block;
position: relative;
padding-left: 30rpx;
// padding-left: 30rpx;
font-family: PingFang SC;
font-size: 28rpx;
color: #333333;
margin-bottom: 16rpx;
}
&::before {
content: "";
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 20rpx;
height: 20rpx;
border: 2rpx solid #4370fe;
border-radius: 4rpx;
}
.image_icon {
width: 28rpx;
height: 28rpx;
vertical-align: text-top;
margin-right: 16rpx;
}
&:nth-child(2) {
@ -264,13 +491,38 @@ export default {
right: 0;
top: 50%;
transform: translateY(-50%);
width: 220rpx;
height: 60rpx;
line-height: 60rpx;
padding: 0 30rpx;
padding-left: 30rpx;
color: #4370fe;
font-size: 28rpx;
border-left: 1px solid #557bf9;
border-left: 1px solid #b7c1ff;
}
.captcha-img {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
height: 60rpx;
width: 220rpx;
padding-left: 30rpx;
border-left: 1px solid #b7c1ff;
image {
width: 80%;
height: 100%;
}
}
}
.error-tip {
display: block;
font-size: 24rpx;
color: #FF6481;
margin-top: 10rpx;
padding-left: 10rpx;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB