feat:更新朋友圈功能
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vueuse/core": "^13.1.0",
|
"@vueuse/core": "^13.1.0",
|
||||||
|
"element-china-area-data": "^6.1.0",
|
||||||
"element-plus": "^2.9.7",
|
"element-plus": "^2.9.7",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue3-common": "git+https://gitee.com/Cxx0822/vue3-common",
|
"vue3-common": "git+https://gitee.com/Cxx0822/vue3-common",
|
||||||
|
|||||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@@ -11,6 +11,9 @@ importers:
|
|||||||
'@vueuse/core':
|
'@vueuse/core':
|
||||||
specifier: ^13.1.0
|
specifier: ^13.1.0
|
||||||
version: 13.4.0(vue@3.5.17(typescript@5.8.3))
|
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:
|
element-plus:
|
||||||
specifier: ^2.9.7
|
specifier: ^2.9.7
|
||||||
version: 2.10.2(vue@3.5.17(typescript@5.8.3))
|
version: 2.10.2(vue@3.5.17(typescript@5.8.3))
|
||||||
@@ -833,6 +836,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
china-division@2.7.0:
|
||||||
|
resolution: {integrity: sha512-4uUPAT+1WfqDh5jytq7omdCmHNk3j+k76zEG/2IqaGcYB90c2SwcixttcypdsZ3T/9tN1TTpBDoeZn+Yw/qBEA==}
|
||||||
|
|
||||||
chokidar@4.0.3:
|
chokidar@4.0.3:
|
||||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||||
engines: {node: '>= 14.16.0'}
|
engines: {node: '>= 14.16.0'}
|
||||||
@@ -958,6 +964,9 @@ packages:
|
|||||||
echarts@5.6.0:
|
echarts@5.6.0:
|
||||||
resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==}
|
resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==}
|
||||||
|
|
||||||
|
element-china-area-data@6.1.0:
|
||||||
|
resolution: {integrity: sha512-IkpcjwQv2A/2AxFiSoaISZ+oMw1rZCPUSOg5sOCwT5jKc96TaawmKZeY81xfxXsO0QbKxU5LLc6AirhG52hUmg==}
|
||||||
|
|
||||||
element-plus@2.10.2:
|
element-plus@2.10.2:
|
||||||
resolution: {integrity: sha512-p2KiAa0jEGXrzdlTAfpiS7HQFAhla4gvx6H7RuDf+OO0uC3DGpolxvdHjFR8gt7+vaWyxQNcHa1sAdBkmjqlgA==}
|
resolution: {integrity: sha512-p2KiAa0jEGXrzdlTAfpiS7HQFAhla4gvx6H7RuDf+OO0uC3DGpolxvdHjFR8gt7+vaWyxQNcHa1sAdBkmjqlgA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2772,6 +2781,8 @@ snapshots:
|
|||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
supports-color: 7.2.0
|
supports-color: 7.2.0
|
||||||
|
|
||||||
|
china-division@2.7.0: {}
|
||||||
|
|
||||||
chokidar@4.0.3:
|
chokidar@4.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
readdirp: 4.1.2
|
readdirp: 4.1.2
|
||||||
@@ -2891,6 +2902,10 @@ snapshots:
|
|||||||
tslib: 2.3.0
|
tslib: 2.3.0
|
||||||
zrender: 5.6.1
|
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)):
|
element-plus@2.10.2(vue@3.5.17(typescript@5.8.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ctrl/tinycolor': 3.6.1
|
'@ctrl/tinycolor': 3.6.1
|
||||||
|
|||||||
@@ -1,9 +1,22 @@
|
|||||||
import { cloudService } from './index'
|
import { cloudService } from './index'
|
||||||
import type { ISession, IUser } from '@/types/auth'
|
import type { ISession, IUser } from '@/types/auth'
|
||||||
|
|
||||||
export const registerApi = (user: IUser): Promise<ISession> =>
|
export const registerApi = (username: string, password: string): Promise<ISession> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/user',
|
url: '/food-service/user',
|
||||||
method: 'post',
|
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
|
data: user
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<div class="button-info">
|
<div class="button-info">
|
||||||
<el-button type="primary" :icon="Edit" circle :size="'small'"
|
<el-button type="primary" :icon="Edit" circle :size="'small'"
|
||||||
@click="editClick"/>
|
@click="editClick"/>
|
||||||
<el-button type="primary" :icon="MoreFilled" circle :size="'small'"
|
<el-button type="primary" :icon="Collection" circle :size="'small'"
|
||||||
@click="viewClick()" />
|
@click="viewClick()" />
|
||||||
<el-button type="danger" :icon="DeleteFilled" circle :size="'small'"
|
<el-button type="danger" :icon="DeleteFilled" circle :size="'small'"
|
||||||
@click="deleteClick()" />
|
@click="deleteClick()" />
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 { useFoodStore } from '@/store/food'
|
||||||
import { defineProps, onMounted, reactive } from 'vue'
|
import { defineProps, onMounted, reactive } from 'vue'
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useFoodStore } from '@/store/food'
|
import { useFoodStore } from '@/store/food'
|
||||||
import { nextTick, reactive, ref } from 'vue'
|
import { nextTick, reactive, ref } from 'vue'
|
||||||
import { FormRules } from 'element-plus'
|
import type { FormRules } from 'element-plus'
|
||||||
import { foodMaterialRules } from '@/utils/element/elRules'
|
import { foodMaterialRules } from '@/utils/element/elRules'
|
||||||
const rules = reactive<FormRules>(foodMaterialRules)
|
const rules = reactive<FormRules>(foodMaterialRules)
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog v-model="foodStore.stepDialog.visible"
|
<el-dialog v-model="foodStore.stepDialog.visible"
|
||||||
:title="foodStore.stepDialog.title"
|
:title="`${foodStore.stepDialog.title}${foodStore.editStepIndex + 1}`"
|
||||||
center @open="openDialogEvent"
|
center @open="openDialogEvent"
|
||||||
class="food-step-dialog">
|
class="food-step-dialog">
|
||||||
<el-form ref="formRef" :model="foodStore.stepDialog.data"
|
<el-form ref="formRef" :model="foodStore.stepDialog.data"
|
||||||
:rules="rules" label-width="60px">
|
:rules="rules" label-width="60px">
|
||||||
<el-form-item label="内容" prop="content">
|
<el-form-item label="内容" prop="content">
|
||||||
<el-input v-model="foodStore.stepDialog.data.content" autocomplete="off"
|
<el-input v-model="foodStore.stepDialog.data.content" autocomplete="off"
|
||||||
placeholder="请输入内容" clearable
|
:placeholder="`请输入步骤${foodStore.editStepIndex + 1}`" clearable
|
||||||
type="textarea" :rows="3"
|
type="textarea" :rows="3"
|
||||||
show-word-limit maxlength="50">
|
show-word-limit maxlength="50">
|
||||||
</el-input>
|
</el-input>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
import { UploadImage } from 'vue3-common'
|
import { UploadImage } from 'vue3-common'
|
||||||
import { useFoodStore } from '@/store/food'
|
import { useFoodStore } from '@/store/food'
|
||||||
import { ref, reactive, nextTick } from 'vue'
|
import { ref, reactive, nextTick } from 'vue'
|
||||||
import { FormRules } from 'element-plus'
|
import type { FormRules } from 'element-plus'
|
||||||
import { foodStepRules } from '@/utils/element/elRules'
|
import { foodStepRules } from '@/utils/element/elRules'
|
||||||
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
|
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
|
||||||
|
|
||||||
@@ -60,11 +60,13 @@ const openDialogEvent = () => {
|
|||||||
const confirmClick = () => {
|
const confirmClick = () => {
|
||||||
formRef.value.validate()
|
formRef.value.validate()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
|
const { editStepIndex: index } = foodStore
|
||||||
if (foodStore.stepApiType === 'UPDATE') {
|
if (foodStore.stepApiType === 'UPDATE') {
|
||||||
const { editStepIndex: index } = foodStore
|
|
||||||
foodStore.currentRecipe.stepList[index] = foodStore.stepDialog.data
|
foodStore.currentRecipe.stepList[index] = foodStore.stepDialog.data
|
||||||
} else {
|
} else {
|
||||||
|
foodStore.stepDialog.data.sort = foodStore.editStepIndex
|
||||||
foodStore.currentRecipe.stepList.push(foodStore.stepDialog.data)
|
foodStore.currentRecipe.stepList.push(foodStore.stepDialog.data)
|
||||||
|
foodStore.editStepIndex++
|
||||||
}
|
}
|
||||||
foodStore.stepDialog.visible = false
|
foodStore.stepDialog.visible = false
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -91,10 +91,14 @@ const userStore = useUserStore()
|
|||||||
|
|
||||||
const loginFormRef = ref()
|
const loginFormRef = ref()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
const storageLoginForm = getLocalStorage('loginForm')
|
const storageLoginForm = getLocalStorage('loginForm')
|
||||||
if (storageLoginForm) {
|
if (storageLoginForm) {
|
||||||
authStore.loginForm = JSON.parse(storageLoginForm)
|
authStore.loginForm = JSON.parse(storageLoginForm)
|
||||||
|
|
||||||
|
if (!authStore.isLogout) {
|
||||||
|
await doLogin('common')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -109,15 +113,7 @@ const handleLogin = async (type: ILoginType) => {
|
|||||||
authStore.loginLoading = true
|
authStore.loginLoading = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await authStore.handleLogin(type)
|
await doLogin(type)
|
||||||
userStore.handleSession(authStore.session)
|
|
||||||
|
|
||||||
// 2. 存储账户密码到localStorage中
|
|
||||||
if (authStore.loginForm.isRemember) {
|
|
||||||
setLocalStorage('loginForm', JSON.stringify(authStore.loginForm))
|
|
||||||
}
|
|
||||||
|
|
||||||
await router.push('/record')
|
|
||||||
} finally {
|
} finally {
|
||||||
authStore.loginLoading = false
|
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>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="moment-card card-item">
|
<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">
|
<div class="moment-content">
|
||||||
<span class="username">{{ props.item.username }}</span>
|
<span class="username">{{ props.item.username }}</span>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<div class="moment-image-list">
|
<div class="moment-image-list">
|
||||||
<el-image v-for="(item, index) in props.item.imageList" :key="index"
|
<el-image v-for="(item, index) in props.item.imageList" :key="index"
|
||||||
:src="serviceFileRootPath + item" fit="cover"
|
:src="serviceFileRootPath + item" fit="cover"
|
||||||
:preview-src-list="[serviceFileRootPath + item]"/>
|
:preview-src-list=previewList />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>{{ formatTimeAgo(props.item.date) }}</p>
|
<p>{{ formatTimeAgo(props.item.date) }}</p>
|
||||||
@@ -67,17 +67,19 @@
|
|||||||
import EmojiPicker from 'vue3-emoji-picker'
|
import EmojiPicker from 'vue3-emoji-picker'
|
||||||
import 'vue3-emoji-picker/css'
|
import 'vue3-emoji-picker/css'
|
||||||
import { Edit } from '@element-plus/icons-vue'
|
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 { PropType } from 'vue'
|
||||||
import type { IMoment } from '@/types/food'
|
import type { IMoment } from '@/types/food'
|
||||||
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
|
import { serviceFileRootPath } from '@/utils'
|
||||||
import { formatTimeAgo } from '@/utils/dateUtils'
|
import { formatTimeAgo } from '@/utils/dateUtils'
|
||||||
import { useFoodStore } from '@/store/food'
|
import { useFoodStore } from '@/store/food'
|
||||||
import { useUserStore } from '@/store/user'
|
import { useUserStore } from '@/store/user'
|
||||||
|
import { useAppStore } from '@/store/app'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
const appStore = useAppStore()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
item: {
|
item: {
|
||||||
@@ -93,10 +95,22 @@ const momentInfo = reactive({
|
|||||||
comment: ''
|
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 = () => {
|
const editClick = () => {
|
||||||
foodStore.momentApiType = 'UPDATE'
|
foodStore.momentApiType = 'UPDATE'
|
||||||
foodStore.currentMoment = props.item as IMoment
|
foodStore.currentMoment = props.item as IMoment
|
||||||
router.push('/moment/momentForm')
|
router.push('/moment/form')
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSelectEmoji = (emoji) => {
|
const onSelectEmoji = (emoji) => {
|
||||||
@@ -109,6 +123,15 @@ const sendCommentClick = async () => {
|
|||||||
momentInfo.comment = ''
|
momentInfo.comment = ''
|
||||||
momentInfo.isShowComment = false
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
120
src/components/Profile/ProfileCard.vue
Normal file
120
src/components/Profile/ProfileCard.vue
Normal 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>
|
||||||
@@ -29,13 +29,11 @@ import TitleBar from '@/layout/TitleBar.vue'
|
|||||||
import TabBar from '@/layout/TabBar.vue'
|
import TabBar from '@/layout/TabBar.vue'
|
||||||
import DraggableButton from '@/components/DraggableButton.vue'
|
import DraggableButton from '@/components/DraggableButton.vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { useAppStore } from '@/store/app'
|
|
||||||
import { useFoodStore } from '@/store/food'
|
import { useFoodStore } from '@/store/food'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const appStore = useAppStore()
|
|
||||||
const foodStore = useFoodStore()
|
const foodStore = useFoodStore()
|
||||||
|
|
||||||
const dialogFormVisible = ref(false)
|
const dialogFormVisible = ref(false)
|
||||||
@@ -46,7 +44,7 @@ const checkIsShow = (): boolean => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.path.includes('moment')) {
|
if (route.path.includes('moment/index')) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +63,7 @@ const addClick = () => {
|
|||||||
if (route.path.includes('moment')) {
|
if (route.path.includes('moment')) {
|
||||||
foodStore.momentApiType = 'ADD'
|
foodStore.momentApiType = 'ADD'
|
||||||
foodStore.currentMoment = foodStore.getEmptyMoment()
|
foodStore.currentMoment = foodStore.getEmptyMoment()
|
||||||
router.push('/moment/momentForm')
|
router.push('/moment/form')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const useAppStore = defineStore('ipp', {
|
|||||||
isDark: false,
|
isDark: false,
|
||||||
isWebSocket: false,
|
isWebSocket: false,
|
||||||
serverIp: '14.103.235.151',
|
serverIp: '14.103.235.151',
|
||||||
version: 'v1.0.0.2025062702'
|
version: 'v1.0.0.2025062802'
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
initApp() {
|
initApp() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defineStore } from 'pinia'
|
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 { loginApi, logoutApi } from '@/apis/session'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { emailPattern, phonePattern } from '@/utils/element/elRules'
|
import { emailPattern, phonePattern } from '@/utils/element/elRules'
|
||||||
@@ -29,6 +29,7 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
loginLoading: false,
|
loginLoading: false,
|
||||||
isVerified: false,
|
isVerified: false,
|
||||||
isGettingCode: false,
|
isGettingCode: false,
|
||||||
|
isLogout: false,
|
||||||
getCodeCountdown: 60,
|
getCodeCountdown: 60,
|
||||||
getCodeTimer: null as NodeJS.Timeout | null,
|
getCodeTimer: null as NodeJS.Timeout | null,
|
||||||
session: {} as ISession
|
session: {} as ISession
|
||||||
@@ -99,17 +100,16 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
this.isLogout = false
|
||||||
ElMessage.success('登录成功')
|
ElMessage.success('登录成功')
|
||||||
},
|
},
|
||||||
async handleRegister() {
|
async handleRegister() {
|
||||||
const user: IUser = {
|
const { username, password } = this.registerForm
|
||||||
username: this.registerForm.username,
|
await registerApi(username, password)
|
||||||
password: this.registerForm.password
|
|
||||||
}
|
|
||||||
await registerApi(user)
|
|
||||||
},
|
},
|
||||||
async handleLogout(username: string) {
|
async handleLogout(username: string) {
|
||||||
await logoutApi(username)
|
await logoutApi(username)
|
||||||
|
this.isLogout = true
|
||||||
ElMessage.success('退出登录成功')
|
ElMessage.success('退出登录成功')
|
||||||
},
|
},
|
||||||
resetLoginForm() {
|
resetLoginForm() {
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export const useFoodStore = defineStore('food', {
|
|||||||
editStepIndex: 0,
|
editStepIndex: 0,
|
||||||
recordApiType: 'ADD' as IHandleApi,
|
recordApiType: 'ADD' as IHandleApi,
|
||||||
editRecordIndex: 0,
|
editRecordIndex: 0,
|
||||||
|
currentMomentUserId: 0,
|
||||||
currentMoment: {
|
currentMoment: {
|
||||||
content: '',
|
content: '',
|
||||||
imageList: []
|
imageList: []
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ import { defineStore } from 'pinia'
|
|||||||
import { getLocalStorage, setLocalStorage, removeLocalStorage } from 'vue3-common/utils/storageUtil'
|
import { getLocalStorage, setLocalStorage, removeLocalStorage } from 'vue3-common/utils/storageUtil'
|
||||||
import type { ILoginForm } from 'vue3-common'
|
import type { ILoginForm } from 'vue3-common'
|
||||||
import type { ISession, IUser } from '@/types/auth'
|
import type { ISession, IUser } from '@/types/auth'
|
||||||
|
import { queryUserApi, updateUserApi } from '@/apis/user'
|
||||||
|
|
||||||
export const useUserStore = defineStore('user', {
|
export const useUserStore = defineStore('user', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
userInfo: {
|
userInfo: {
|
||||||
id: 0,
|
id: 0,
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
|
||||||
gender: 0,
|
gender: 0,
|
||||||
idNumber: '',
|
|
||||||
phoneNumber: '',
|
phoneNumber: '',
|
||||||
email: '',
|
email: '',
|
||||||
birthDate: '',
|
birthDate: '',
|
||||||
@@ -20,7 +19,8 @@ export const useUserStore = defineStore('user', {
|
|||||||
address: '',
|
address: '',
|
||||||
job: '',
|
job: '',
|
||||||
tags: []
|
tags: []
|
||||||
} as IUser
|
} as IUser,
|
||||||
|
imagePath: 'food-hub'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
@@ -38,11 +38,11 @@ export const useUserStore = defineStore('user', {
|
|||||||
this.setUserId(session.userInfo?.id as number)
|
this.setUserId(session.userInfo?.id as number)
|
||||||
this.userInfo = session.userInfo
|
this.userInfo = session.userInfo
|
||||||
},
|
},
|
||||||
async queryUser() {
|
async queryUser(userId: number) {
|
||||||
// this.userInfo = await queryUserApi(Number(this.getUserId()))
|
this.userInfo = await queryUserApi(userId)
|
||||||
},
|
},
|
||||||
async updateUser() {
|
async updateUser() {
|
||||||
// await updateUserApi(this.userInfo.id, this.userInfo)
|
await updateUserApi(this.userInfo.id, this.userInfo)
|
||||||
},
|
},
|
||||||
async updateAccountPassword(oldPassword: string, newPassword: string) {
|
async updateAccountPassword(oldPassword: string, newPassword: string) {
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 40px 1fr;
|
grid-template-rows: 40px 1fr;
|
||||||
align-items: center;
|
// align-items: center;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 1vh;
|
gap: 1vh;
|
||||||
min-height: 28vh;
|
// min-height: 28vh;
|
||||||
|
|
||||||
.step-container {
|
.step-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ export type IAccountType = 'QQ' | 'WeChat' | 'Phone' | 'EMail'
|
|||||||
export interface IUser {
|
export interface IUser {
|
||||||
id?: number;
|
id?: number;
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
|
||||||
gender?: number;
|
gender?: number;
|
||||||
idNumber?: string;
|
|
||||||
phoneNumber?: string;
|
phoneNumber?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
birthDate?: string;
|
birthDate?: string;
|
||||||
|
|||||||
@@ -78,3 +78,31 @@ export const foodMomentRules = {
|
|||||||
{ required: true, message: '请输入内容', trigger: 'blur' }
|
{ 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' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|||||||
28
src/views/moment/profile.vue
Normal file
28
src/views/moment/profile.vue
Normal 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>
|
||||||
@@ -1,52 +1,150 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="common-mobile-view profile">
|
<div class="profile-view common-mobile-view">
|
||||||
个人中心
|
<profile-card />
|
||||||
<div>
|
|
||||||
<span>版本号:</span>
|
<div class="menu-card card-item">
|
||||||
<span>{{ appStore.version }}</span>
|
<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>
|
||||||
|
|
||||||
<div>
|
<div class="version">版本号 {{ appStore.version }}</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
import { useAppStore } from '@/store/app.ts'
|
import ProfileCard from '@/components/Profile/ProfileCard.vue'
|
||||||
import { useUserStore } from '@/store/user.ts'
|
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 { 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 userStore = useUserStore()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
const appStore = useAppStore()
|
||||||
const router = useRouter()
|
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('/')
|
await router.push('/')
|
||||||
location.reload()
|
location.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
const logoutClick = async () => {
|
const logoutClick = () => {
|
||||||
await authStore.handleLogout(userStore.userInfo.username)
|
commonElMessageBox('请确认是否退出系统?').then(async () => {
|
||||||
await router.push('/')
|
userStore.removeAll()
|
||||||
|
await authStore.handleLogout(userStore.userInfo.username)
|
||||||
|
await router.push('/')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.profile {
|
.profile-view {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
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>
|
</style>
|
||||||
|
|||||||
125
src/views/profile/info.vue
Normal file
125
src/views/profile/info.vue
Normal 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>
|
||||||
70
src/views/profile/password.vue
Normal file
70
src/views/profile/password.vue
Normal 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>
|
||||||
Reference in New Issue
Block a user