feat(profile): 新增用户设置功能,包括头像、用户名和密码修改
新增了用户设置页面及其子页面,支持用户修改头像、用户名和密码。添加了相关API接口,并在路由中配置了对应的路径。同时引入了@heroicons/vue库以支持页面中的图标使用。
Showing
10 changed files
with
447 additions
and
0 deletions
| ... | @@ -8,6 +8,7 @@ | ... | @@ -8,6 +8,7 @@ |
| 8 | "name": "vue-vite", | 8 | "name": "vue-vite", |
| 9 | "version": "0.0.0", | 9 | "version": "0.0.0", |
| 10 | "dependencies": { | 10 | "dependencies": { |
| 11 | + "@heroicons/vue": "^2.2.0", | ||
| 11 | "@vant/use": "^1.6.0", | 12 | "@vant/use": "^1.6.0", |
| 12 | "dayjs": "^1.11.13", | 13 | "dayjs": "^1.11.13", |
| 13 | "swiper": "^11.2.6", | 14 | "swiper": "^11.2.6", |
| ... | @@ -824,6 +825,14 @@ | ... | @@ -824,6 +825,14 @@ |
| 824 | "node": ">=18" | 825 | "node": ">=18" |
| 825 | } | 826 | } |
| 826 | }, | 827 | }, |
| 828 | + "node_modules/@heroicons/vue": { | ||
| 829 | + "version": "2.2.0", | ||
| 830 | + "resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-2.2.0.tgz", | ||
| 831 | + "integrity": "sha512-G3dbSxoeEKqbi/DFalhRxJU4mTXJn7GwZ7ae8NuEQzd1bqdd0jAbdaBZlHPcvPD2xI1iGzNVB4k20Un2AguYPw==", | ||
| 832 | + "peerDependencies": { | ||
| 833 | + "vue": ">= 3" | ||
| 834 | + } | ||
| 835 | + }, | ||
| 827 | "node_modules/@isaacs/cliui": { | 836 | "node_modules/@isaacs/cliui": { |
| 828 | "version": "8.0.2", | 837 | "version": "8.0.2", |
| 829 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", | 838 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", | ... | ... |
| ... | @@ -15,6 +15,7 @@ | ... | @@ -15,6 +15,7 @@ |
| 15 | "dev_upload": "npm run build_tar && npm run scp-dev && npm run dec-dev && npm run remove_tar" | 15 | "dev_upload": "npm run build_tar && npm run scp-dev && npm run dec-dev && npm run remove_tar" |
| 16 | }, | 16 | }, |
| 17 | "dependencies": { | 17 | "dependencies": { |
| 18 | + "@heroicons/vue": "^2.2.0", | ||
| 18 | "@vant/use": "^1.6.0", | 19 | "@vant/use": "^1.6.0", |
| 19 | "dayjs": "^1.11.13", | 20 | "dayjs": "^1.11.13", |
| 20 | "swiper": "^11.2.6", | 21 | "swiper": "^11.2.6", | ... | ... |
| ... | @@ -11,6 +11,9 @@ const Api = { | ... | @@ -11,6 +11,9 @@ const Api = { |
| 11 | USER_LOGIN: '/users/login', | 11 | USER_LOGIN: '/users/login', |
| 12 | USER_REGISTER: '/users/register', | 12 | USER_REGISTER: '/users/register', |
| 13 | USER_INFO: '/users/info', | 13 | USER_INFO: '/users/info', |
| 14 | + USER_UPDATE: '/users/update', | ||
| 15 | + USER_AVATAR: '/users/avatar', | ||
| 16 | + USER_PASSWORD: '/users/password', | ||
| 14 | } | 17 | } |
| 15 | 18 | ||
| 16 | /** | 19 | /** |
| ... | @@ -32,3 +35,22 @@ export const registerAPI = (params) => fn(fetch.post(Api.USER_REGISTER, params)) | ... | @@ -32,3 +35,22 @@ export const registerAPI = (params) => fn(fetch.post(Api.USER_REGISTER, params)) |
| 32 | * @description: 获取用户信息 | 35 | * @description: 获取用户信息 |
| 33 | */ | 36 | */ |
| 34 | export const getUserInfoAPI = () => fn(fetch.get(Api.USER_INFO)); | 37 | export const getUserInfoAPI = () => fn(fetch.get(Api.USER_INFO)); |
| 38 | + | ||
| 39 | +/** | ||
| 40 | + * @description: 更新用户信息 | ||
| 41 | + * @param: name 用户名称 | ||
| 42 | + */ | ||
| 43 | +export const updateUserInfoAPI = (params) => fn(fetch.put(Api.USER_UPDATE, params)); | ||
| 44 | + | ||
| 45 | +/** | ||
| 46 | + * @description: 上传用户头像 | ||
| 47 | + * @param: avatar 头像文件 | ||
| 48 | + */ | ||
| 49 | +export const uploadAvatarAPI = (formData) => fn(fetch.post(Api.USER_AVATAR, formData)); | ||
| 50 | + | ||
| 51 | +/** | ||
| 52 | + * @description: 修改用户密码 | ||
| 53 | + * @param: oldPassword 原密码 | ||
| 54 | + * @param: newPassword 新密码 | ||
| 55 | + */ | ||
| 56 | +export const updatePasswordAPI = (params) => fn(fetch.put(Api.USER_PASSWORD, params)); | ... | ... |
| ... | @@ -23,14 +23,21 @@ declare module 'vue' { | ... | @@ -23,14 +23,21 @@ declare module 'vue' { |
| 23 | SearchBar: typeof import('./components/ui/SearchBar.vue')['default'] | 23 | SearchBar: typeof import('./components/ui/SearchBar.vue')['default'] |
| 24 | SummerCampCard: typeof import('./components/ui/SummerCampCard.vue')['default'] | 24 | SummerCampCard: typeof import('./components/ui/SummerCampCard.vue')['default'] |
| 25 | TermsPopup: typeof import('./components/ui/TermsPopup.vue')['default'] | 25 | TermsPopup: typeof import('./components/ui/TermsPopup.vue')['default'] |
| 26 | + VanButton: typeof import('vant/es')['Button'] | ||
| 27 | + VanCell: typeof import('vant/es')['Cell'] | ||
| 28 | + VanCellGroup: typeof import('vant/es')['CellGroup'] | ||
| 26 | VanCheckbox: typeof import('vant/es')['Checkbox'] | 29 | VanCheckbox: typeof import('vant/es')['Checkbox'] |
| 27 | VanDatePicker: typeof import('vant/es')['DatePicker'] | 30 | VanDatePicker: typeof import('vant/es')['DatePicker'] |
| 31 | + VanField: typeof import('vant/es')['Field'] | ||
| 32 | + VanForm: typeof import('vant/es')['Form'] | ||
| 28 | VanIcon: typeof import('vant/es')['Icon'] | 33 | VanIcon: typeof import('vant/es')['Icon'] |
| 34 | + VanImage: typeof import('vant/es')['Image'] | ||
| 29 | VanList: typeof import('vant/es')['List'] | 35 | VanList: typeof import('vant/es')['List'] |
| 30 | VanPickerGroup: typeof import('vant/es')['PickerGroup'] | 36 | VanPickerGroup: typeof import('vant/es')['PickerGroup'] |
| 31 | VanPopup: typeof import('vant/es')['Popup'] | 37 | VanPopup: typeof import('vant/es')['Popup'] |
| 32 | VanRate: typeof import('vant/es')['Rate'] | 38 | VanRate: typeof import('vant/es')['Rate'] |
| 33 | VanTab: typeof import('vant/es')['Tab'] | 39 | VanTab: typeof import('vant/es')['Tab'] |
| 34 | VanTabs: typeof import('vant/es')['Tabs'] | 40 | VanTabs: typeof import('vant/es')['Tabs'] |
| 41 | + VanUploader: typeof import('vant/es')['Uploader'] | ||
| 35 | } | 42 | } |
| 36 | } | 43 | } | ... | ... |
| ... | @@ -140,6 +140,30 @@ const routes = [ | ... | @@ -140,6 +140,30 @@ const routes = [ |
| 140 | meta: { title: '消息详情' }, | 140 | meta: { title: '消息详情' }, |
| 141 | }, | 141 | }, |
| 142 | { | 142 | { |
| 143 | + path: '/profile/settings', | ||
| 144 | + name: 'Settings', | ||
| 145 | + component: () => import('../views/profile/SettingsPage.vue'), | ||
| 146 | + meta: { title: '设置' }, | ||
| 147 | + }, | ||
| 148 | + { | ||
| 149 | + path: '/profile/settings/avatar', | ||
| 150 | + name: 'AvatarSetting', | ||
| 151 | + component: () => import('../views/profile/settings/AvatarSettingPage.vue'), | ||
| 152 | + meta: { title: '修改头像' }, | ||
| 153 | + }, | ||
| 154 | + { | ||
| 155 | + path: '/profile/settings/username', | ||
| 156 | + name: 'UsernameSetting', | ||
| 157 | + component: () => import('../views/profile/settings/UsernameSettingPage.vue'), | ||
| 158 | + meta: { title: '修改用户名' }, | ||
| 159 | + }, | ||
| 160 | + { | ||
| 161 | + path: '/profile/settings/password', | ||
| 162 | + name: 'PasswordSetting', | ||
| 163 | + component: () => import('../views/profile/settings/PasswordSettingPage.vue'), | ||
| 164 | + meta: { title: '修改密码' }, | ||
| 165 | + }, | ||
| 166 | + { | ||
| 143 | path: '/test', | 167 | path: '/test', |
| 144 | name: 'test', | 168 | name: 'test', |
| 145 | component: () => import('../views/test.vue'), | 169 | component: () => import('../views/test.vue'), | ... | ... |
| ... | @@ -328,6 +328,11 @@ const menuItems3 = [ | ... | @@ -328,6 +328,11 @@ const menuItems3 = [ |
| 328 | badge: "3", | 328 | badge: "3", |
| 329 | }, | 329 | }, |
| 330 | { | 330 | { |
| 331 | + icon: "settings", | ||
| 332 | + title: "设置", | ||
| 333 | + path: "/profile/settings", | ||
| 334 | + }, | ||
| 335 | + { | ||
| 331 | icon: "question", | 336 | icon: "question", |
| 332 | title: "帮助中心", | 337 | title: "帮助中心", |
| 333 | path: "/profile/help", | 338 | path: "/profile/help", | ... | ... |
src/views/profile/SettingsPage.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-03-24 13:04:21 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-24 13:22:18 | ||
| 5 | + * @FilePath: /mlaj/src/views/profile/SettingsPage.vue | ||
| 6 | + * @Description: 用户设置页面 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <AppLayout title="设置"> | ||
| 10 | + <div class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen"> | ||
| 11 | + <div class="px-4 py-6"> | ||
| 12 | + <FrostedGlass class="rounded-xl overflow-hidden"> | ||
| 13 | + <!-- 设置列表 --> | ||
| 14 | + <div class="divide-y divide-gray-100"> | ||
| 15 | + <!-- 头像设置 --> | ||
| 16 | + <div class="p-4" @click="router.push('/profile/settings/avatar')"> | ||
| 17 | + <div class="flex items-center justify-between"> | ||
| 18 | + <div class="flex items-center"> | ||
| 19 | + <!-- <div class="relative"> | ||
| 20 | + <img | ||
| 21 | + :src="userAvatar || '/src/assets/images/avatar1.jpg'" | ||
| 22 | + alt="用户头像" | ||
| 23 | + class="w-16 h-16 rounded-full object-cover" | ||
| 24 | + /> | ||
| 25 | + </div> --> | ||
| 26 | + <div class="ml-4"> | ||
| 27 | + <h3 class="text-base font-medium text-gray-900">修改头像</h3> | ||
| 28 | + <p class="text-sm text-gray-500">支持 jpg、png 格式,大小不超过 2MB</p> | ||
| 29 | + </div> | ||
| 30 | + </div> | ||
| 31 | + <ChevronRightIcon class="w-5 h-5 text-gray-400" /> | ||
| 32 | + </div> | ||
| 33 | + </div> | ||
| 34 | + | ||
| 35 | + <!-- 用户名设置 --> | ||
| 36 | + <div class="p-4" @click="router.push('/profile/settings/username')"> | ||
| 37 | + <div class="flex items-center justify-between"> | ||
| 38 | + <div> | ||
| 39 | + <h3 class="text-base font-medium text-gray-900">修改用户名</h3> | ||
| 40 | + <p class="text-sm text-gray-500">{{ username }}</p> | ||
| 41 | + </div> | ||
| 42 | + <ChevronRightIcon class="w-5 h-5 text-gray-400" /> | ||
| 43 | + </div> | ||
| 44 | + </div> | ||
| 45 | + | ||
| 46 | + <!-- 密码设置 --> | ||
| 47 | + <div class="p-4" @click="router.push('/profile/settings/password')"> | ||
| 48 | + <div class="flex items-center justify-between"> | ||
| 49 | + <div> | ||
| 50 | + <h3 class="text-base font-medium text-gray-900">修改密码</h3> | ||
| 51 | + <p class="text-sm text-gray-500">定期修改密码更安全</p> | ||
| 52 | + </div> | ||
| 53 | + <ChevronRightIcon class="w-5 h-5 text-gray-400" /> | ||
| 54 | + </div> | ||
| 55 | + </div> | ||
| 56 | + </div> | ||
| 57 | + </FrostedGlass> | ||
| 58 | + </div> | ||
| 59 | + </div> | ||
| 60 | + </AppLayout> | ||
| 61 | +</template> | ||
| 62 | + | ||
| 63 | +<script setup> | ||
| 64 | +import { ref, onMounted } from 'vue'; | ||
| 65 | +import { useRoute, useRouter } from 'vue-router'; | ||
| 66 | +import AppLayout from '@/components/layout/AppLayout.vue'; | ||
| 67 | +import FrostedGlass from '@/components/ui/FrostedGlass.vue'; | ||
| 68 | +import { ChevronRightIcon } from '@heroicons/vue/24/outline'; | ||
| 69 | +import { useTitle } from '@vueuse/core'; | ||
| 70 | +import { getUserInfoAPI } from '@/api/users'; | ||
| 71 | + | ||
| 72 | +const $route = useRoute(); | ||
| 73 | +const router = useRouter(); | ||
| 74 | +useTitle($route.meta.title); | ||
| 75 | + | ||
| 76 | +// 用户头像 | ||
| 77 | +const userAvatar = ref(''); | ||
| 78 | + | ||
| 79 | +// 用户名 | ||
| 80 | +const username = ref(''); | ||
| 81 | + | ||
| 82 | +// 获取用户信息 | ||
| 83 | +onMounted(async () => { | ||
| 84 | + try { | ||
| 85 | + const response = await getUserInfoAPI(); | ||
| 86 | + if (response.data) { | ||
| 87 | + userAvatar.value = response.data.avatar; | ||
| 88 | + username.value = response.data.name; | ||
| 89 | + } | ||
| 90 | + } catch (error) { | ||
| 91 | + console.error('获取用户信息失败:', error); | ||
| 92 | + } | ||
| 93 | +}); | ||
| 94 | +</script> |
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-03-24 13:04:21 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-24 13:32:25 | ||
| 5 | + * @FilePath: /mlaj/src/views/profile/settings/AvatarSettingPage.vue | ||
| 6 | + * @Description: 修改头像页面 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <AppLayout title=""> | ||
| 10 | + <div | ||
| 11 | + class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen" | ||
| 12 | + > | ||
| 13 | + <div class="px-4 py-6"> | ||
| 14 | + <FrostedGlass class="rounded-xl overflow-hidden"> | ||
| 15 | + <div class="p-4"> | ||
| 16 | + <div class="flex flex-col items-center"> | ||
| 17 | + <div class="mb-6"> | ||
| 18 | + <van-image | ||
| 19 | + round | ||
| 20 | + :src=" | ||
| 21 | + userAvatar || 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg' | ||
| 22 | + " | ||
| 23 | + alt="用户头像" | ||
| 24 | + class="rounded-full border-4 border-white shadow-lg" | ||
| 25 | + width="8rem" | ||
| 26 | + height="8rem" | ||
| 27 | + fit="cover" | ||
| 28 | + /> | ||
| 29 | + </div> | ||
| 30 | + <p class="text-sm text-gray-500 mb-4">支持 jpg、png 格式,大小不超过 2MB</p> | ||
| 31 | + <van-uploader | ||
| 32 | + :after-read="handleAvatarChange" | ||
| 33 | + :max-size="2 * 1024 * 1024" | ||
| 34 | + accept="image/jpeg,image/png" | ||
| 35 | + :preview-image="false" | ||
| 36 | + > | ||
| 37 | + <van-button | ||
| 38 | + block | ||
| 39 | + type="primary" | ||
| 40 | + class="max-w-xs" | ||
| 41 | + > | ||
| 42 | + 选择新头像 | ||
| 43 | + </van-button> | ||
| 44 | + </van-uploader> | ||
| 45 | + </div> | ||
| 46 | + </div> | ||
| 47 | + </FrostedGlass> | ||
| 48 | + </div> | ||
| 49 | + </div> | ||
| 50 | + </AppLayout> | ||
| 51 | +</template> | ||
| 52 | + | ||
| 53 | +<script setup> | ||
| 54 | +import { ref, onMounted } from "vue"; | ||
| 55 | +import AppLayout from "@/components/layout/AppLayout.vue"; | ||
| 56 | +import FrostedGlass from "@/components/ui/FrostedGlass.vue"; | ||
| 57 | +import { getUserInfoAPI, uploadAvatarAPI } from "@/api/users"; | ||
| 58 | + | ||
| 59 | +import { useTitle } from '@vueuse/core'; | ||
| 60 | + | ||
| 61 | +const $route = useRoute(); | ||
| 62 | +useTitle($route.meta.title); | ||
| 63 | + | ||
| 64 | + | ||
| 65 | +// 用户头像 | ||
| 66 | +const userAvatar = ref(""); | ||
| 67 | + | ||
| 68 | +// 获取用户信息 | ||
| 69 | +onMounted(async () => { | ||
| 70 | + try { | ||
| 71 | + const response = await getUserInfoAPI(); | ||
| 72 | + if (response.data) { | ||
| 73 | + userAvatar.value = response.data.avatar; | ||
| 74 | + } | ||
| 75 | + } catch (error) { | ||
| 76 | + console.error("获取用户信息失败:", error); | ||
| 77 | + } | ||
| 78 | +}); | ||
| 79 | + | ||
| 80 | +// 处理头像上传 | ||
| 81 | +const handleAvatarChange = async (file) => { | ||
| 82 | + try { | ||
| 83 | + const formData = new FormData(); | ||
| 84 | + formData.append("avatar", file); | ||
| 85 | + | ||
| 86 | + const response = await uploadAvatarAPI(formData); | ||
| 87 | + if (response.data) { | ||
| 88 | + userAvatar.value = response.data.url; | ||
| 89 | + alert("头像上传成功"); | ||
| 90 | + } | ||
| 91 | + } catch (error) { | ||
| 92 | + console.error("头像上传失败:", error); | ||
| 93 | + alert("头像上传失败,请重试"); | ||
| 94 | + } | ||
| 95 | +}; | ||
| 96 | +</script> |
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-03-24 13:04:21 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-24 13:19:54 | ||
| 5 | + * @FilePath: /mlaj/src/views/profile/settings/PasswordSettingPage.vue | ||
| 6 | + * @Description: 修改密码页面 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <AppLayout title=""> | ||
| 10 | + <div class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen"> | ||
| 11 | + <div class="px-4 py-6"> | ||
| 12 | + <FrostedGlass class="rounded-xl overflow-hidden"> | ||
| 13 | + <div class="p-4"> | ||
| 14 | + <van-form @submit="handlePasswordChange"> | ||
| 15 | + <van-cell-group inset> | ||
| 16 | + <van-field | ||
| 17 | + v-model="oldPassword" | ||
| 18 | + type="password" | ||
| 19 | + name="oldPassword" | ||
| 20 | + label="原密码" | ||
| 21 | + placeholder="请输入原密码" | ||
| 22 | + :rules="[{ required: true, message: '请输入原密码' }, { min: 6, message: '密码长度至少6位' }]" | ||
| 23 | + /> | ||
| 24 | + <van-field | ||
| 25 | + v-model="newPassword" | ||
| 26 | + type="password" | ||
| 27 | + name="newPassword" | ||
| 28 | + label="新密码" | ||
| 29 | + placeholder="请输入新密码" | ||
| 30 | + :rules="[{ required: true, message: '请输入新密码' }, { min: 6, message: '密码长度至少6位' }]" | ||
| 31 | + /> | ||
| 32 | + <van-field | ||
| 33 | + v-model="confirmPassword" | ||
| 34 | + type="password" | ||
| 35 | + name="confirmPassword" | ||
| 36 | + label="确认新密码" | ||
| 37 | + placeholder="请确认新密码" | ||
| 38 | + :rules="[{ required: true, message: '请确认新密码' }, { validator: validateConfirmPassword, message: '两次输入的密码不一致' }]" | ||
| 39 | + /> | ||
| 40 | + </van-cell-group> | ||
| 41 | + <div style="margin: 16px;"> | ||
| 42 | + <van-button | ||
| 43 | + native-type="submit" | ||
| 44 | + type="primary" | ||
| 45 | + block | ||
| 46 | + round | ||
| 47 | + :loading="loading" | ||
| 48 | + class="bg-green-500 hover:bg-green-600 transition-colors" | ||
| 49 | + > | ||
| 50 | + 保存修改 | ||
| 51 | + </van-button> | ||
| 52 | + </div> | ||
| 53 | + </van-form> | ||
| 54 | + </div> | ||
| 55 | + </FrostedGlass> | ||
| 56 | + </div> | ||
| 57 | + </div> | ||
| 58 | + </AppLayout> | ||
| 59 | +</template> | ||
| 60 | + | ||
| 61 | +<script setup> | ||
| 62 | +import { ref } from 'vue'; | ||
| 63 | +import AppLayout from '@/components/layout/AppLayout.vue'; | ||
| 64 | +import FrostedGlass from '@/components/ui/FrostedGlass.vue'; | ||
| 65 | +import { updatePasswordAPI } from '@/api/users'; | ||
| 66 | + | ||
| 67 | +import { useTitle } from '@vueuse/core'; | ||
| 68 | + | ||
| 69 | +const $route = useRoute(); | ||
| 70 | +useTitle($route.meta.title); | ||
| 71 | + | ||
| 72 | +// 密码 | ||
| 73 | +const oldPassword = ref(''); | ||
| 74 | +const newPassword = ref(''); | ||
| 75 | +const confirmPassword = ref(''); | ||
| 76 | +const loading = ref(false); | ||
| 77 | + | ||
| 78 | +// 确认密码验证 | ||
| 79 | +const validateConfirmPassword = (val) => val === newPassword.value; | ||
| 80 | + | ||
| 81 | +// 处理密码修改 | ||
| 82 | +const handlePasswordChange = async () => { | ||
| 83 | + loading.value = true; | ||
| 84 | + try { | ||
| 85 | + const response = await updatePasswordAPI({ | ||
| 86 | + oldPassword: oldPassword.value, | ||
| 87 | + newPassword: newPassword.value | ||
| 88 | + }); | ||
| 89 | + | ||
| 90 | + if (response.data) { | ||
| 91 | + // 清空输入框 | ||
| 92 | + oldPassword.value = ''; | ||
| 93 | + newPassword.value = ''; | ||
| 94 | + confirmPassword.value = ''; | ||
| 95 | + showSuccessToast('密码修改成功'); | ||
| 96 | + } | ||
| 97 | + } catch (error) { | ||
| 98 | + console.error('密码修改失败:', error); | ||
| 99 | + showFailToast('密码修改失败,请重试'); | ||
| 100 | + } finally { | ||
| 101 | + loading.value = false; | ||
| 102 | + } | ||
| 103 | +}; | ||
| 104 | +</script> |
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-03-24 13:04:21 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-03-24 13:33:27 | ||
| 5 | + * @FilePath: /mlaj/src/views/profile/settings/UsernameSettingPage.vue | ||
| 6 | + * @Description: 修改用户名页面 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <AppLayout title=""> | ||
| 10 | + <div class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen"> | ||
| 11 | + <div class="px-4 py-6"> | ||
| 12 | + <FrostedGlass class="rounded-xl overflow-hidden"> | ||
| 13 | + <div class="p-4"> | ||
| 14 | + <div class="space-y-4"> | ||
| 15 | + <div class="mb-6"> | ||
| 16 | + <label for="username" class="block text-sm font-medium text-gray-700 mb-2">新用户名</label> | ||
| 17 | + <input | ||
| 18 | + v-model="username" | ||
| 19 | + type="text" | ||
| 20 | + id="username" | ||
| 21 | + placeholder="请输入新的用户名" | ||
| 22 | + class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500 focus:border-transparent" | ||
| 23 | + /> | ||
| 24 | + </div> | ||
| 25 | + <van-button | ||
| 26 | + @click="handleUsernameChange" | ||
| 27 | + type="primary" | ||
| 28 | + block | ||
| 29 | + round | ||
| 30 | + class="bg-green-500 hover:bg-green-600 transition-colors" | ||
| 31 | + > | ||
| 32 | + 保存修改 | ||
| 33 | + </van-button> | ||
| 34 | + </div> | ||
| 35 | + </div> | ||
| 36 | + </FrostedGlass> | ||
| 37 | + </div> | ||
| 38 | + </div> | ||
| 39 | + </AppLayout> | ||
| 40 | +</template> | ||
| 41 | + | ||
| 42 | +<script setup> | ||
| 43 | +import { ref, onMounted } from 'vue'; | ||
| 44 | +import AppLayout from '@/components/layout/AppLayout.vue'; | ||
| 45 | +import FrostedGlass from '@/components/ui/FrostedGlass.vue'; | ||
| 46 | +import { getUserInfoAPI, updateUserInfoAPI } from '@/api/users'; | ||
| 47 | + | ||
| 48 | +import { useTitle } from '@vueuse/core'; | ||
| 49 | + | ||
| 50 | +const $route = useRoute(); | ||
| 51 | +useTitle($route.meta.title); | ||
| 52 | + | ||
| 53 | +// 用户名 | ||
| 54 | +const username = ref(''); | ||
| 55 | + | ||
| 56 | +// 获取用户信息 | ||
| 57 | +onMounted(async () => { | ||
| 58 | + try { | ||
| 59 | + const response = await getUserInfoAPI(); | ||
| 60 | + if (response.data) { | ||
| 61 | + username.value = response.data.name; | ||
| 62 | + } | ||
| 63 | + } catch (error) { | ||
| 64 | + console.error('获取用户信息失败:', error); | ||
| 65 | + } | ||
| 66 | +}); | ||
| 67 | + | ||
| 68 | +// 处理用户名修改 | ||
| 69 | +const handleUsernameChange = async () => { | ||
| 70 | + if (!username.value) { | ||
| 71 | + alert('请输入新的用户名'); | ||
| 72 | + return; | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + try { | ||
| 76 | + const response = await updateUserInfoAPI({ name: username.value }); | ||
| 77 | + if (response.data) { | ||
| 78 | + alert('用户名修改成功'); | ||
| 79 | + } | ||
| 80 | + } catch (error) { | ||
| 81 | + console.error('用户名修改失败:', error); | ||
| 82 | + alert('用户名修改失败,请重试'); | ||
| 83 | + } | ||
| 84 | +}; | ||
| 85 | +</script> |
-
Please register or login to post a comment