Update API request formatting and routing structure

This commit is contained in:
wangqiao
2025-08-27 21:54:08 +08:00
parent 0f96406b5a
commit 170113e11c
23 changed files with 124 additions and 118 deletions

View File

@ -0,0 +1,433 @@
<template>
<div class="box-border h-[782px] w-[913px] border border-[#EEEEEE] rounded-[6px] border-solid bg-[#FFFFFF] px-[30px] py-[21px]">
<div class="flex items-center justify-between border-b-[1px] border-b-[#eeeeee] border-b-solid pb-[18px]">
<div class="text-[16px] text-[#333333] font-normal">个人资料</div>
<div class="flex items-center">
<!-- <img src="~/assets/images/fans.png" alt="" srcset="" /> -->
<span class="ml-[8px] text-[14px] text-[#333333] font-normal"></span>
</div>
</div>
<div class="user-profile-container">
<div class="avatar-section">
<el-upload class="avatar-uploader" action="#" :show-file-list="false" :auto-upload="false" :on-change="handleAvatarChange">
<div class="flex flex-col items-center">
<el-avatar :size="100" :src="userForm.avatar" />
<div class="mt-[15px]">
<el-button type="primary" plain>更改头像</el-button>
</div>
</div>
</el-upload>
</div>
<el-form ref="userFormRef" :model="userForm" label-width="120px" class="profile-form" :rules="rules">
<!-- User avatar section -->
<!-- User information section -->
<el-form-item label="用户名:" prop="nickname">
<div class="flex items-center">
<el-input v-model="userForm.nickname" class="w-[247px]" />
<el-button type="primary" class="verify-btn" @click="handleVerify">实名认证</el-button>
</div>
</el-form-item>
<!-- <el-form-item label="真实姓名:" prop="trueName">
<div class="flex items-center">
<el-input v-model="userForm.trueName" placeholder="请输入真实姓名" class="w-247px" />
<el-button type="primary" class="verify-btn" @click="handleVerify">实名认证</el-button>
</div>
</el-form-item> -->
<el-form-item label="手机号:" prop="phone">
<div class="flex items-center">
<el-input v-model="userForm.phone" disabled class="w-[247px]" />
<!-- <el-link type="primary" class="modify-link">修改</el-link> -->
</div>
</el-form-item>
<el-form-item label="电子邮箱:" prop="email">
<div class="flex items-center">
<el-input v-model="userForm.email" placeholder="请输入电子邮箱" class="w-[247px]" />
<!-- <el-link type="primary" class="modify-link">绑定</el-link> -->
</div>
</el-form-item>
<div class="flex items-center">
<el-form-item label="所在地区:" prop="isDomestic">
<el-select v-model="userForm.isDomestic" placeholder="请选择" class="w-[120px]!" @change="handleCountryChange">
<el-option label="国内" :value="1"></el-option>
<el-option label="国外" :value="0"></el-option>
</el-select>
</el-form-item>
<el-form-item label-width="6px" prop="province">
<el-select v-model="userForm.province" placeholder="请选择省份" class="w-[120px]!" @change="handleProvinceChange">
<el-option v-for="item in provinceList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label-width="6px" prop="city">
<el-select v-model="userForm.city" placeholder="请选择城市" class="w-[120px]!" @change="handleCityChange">
<el-option v-for="item in cityList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label-width="6px" prop="county">
<el-select v-model="userForm.county" placeholder="请选择区县" class="w-[120px]!">
<el-option v-for="item in countyList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</div>
<el-form-item label="技能标签:" prop="labels">
<el-select
v-model="userForm.labels"
:remote-method="remoteMethod"
:loading="loading"
multiple
filterable
remote
placeholder="请输入搜索标签"
class="w-[498px]!"
>
<el-option v-for="(item, index) in labelsList" :key="index" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="技能证书:" prop="files">
<KlUploader
v-model:file-list="userForm.files"
list-type="picture-card"
:limit="1000"
:size="1"
tips="上传图片支持jpg/gif/png格式、第一张为封面图片、每张图片大小不得超过1M"
>
<div class="h-[77px] w-[161px] flex items-center justify-center bg-[#fafafa]">
<el-icon class="text-[#999999]"><Plus /></el-icon>
<div class="ml-[4px] mt-[2px] text-[14px] text-[#999999] font-normal">上传图纸</div>
</div>
</KlUploader>
</el-form-item>
<el-form-item label="个人简介:" prop="description">
<el-input v-model="userForm.description" type="textarea" :rows="5" placeholder="请输入个人简介" class="full-width" />
</el-form-item>
<el-form-item>
<el-button type="primary" class="w-[120px] !h-[37px]" :loading="submitLoading" @click="submitForm">提交</el-button>
</el-form-item>
</el-form>
</div>
<!-- <div class="flex items-center justify-between border-b-[1px] border-b-[#eeeeee] border-b-solid pb-[18px]">
<div class="text-[16px] text-[#333333] font-normal">社交帐号绑定</div>
</div>
<div class="flex flex-col justify-center text-[14px] text-[#333333] font-normal">
<div class="mt-[30px] flex items-center">
<img src="~/assets/images/qq-v2.png" alt="" srcset="" class="h-[35px] w-[34px]" />
<div class="ml-[19px]">QQ</div>
<div class="ml-[100px] flex items-center"><div class="w-[90px]">QQ昵称</div><div class="w-[180px]">xxx</div></div>
<div class="btn">绑定</div>
</div>
<div class="mt-[30px] flex items-center">
<img src="~/assets/images/weixin-v2.png" alt="" srcset="" class="h-[35px] w-[34px]" />
<div class="ml-[19px]">微信</div>
<div class="ml-[95px] flex items-center"><div class="w-[90px]">微信昵称</div><div class="w-[180px]">xxx</div></div>
<div class="btn">绑定</div>
</div>
</div> -->
</div>
<verifyDialog ref="verifyDialogRef" />
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { Plus } from '@element-plus/icons-vue'
import { tree, upload } from '~/api/common/index'
import { keywords } from '~/api/upnew/index'
import { userExtend, getUserInfo, updateUserExtend } from '~/api/personal-center/index'
import type { UserExtendSaveReqVO } from '~/api/personal-center/types'
import verifyDialog from './verify-dialog.vue'
const userFormRef = ref()
const userForm = reactive<UserExtendSaveReqVO>({
id: undefined,
phone: '',
username: '',
avatar: '',
trueName: '',
city: '',
email: '',
isDomestic: undefined,
area: '',
country: '',
province: '',
county: '',
labels: [],
description: '',
authStatus: 0,
files: [],
nickname: '',
})
const rules = {
username: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
city: [{ required: true, message: '请选择城市', trigger: 'change' }],
email: [
{ required: false, message: '请输入电子邮箱', trigger: 'blur' },
{ type: 'email' as const, message: '请输入正确的邮箱格式', trigger: 'blur' },
],
// 标签
labels: [{ required: false, message: '请选择技能标签', trigger: 'change' }],
// 技能证书
files: [{ required: false, message: '请上传技能证书', trigger: 'change' }],
// 所在地区
isDomestic: [{ required: false, message: '请选择国家', trigger: 'change' }],
province: [{ required: false, message: '请选择省份', trigger: 'change' }],
county: [{ required: false, message: '请选择区县', trigger: 'change' }],
}
const verifyDialogRef = ref()
const handleVerify = () => {
verifyDialogRef.value.open()
}
// 省份地址
const provinceList = ref()
// 城市地址
const cityList = ref()
// 区县地址
const countyList = ref()
// 获取地址
const getAdress = async (type: string, val?: any) => {
const res = await tree({
id: val,
})
if (res.code === 0) {
if (type === 'province') {
provinceList.value = res.data
} else if (type === 'city') {
cityList.value = res.data
} else if (type === 'county') {
countyList.value = res.data
}
}
}
// 切换国家
const handleCountryChange = (value: any) => {
userForm.province = ''
userForm.city = ''
userForm.county = ''
provinceList.value = []
cityList.value = []
countyList.value = []
getAdress('province', value)
}
// 监听省份变化
const handleProvinceChange = (value: string) => {
userForm.city = ''
userForm.county = ''
cityList.value = []
countyList.value = []
getAdress('city', value)
}
// 监听城市变化
const handleCityChange = (value: string) => {
userForm.county = ''
countyList.value = []
getAdress('county', value)
}
// 处理头像上传变化
const handleAvatarChange = (file: any) => {
// 这里可以添加文件类型和大小的验证
if (file) {
// 创建本地预览URL
userForm.avatar = URL.createObjectURL(file.raw)
// 这里可以添加实际的上传逻辑
console.log('Avatar file:', file)
// 上传文件到服务器
const formData = new FormData()
formData.append('fieldName', file?.name)
formData.append('file', file.raw as Blob)
upload('/prod-api/app-api/infra/file/upload', formData)
.then((response) => {
if (response.code === 0) {
userForm.avatar = response.data
}
})
.catch((error) => {
console.error('Error uploading avatar:', error)
})
}
}
const loading = ref(false)
/** 获取标签 */
const labelsList = ref<any>([])
const remoteMethod = (query: string) => {
if (query) {
loading.value = true
keywords({
type: 1,
keywords: query,
})
.then((res) => {
labelsList.value = res.data
})
.finally(() => {
loading.value = false
})
} else {
labelsList.value = []
}
}
// 提交表单
const submitLoading = ref(false)
const submitForm = async () => {
if (!userFormRef.value) return
try {
await userFormRef.value.validate()
submitLoading.value = true
const res = userForm.id ? await updateUserExtend(userForm) : await userExtend(userForm)
if (res.code === 0) {
ElMessage.success('个人信息提交成功')
} else {
ElMessage.error('个人信息提交失败')
}
} catch (error) {
console.error('Validation failed:', error)
ElMessage.error('表单验证失败,请检查输入')
} finally {
submitLoading.value = false
}
}
// 初始回显
const init = async () => {
const res = await getUserInfo()
if (res.code === 0) {
userForm.id = res.data.id
userForm.nickname = res.data.nickname
userForm.phone = res.data.mobile
userForm.email = res.data.email
userForm.isDomestic = +res.data.isDomestic
userForm.country = res.data.country
await getAdress('province', userForm.isDomestic)
// @ts-ignore
userForm.province = +res.data.province
await getAdress('city', userForm.province)
// @ts-ignore
userForm.city = +res.data.city
await getAdress('county', userForm.city)
// @ts-ignore
userForm.county = +res.data.county
userForm.labels = res.data.labels
userForm.description = res.data.description
userForm.avatar = res.data.avatar
userForm.files = res.data.files.map((item: any) => {
return {
...item,
url: item.url,
name: item.title,
uid: item.id,
status: 'success',
}
})
}
}
init()
</script>
<style scoped>
.btn {
width: 91px;
height: 37px;
background: #1a65ff;
border-radius: 4px;
border: 1px solid #1a65ff;
text-align: center;
line-height: 37px;
font-weight: 400;
font-size: 14px;
color: #ffffff;
margin-left: 30px;
}
.user-profile-container {
margin: 0 auto;
padding: 30px;
display: flex;
}
.profile-form {
/* margin-top: 20px; */
}
.avatar-section {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 30px;
}
.update-avatar-btn {
margin-top: 15px;
}
.verify-btn {
margin-left: 10px;
}
.modify-link {
margin-left: 10px;
min-width: fit-content;
white-space: nowrap;
}
.certificate-uploader {
width: 100%;
}
.upload-area {
width: 200px;
height: 100px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
}
.upload-area:hover {
border-color: #409eff;
}
.full-width {
width: 100%;
}
.flex {
display: flex;
}
.gap-2 {
gap: 0.5rem;
}
.w-120px {
width: 120px;
}
/* 响应式布局调整 */
@media (max-width: 768px) {
.user-profile-container {
padding: 20px 10px;
}
.verify-btn {
margin-top: 10px;
margin-left: 0;
}
}
</style>