添加登录逻辑
9
.hbuilderx/launch.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"version" : "1.0",
|
||||||
|
"configurations" : [
|
||||||
|
{
|
||||||
|
"playground" : "standard",
|
||||||
|
"type" : "uni-app:app-ios"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
23
.prettierrc.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// @ts-check
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
printWidth: 74,
|
||||||
|
tabWidth: 2,
|
||||||
|
semi: false,
|
||||||
|
arrowParens: 'avoid',
|
||||||
|
singleQuote: true,
|
||||||
|
trailingComma: 'none',
|
||||||
|
bracketSpacing: true,
|
||||||
|
htmlWhitespaceSensitivity: 'ignore',
|
||||||
|
endOfLine: 'auto',
|
||||||
|
insertPragma: false,
|
||||||
|
proseWrap: 'preserve',
|
||||||
|
'objectCurly-newline': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
multiline: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'array-bracket-newline': ['error', 'consistent'],
|
||||||
|
vueIndentScriptAndStyle: true
|
||||||
|
}
|
||||||
5
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"uni-helper.uni-app-snippets-vscode"
|
||||||
|
]
|
||||||
|
}
|
||||||
51
App.vue
@@ -1,34 +1,35 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
|
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
|
||||||
import { useTokenStore } from '@/stores/token'
|
import { useTokenStore } from './stores/token'
|
||||||
import { reLaunch } from '@/utils/router'
|
import { reLaunch } from './utils/router'
|
||||||
|
|
||||||
/** 静默登录逻辑 */
|
/** 静默登录逻辑 */
|
||||||
const silentLogin = async () => {
|
const silentLogin = async () => {
|
||||||
const tokenStore = useTokenStore()
|
const tokenStore = useTokenStore()
|
||||||
if (tokenStore.token && !tokenStore.isTokenExpired()) {
|
if (tokenStore.token && !tokenStore.isTokenExpired()) {
|
||||||
console.log('去验证token')
|
console.log('去验证token')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 没有token去登录页
|
// 没有token去登录页
|
||||||
reLaunch('/pages/login/login?id=1')
|
reLaunch('/pages/login/login?id=1')
|
||||||
}
|
}
|
||||||
|
|
||||||
onLaunch(() => {
|
onLaunch(() => {
|
||||||
console.log('App Launch111')
|
console.log('App Launch111')
|
||||||
silentLogin()
|
silentLogin()
|
||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
console.log('App Show222')
|
console.log('App Show222')
|
||||||
})
|
})
|
||||||
|
|
||||||
onHide(() => {
|
onHide(() => {
|
||||||
console.log('App Hide333')
|
console.log('App Hide333')
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style lang="scss">
|
||||||
/*每个页面公共css */
|
/*每个页面公共css */
|
||||||
|
@import './styles/global.scss';
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
62
components/agreement-checkbox/agreement-checkbox.vue
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<script setup>
|
||||||
|
const isShow = defineModel({
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="agreement-checkbox">
|
||||||
|
<view
|
||||||
|
v-show="!isShow"
|
||||||
|
class="checkbox-box"
|
||||||
|
@click="isShow = true"
|
||||||
|
></view>
|
||||||
|
<image
|
||||||
|
v-show="isShow"
|
||||||
|
src="/static/images/public/check-to-confirm.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
class="left-icon"
|
||||||
|
@click="isShow = false"
|
||||||
|
></image>
|
||||||
|
<text class="text">我已阅读并同意</text>
|
||||||
|
<text class="on">《用户权益》</text>
|
||||||
|
<text class="text">与</text>
|
||||||
|
<text class="on">《隐私政策》</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.agreement-checkbox {
|
||||||
|
margin-top: 48px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.checkbox-box,
|
||||||
|
.left-icon {
|
||||||
|
margin-right: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-box {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 48rpx;
|
||||||
|
border: 2rpx solid #999999;
|
||||||
|
}
|
||||||
|
.left-icon {
|
||||||
|
width: 36.25rpx;
|
||||||
|
height: 36.25rpx;
|
||||||
|
}
|
||||||
|
.text,
|
||||||
|
.on {
|
||||||
|
font-family: PingFang SC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999999;
|
||||||
|
font-style: normal;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
.on {
|
||||||
|
color: #00d9c5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
46
components/cb-button/cb-button.vue
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 抛出点击事件
|
||||||
|
const emits = defineEmits(['click'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="cb-button">
|
||||||
|
<button
|
||||||
|
:disabled="props.disabled"
|
||||||
|
@click="emits('click')"
|
||||||
|
class="cb-button"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.cb-button {
|
||||||
|
button {
|
||||||
|
height: 96rpx;
|
||||||
|
line-height: 96rpx;
|
||||||
|
border-radius: 96rpx;
|
||||||
|
background: linear-gradient(180deg, #00d993 0%, #00d9c5 100%);
|
||||||
|
font-family: PingFang SC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #ffffff;
|
||||||
|
font-style: normal;
|
||||||
|
text-transform: none;
|
||||||
|
&::after {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
&[disabled] {
|
||||||
|
background: #d9d9d9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
196
components/cb-input/cb-input.vue
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import account from '@/static/images/login/account.png'
|
||||||
|
import password from '@/static/images/login/password.png'
|
||||||
|
import phone from '@/static/images/login/phone.png'
|
||||||
|
import invitation from '@/static/images/login/invitation.png'
|
||||||
|
import email from '@/static/images/login/email.png'
|
||||||
|
import codeIcon from '@/static/images/login/code.png'
|
||||||
|
import viewPassword from '@/static/images/login/view.png'
|
||||||
|
import concealPassword from '@/static/images/login/conceal.png'
|
||||||
|
|
||||||
|
const name = defineModel({
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const isCode = defineModel('code', {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 倒计时 */
|
||||||
|
const countdown = ref(0)
|
||||||
|
|
||||||
|
/** 启动倒计时 */
|
||||||
|
const startCountdown = () => {
|
||||||
|
countdown.value = 60
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
if (countdown.value > 0) {
|
||||||
|
countdown.value--
|
||||||
|
} else {
|
||||||
|
isCode.value = false
|
||||||
|
clearInterval(timer)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => isCode.value,
|
||||||
|
v => {
|
||||||
|
if (v) {
|
||||||
|
startCountdown()
|
||||||
|
} else {
|
||||||
|
countdown.value = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
/**
|
||||||
|
* 输入框状态类型
|
||||||
|
* text: 文本
|
||||||
|
* password: 密码
|
||||||
|
* number: 数字
|
||||||
|
* tel: 手机号
|
||||||
|
* email: 邮箱
|
||||||
|
*/
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'text'
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 输入框图标
|
||||||
|
* 1: 手机号/邮箱
|
||||||
|
* 2: 密码
|
||||||
|
* 3: 手机号
|
||||||
|
* 4: 邀请码
|
||||||
|
* 5: 邮箱
|
||||||
|
* 6: 验证码
|
||||||
|
*/
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: '1'
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '请输入'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits(['onGetCode'])
|
||||||
|
|
||||||
|
/** 切换查看密码状态 */
|
||||||
|
const showPassword = ref(false)
|
||||||
|
|
||||||
|
const placeholderStyle = `font-family: PingFang SC, PingFang SC; font-weight: 500; color: #D9D9D9; font-size: 28rpx; font-style: normal; text-transform: none;`
|
||||||
|
|
||||||
|
const leftIcon = computed(() => {
|
||||||
|
switch (props.icon) {
|
||||||
|
case '1':
|
||||||
|
return account
|
||||||
|
case '2':
|
||||||
|
return password
|
||||||
|
case '3':
|
||||||
|
return phone
|
||||||
|
case '4':
|
||||||
|
return invitation
|
||||||
|
case '5':
|
||||||
|
return email
|
||||||
|
case '6':
|
||||||
|
return codeIcon
|
||||||
|
default:
|
||||||
|
return account
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="cb-input">
|
||||||
|
<image :src="leftIcon" mode="heightFix" class="left-icon"></image>
|
||||||
|
<input
|
||||||
|
v-if="props.type === 'password'"
|
||||||
|
v-model="name"
|
||||||
|
:password="!showPassword"
|
||||||
|
:placeholder-style="placeholderStyle"
|
||||||
|
:placeholder="props.placeholder"
|
||||||
|
class="input-box"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
v-else
|
||||||
|
v-model="name"
|
||||||
|
:type="props.type"
|
||||||
|
:placeholder-style="placeholderStyle"
|
||||||
|
:placeholder="props.placeholder"
|
||||||
|
class="input-box"
|
||||||
|
/>
|
||||||
|
<text
|
||||||
|
v-if="props.icon === '6'"
|
||||||
|
:class="{ 'text-decoration': isCode }"
|
||||||
|
class="right-text"
|
||||||
|
@click="!isCode && emits('onGetCode')"
|
||||||
|
>
|
||||||
|
{{ isCode ? `${countdown}秒后重新获取` : '获取验证码' }}
|
||||||
|
</text>
|
||||||
|
<image
|
||||||
|
v-if="props.type === 'password'"
|
||||||
|
:src="showPassword ? viewPassword : concealPassword"
|
||||||
|
mode="heightFix"
|
||||||
|
class="right-icon"
|
||||||
|
@click="showPassword = !showPassword"
|
||||||
|
></image>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.cb-input + .cb-input {
|
||||||
|
margin-top: 48rpx;
|
||||||
|
}
|
||||||
|
.cb-input {
|
||||||
|
width: calc(100% - 64rpx);
|
||||||
|
height: 96rpx;
|
||||||
|
background: #f9f9f9;
|
||||||
|
border-radius: 128rpx;
|
||||||
|
padding: 0 32rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.left-icon,
|
||||||
|
.right-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 48rpx;
|
||||||
|
}
|
||||||
|
.left-icon {
|
||||||
|
margin-right: 16rpx;
|
||||||
|
}
|
||||||
|
.input-box {
|
||||||
|
width: 100%;
|
||||||
|
font-family: PingFang SC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333333;
|
||||||
|
font-style: normal;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
.right-icon {
|
||||||
|
margin-left: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-text {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
width: 140rpx;
|
||||||
|
color: #00d9c5;
|
||||||
|
font-family: PingFang SC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-style: normal;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-decoration {
|
||||||
|
width: 202rpx;
|
||||||
|
text-align: right;
|
||||||
|
color: #d9d9d9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
44
components/nav-bar/nav-bar.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<script setup>
|
||||||
|
import { navigateBack } from '@/utils/router'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
})
|
||||||
|
|
||||||
|
const onBack = () => {
|
||||||
|
navigateBack()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="nav-bar">
|
||||||
|
<view class="status_bar">
|
||||||
|
<!-- 这里是状态栏 -->
|
||||||
|
</view>
|
||||||
|
<view class="nav-bar-box">
|
||||||
|
<view @click="onBack">
|
||||||
|
<!-- 返回图标插槽 -->
|
||||||
|
<slot name="back"></slot>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.nav-bar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.status_bar {
|
||||||
|
height: var(--status-bar-height);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-bar-box {
|
||||||
|
padding: 0 36rpx;
|
||||||
|
height: 58rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
119
components/register-app/register-app.vue
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<script setup>
|
||||||
|
import { reactive, computed } from 'vue'
|
||||||
|
import { reLaunch } from '@/utils/router'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
/**
|
||||||
|
* 注册方式
|
||||||
|
* phone: 手机号
|
||||||
|
* email: 邮箱
|
||||||
|
*/
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: 'phone'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const isPhone = computed(() => props.type === 'phone')
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
// 手机号
|
||||||
|
name: '',
|
||||||
|
// 验证码
|
||||||
|
code: '',
|
||||||
|
// 密码
|
||||||
|
password: '',
|
||||||
|
// 确认密码
|
||||||
|
confirmPassword: '',
|
||||||
|
// 邀请码
|
||||||
|
inviteCode: '',
|
||||||
|
agreement: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const isBtn = computed(() => {
|
||||||
|
return (
|
||||||
|
formData.name &&
|
||||||
|
formData.code &&
|
||||||
|
formData.password &&
|
||||||
|
formData.confirmPassword &&
|
||||||
|
formData.inviteCode &&
|
||||||
|
!formData.agreement
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const onRegister = () => {
|
||||||
|
console.log('注册')
|
||||||
|
}
|
||||||
|
|
||||||
|
const onLogin = () => {
|
||||||
|
reLaunch('/pages/login/login')
|
||||||
|
}
|
||||||
|
|
||||||
|
const onTopRight = () => {
|
||||||
|
console.log('切换注册方式', isPhone.value)
|
||||||
|
const url = isPhone.value
|
||||||
|
? '/pages/login/email-register/email-register'
|
||||||
|
: '/pages/login/phone-register/phone-register'
|
||||||
|
reLaunch(url)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="register-app">
|
||||||
|
<view class="top-register-nav">
|
||||||
|
<text class="title-left">{{ isPhone ? '手机' : '邮箱' }}注册</text>
|
||||||
|
<text class="title-right" @click="onTopRight">
|
||||||
|
{{ isPhone ? '邮箱' : '手机号' }}注册
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<cb-input
|
||||||
|
v-model="formData.name"
|
||||||
|
:type="isPhone ? 'number' : 'email'"
|
||||||
|
icon="3"
|
||||||
|
:placeholder="`请输入${isPhone ? '手机号' : '邮箱'}`"
|
||||||
|
></cb-input>
|
||||||
|
<cb-input
|
||||||
|
v-model="formData.code"
|
||||||
|
type="number"
|
||||||
|
icon="6"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
></cb-input>
|
||||||
|
<cb-input
|
||||||
|
v-model="formData.password"
|
||||||
|
type="password"
|
||||||
|
icon="2"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
></cb-input>
|
||||||
|
<cb-input
|
||||||
|
v-model="formData.confirmPassword"
|
||||||
|
type="password"
|
||||||
|
icon="2"
|
||||||
|
placeholder="请输入确认密码"
|
||||||
|
></cb-input>
|
||||||
|
<cb-input
|
||||||
|
v-model="formData.inviteCode"
|
||||||
|
type="number"
|
||||||
|
icon="4"
|
||||||
|
placeholder="请输入邀请码"
|
||||||
|
></cb-input>
|
||||||
|
<agreement-checkbox v-model="formData.agreement" />
|
||||||
|
<cb-button class="bottom-btn" :disabled="isBtn" @click="onRegister">
|
||||||
|
注册
|
||||||
|
</cb-button>
|
||||||
|
<view class="bottom-text">
|
||||||
|
<text class="text">已有账号?</text>
|
||||||
|
<text class="text" @click="onLogin">去登录</text>
|
||||||
|
</view>
|
||||||
|
</div>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '/styles/login.scss';
|
||||||
|
.register-app {
|
||||||
|
.bottom-btn {
|
||||||
|
margin: 100rpx 0 64rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
16
main.js
@@ -6,7 +6,7 @@ import './uni.promisify.adaptor'
|
|||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
App.mpType = 'app'
|
App.mpType = 'app'
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
...App
|
...App
|
||||||
})
|
})
|
||||||
app.$mount()
|
app.$mount()
|
||||||
// #endif
|
// #endif
|
||||||
@@ -15,11 +15,11 @@ app.$mount()
|
|||||||
import { createSSRApp } from 'vue'
|
import { createSSRApp } from 'vue'
|
||||||
import * as Pinia from 'pinia'
|
import * as Pinia from 'pinia'
|
||||||
export function createApp() {
|
export function createApp() {
|
||||||
const app = createSSRApp(App)
|
const app = createSSRApp(App)
|
||||||
app.use(Pinia.createPinia())
|
app.use(Pinia.createPinia())
|
||||||
return {
|
return {
|
||||||
app,
|
app,
|
||||||
Pinia, // 此处必须将 Pinia 返回
|
Pinia // 此处必须将 Pinia 返回
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// #endif
|
// #endif
|
||||||
|
|||||||
26
pages.json
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
"pages": [
|
||||||
|
//pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
|
||||||
{
|
{
|
||||||
"path": "pages/index/index",
|
"path": "pages/index/index",
|
||||||
"style": {
|
"style": {
|
||||||
@@ -11,6 +12,27 @@
|
|||||||
"style": {
|
"style": {
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/login/phone-register/phone-register",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "手机注册",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/login/email-register/email-register",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "邮箱注册",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/login/forgot-password/forgot-password",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "忘记密码",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"globalStyle": {
|
"globalStyle": {
|
||||||
@@ -20,4 +42,4 @@
|
|||||||
"backgroundColor": "#F8F8F8"
|
"backgroundColor": "#F8F8F8"
|
||||||
},
|
},
|
||||||
"uniIdRouter": {}
|
"uniIdRouter": {}
|
||||||
}
|
}
|
||||||
10
pages/login/email-register/email-register.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<template>
|
||||||
|
<register-app type="email"></register-app>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
</style>
|
||||||
86
pages/login/forgot-password/forgot-password.vue
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: '',
|
||||||
|
code: ''
|
||||||
|
})
|
||||||
|
const isCode = ref(false)
|
||||||
|
|
||||||
|
const getCode = () => {
|
||||||
|
console.log('获取验证码')
|
||||||
|
isCode.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const onBottomBtn = () => {
|
||||||
|
console.log('确认')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="forgot-password">
|
||||||
|
<nav-bar>
|
||||||
|
<template #back>
|
||||||
|
<image
|
||||||
|
src="/static/images/login/back.png"
|
||||||
|
mode="heightFix"
|
||||||
|
class="left-icon"
|
||||||
|
></image>
|
||||||
|
</template>
|
||||||
|
</nav-bar>
|
||||||
|
<view class="top-nav">
|
||||||
|
<text class="title-left">忘记密码</text>
|
||||||
|
</view>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<cb-input
|
||||||
|
v-model="formData.username"
|
||||||
|
placeholder="请输入手机号/邮箱"
|
||||||
|
></cb-input>
|
||||||
|
<cb-input
|
||||||
|
v-model="formData.code"
|
||||||
|
v-model:code="isCode"
|
||||||
|
type="number"
|
||||||
|
icon="6"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
@onGetCode="getCode"
|
||||||
|
></cb-input>
|
||||||
|
<cb-input
|
||||||
|
v-model="formData.password"
|
||||||
|
type="password"
|
||||||
|
icon="2"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
></cb-input>
|
||||||
|
<cb-input
|
||||||
|
v-model="formData.confirmPassword"
|
||||||
|
type="password"
|
||||||
|
icon="2"
|
||||||
|
placeholder="请输入确认密码"
|
||||||
|
></cb-input>
|
||||||
|
<cb-button
|
||||||
|
class="bottom-btn"
|
||||||
|
:disabled="
|
||||||
|
!formData.username ||
|
||||||
|
!formData.password ||
|
||||||
|
!formData.confirmPassword ||
|
||||||
|
!formData.code
|
||||||
|
"
|
||||||
|
@click="onBottomBtn"
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</cb-button>
|
||||||
|
</div>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '/styles/login.scss';
|
||||||
|
.left-icon {
|
||||||
|
height: 36rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-btn {
|
||||||
|
margin-top: 140rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,15 +1,75 @@
|
|||||||
<template>
|
|
||||||
<view>{{ name }}</view>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { reactive } from 'vue'
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
|
import { useUI } from '@/utils/use-ui'
|
||||||
|
import { reLaunch, navigateTo } from '@/utils/router'
|
||||||
|
|
||||||
const name = ref('登1录')
|
const { showToast } = useUI()
|
||||||
onLoad(e => {
|
|
||||||
console.log('接收==:', e.id)
|
const formData = reactive({
|
||||||
})
|
username: '',
|
||||||
|
password: '',
|
||||||
|
agreement: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const onLogin = () => {
|
||||||
|
showToast('登录成功')
|
||||||
|
console.log('登录:', formData)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onRegister = () => {
|
||||||
|
reLaunch('/pages/login/phone-register/phone-register')
|
||||||
|
}
|
||||||
|
|
||||||
|
const onTopRight = () => {
|
||||||
|
navigateTo('/pages/login/forgot-password/forgot-password')
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad(e => {
|
||||||
|
console.log('接收==:', e.id)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
<template>
|
||||||
|
<view class="login">
|
||||||
|
<view class="top-nav">
|
||||||
|
<text class="title-left">登录</text>
|
||||||
|
<text class="title-right" @click="onTopRight">忘记密码</text>
|
||||||
|
</view>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<cb-input
|
||||||
|
v-model="formData.username"
|
||||||
|
placeholder="请输入手机号/邮箱"
|
||||||
|
></cb-input>
|
||||||
|
<cb-input
|
||||||
|
v-model="formData.password"
|
||||||
|
type="password"
|
||||||
|
icon="2"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
></cb-input>
|
||||||
|
<agreement-checkbox v-model="formData.agreement" />
|
||||||
|
<cb-button
|
||||||
|
class="bottom-btn"
|
||||||
|
:disabled="
|
||||||
|
!formData.username || !formData.password || !formData.agreement
|
||||||
|
"
|
||||||
|
@click="onLogin"
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</cb-button>
|
||||||
|
<view class="bottom-text">
|
||||||
|
<text class="text">还没账号?</text>
|
||||||
|
<text class="text" @click="onRegister">去注册</text>
|
||||||
|
</view>
|
||||||
|
</div>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '/styles/login.scss';
|
||||||
|
.login {
|
||||||
|
.bottom-btn {
|
||||||
|
margin: 160rpx 0 64rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
10
pages/login/phone-register/phone-register.vue
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<template>
|
||||||
|
<register-app></register-app>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
</style>
|
||||||
BIN
static/images/login/account.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
static/images/login/back.png
Normal file
|
After Width: | Height: | Size: 276 B |
BIN
static/images/login/code.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
static/images/login/conceal.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
static/images/login/email.png
Normal file
|
After Width: | Height: | Size: 842 B |
BIN
static/images/login/invitation.png
Normal file
|
After Width: | Height: | Size: 742 B |
BIN
static/images/login/password.png
Normal file
|
After Width: | Height: | Size: 799 B |
BIN
static/images/login/phone.png
Normal file
|
After Width: | Height: | Size: 561 B |
BIN
static/images/login/top.png
Normal file
|
After Width: | Height: | Size: 171 KiB |
BIN
static/images/login/view.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
static/images/public/check-to-confirm.png
Normal file
|
After Width: | Height: | Size: 825 B |
@@ -1,6 +1,6 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { STORAGE_KEYS } from '@/constants/storageKeys';
|
import { STORAGE_KEYS } from '@/constants/storage-keys';
|
||||||
import { getToken, removeToken } from '@/utils/storage';
|
import { getToken, removeToken } from '@/utils/storage';
|
||||||
|
|
||||||
/** 登录状态 */
|
/** 登录状态 */
|
||||||
|
|||||||
4
styles/global.scss
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/* 设置全局背景色 */
|
||||||
|
page {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
75
styles/login.scss
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// 登录,忘记密码顶部样式
|
||||||
|
.top-nav {
|
||||||
|
padding: 0 32rpx;
|
||||||
|
height: 446rpx;
|
||||||
|
background-image: url('/static/images/login/top.png');
|
||||||
|
background-size: cover;
|
||||||
|
background-position: 10rpx 0;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
.title-left,
|
||||||
|
.title-right {
|
||||||
|
font-family: PingFang SC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
text-transform: none;
|
||||||
|
margin-bottom: 86rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-left {
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
.title-right {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册顶部样式
|
||||||
|
.top-register-nav {
|
||||||
|
padding: 120rpx 32rpx 66rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
.title-left,
|
||||||
|
.title-right {
|
||||||
|
font-family: PingFang SC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-left {
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
.title-right {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输入框排版样式
|
||||||
|
.input-wrapper {
|
||||||
|
margin-top: 10rpx;
|
||||||
|
padding: 0 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 底部文字
|
||||||
|
.bottom-text {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
.text {
|
||||||
|
font-family: PingFang SC, PingFang SC;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #00d9c5;
|
||||||
|
font-style: normal;
|
||||||
|
text-transform: none;
|
||||||
|
&:first-child {
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
uni.scss
@@ -21,32 +21,32 @@ $uni-color-warning: #f0ad4e;
|
|||||||
$uni-color-error: #dd524d;
|
$uni-color-error: #dd524d;
|
||||||
|
|
||||||
/* 文字基本颜色 */
|
/* 文字基本颜色 */
|
||||||
$uni-text-color:#333;//基本色
|
$uni-text-color: #333; //基本色
|
||||||
$uni-text-color-inverse:#fff;//反色
|
$uni-text-color-inverse: #fff; //反色
|
||||||
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
|
$uni-text-color-grey: #999; //辅助灰色,如加载更多的提示信息
|
||||||
$uni-text-color-placeholder: #808080;
|
$uni-text-color-placeholder: #808080;
|
||||||
$uni-text-color-disable:#c0c0c0;
|
$uni-text-color-disable: #c0c0c0;
|
||||||
|
|
||||||
/* 背景颜色 */
|
/* 背景颜色 */
|
||||||
$uni-bg-color:#ffffff;
|
$uni-bg-color: #ffffff;
|
||||||
$uni-bg-color-grey:#f8f8f8;
|
$uni-bg-color-grey: #f8f8f8;
|
||||||
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
|
$uni-bg-color-hover: #f1f1f1; //点击状态颜色
|
||||||
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
|
$uni-bg-color-mask: rgba(0, 0, 0, 0.4); //遮罩颜色
|
||||||
|
|
||||||
/* 边框颜色 */
|
/* 边框颜色 */
|
||||||
$uni-border-color:#c8c7cc;
|
$uni-border-color: #c8c7cc;
|
||||||
|
|
||||||
/* 尺寸变量 */
|
/* 尺寸变量 */
|
||||||
|
|
||||||
/* 文字尺寸 */
|
/* 文字尺寸 */
|
||||||
$uni-font-size-sm:12px;
|
$uni-font-size-sm: 12px;
|
||||||
$uni-font-size-base:14px;
|
$uni-font-size-base: 14px;
|
||||||
$uni-font-size-lg:16px;
|
$uni-font-size-lg: 16px;
|
||||||
|
|
||||||
/* 图片尺寸 */
|
/* 图片尺寸 */
|
||||||
$uni-img-size-sm:20px;
|
$uni-img-size-sm: 20px;
|
||||||
$uni-img-size-base:26px;
|
$uni-img-size-base: 26px;
|
||||||
$uni-img-size-lg:40px;
|
$uni-img-size-lg: 40px;
|
||||||
|
|
||||||
/* Border Radius */
|
/* Border Radius */
|
||||||
$uni-border-radius-sm: 2px;
|
$uni-border-radius-sm: 2px;
|
||||||
@@ -68,9 +68,9 @@ $uni-spacing-col-lg: 12px;
|
|||||||
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||||
|
|
||||||
/* 文章场景相关 */
|
/* 文章场景相关 */
|
||||||
$uni-color-title: #2C405A; // 文章标题颜色
|
$uni-color-title: #2c405a; // 文章标题颜色
|
||||||
$uni-font-size-title:20px;
|
$uni-font-size-title: 20px;
|
||||||
$uni-color-subtitle: #555555; // 二级标题颜色
|
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||||
$uni-font-size-subtitle:26px;
|
$uni-font-size-subtitle: 26px;
|
||||||
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
$uni-color-paragraph: #3f536e; // 文章段落颜色
|
||||||
$uni-font-size-paragraph:15px;
|
$uni-font-size-paragraph: 15px;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const request = (options) => {
|
|||||||
title: '加载中...',
|
title: '加载中...',
|
||||||
mask: true
|
mask: true
|
||||||
})
|
})
|
||||||
}
|
};
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
uni.request({
|
uni.request({
|
||||||
|
|||||||
@@ -7,60 +7,66 @@
|
|||||||
* @returns {string} 拼接后的完整 url
|
* @returns {string} 拼接后的完整 url
|
||||||
*/
|
*/
|
||||||
const appendParams = (url, params) => {
|
const appendParams = (url, params) => {
|
||||||
if (!params || Object.keys(params).length === 0) {
|
if (!params || Object.keys(params).length === 0) {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = Object.entries(params).map(([key, value]) => {
|
const query = Object.entries(params)
|
||||||
// 处理复杂类型(如对象、数组)需序列化
|
.map(([key, value]) => {
|
||||||
if (typeof value === 'object') {
|
// 处理复杂类型(如对象、数组)需序列化
|
||||||
value = encodeURIComponent(JSON.stringify(value))
|
if (typeof value === 'object') {
|
||||||
} else {
|
value = encodeURIComponent(JSON.stringify(value))
|
||||||
value = encodeURIComponent(String(value))
|
} else {
|
||||||
}
|
value = encodeURIComponent(String(value))
|
||||||
return `${key}=${value}`
|
}
|
||||||
}).join('&')
|
return `${key}=${value}`
|
||||||
|
})
|
||||||
|
.join('&')
|
||||||
|
|
||||||
return url.includes('?') ? `${url}&${query}` : `${url}?${query}`
|
return url.includes('?') ? `${url}&${query}` : `${url}?${query}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 普通跳转(保留返回)
|
* 普通跳转(保留返回)
|
||||||
*/
|
*/
|
||||||
export const navigateTo = (url, params = {}) => {
|
export const navigateTo = (url, params = {}) => {
|
||||||
const finalUrl = appendParams(url, params)
|
const finalUrl = appendParams(url, params)
|
||||||
return uni.navigateTo({ url: finalUrl })
|
return uni.navigateTo({
|
||||||
|
url: finalUrl
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭当前页,跳转到应用内某个页面(不可返回)
|
* 关闭当前页,跳转到应用内某个页面(不可返回)
|
||||||
*/
|
*/
|
||||||
export const redirectTo = (url, params = {}) => {
|
export const redirectTo = (url, params = {}) => {
|
||||||
const finalUrl = appendParams(url, params)
|
const finalUrl = appendParams(url, params)
|
||||||
return uni.redirectTo({ url: finalUrl })
|
return uni.redirectTo({ url: finalUrl })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭所有页面,打开到应用内某个页面
|
* 关闭所有页面,打开到应用内某个页面
|
||||||
*/
|
*/
|
||||||
export const reLaunch = (url, params = {}) => {
|
export const reLaunch = (url, params = {}) => {
|
||||||
const finalUrl = appendParams(url, params)
|
const finalUrl = appendParams(url, params)
|
||||||
return uni.reLaunch({ url: finalUrl })
|
return uni.reLaunch({ url: finalUrl })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回上一页(可指定 delta)
|
* 返回上一页(可指定 delta)
|
||||||
*/
|
*/
|
||||||
export const navigateBack = (delta = 1) => {
|
export const navigateBack = (delta = 1) => {
|
||||||
return uni.navigateBack({ delta })
|
return uni.navigateBack({ delta })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 跳转到 tabBar 页面(只能用 switchTab)
|
* 跳转到 tabBar 页面(只能用 switchTab)
|
||||||
*/
|
*/
|
||||||
export const switchTab = (url, params = {}) => {
|
export const switchTab = (url, params = {}) => {
|
||||||
if (Object.keys(params).length > 0) {
|
if (Object.keys(params).length > 0) {
|
||||||
console.warn('switchTab 不支持携带参数,请使用全局状态或 storage 传递')
|
console.warn(
|
||||||
}
|
'switchTab 不支持携带参数,请使用全局状态或 storage 传递'
|
||||||
return uni.switchTab({ url })
|
)
|
||||||
}
|
}
|
||||||
|
return uni.switchTab({ url })
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { STORAGE_KEYS } from '@/constants/storageKeys'
|
import { STORAGE_KEYS } from '@/constants/storage-keys'
|
||||||
|
|
||||||
/** 保存 token */
|
/** 保存 token */
|
||||||
export const setToken = (v) => {
|
export const setToken = (v) => {
|
||||||
|
|||||||
56
utils/use-ui.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// 加载状态,提示语
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
// 全局 loading 状态(可用于页面绑定 v-if)
|
||||||
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示 loading
|
||||||
|
* @param {string} title - 提示文字(H5 支持,App 小程序部分支持)
|
||||||
|
*/
|
||||||
|
const showLoading = (title = '加载中...') => {
|
||||||
|
isLoading.value = true
|
||||||
|
// uni.showLoading 在 H5 和 App 中行为略有不同,但基本可用
|
||||||
|
uni.showLoading({
|
||||||
|
title,
|
||||||
|
mask: true // 防止穿透点击
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 隐藏 loading
|
||||||
|
*/
|
||||||
|
const hideLoading = () => {
|
||||||
|
isLoading.value = false
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一 Toast 提示
|
||||||
|
* @param {string} message - 提示内容
|
||||||
|
* @param {string} type - 'success' | 'error' | 'warning' | 'none'
|
||||||
|
* @param {number} duration - 持续时间(毫秒)
|
||||||
|
*/
|
||||||
|
const showToast = (message, type = 'none', duration = 2000) => {
|
||||||
|
let icon = 'none'
|
||||||
|
if (type === 'success') icon = 'success'
|
||||||
|
if (type === 'error') icon = 'error'
|
||||||
|
if (type === 'warning') icon = 'none'
|
||||||
|
|
||||||
|
uni.showToast({
|
||||||
|
title: message,
|
||||||
|
icon,
|
||||||
|
duration,
|
||||||
|
mask: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出响应式状态和方法
|
||||||
|
export const useUI = () => {
|
||||||
|
return {
|
||||||
|
isLoading: isLoading, // 可用于模板中 v-if="isLoading"
|
||||||
|
showLoading,
|
||||||
|
hideLoading,
|
||||||
|
showToast
|
||||||
|
}
|
||||||
|
}
|
||||||