feat:增加登录模块
This commit is contained in:
@@ -1,9 +1,16 @@
|
|||||||
import { cloudService } from './index'
|
import { cloudService } from './index'
|
||||||
import type { ISession } from '@/types/auth'
|
import type { ISession } from '@/types/auth'
|
||||||
|
|
||||||
export const loginApi = (name: string, password: string): Promise<ISession> =>
|
export const loginApi = (username: string, password: string): Promise<ISession> =>
|
||||||
cloudService({
|
cloudService({
|
||||||
url: '/food-service/session',
|
url: '/food-service/session',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
params: { name, password }
|
params: { username, password }
|
||||||
|
})
|
||||||
|
|
||||||
|
export const logoutApi = (username: string): Promise<ISession> =>
|
||||||
|
cloudService({
|
||||||
|
url: '/food-service/session',
|
||||||
|
method: 'delete',
|
||||||
|
params: { username }
|
||||||
})
|
})
|
||||||
|
|||||||
9
src/apis/user.ts
Normal file
9
src/apis/user.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { cloudService } from './index'
|
||||||
|
import type { ISession, IUser } from '@/types/auth'
|
||||||
|
|
||||||
|
export const registerApi = (user: IUser): Promise<ISession> =>
|
||||||
|
cloudService({
|
||||||
|
url: '/food-service/user',
|
||||||
|
method: 'post',
|
||||||
|
data: user
|
||||||
|
})
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-empty v-if="dailyItemInfo.itemList.length === 0" />
|
<el-empty v-if="dailyItemInfo.itemList.length === 0" description="暂无记录"/>
|
||||||
|
|
||||||
<div v-else class="food-daily-item card-item">
|
<div v-else class="food-daily-item card-item">
|
||||||
<daily-summary :date="dailyItemInfo.date"/>
|
<daily-summary :date="dailyItemInfo.date"/>
|
||||||
@@ -82,11 +82,11 @@ const getFoodDailyByDate = (date: string) => {
|
|||||||
* @param record 美食记录
|
* @param record 美食记录
|
||||||
*/
|
*/
|
||||||
const selectFoodRecordClick = async (record: IRecord) => {
|
const selectFoodRecordClick = async (record: IRecord) => {
|
||||||
// foodStore.foodRecordApiType = 'UPDATE'
|
foodStore.recordApiType = 'UPDATE'
|
||||||
|
|
||||||
// appStore.isShowMobileBack = true
|
appStore.isShowMobileBack = true
|
||||||
// foodStore.currentFoodRecord = foodRecord
|
foodStore.currentRecord = record
|
||||||
// await router.push({ name: 'MobileHomeFoodRecordForm' })
|
await router.push('/record/recordForm')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="food-list">
|
<div class="food-list">
|
||||||
<el-empty v-if="foodStore.recipeList.length === 0" description="暂无美食记录"/>
|
<el-empty v-if="foodStore.recipeList.length === 0" description="暂无记录"/>
|
||||||
|
|
||||||
<div v-else class="list">
|
<div v-else class="list">
|
||||||
<food-card v-for="(item, index) in foodStore.recipeList"
|
<food-card v-for="(item, index) in foodStore.recipeList"
|
||||||
|
|||||||
130
src/components/Login/CommonLogin.vue
Normal file
130
src/components/Login/CommonLogin.vue
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<template>
|
||||||
|
<div class="common-login">
|
||||||
|
<el-form ref="loginFormRef" :model="authStore.loginForm"
|
||||||
|
:rules="rules" class="login-form"
|
||||||
|
autocomplete="on" label-position="left"
|
||||||
|
@submit.native.prevent>
|
||||||
|
<!-- 账户名表单选项 -->
|
||||||
|
<el-form-item prop="username" class="form-item">
|
||||||
|
<el-input
|
||||||
|
v-model="authStore.loginForm.username"
|
||||||
|
placeholder="请输入账号" clearable
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<i class="fa-solid fa-user"></i>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<!-- password表单选项 -->
|
||||||
|
<el-form-item prop="password" class="form-item">
|
||||||
|
<el-input
|
||||||
|
v-model="authStore.loginForm.password"
|
||||||
|
placeholder="请输入密码" type="password"
|
||||||
|
show-password clearable
|
||||||
|
@keyup.enter.native="handleLogin('common')"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<i class="fa-solid fa-lock"></i>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item>
|
||||||
|
<login-auth />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item class="login-option">
|
||||||
|
<el-checkbox v-model="authStore.loginForm.isRemember">记住密码</el-checkbox>
|
||||||
|
<el-link type="primary">忘记密码?</el-link>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-button type="primary" style="width: 100%;"
|
||||||
|
:disabled="!authStore.isVerified"
|
||||||
|
:loading="authStore.loginLoading"
|
||||||
|
@click.prevent="handleLogin('common')">登录
|
||||||
|
</el-button>
|
||||||
|
<div />
|
||||||
|
|
||||||
|
<div class="login-type">
|
||||||
|
<el-button @click="authStore.changeLoginType('phone')">手机号登录</el-button>
|
||||||
|
<el-button>扫码登录</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="login-others-type">
|
||||||
|
<el-divider content-position="center">其他登录方式</el-divider>
|
||||||
|
|
||||||
|
<div class="type-list">
|
||||||
|
<i class="fa-brands fa-weixin" style="color: #3CB034"></i>
|
||||||
|
<i class="fa-brands fa-qq" style="color: #29A4D9"></i>
|
||||||
|
<i class="fa-solid fa-envelope" style="color: #4971B8"
|
||||||
|
@click="authStore.changeLoginType('email')"></i>
|
||||||
|
<i class="fa-brands fa-github" style="color: #555555"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="login-register">
|
||||||
|
<span>还没有账户?</span>
|
||||||
|
<el-link type="primary" @click="authStore.changeLoginType('register')">创建账户</el-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import LoginAuth from '@/components/Login/LoginAuth.vue'
|
||||||
|
import { useAuthStore } from '@/store/auth'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useUserStore } from '@/store/user'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import type { FormRules } from 'element-plus'
|
||||||
|
import { onMounted, reactive, ref } from 'vue'
|
||||||
|
import { loginRules } from '@/utils/element/elRules'
|
||||||
|
import { getLocalStorage, setLocalStorage } from 'vue3-common/utils/storageUtil'
|
||||||
|
import type { ILoginType } from '@/types/auth'
|
||||||
|
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||||
|
|
||||||
|
const rules = reactive<FormRules>(loginRules)
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const router = useRouter()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const loginFormRef = ref()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const storageLoginForm = getLocalStorage('loginForm')
|
||||||
|
if (storageLoginForm) {
|
||||||
|
authStore.loginForm = JSON.parse(storageLoginForm)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleLogin = async (type: ILoginType) => {
|
||||||
|
loginFormRef.value.validate(async (valid: any) => {
|
||||||
|
if (valid) {
|
||||||
|
if (!isMobile()) {
|
||||||
|
ElMessage.error('目前只支持移动端')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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')
|
||||||
|
} finally {
|
||||||
|
authStore.loginLoading = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.error('请输入正确的信息')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
79
src/components/Login/EmailLogin.vue
Normal file
79
src/components/Login/EmailLogin.vue
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<template>
|
||||||
|
<div class="email-login">
|
||||||
|
<el-form ref="loginFormRef" :model="authStore.emailLoginForm"
|
||||||
|
:rules="rules" class="login-form"
|
||||||
|
autocomplete="on" label-position="left"
|
||||||
|
@submit.native.prevent>
|
||||||
|
<el-form-item prop="email" class="form-item">
|
||||||
|
<el-input
|
||||||
|
v-model="authStore.emailLoginForm.email"
|
||||||
|
placeholder="请输入邮箱地址" clearable
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<i class="fa-solid fa-envelope"></i>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item prop="code" class="code-item">
|
||||||
|
<el-input
|
||||||
|
v-model="authStore.emailLoginForm.code"
|
||||||
|
placeholder="请输入验证码" type="password"
|
||||||
|
show-password clearable
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<i class="fa-solid fa-shield-halved"></i>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-button type="primary"
|
||||||
|
:disabled="authStore.isGettingCode"
|
||||||
|
@click="authStore.getAuthCode('email')">
|
||||||
|
{{ authStore.isGettingCode ? authStore.getCodeCountdown + 's' : '获取验证码' }}
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-button type="primary" style="width: 100%;"
|
||||||
|
:loading="authStore.loginLoading"
|
||||||
|
@click.prevent="handleLogin()">登录
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-button style="width: 100%;margin-left: 0;"
|
||||||
|
@click="authStore.changeLoginType('common')">取消
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAuthStore } from '@/store/auth'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import type { FormRules } from 'element-plus'
|
||||||
|
import { reactive, ref } from 'vue'
|
||||||
|
import { loginRules } from '@/utils/element/elRules'
|
||||||
|
|
||||||
|
const rules = reactive<FormRules>(loginRules)
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const loginFormRef = ref()
|
||||||
|
|
||||||
|
const handleLogin = async () => {
|
||||||
|
loginFormRef.value.validate(async (valid: any) => {
|
||||||
|
if (valid) {
|
||||||
|
authStore.loginLoading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await authStore.handleLogin('email')
|
||||||
|
await router.push('/home')
|
||||||
|
} finally {
|
||||||
|
authStore.loginLoading = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.error('请输入正确的信息')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
182
src/components/Login/LoginAuth.vue
Normal file
182
src/components/Login/LoginAuth.vue
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
<template>
|
||||||
|
<div class="login-auth">
|
||||||
|
<div class="slider-container"
|
||||||
|
@mousedown="startDrag" @mousemove="handleDrag"
|
||||||
|
@mouseup="endDrag" @mouseleave="endDrag"
|
||||||
|
@touchstart="startDrag" @touchmove="handleDrag" @touchend="endDrag">
|
||||||
|
<div ref="draggableRef" class="draggable"
|
||||||
|
:style="{ left: `${loginAuth.dragLeft}px` }">
|
||||||
|
<i v-if="authStore.isVerified" class="fas fa-check"></i>
|
||||||
|
<i v-else class="fas fa-arrow-right"></i>
|
||||||
|
</div>
|
||||||
|
<div :style="{ width: loginAuth.progressWidth + '%' }" class="slider-progress"></div>
|
||||||
|
<div class="slider-message" :style="{color: authStore.isVerified ? '#FFFFFF' : '#696969'}">
|
||||||
|
<span v-if="authStore.isVerified">验证成功!</span>
|
||||||
|
<span v-else-if="loginAuth.isDragging">拖动中...</span>
|
||||||
|
<span v-else>向右拖动滑块 完成验证</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { reactive, ref } from 'vue'
|
||||||
|
import { useAuthStore } from '@/store/auth'
|
||||||
|
|
||||||
|
const draggableRef = ref(null)
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
const loginAuth = reactive({
|
||||||
|
isDragging: false,
|
||||||
|
initialX: 0,
|
||||||
|
offsetX: 0,
|
||||||
|
dragLeft: 0,
|
||||||
|
progressWidth: 0,
|
||||||
|
targetWidth: 100,
|
||||||
|
// 记录拖动的开始时间
|
||||||
|
startTime: 0,
|
||||||
|
// 记录拖动过程中的位置
|
||||||
|
positions: [],
|
||||||
|
// 记录拖动过程中的时间戳
|
||||||
|
timestamps: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const startDrag = (event) => {
|
||||||
|
// 验证成功后禁止拖动
|
||||||
|
if (authStore.isVerified) return
|
||||||
|
loginAuth.isDragging = true
|
||||||
|
|
||||||
|
const touch = event.changedTouches ? event.changedTouches[0] : event
|
||||||
|
loginAuth.initialX = touch.clientX
|
||||||
|
loginAuth.offsetX = draggableRef.value?.offsetLeft
|
||||||
|
|
||||||
|
loginAuth.startTime = Date.now()
|
||||||
|
loginAuth.positions = []
|
||||||
|
loginAuth.timestamps = []
|
||||||
|
loginAuth.positions.push(loginAuth.initialX)
|
||||||
|
loginAuth.timestamps.push(loginAuth.startTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDrag = (event) => {
|
||||||
|
if (loginAuth.isDragging && !authStore.isVerified) {
|
||||||
|
const touch = event.changedTouches ? event.changedTouches[0] : event
|
||||||
|
const dx = touch.clientX - loginAuth.initialX
|
||||||
|
const newLeft = loginAuth.offsetX + dx
|
||||||
|
const sliderWidth = draggableRef.value?.parentNode.offsetWidth
|
||||||
|
const handleWidth = draggableRef.value?.offsetWidth
|
||||||
|
|
||||||
|
if (newLeft < 0) {
|
||||||
|
loginAuth.dragLeft = 0
|
||||||
|
loginAuth.progressWidth = 0
|
||||||
|
} else if (newLeft > sliderWidth - handleWidth) {
|
||||||
|
loginAuth.dragLeft = sliderWidth - handleWidth
|
||||||
|
loginAuth.progressWidth = loginAuth.targetWidth
|
||||||
|
} else {
|
||||||
|
loginAuth.dragLeft = newLeft
|
||||||
|
loginAuth.progressWidth = (newLeft / (sliderWidth - handleWidth)) * loginAuth.targetWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
loginAuth.positions.push(touch.clientX)
|
||||||
|
loginAuth.timestamps.push(Date.now())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const endDrag = () => {
|
||||||
|
loginAuth.isDragging = false
|
||||||
|
if (loginAuth.progressWidth === loginAuth.targetWidth && !checkIsHuman()) {
|
||||||
|
authStore.isVerified = true
|
||||||
|
} else {
|
||||||
|
loginAuth.dragLeft = 0
|
||||||
|
loginAuth.progressWidth = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkIsHuman = () => {
|
||||||
|
const endTime = Date.now()
|
||||||
|
const dragDuration = endTime - loginAuth.startTime
|
||||||
|
|
||||||
|
const speedChanges = []
|
||||||
|
for (let i = 1; i < loginAuth.positions.length; i++) {
|
||||||
|
const distance = loginAuth.positions[i] - loginAuth.positions[i - 1]
|
||||||
|
const time = loginAuth.timestamps[i] - loginAuth.timestamps[i - 1]
|
||||||
|
const speed = time > 0 ? distance / time : 0
|
||||||
|
speedChanges.push(speed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算速度变化的标准差
|
||||||
|
const meanSpeed = speedChanges.reduce((sum, speed) => sum + speed, 0) / speedChanges.length
|
||||||
|
const variance = speedChanges.reduce((sum, speed) => sum + Math.pow(speed - meanSpeed, 2), 0) / speedChanges.length
|
||||||
|
const stdDevSpeed = Math.sqrt(variance)
|
||||||
|
|
||||||
|
// 检查是否有停顿
|
||||||
|
let hasPause = false
|
||||||
|
for (let i = 1; i < loginAuth.timestamps.length; i++) {
|
||||||
|
if (loginAuth.timestamps[i] - loginAuth.timestamps[i - 1] > 200) {
|
||||||
|
hasPause = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 综合判断是否为机器操作
|
||||||
|
return dragDuration >= 100 && stdDevSpeed >= 0.1 && hasPause
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.login-auth {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.slider-container {
|
||||||
|
position: relative;
|
||||||
|
height: 32px;
|
||||||
|
background-color: #E7E9E8;
|
||||||
|
border: 1px solid #E7E9E8;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.draggable {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 35px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--el-color-primary);
|
||||||
|
color: #FFFFFF;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 3;
|
||||||
|
border-left: 1px solid #E7E9E8;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-progress {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--el-color-primary);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider-message {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 2;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
span {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
79
src/components/Login/PhoneLogin.vue
Normal file
79
src/components/Login/PhoneLogin.vue
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<template>
|
||||||
|
<div class="phone-login">
|
||||||
|
<el-form ref="loginFormRef" :model="authStore.phoneLoginForm"
|
||||||
|
:rules="rules" class="login-form"
|
||||||
|
autocomplete="on" label-position="left"
|
||||||
|
@submit.native.prevent>
|
||||||
|
<el-form-item prop="phoneNumber" class="form-item">
|
||||||
|
<el-input
|
||||||
|
v-model="authStore.phoneLoginForm.phoneNumber"
|
||||||
|
placeholder="请输入手机号" clearable
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<i class="fa-solid fa-mobile-screen"></i>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item prop="code" class="code-item">
|
||||||
|
<el-input
|
||||||
|
v-model="authStore.phoneLoginForm.code"
|
||||||
|
placeholder="请输入验证码" type="password"
|
||||||
|
show-password clearable
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<i class="fa-solid fa-shield-halved"></i>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
|
<el-button type="primary"
|
||||||
|
:disabled="authStore.isGettingCode"
|
||||||
|
@click="authStore.getAuthCode('phone')">
|
||||||
|
{{ authStore.isGettingCode ? authStore.getCodeCountdown + 's' : '获取验证码' }}
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-button type="primary" style="width: 100%;"
|
||||||
|
:loading="authStore.loginLoading"
|
||||||
|
@click.prevent="handleLogin()">登录
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-button style="width: 100%;margin-left: 0;"
|
||||||
|
@click="authStore.changeLoginType('common')">取消
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAuthStore } from '@/store/auth'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import type { FormRules } from 'element-plus'
|
||||||
|
import { reactive, ref } from 'vue'
|
||||||
|
import { loginRules } from '@/utils/element/elRules'
|
||||||
|
|
||||||
|
const rules = reactive<FormRules>(loginRules)
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const loginFormRef = ref()
|
||||||
|
|
||||||
|
const handleLogin = async () => {
|
||||||
|
loginFormRef.value.validate(async (valid: any) => {
|
||||||
|
if (valid) {
|
||||||
|
authStore.loginLoading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await authStore.handleLogin('phone')
|
||||||
|
await router.push('/home')
|
||||||
|
} finally {
|
||||||
|
authStore.loginLoading = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.error('请输入正确的信息')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
95
src/components/Login/Register.vue
Normal file
95
src/components/Login/Register.vue
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<div class="register">
|
||||||
|
<el-form ref="loginFormRef" :model="authStore.registerForm"
|
||||||
|
:rules="rules" class="login-form"
|
||||||
|
autocomplete="on" label-position="left"
|
||||||
|
@submit.native.prevent>
|
||||||
|
<!-- 账户名表单选项 -->
|
||||||
|
<el-form-item prop="username" class="form-item">
|
||||||
|
<el-input
|
||||||
|
v-model="authStore.registerForm.username"
|
||||||
|
placeholder="请输入账号" clearable
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<i class="fa-solid fa-user"></i>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<!-- password表单选项 -->
|
||||||
|
<el-form-item prop="password" class="form-item">
|
||||||
|
<el-input
|
||||||
|
v-model="authStore.registerForm.password"
|
||||||
|
placeholder="请输入密码" type="password"
|
||||||
|
show-password clearable
|
||||||
|
@keyup.enter.native="handleLogin()"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<i class="fa-solid fa-lock"></i>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<!-- password表单选项 -->
|
||||||
|
<el-form-item prop="password" class="form-item">
|
||||||
|
<el-input
|
||||||
|
v-model="authStore.registerForm.confirmPassword"
|
||||||
|
placeholder="请输入密码" type="password"
|
||||||
|
show-password clearable
|
||||||
|
@keyup.enter.native="handleLogin()"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<i class="fa-solid fa-lock"></i>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-button type="primary" style="width: 100%;"
|
||||||
|
:loading="authStore.loginLoading"
|
||||||
|
@click.prevent="handleLogin()">注册
|
||||||
|
</el-button>
|
||||||
|
|
||||||
|
<el-button style="width: 100%;margin-left: 0;"
|
||||||
|
@click="authStore.changeLoginType('common')">取消
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAuthStore } from '@/store/auth'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import type { FormRules } from 'element-plus'
|
||||||
|
import { reactive, ref } from 'vue'
|
||||||
|
import { loginRules } from '@/utils/element/elRules'
|
||||||
|
|
||||||
|
const rules = reactive<FormRules>(loginRules)
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
const loginFormRef = ref()
|
||||||
|
|
||||||
|
const handleLogin = async () => {
|
||||||
|
loginFormRef.value.validate(async (valid: any) => {
|
||||||
|
if (valid) {
|
||||||
|
const { password, confirmPassword } = authStore.registerForm
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
ElMessage.error('请确认密码是否一致')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
authStore.loginLoading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await authStore.handleRegister()
|
||||||
|
ElMessage.success('注册成功 请登录')
|
||||||
|
await authStore.changeLoginType('common')
|
||||||
|
} finally {
|
||||||
|
authStore.loginLoading = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.error('请输入正确的信息')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -30,24 +30,10 @@ onMounted(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const getAverageCount = () => {
|
const getAverageCount = () => {
|
||||||
if (foodStore.queryDateType === 'month') {
|
if (foodStore.recordStats.length === 0) {
|
||||||
return foodStore.recordList.length
|
return 0
|
||||||
} else {
|
|
||||||
const result = {}
|
|
||||||
|
|
||||||
// 遍历数据,按月统计出现次数
|
|
||||||
foodStore.recordList.forEach((item) => {
|
|
||||||
const date = new Date(item.date)
|
|
||||||
const month = String(date.getMonth() + 1)
|
|
||||||
|
|
||||||
if (result[month]) {
|
|
||||||
result[month].count += 1
|
|
||||||
} else {
|
|
||||||
result[month] = { count: 1 }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return foodStore.recordList.length / Object.keys(result).length
|
|
||||||
}
|
}
|
||||||
|
const sum = foodStore.recordStats.map((item) => item.value).reduce((acc, curr) => acc + curr, 0)
|
||||||
|
return (sum / foodStore.recordStats.length).toFixed(2)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -28,7 +28,15 @@ const checkIsShow = (): boolean => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return route.path.includes('record') || route.path.includes('moment')
|
if (route.path.includes('moment')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.path.includes('record') && foodStore.recordSegment !== 'timeline') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const addClick = () => {
|
const addClick = () => {
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import type { ILoginType, ISession } from '@/types/auth'
|
import type { ILoginType, ISession, IUser } from '@/types/auth'
|
||||||
import { loginApi } 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'
|
||||||
|
import { registerApi } from '@/apis/user.ts'
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', {
|
export const useAuthStore = defineStore('auth', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
loginForm: {
|
loginForm: {
|
||||||
username: '132',
|
username: '',
|
||||||
password: '123',
|
password: '',
|
||||||
isRemember: false
|
isRemember: false
|
||||||
},
|
},
|
||||||
phoneLoginForm: {
|
phoneLoginForm: {
|
||||||
@@ -19,14 +20,18 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
email: '',
|
email: '',
|
||||||
code: ''
|
code: ''
|
||||||
},
|
},
|
||||||
|
registerForm: {
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: ''
|
||||||
|
},
|
||||||
loginType: 'common' as ILoginType,
|
loginType: 'common' as ILoginType,
|
||||||
loginLoading: false,
|
loginLoading: false,
|
||||||
loginAdminLoading: false,
|
|
||||||
isVerified: false,
|
isVerified: false,
|
||||||
isGettingCode: false,
|
isGettingCode: false,
|
||||||
getCodeCountdown: 60,
|
getCodeCountdown: 60,
|
||||||
getCodeTimer: null as NodeJS.Timeout | null,
|
getCodeTimer: null as NodeJS.Timeout | null,
|
||||||
homeSession: {} as ISession
|
session: {} as ISession
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
changeLoginType(type: ILoginType) {
|
changeLoginType(type: ILoginType) {
|
||||||
@@ -83,19 +88,30 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
const { email, code: emailCode } = this.emailLoginForm
|
const { email, code: emailCode } = this.emailLoginForm
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'common':
|
case 'common':
|
||||||
this.homeSession = await loginApi(username, password)
|
this.session = await loginApi(username, password)
|
||||||
break
|
break
|
||||||
case 'phone':
|
case 'phone':
|
||||||
this.homeSession = await loginApi(phoneNumber, phoneCode)
|
this.session = await loginApi(phoneNumber, phoneCode)
|
||||||
break
|
break
|
||||||
case 'email':
|
case 'email':
|
||||||
this.homeSession = await loginApi(email, emailCode)
|
this.session = await loginApi(email, emailCode)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
ElMessage.success('登录成功')
|
ElMessage.success('登录成功')
|
||||||
},
|
},
|
||||||
|
async handleRegister() {
|
||||||
|
const user: IUser = {
|
||||||
|
username: this.registerForm.username,
|
||||||
|
password: this.registerForm.password
|
||||||
|
}
|
||||||
|
await registerApi(user)
|
||||||
|
},
|
||||||
|
async handleLogout(username: string) {
|
||||||
|
await logoutApi(username)
|
||||||
|
ElMessage.success('退出登录成功')
|
||||||
|
},
|
||||||
resetLoginForm() {
|
resetLoginForm() {
|
||||||
switch (this.loginType) {
|
switch (this.loginType) {
|
||||||
case 'common':
|
case 'common':
|
||||||
@@ -117,6 +133,13 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
code: ''
|
code: ''
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case 'register':
|
||||||
|
this.registerForm = {
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: ''
|
||||||
|
}
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import type {
|
import type {
|
||||||
IRecipe, IMaterial, IStep,
|
IRecipe, IMaterial, IStep,
|
||||||
IStats, IRecord, IMoment, IFoodSegment, IComment
|
IStats, IRecord, IMoment, IFoodSegment
|
||||||
} from '@/types/food'
|
} from '@/types/food'
|
||||||
import {
|
import {
|
||||||
addRecipeApi,
|
addRecipeApi,
|
||||||
@@ -78,7 +78,7 @@ export const useFoodStore = defineStore('food', {
|
|||||||
content: ''
|
content: ''
|
||||||
} as IMoment,
|
} as IMoment,
|
||||||
momentApiType: 'ADD' as IHandleApi,
|
momentApiType: 'ADD' as IHandleApi,
|
||||||
imagePath: 'food',
|
imagePath: 'food-hub',
|
||||||
rateColorList: ['#E2DEDC', '#F7BA2A', '#009FA8'],
|
rateColorList: ['#E2DEDC', '#F7BA2A', '#009FA8'],
|
||||||
pageInfo: {
|
pageInfo: {
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
@@ -223,7 +223,7 @@ export const useFoodStore = defineStore('food', {
|
|||||||
},
|
},
|
||||||
getEmptyRecord(): IRecord {
|
getEmptyRecord(): IRecord {
|
||||||
return {
|
return {
|
||||||
person: 0,
|
person: '',
|
||||||
category: '',
|
category: '',
|
||||||
date: '',
|
date: '',
|
||||||
id: 0,
|
id: 0,
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ export const useUserStore = defineStore('user', {
|
|||||||
state: () => ({
|
state: () => ({
|
||||||
userInfo: {
|
userInfo: {
|
||||||
id: 0,
|
id: 0,
|
||||||
accountId: 0,
|
|
||||||
accountName: '',
|
|
||||||
username: '',
|
username: '',
|
||||||
|
password: '',
|
||||||
gender: 0,
|
gender: 0,
|
||||||
idNumber: '',
|
idNumber: '',
|
||||||
phoneNumber: '',
|
phoneNumber: '',
|
||||||
@@ -36,8 +35,8 @@ export const useUserStore = defineStore('user', {
|
|||||||
},
|
},
|
||||||
handleSession(session: ISession) {
|
handleSession(session: ISession) {
|
||||||
this.setToken(session.saToken.tokenValue)
|
this.setToken(session.saToken.tokenValue)
|
||||||
// this.setUserId(session.userInfo.id)
|
this.setUserId(session.userInfo?.id as number)
|
||||||
// this.userInfo = session.userInfo
|
this.userInfo = session.userInfo
|
||||||
},
|
},
|
||||||
async queryUser() {
|
async queryUser() {
|
||||||
// this.userInfo = await queryUserApi(Number(this.getUserId()))
|
// this.userInfo = await queryUserApi(Number(this.getUserId()))
|
||||||
@@ -46,7 +45,7 @@ export const useUserStore = defineStore('user', {
|
|||||||
// 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) {
|
||||||
console.log(this.userInfo.accountId)
|
|
||||||
// await updateAccountPasswordApi(this.userInfo.accountId, oldPassword, newPassword)
|
// await updateAccountPasswordApi(this.userInfo.accountId, oldPassword, newPassword)
|
||||||
},
|
},
|
||||||
setToken(token: string) {
|
setToken(token: string) {
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
export type ILoginType = 'common' | 'phone' | 'email' | 'admin'
|
export type ILoginType = 'common' | 'phone' | 'email' | 'admin' | 'register'
|
||||||
|
|
||||||
export type IAccountType = 'QQ' | 'WeChat' | 'Phone' | 'EMail'
|
export type IAccountType = 'QQ' | 'WeChat' | 'Phone' | 'EMail'
|
||||||
|
|
||||||
export interface IUser {
|
export interface IUser {
|
||||||
id: number;
|
id?: number;
|
||||||
accountId: number;
|
|
||||||
accountName: string;
|
|
||||||
username: string;
|
username: string;
|
||||||
gender: number;
|
password: string;
|
||||||
idNumber: string;
|
gender?: number;
|
||||||
phoneNumber: string;
|
idNumber?: string;
|
||||||
email: string;
|
phoneNumber?: string;
|
||||||
birthDate: string;
|
email?: string;
|
||||||
avatar: string;
|
birthDate?: string;
|
||||||
description: string;
|
avatar?: string;
|
||||||
area: string[];
|
description?: string;
|
||||||
address: string;
|
area?: string[];
|
||||||
job: string;
|
address?: string;
|
||||||
tags: string[];
|
job?: string;
|
||||||
|
tags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISession {
|
export interface ISession {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export interface IRecord {
|
|||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
category: string;
|
category: string;
|
||||||
person: number;
|
person: string;
|
||||||
date: string;
|
date: string;
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ export const foodRecordRules = {
|
|||||||
],
|
],
|
||||||
person: [
|
person: [
|
||||||
{ required: true, message: '请选择完成人', trigger: 'change' }
|
{ required: true, message: '请选择完成人', trigger: 'change' }
|
||||||
|
],
|
||||||
|
imageUrl: [
|
||||||
|
{ required: true, message: '请选择图片', trigger: 'change' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,156 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="login-view">
|
||||||
<el-button type="primary" @click="loginClick">登录</el-button>
|
<div class="login-container">
|
||||||
|
<div class="login-title">
|
||||||
|
<span class="title">
|
||||||
|
{{ appStore.appName }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<common-login v-if="authStore.loginType === 'common'"
|
||||||
|
class="animate__animated animate__bounceIn"/>
|
||||||
|
<phone-login v-if="authStore.loginType === 'phone'"
|
||||||
|
class="animate__animated animate__bounceIn"/>
|
||||||
|
<email-login v-if="authStore.loginType === 'email'"
|
||||||
|
class="animate__animated animate__bounceIn"/>
|
||||||
|
<register v-if="authStore.loginType === 'register'"
|
||||||
|
class="animate__animated animate__bounceIn"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import CommonLogin from '@/components/Login/CommonLogin.vue'
|
||||||
|
import PhoneLogin from '@/components/Login/PhoneLogin.vue'
|
||||||
|
import EmailLogin from '@/components/Login/EmailLogin.vue'
|
||||||
|
import Register from '@/components/Login/Register.vue'
|
||||||
import { useAuthStore } from '@/store/auth'
|
import { useAuthStore } from '@/store/auth'
|
||||||
import { useRouter } from 'vue-router'
|
import { useAppStore } from '@/store/app.ts'
|
||||||
import { useUserStore } from '@/store/user'
|
import { onMounted, onUnmounted } from 'vue'
|
||||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const router = useRouter()
|
const appStore = useAppStore()
|
||||||
const userStore = useUserStore()
|
|
||||||
|
|
||||||
const loginClick = async () => {
|
onMounted(() => {
|
||||||
if (!isMobile()) {
|
authStore.isVerified = false
|
||||||
ElMessage.error('暂不支持PC端')
|
// authStore.resetLoginForm()
|
||||||
}
|
})
|
||||||
await authStore.handleLogin('common')
|
|
||||||
userStore.handleSession(authStore.homeSession)
|
onUnmounted(() => {
|
||||||
await router.push('/record')
|
authStore.stopCountdown()
|
||||||
}
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style lang="scss">
|
||||||
|
$form-item-width: 300px;
|
||||||
|
$login-height: 500px;
|
||||||
|
|
||||||
|
.login-view {
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
// height: $login-height;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 2px 2px 5px #D3D3D3, 4px 4px 10px #A9A9A9;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
// justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1vh;
|
||||||
|
|
||||||
|
.login-title {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
.title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.common-login {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1vh;
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
.el-form-item {
|
||||||
|
width: $form-item-width;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-option {
|
||||||
|
.el-form-item__content {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-type {
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-others-type {
|
||||||
|
margin-top: 5vh;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.el-divider {
|
||||||
|
.el-divider__text {
|
||||||
|
color: #A9A9A9;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-list {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
i:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
filter: brightness(1.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-register {
|
||||||
|
margin-top: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
span {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.phone-login, .email-login, .register {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1vh;
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
.el-form-item {
|
||||||
|
width: $form-item-width;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-item {
|
||||||
|
.el-form-item__content {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="common-mobile-view food-moment">
|
<div class="common-mobile-view food-moment">
|
||||||
<moment-card v-for="(item, index) in foodStore.momentList"
|
<el-empty v-if="foodStore.momentList.length === 0" description="暂无朋友圈记录"/>
|
||||||
:key="index" :item="item"/>
|
|
||||||
|
<div v-else class="moment-list">
|
||||||
|
<moment-card v-for="(item, index) in foodStore.momentList"
|
||||||
|
:key="index" :item="item"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -22,5 +26,11 @@ onMounted(async () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
|
.moment-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -14,17 +14,22 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useAppStore } from '@/store/app.ts'
|
import { useAppStore } from '@/store/app.ts'
|
||||||
|
import { useUserStore } from '@/store/user.ts'
|
||||||
|
import { useAuthStore } from '@/store/auth.ts'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const authStore = useAuthStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const viewLatestVersionClick = () => {
|
const viewLatestVersionClick = () => {
|
||||||
location.reload()
|
location.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
const logoutClick = () => {
|
const logoutClick = async () => {
|
||||||
router.push('/')
|
await authStore.handleLogout(userStore.userInfo.username)
|
||||||
|
await router.push('/')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
value-format="YYYY-MM-DD"/>
|
value-format="YYYY-MM-DD"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="图片">
|
<el-form-item label="图片" prop="imageUrl">
|
||||||
<upload-image v-model="foodStore.currentRecord.imageUrl"
|
<upload-image v-model="foodStore.currentRecord.imageUrl"
|
||||||
:service-url="fileServiceUrl"
|
:service-url="fileServiceUrl"
|
||||||
:image-limit="1"
|
:image-limit="1"
|
||||||
|
|||||||
Reference in New Issue
Block a user