feat:更新朋友圈功能

This commit is contained in:
2025-06-28 15:54:02 +08:00
parent c77c3ff512
commit 0969af5ade
22 changed files with 598 additions and 70 deletions

View File

@@ -9,6 +9,7 @@
},
"dependencies": {
"@vueuse/core": "^13.1.0",
"element-china-area-data": "^6.1.0",
"element-plus": "^2.9.7",
"vue": "^3.5.13",
"vue3-common": "git+https://gitee.com/Cxx0822/vue3-common",

15
pnpm-lock.yaml generated
View File

@@ -11,6 +11,9 @@ importers:
'@vueuse/core':
specifier: ^13.1.0
version: 13.4.0(vue@3.5.17(typescript@5.8.3))
element-china-area-data:
specifier: ^6.1.0
version: 6.1.0
element-plus:
specifier: ^2.9.7
version: 2.10.2(vue@3.5.17(typescript@5.8.3))
@@ -833,6 +836,9 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
china-division@2.7.0:
resolution: {integrity: sha512-4uUPAT+1WfqDh5jytq7omdCmHNk3j+k76zEG/2IqaGcYB90c2SwcixttcypdsZ3T/9tN1TTpBDoeZn+Yw/qBEA==}
chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
@@ -958,6 +964,9 @@ packages:
echarts@5.6.0:
resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==}
element-china-area-data@6.1.0:
resolution: {integrity: sha512-IkpcjwQv2A/2AxFiSoaISZ+oMw1rZCPUSOg5sOCwT5jKc96TaawmKZeY81xfxXsO0QbKxU5LLc6AirhG52hUmg==}
element-plus@2.10.2:
resolution: {integrity: sha512-p2KiAa0jEGXrzdlTAfpiS7HQFAhla4gvx6H7RuDf+OO0uC3DGpolxvdHjFR8gt7+vaWyxQNcHa1sAdBkmjqlgA==}
peerDependencies:
@@ -2772,6 +2781,8 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
china-division@2.7.0: {}
chokidar@4.0.3:
dependencies:
readdirp: 4.1.2
@@ -2891,6 +2902,10 @@ snapshots:
tslib: 2.3.0
zrender: 5.6.1
element-china-area-data@6.1.0:
dependencies:
china-division: 2.7.0
element-plus@2.10.2(vue@3.5.17(typescript@5.8.3)):
dependencies:
'@ctrl/tinycolor': 3.6.1

View File

@@ -1,9 +1,22 @@
import { cloudService } from './index'
import type { ISession, IUser } from '@/types/auth'
export const registerApi = (user: IUser): Promise<ISession> =>
export const registerApi = (username: string, password: string): Promise<ISession> =>
cloudService({
url: '/food-service/user',
method: 'post',
params: { username, password }
})
export const queryUserApi = (id: number): Promise<IUser> =>
cloudService({
url: `/food-service/user/${id}`,
method: 'get'
})
export const updateUserApi = (id: number, user: IUser): Promise<boolean> =>
cloudService({
url: `/food-service/user/${id}`,
method: 'put',
data: user
})

View File

@@ -28,7 +28,7 @@
<div class="button-info">
<el-button type="primary" :icon="Edit" circle :size="'small'"
@click="editClick"/>
<el-button type="primary" :icon="MoreFilled" circle :size="'small'"
<el-button type="primary" :icon="Collection" circle :size="'small'"
@click="viewClick()" />
<el-button type="danger" :icon="DeleteFilled" circle :size="'small'"
@click="deleteClick()" />
@@ -59,7 +59,7 @@
</template>
<script setup lang="ts">
import { Edit, MoreFilled, DeleteFilled } from '@element-plus/icons-vue'
import { Edit, Collection, DeleteFilled } from '@element-plus/icons-vue'
import { useFoodStore } from '@/store/food'
import { defineProps, onMounted, reactive } from 'vue'
import type { PropType } from 'vue'

View File

@@ -44,7 +44,7 @@
<script lang="ts" setup>
import { useFoodStore } from '@/store/food'
import { nextTick, reactive, ref } from 'vue'
import { FormRules } from 'element-plus'
import type { FormRules } from 'element-plus'
import { foodMaterialRules } from '@/utils/element/elRules'
const rules = reactive<FormRules>(foodMaterialRules)

View File

@@ -1,13 +1,13 @@
<template>
<el-dialog v-model="foodStore.stepDialog.visible"
:title="foodStore.stepDialog.title"
:title="`${foodStore.stepDialog.title}${foodStore.editStepIndex + 1}`"
center @open="openDialogEvent"
class="food-step-dialog">
<el-form ref="formRef" :model="foodStore.stepDialog.data"
:rules="rules" label-width="60px">
<el-form-item label="内容" prop="content">
<el-input v-model="foodStore.stepDialog.data.content" autocomplete="off"
placeholder="请输入内容" clearable
:placeholder="`请输入步骤${foodStore.editStepIndex + 1}`" clearable
type="textarea" :rows="3"
show-word-limit maxlength="50">
</el-input>
@@ -36,7 +36,7 @@
import { UploadImage } from 'vue3-common'
import { useFoodStore } from '@/store/food'
import { ref, reactive, nextTick } from 'vue'
import { FormRules } from 'element-plus'
import type { FormRules } from 'element-plus'
import { foodStepRules } from '@/utils/element/elRules'
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
@@ -60,11 +60,13 @@ const openDialogEvent = () => {
const confirmClick = () => {
formRef.value.validate()
.then(async () => {
const { editStepIndex: index } = foodStore
if (foodStore.stepApiType === 'UPDATE') {
const { editStepIndex: index } = foodStore
foodStore.currentRecipe.stepList[index] = foodStore.stepDialog.data
} else {
foodStore.stepDialog.data.sort = foodStore.editStepIndex
foodStore.currentRecipe.stepList.push(foodStore.stepDialog.data)
foodStore.editStepIndex++
}
foodStore.stepDialog.visible = false
})

View File

@@ -91,10 +91,14 @@ const userStore = useUserStore()
const loginFormRef = ref()
onMounted(() => {
onMounted(async () => {
const storageLoginForm = getLocalStorage('loginForm')
if (storageLoginForm) {
authStore.loginForm = JSON.parse(storageLoginForm)
if (!authStore.isLogout) {
await doLogin('common')
}
}
})
@@ -109,15 +113,7 @@ const handleLogin = async (type: ILoginType) => {
authStore.loginLoading = true
try {
await authStore.handleLogin(type)
userStore.handleSession(authStore.session)
// 2. 存储账户密码到localStorage中
if (authStore.loginForm.isRemember) {
setLocalStorage('loginForm', JSON.stringify(authStore.loginForm))
}
await router.push('/record')
await doLogin(type)
} finally {
authStore.loginLoading = false
}
@@ -127,4 +123,16 @@ const handleLogin = async (type: ILoginType) => {
}
})
}
const doLogin = async (type: ILoginType) => {
await authStore.handleLogin(type)
userStore.handleSession(authStore.session)
// 2. 存储账户密码到localStorage中
if (authStore.loginForm.isRemember) {
setLocalStorage('loginForm', JSON.stringify(authStore.loginForm))
}
await router.push('/record')
}
</script>

View File

@@ -1,6 +1,6 @@
<template>
<div class="moment-card card-item">
<el-avatar :src="serviceFileRootPath + props.item.avatar" fit="contain"/>
<el-avatar :src="avatarUrl" fit="contain" @click="viewProfileClick"/>
<div class="moment-content">
<span class="username">{{ props.item.username }}</span>
@@ -9,7 +9,7 @@
<div class="moment-image-list">
<el-image v-for="(item, index) in props.item.imageList" :key="index"
:src="serviceFileRootPath + item" fit="cover"
:preview-src-list="[serviceFileRootPath + item]"/>
:preview-src-list=previewList />
</div>
<p>{{ formatTimeAgo(props.item.date) }}</p>
@@ -67,17 +67,19 @@
import EmojiPicker from 'vue3-emoji-picker'
import 'vue3-emoji-picker/css'
import { Edit } from '@element-plus/icons-vue'
import { defineProps, reactive } from 'vue'
import { computed, defineProps, reactive } from 'vue'
import type { PropType } from 'vue'
import type { IMoment } from '@/types/food'
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
import { serviceFileRootPath } from '@/utils'
import { formatTimeAgo } from '@/utils/dateUtils'
import { useFoodStore } from '@/store/food'
import { useUserStore } from '@/store/user'
import { useAppStore } from '@/store/app'
import { useRouter } from 'vue-router'
const router = useRouter()
const userStore = useUserStore()
const appStore = useAppStore()
const props = defineProps({
item: {
@@ -93,10 +95,22 @@ const momentInfo = reactive({
comment: ''
})
const avatarUrl = computed(() => {
if (props.item?.avatar) {
return serviceFileRootPath + props.item.avatar
} else {
return 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'
}
})
const previewList = computed(() => {
return props.item?.imageList.map((item) => serviceFileRootPath + item)
})
const editClick = () => {
foodStore.momentApiType = 'UPDATE'
foodStore.currentMoment = props.item as IMoment
router.push('/moment/momentForm')
router.push('/moment/form')
}
const onSelectEmoji = (emoji) => {
@@ -109,6 +123,15 @@ const sendCommentClick = async () => {
momentInfo.comment = ''
momentInfo.isShowComment = false
}
const viewProfileClick = async () => {
if (props.item?.userId !== userStore.userInfo.id) {
appStore.isShowMobileBack = true
foodStore.currentMomentUserId = props.item?.userId as number
await userStore.queryUser(foodStore.currentMomentUserId)
await router.push('/moment/profile')
}
}
</script>
<style lang="scss">

View File

@@ -0,0 +1,120 @@
<template>
<div class="profile-card">
<div class="avatar-wrapper">
<el-avatar :src="avatarUrl" alt="用户头像" :size="80"
fit="contain" @click="previewVisible = true"
/>
<el-image-viewer
v-if="previewVisible"
:url-list="[serviceFileRootPath + userStore.userInfo.avatar]"
@close="previewVisible = false"
/>
<i v-if="userStore.userInfo.gender"
class="fas fa-mars gender-icon" style="color: #409EFF"></i>
<i v-else class="fas fa fa-venus gender-icon" style="color: #FFB6C1"></i>
</div>
<span class="user-name">{{ userStore.userInfo.username }}</span>
<div class="user-info">
<div v-if="userStore.userInfo.area" class="info-item">
<i class="fas fa-map-marker-alt"></i>
<span>{{ userStore.userInfo.area.join('-') }}</span>
</div>
<div v-if="userStore.userInfo.birthDate" class="info-item">
<i class="fas fa-birthday-cake"></i>
<span>{{ userStore.userInfo.birthDate }}</span>
</div>
<div v-if="userStore.userInfo.job" class="info-item">
<i class="fas fa-user-tie"></i>
<span>{{ userStore.userInfo.job }}</span>
</div>
</div>
<div class="user-info">
<div v-if="userStore.userInfo.phoneNumber" class="info-item">
<i class="fas fa-mobile-alt"></i>
<span>{{ userStore.userInfo.phoneNumber }}</span>
</div>
<div v-if="userStore.userInfo.email" class="info-item">
<i class="fas fa-envelope"></i>
<span>{{ userStore.userInfo.email }}</span>
</div>
</div>
<div v-if="userStore.userInfo.tags" class="user-info">
<el-tag v-for="(item, index) in userStore.userInfo.tags"
:key="index">{{ item }}</el-tag>
</div>
<span v-if="userStore.userInfo.description" class="user-bio">
{{ userStore.userInfo.description }}
</span>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { useUserStore } from '@/store/user.ts'
import { serviceFileRootPath } from '@/utils'
const userStore = useUserStore()
const previewVisible = ref(false)
const avatarUrl = computed(() => {
if (userStore.userInfo.avatar) {
return serviceFileRootPath + userStore.userInfo.avatar
} else {
return 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'
}
})
</script>
<style lang="scss">
.profile-card {
background: linear-gradient(135deg, #6e8efb, #a777e3);
border-radius: 10px;
padding: 20px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
text-align: center;
color: white;
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
.avatar-wrapper {
position: relative;
display: inline-block;
.gender-icon {
position: absolute;
right: 2px;
bottom: 0;
background: white;
border-radius: 50%;
padding: 3px;
}
}
.user-name {
font-size: 1.5rem;
}
.user-info {
display: flex;
justify-content: center;
gap: 10px;
font-size: 1.1rem;
.info-item {
display: flex;
align-items: center;
gap: 5px;
}
}
}
</style>

View File

@@ -29,13 +29,11 @@ import TitleBar from '@/layout/TitleBar.vue'
import TabBar from '@/layout/TabBar.vue'
import DraggableButton from '@/components/DraggableButton.vue'
import { useRouter, useRoute } from 'vue-router'
import { useAppStore } from '@/store/app'
import { useFoodStore } from '@/store/food'
import { ref } from 'vue'
const router = useRouter()
const route = useRoute()
const appStore = useAppStore()
const foodStore = useFoodStore()
const dialogFormVisible = ref(false)
@@ -46,7 +44,7 @@ const checkIsShow = (): boolean => {
return false
}
if (route.path.includes('moment')) {
if (route.path.includes('moment/index')) {
return true
}
@@ -65,7 +63,7 @@ const addClick = () => {
if (route.path.includes('moment')) {
foodStore.momentApiType = 'ADD'
foodStore.currentMoment = foodStore.getEmptyMoment()
router.push('/moment/momentForm')
router.push('/moment/form')
}
}

View File

@@ -14,7 +14,7 @@ export const useAppStore = defineStore('ipp', {
isDark: false,
isWebSocket: false,
serverIp: '14.103.235.151',
version: 'v1.0.0.2025062702'
version: 'v1.0.0.2025062802'
}),
actions: {
initApp() {

View File

@@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import type { ILoginType, ISession, IUser } from '@/types/auth'
import type { ILoginType, ISession } from '@/types/auth'
import { loginApi, logoutApi } from '@/apis/session'
import { ElMessage } from 'element-plus'
import { emailPattern, phonePattern } from '@/utils/element/elRules'
@@ -29,6 +29,7 @@ export const useAuthStore = defineStore('auth', {
loginLoading: false,
isVerified: false,
isGettingCode: false,
isLogout: false,
getCodeCountdown: 60,
getCodeTimer: null as NodeJS.Timeout | null,
session: {} as ISession
@@ -99,17 +100,16 @@ export const useAuthStore = defineStore('auth', {
default:
break
}
this.isLogout = false
ElMessage.success('登录成功')
},
async handleRegister() {
const user: IUser = {
username: this.registerForm.username,
password: this.registerForm.password
}
await registerApi(user)
const { username, password } = this.registerForm
await registerApi(username, password)
},
async handleLogout(username: string) {
await logoutApi(username)
this.isLogout = true
ElMessage.success('退出登录成功')
},
resetLoginForm() {

View File

@@ -74,6 +74,7 @@ export const useFoodStore = defineStore('food', {
editStepIndex: 0,
recordApiType: 'ADD' as IHandleApi,
editRecordIndex: 0,
currentMomentUserId: 0,
currentMoment: {
content: '',
imageList: []

View File

@@ -2,15 +2,14 @@ import { defineStore } from 'pinia'
import { getLocalStorage, setLocalStorage, removeLocalStorage } from 'vue3-common/utils/storageUtil'
import type { ILoginForm } from 'vue3-common'
import type { ISession, IUser } from '@/types/auth'
import { queryUserApi, updateUserApi } from '@/apis/user'
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: {
id: 0,
username: '',
password: '',
gender: 0,
idNumber: '',
phoneNumber: '',
email: '',
birthDate: '',
@@ -20,7 +19,8 @@ export const useUserStore = defineStore('user', {
address: '',
job: '',
tags: []
} as IUser
} as IUser,
imagePath: 'food-hub'
}),
actions: {
@@ -38,11 +38,11 @@ export const useUserStore = defineStore('user', {
this.setUserId(session.userInfo?.id as number)
this.userInfo = session.userInfo
},
async queryUser() {
// this.userInfo = await queryUserApi(Number(this.getUserId()))
async queryUser(userId: number) {
this.userInfo = await queryUserApi(userId)
},
async updateUser() {
// await updateUserApi(this.userInfo.id, this.userInfo)
await updateUserApi(this.userInfo.id, this.userInfo)
},
async updateAccountPassword(oldPassword: string, newPassword: string) {

View File

@@ -2,7 +2,7 @@
position: relative;
display: grid;
grid-template-rows: 40px 1fr;
align-items: center;
// align-items: center;
.title {
text-align: center;
@@ -100,7 +100,7 @@
flex-direction: column;
justify-content: space-between;
gap: 1vh;
min-height: 28vh;
// min-height: 28vh;
.step-container {
display: grid;

View File

@@ -5,9 +5,7 @@ export type IAccountType = 'QQ' | 'WeChat' | 'Phone' | 'EMail'
export interface IUser {
id?: number;
username: string;
password: string;
gender?: number;
idNumber?: string;
phoneNumber?: string;
email?: string;
birthDate?: string;

View File

@@ -78,3 +78,31 @@ export const foodMomentRules = {
{ required: true, message: '请输入内容', trigger: 'blur' }
]
}
export const profileInfoRules = {
username: [
{ required: true, message: '请输入用户姓名', trigger: 'blur' }
],
phoneNumber: [
{ pattern: phonePattern, message: '请输入正确的手机号格式', trigger: ['blur'] }
],
email: [
{ pattern: emailPattern, message: '请输入正确的邮箱格式', trigger: ['blur'] }
]
}
export const profilePasswordRules = {
oldPassword: [
{ required: true, message: '请输入旧密码', trigger: 'blur' },
{ min: 6, max: 20, message: '请输入6~20位的密码', trigger: 'change' }
],
newPassword: [
{ required: true, message: '请输入新密码', trigger: 'blur' },
{ min: 6, max: 20, message: '请输入6~20位的密码', trigger: 'change' }
],
confirmPassword: [
{ required: true, message: '请输入确认密码', trigger: 'blur' },
{ min: 6, max: 20, message: '请输入6~20位的密码', trigger: 'change' }
]
}

View File

@@ -0,0 +1,28 @@
<template>
<div class="moment-profile-view common-mobile-view">
<profile-card />
</div>
</template>
<script lang="ts" setup>
import ProfileCard from '@/components/Profile/ProfileCard.vue'
import { useUserStore } from '@/store/user.ts'
import { useAppStore } from '@/store/app.ts'
import { useRouter } from 'vue-router'
import { onMounted } from 'vue'
const appStore = useAppStore()
const router = useRouter()
onMounted(() => {
})
</script>
<style lang="scss">
.moment-profile-view {
display: flex;
flex-direction: column;
gap: 10px;
}
</style>

View File

@@ -1,52 +1,150 @@
<template>
<div class="common-mobile-view profile">
个人中心
<div>
<span>版本号</span>
<span>{{ appStore.version }}</span>
<div class="profile-view common-mobile-view">
<profile-card />
<div class="menu-card card-item">
<div class="menu-item" @click="infoClick">
<i class="fas fa-user"></i>
<span>基本资料</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-divider"></div>
<div class="menu-item">
<i class="fas fa-cog"></i>
<span>账户设置</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-divider"></div>
<div class="menu-item">
<i class="fa-solid fa-comment-dots"></i>
<span>反馈意见</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-divider"></div>
<div class="menu-item" @click="passwordClick">
<i class="fas fa-key"></i>
<span>更改密码</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-divider"></div>
<div class="menu-item">
<i class="fa-solid fa-file-arrow-down"></i>
<span>软件下载</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-divider"></div>
<div class="menu-item" @click="refreshClick">
<i class="fa-solid fa-arrows-rotate"></i>
<span>检查更新</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="menu-divider"></div>
<div class="menu-item" @click="logoutClick">
<i class="fas fa-sign-out-alt"></i>
<span>退出登录</span>
<i class="fas fa-chevron-right"></i>
</div>
</div>
<div>
IOS下载地址<el-image :src="getAssetsImageFile('ios.png')"/>
</div>
<div>
Android下载地址
</div>
<div class="button-container">
<el-button type="primary" @click="viewLatestVersionClick">检查新版本</el-button>
<el-button type="primary" @click="logoutClick">退出登录</el-button>
</div>
<div class="version">版本号 {{ appStore.version }}</div>
</div>
</template>
<script setup lang="ts">
import { useAppStore } from '@/store/app.ts'
<script lang="ts" setup>
import ProfileCard from '@/components/Profile/ProfileCard.vue'
import { useUserStore } from '@/store/user.ts'
import { useAuthStore } from '@/store/auth.ts'
import { useAppStore } from '@/store/app.ts'
import { useAuthStore } from '@/store/auth'
import { useRouter } from 'vue-router'
import { getAssetsImageFile } from '@/utils'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { onMounted } from 'vue'
const appStore = useAppStore()
const userStore = useUserStore()
const authStore = useAuthStore()
const appStore = useAppStore()
const router = useRouter()
const viewLatestVersionClick = async () => {
onMounted(() => {
userStore.queryUser(userStore.getUserId() as number)
})
const infoClick = () => {
router.push('/profile/info')
}
const passwordClick = () => {
// appStore.isShowMobileBack = true
// router.push('/mobile-home/profile/password')
}
const refreshClick = async () => {
await router.push('/')
location.reload()
}
const logoutClick = async () => {
await authStore.handleLogout(userStore.userInfo.username)
await router.push('/')
const logoutClick = () => {
commonElMessageBox('请确认是否退出系统?').then(async () => {
userStore.removeAll()
await authStore.handleLogout(userStore.userInfo.username)
await router.push('/')
})
}
</script>
<style lang="scss">
.profile {
.profile-view {
display: flex;
flex-direction: column;
gap: 10px;
.menu-card {
border-radius: 10px;
padding: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
gap: 5px;
.menu-item {
padding: 10px;
display: grid;
grid-template-columns: 20px 1fr 15px;
align-items: center;
gap: 5px;
cursor: pointer;
font-size: 1.2rem;
i {
color: var(--el-color-primary);
}
span {
color: var(--color-text);
}
}
.menu-divider {
height: 1px;
background-color: #E8E8E8;
margin: 0 5px;
}
}
.version {
align-self: center;
color: #999;
}
}
</style>

125
src/views/profile/info.vue Normal file
View File

@@ -0,0 +1,125 @@
<template>
<div class="profile-info common-mobile-view">
<el-form ref="ruleFormRef"
:model="userStore.userInfo"
:rules="rules" label-width="auto">
<el-form-item label="姓名" prop="username">
<el-input v-model="userStore.userInfo.username"
:maxlength="10" show-word-limit
clearable/>
</el-form-item>
<el-form-item label="头像">
<upload-image v-model="userStore.userInfo.avatar"
:image-limit="1"
:service-url="fileServiceUrl"
:service-file-root-path="serviceFileRootPath"
:image-path="userStore.imagePath"/>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="userStore.userInfo.gender">
<el-radio :value="1"></el-radio>
<el-radio :value="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="生日">
<el-date-picker v-model="userStore.userInfo.birthDate"
type="date" :editable="false" value-format="YYYY-MM-DD"
placeholder="请选择日期"/>
</el-form-item>
<el-form-item label="地区">
<el-cascader v-model="userStore.userInfo.area"
placeholder="请选择地区" :options="pcTextArr">
</el-cascader>
</el-form-item>
<el-form-item label="手机号">
<el-input v-model="userStore.userInfo.phoneNumber"
:maxlength="11" show-word-limit clearable
placeholder="请输入手机号"/>
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="userStore.userInfo.email"
:maxlength="20" show-word-limit clearable
placeholder="请输入邮箱"/>
</el-form-item>
<el-form-item label="地址">
<el-input v-model="userStore.userInfo.address"
:maxlength="20" show-word-limit clearable
placeholder="请输入地址"/>
</el-form-item>
<el-form-item label="职业">
<el-input v-model="userStore.userInfo.job"
:maxlength="10" show-word-limit clearable
placeholder="请输入职业"/>
</el-form-item>
<el-form-item label="标签">
<el-input-tag v-model="userStore.userInfo.tags"
:max="3" placeholder="请输入标签最多3个" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="userStore.userInfo.description"
:maxlength="20" show-word-limit clearable
placeholder="用一句话描述自己"/>
</el-form-item>
</el-form>
<div class="button-container">
<el-button type="primary" @click="submitForm(ruleFormRef)">保存</el-button>
<el-button @click="cancelClick">取消</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { UploadImage } from 'vue3-common'
import { pcTextArr } from 'element-china-area-data'
import { useUserStore } from '@/store/user.ts'
import { reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { profileInfoRules } from '@/utils/element/elRules.ts'
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
const userStore = useUserStore()
const ruleFormRef = ref<FormInstance>()
const rules = reactive<FormRules>(profileInfoRules)
const cancelClick = () => {
history.back()
}
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid, fields) => {
if (valid) {
await userStore.updateUser()
await userStore.queryUser(userStore.userInfo.id)
ElMessage.success('更改信息成功')
history.back()
} else {
ElMessage.error('请先填写信息')
}
})
}
</script>
<style scoped lang="scss">
.profile-info {
display: flex;
flex-direction: column;
gap: 5px;
.button-container {
align-self: center;
}
}
</style>

View File

@@ -0,0 +1,70 @@
<template>
<div class="profile-password common-mobile-view">
<el-form ref="ruleFormRef"
:model="passwordInfo"
:rules="rules" label-width="auto">
<el-form-item label="原密码" prop="oldPassword">
<el-input v-model="passwordInfo.oldPassword" type="password" show-password clearable/>
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input v-model="passwordInfo.newPassword" type="password" show-password clearable/>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input v-model="passwordInfo.confirmPassword" type="password" show-password clearable/>
</el-form-item>
</el-form>
<div class="button-container">
<el-button type="primary" @click="submitForm(ruleFormRef)">确认</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { useUserStore } from '@/store/user.ts'
import { reactive, ref } from 'vue'
import { ElMessage } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { profilePasswordRules } from '@/utils/element/elRules.ts'
const userStore = useUserStore()
const ruleFormRef = ref<FormInstance>()
const rules = reactive<FormRules>(profilePasswordRules)
const passwordInfo = reactive({
oldPassword: '',
newPassword: '',
confirmPassword: ''
})
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate(async (valid, fields) => {
if (valid) {
if (passwordInfo.newPassword !== passwordInfo.confirmPassword) {
ElMessage.error('确认密码和新密码不一致')
return
}
await userStore.updateAccountPassword(passwordInfo.oldPassword, passwordInfo.newPassword)
ElMessage.success('更改密码成功')
}
})
}
</script>
<style scoped lang="scss">
.profile-password {
display: flex;
flex-direction: column;
gap: 5px;
.button-container {
align-self: center;
}
}
</style>