Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
basic-uniapp-v3
概览
概览
详情
活动
周期分析
版本库
存储库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
1
合并请求
1
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
Basic
basic-uniapp-v3
Commits
dcb2c886
提交
dcb2c886
authored
4月 30, 2026
作者:
廖在望
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 优化登录和注册页面逻辑。
上级
2a2d7e73
隐藏空白字符变更
内嵌
并排
正在显示
3 个修改的文件
包含
738 行增加
和
538 行删除
+738
-538
SlideVerify.vue
src/components/slide-verify/SlideVerify.vue
+0
-211
login.vue
src/pages/login/login.vue
+456
-234
register.vue
src/pages/login/register.vue
+282
-93
没有找到文件。
src/components/slide-verify/SlideVerify.vue
deleted
100644 → 0
浏览文件 @
2a2d7e73
<
script
setup
lang=
"ts"
>
/**
* 滑动验证组件
* 用法:<SlideVerify v-model:show="showSlide" @success="onVerifySuccess" />
*/
const
props
=
defineProps
<
{
show
:
boolean
}
>
()
const
emit
=
defineEmits
<
{
(
e
:
'update:show'
,
val
:
boolean
):
void
(
e
:
'success'
):
void
(
e
:
'close'
):
void
}
>
()
const
TRACK_WIDTH
=
560
// rpx,滑道宽度(与样式保持一致)
const
BTN_WIDTH
=
96
// rpx,滑块宽度
const
maxRpx
=
TRACK_WIDTH
-
BTN_WIDTH
// 最大可拖动距离(rpx)
const
state
=
reactive
({
translateX
:
0
,
// 当前滑块位移(px,运行时换算)
isDragging
:
false
,
startX
:
0
,
maxPx
:
0
,
// 运行时根据屏幕宽度换算
verified
:
false
,
text
:
'按住滑块,拖动到最右侧'
,
})
// 将 rpx 换算为 px
function
rpxToPx
(
rpx
:
number
):
number
{
const
screenWidth
=
uni
.
getSystemInfoSync
().
windowWidth
return
(
rpx
/
750
)
*
screenWidth
}
watch
(()
=>
props
.
show
,
(
val
)
=>
{
if
(
val
)
{
reset
()
state
.
maxPx
=
rpxToPx
(
maxRpx
)
}
})
function
reset
()
{
state
.
translateX
=
0
state
.
isDragging
=
false
state
.
verified
=
false
state
.
text
=
'按住滑块,拖动到最右侧'
}
function
onTouchStart
(
e
:
any
)
{
if
(
state
.
verified
)
return
state
.
isDragging
=
true
state
.
startX
=
e
.
touches
[
0
].
clientX
}
function
onTouchMove
(
e
:
any
)
{
if
(
!
state
.
isDragging
||
state
.
verified
)
return
const
dx
=
e
.
touches
[
0
].
clientX
-
state
.
startX
state
.
translateX
=
Math
.
max
(
0
,
Math
.
min
(
dx
,
state
.
maxPx
))
}
function
onTouchEnd
()
{
if
(
!
state
.
isDragging
)
return
state
.
isDragging
=
false
// 距终点 10px 以内视为验证通过
if
(
state
.
translateX
>=
state
.
maxPx
-
rpxToPx
(
10
))
{
state
.
translateX
=
state
.
maxPx
state
.
verified
=
true
state
.
text
=
'验证通过'
setTimeout
(()
=>
{
emit
(
'success'
)
close
()
},
600
)
}
else
{
// 未拖到底,回弹
state
.
translateX
=
0
}
}
function
close
()
{
emit
(
'update:show'
,
false
)
emit
(
'close'
)
reset
()
}
</
script
>
<
template
>
<view
class=
"sv-mask"
v-if=
"show"
@
click
.
self=
"close"
>
<view
class=
"sv-panel"
>
<view
class=
"sv-title"
>
安全验证
</view>
<view
class=
"sv-desc"
>
{{
state
.
text
}}
</view>
<!-- 滑道 -->
<view
class=
"sv-track"
>
<!-- 已划过的高亮区 -->
<view
class=
"sv-fill"
:style=
"
{ width: state.translateX + 'px' }" />
<!-- 滑块 -->
<view
class=
"sv-btn"
:class=
"
{ success: state.verified }"
:style="{ transform: `translateX(${state.translateX}px)` }"
@touchstart.prevent="onTouchStart"
@touchmove.prevent="onTouchMove"
@touchend.prevent="onTouchEnd"
>
<text
class=
"sv-arrow"
v-if=
"!state.verified"
>
→
</text>
<text
class=
"sv-check"
v-else
"
>
✓
</text>
</view>
</view>
<view
class=
"sv-close"
@
click=
"close"
>
<text>
取消
</text>
</view>
</view>
</view>
</
template
>
<
style
lang=
"scss"
scoped
>
.sv-mask
{
position
:
fixed
;
inset
:
0
;
background
:
rgba
(
0
,
0
,
0
,
0.45
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
z-index
:
9999
;
}
.sv-panel
{
width
:
640
rpx
;
background
:
#fff
;
border-radius
:
24
rpx
;
padding
:
48
rpx
40
rpx
40
rpx
;
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
box-shadow
:
0
16
rpx
48
rpx
rgba
(
0
,
0
,
0
,
0.16
);
}
.sv-title
{
font-size
:
34
rpx
;
font-weight
:
700
;
color
:
#222
;
margin-bottom
:
12
rpx
;
}
.sv-desc
{
font-size
:
26
rpx
;
color
:
#888
;
margin-bottom
:
40
rpx
;
}
.sv-track
{
position
:
relative
;
width
:
560
rpx
;
height
:
96
rpx
;
background
:
#f0f0f0
;
border-radius
:
48
rpx
;
overflow
:
hidden
;
}
.sv-fill
{
position
:
absolute
;
left
:
0
;
top
:
0
;
height
:
100%
;
background
:
linear-gradient
(
90deg
,
#b8e8c0
,
#5db66f
);
border-radius
:
48
rpx
0
0
48
rpx
;
transition
:
width
0.05s
;
}
.sv-btn
{
position
:
absolute
;
left
:
0
;
top
:
0
;
width
:
96
rpx
;
height
:
96
rpx
;
background
:
#fff
;
border-radius
:
48
rpx
;
box-shadow
:
0
4
rpx
16
rpx
rgba
(
0
,
0
,
0
,
0.18
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
transition
:
background
0.3s
;
z-index
:
2
;
&.success
{
background
:
#5db66f
;
}
}
.sv-arrow
{
font-size
:
40
rpx
;
color
:
#5db66f
;
font-weight
:
700
;
}
.sv-check
{
font-size
:
40
rpx
;
color
:
#fff
;
font-weight
:
700
;
}
.sv-close
{
margin-top
:
32
rpx
;
text
{
font-size
:
28
rpx
;
color
:
#aaa
;
}
}
</
style
>
src/pages/login/login.vue
浏览文件 @
dcb2c886
<
script
setup
lang=
"ts"
>
import
{
useUserStore
}
from
'@/store/modules/user'
import
{
closeSplashscreenAndChechUpgrade
}
from
'@/utils/upgrade'
import
{
isDevMode
}
from
'@/utils/env'
import
Link
from
'@/utils/const/link'
import
SlideVerify
from
'@/components/slide-verify/SlideVerify.vue'
import
*
as
API
from
'/@/api/model/userInfo'
import
{
Message
}
from
'@/common'
const
userStore
=
useUserStore
()
onShow
(
async
()
=>
{
// 恢复倒计时:计算实际剩余秒数,重启定时器
if
(
model
.
countdown
>
0
&&
countdownEndTime
>
0
)
{
const
remaining
=
Math
.
max
(
0
,
Math
.
ceil
((
countdownEndTime
-
Date
.
now
())
/
1000
))
if
(
remaining
>
0
)
{
model
.
countdown
=
remaining
startCountdownTimer
()
}
else
{
model
.
countdown
=
0
countdownEndTime
=
0
}
}
const
token
=
userStore
.
getToken
if
(
token
)
{
model
.
isLogin
=
true
...
...
@@ -24,17 +35,18 @@
}
})
const
defaultText
=
'湘农数智农服'
const
readConfirmShow
=
ref
<
boolean
>
(
false
)
const
form
=
ref
()
const
showSlide
=
ref
(
false
)
const
slideVerifyRef
=
ref
()
const
showSlideVerify
=
ref
(
false
)
const
slideType
=
ref
<
'sms'
|
'login'
>
(
'login'
)
const
canLogin
=
computed
(()
=>
{
return
!!
model
.
form
.
data
.
username
&&
!!
model
.
form
.
data
.
code
})
const
model
=
reactive
({
isLogin
:
false
,
loading
:
false
,
text
:
defaultText
,
countdown
:
0
,
form
:
{
phoneRules
:
[
...
...
@@ -44,7 +56,7 @@
msg
:
[
'请输入手机号'
,
'请输入正确的手机号'
],
},
],
codeL
oginRules
:
[
l
oginRules
:
[
{
name
:
'username'
,
rule
:
[
'required'
,
'isMobile'
],
...
...
@@ -55,20 +67,6 @@
rule
:
[
'required'
],
msg
:
[
'请输入验证码'
],
},
{
name
:
'read'
,
validator
:
[
{
msg
:
'请阅读并同意服务协议和隐私政策'
,
method
:
(
value
:
boolean
)
=>
{
if
(
!
value
)
{
readConfirmShow
.
value
=
true
}
return
value
},
},
],
},
],
data
:
{
username
:
''
,
...
...
@@ -79,18 +77,18 @@
})
let
countdownTimer
:
any
=
null
if
(
isDevMode
())
{
model
.
form
.
data
.
username
=
'admin'
model
.
form
.
data
.
read
=
true
}
let
countdownEndTime
=
0
function
login
()
{
form
?.
value
.
validator
(
model
.
form
.
data
,
model
.
form
.
codeL
oginRules
).
then
(
async
(
res
:
{
isPassed
:
boolean
})
=>
{
form
?.
value
.
validator
(
model
.
form
.
data
,
model
.
form
.
l
oginRules
).
then
(
async
(
res
:
{
isPassed
:
boolean
})
=>
{
if
(
res
.
isPassed
)
{
// 登录前先做滑动验证 (Requirement 7)
slideType
.
value
=
'login'
showSlide
.
value
=
true
if
(
!
model
.
form
.
data
.
read
)
{
readConfirmShow
.
value
=
true
return
}
doLogin
()
}
else
{
Message
.
toast
(
res
.
errorMsg
)
}
})
}
...
...
@@ -98,41 +96,34 @@
function
smsCode
()
{
form
?.
value
.
validator
(
model
.
form
.
data
,
model
.
form
.
phoneRules
).
then
(
async
(
res
:
{
isPassed
:
boolean
})
=>
{
if
(
res
.
isPassed
)
{
if
(
model
.
countdown
>
0
)
return
slideType
.
value
=
'sms'
showSlide
.
value
=
true
if
(
model
.
countdown
>
0
)
return
showSlideVerify
.
value
=
true
slideVerifyRef
.
value
.
reset
()
}
else
{
Message
.
toast
(
res
.
errorMsg
)
}
})
}
function
onSlideSuccess
()
{
if
(
slideType
.
value
===
'sms'
)
{
// 立即开启倒计时(乐观更新),接口报错也不停止,防止恶意连点
startCountdown
()
// 发送验证码
const
params
=
{
mobile
:
model
.
form
.
data
.
username
,
smsmode
:
'1'
,
}
API
.
sysSms
(
params
)
.
then
(
async
()
=>
{
Message
.
toast
(
'验证码已发送'
)
})
.
catch
((
err
)
=>
{
// 即使接口返回 code: 130 等错误,也不干扰已启动的倒计时
console
.
error
(
'短信发送业务异常:'
,
err
)
})
}
else
{
// 执行登录
doLogin
()
showSlideVerify
.
value
=
false
startCountdown
()
const
params
=
{
mobile
:
model
.
form
.
data
.
username
,
smsmode
:
'1'
,
}
API
.
sysSms
(
params
)
.
then
(
async
()
=>
{
Message
.
toast
(
'验证码已发送'
)
})
.
catch
((
err
)
=>
{
console
.
error
(
'短信发送业务异常:'
,
err
)
})
}
function
doLogin
()
{
model
.
loading
=
true
// 验证码登录
const
params
=
{
mobile
:
model
.
form
.
data
.
username
,
captcha
:
model
.
form
.
data
.
code
,
...
...
@@ -155,16 +146,23 @@ return
}
function
startCountdown
()
{
if
(
countdownTimer
)
clearInterval
(
countdownTimer
)
if
(
countdownTimer
)
clearInterval
(
countdownTimer
)
model
.
countdown
=
60
countdownEndTime
=
Date
.
now
()
+
60
*
1000
startCountdownTimer
()
}
function
startCountdownTimer
()
{
if
(
countdownTimer
)
clearInterval
(
countdownTimer
)
countdownTimer
=
setInterval
(()
=>
{
model
.
countdown
--
if
(
model
.
countdown
<=
0
)
{
const
remaining
=
Math
.
max
(
0
,
Math
.
ceil
((
countdownEndTime
-
Date
.
now
())
/
1000
))
model
.
countdown
=
remaining
if
(
remaining
<=
0
)
{
clearInterval
(
countdownTimer
)
countdownTimer
=
null
countdownEndTime
=
0
}
},
10
00
)
},
3
00
)
}
function
goHome
()
{
...
...
@@ -189,60 +187,47 @@ clearInterval(countdownTimer)
}
})
function
onReadConfirm
(
val
:
any
)
{
if
(
val
.
index
===
0
)
{
model
.
form
.
data
.
read
=
false
}
else
{
model
.
form
.
data
.
read
=
true
login
()
}
function
onReadConfirm
()
{
model
.
form
.
data
.
read
=
true
readConfirmShow
.
value
=
false
login
()
}
function
closeAgreeModal
()
{
readConfirmShow
.
value
=
false
}
</
script
>
<
template
>
<view
class=
"login-page"
>
<!-- 顶部设计:数智化农场全景感 -->
<view
class=
"header-visual"
>
<image
class=
"header-scene"
src=
"/static/images/login/farm_base_bg.png"
mode=
"aspectFill"
/
>
<view
class=
"header-overlay"
/
>
<image
class=
"header-scene"
src=
"/static/images/login/farm_base_bg.png"
mode=
"aspectFill"
></image
>
<view
class=
"header-overlay"
></view
>
<!-- 顶部装饰元素:漂浮的气象/农业设备(体现数智感) -->
<image
class=
"float-icon device-1"
src=
"/static/images/nongchang/device1.png"
mode=
"aspectFit"
/>
<image
class=
"float-icon device-2"
src=
"/static/images/nongchang/device2.png"
mode=
"aspectFit"
/>
<image
class=
"float-icon device-1"
src=
"/static/images/nongchang/device1.png"
mode=
"aspectFit"
></image>
<image
class=
"float-icon device-2"
src=
"/static/images/nongchang/device3.png"
mode=
"aspectFit"
></image>
<!-- 顶部状态栏占位与操作 -->
<view
class=
"top-nav-bar"
>
<view
class=
"close-btn"
@
click=
"goHome"
>
<fui-icon
name=
"close"
:size=
"48"
color=
"#fff"
/>
</view>
</view>
<!-- 品牌标识与数智概览 -->
<view
class=
"brand-hero"
>
<view
class=
"logo-box"
>
<image
class=
"logo-img"
src=
"/static/logo.png"
mode=
"aspectFit"
/
>
<image
class=
"logo-img"
src=
"/static/logo.png"
mode=
"aspectFit"
></image
>
</view>
<view
class=
"brand-info"
>
<text
class=
"app-title"
>
湘农数智农服
</text>
<view
class=
"app-slogan"
>
<image
src=
"/static/images/weather/100.svg"
class=
"weather-icon"
/>
<text>
智慧生产 · 农务管家
</text>
</view>
</view>
</view>
</view>
<!-- 登录卡片:磨砂玻璃质感 -->
<view
class=
"main-card-container"
>
<view
class=
"aesthetic-card"
>
<fui-form
ref=
"form"
top=
"0"
:padding=
"['0', '0']"
background=
"transparent"
>
<fui-form
ref=
"form"
top=
"0"
:padding=
"['0', '0']"
background=
"transparent"
:show=
"false"
>
<view
class=
"card-header"
>
<text
class=
"welcome-text"
>
您好,欢迎登录
</text>
<view
class=
"green-line"
/
>
<view
class=
"green-line"
></view
>
</view>
<!-- 输入区域 -->
<view
class=
"input-fields"
>
<view
class=
"field-item"
>
<text
class=
"field-label"
>
手机号
</text>
...
...
@@ -251,11 +236,13 @@ clearInterval(countdownTimer)
:padding=
"['24rpx', '0']"
placeholder=
"请输入手机号"
backgroundColor=
"transparent"
placeholderStyle=
"font-size:16px;"
borderColor=
"#F0F0F0"
borderBottom
height=
"110rpx"
size=
"34"
/>
maxlength=
"11"
></fui-input>
</view>
<view
class=
"field-item"
>
...
...
@@ -265,13 +252,15 @@ clearInterval(countdownTimer)
v-model=
"model.form.data.code"
:padding=
"['24rpx', '0']"
placeholder=
"请输入验证码"
placeholderStyle=
"font-size:16px;"
backgroundColor=
"transparent"
borderColor=
"#F0F0F0"
borderBottom
height=
"110rpx"
size=
"34"
maxlength=
"6"
autocomplete=
"off"
/
>
></fui-input
>
<view
class=
"sms-trigger"
:class=
"
{ inactive: model.countdown > 0 }" @click="smsCode">
{{
model
.
countdown
>
0
?
`${model.countdown
}
s后重发`
:
'获取验证码'
}}
<
/view
>
...
...
@@ -279,41 +268,43 @@ clearInterval(countdownTimer)
<
/view
>
<
/view
>
<!--
登录决策
-->
<
view
class
=
"action-footer"
>
<
fui
-
button
text
=
"开启农务数字化"
background
=
"linear-gradient(90deg, #5DB66F 0%, #4CAF50 100%)"
radius
=
"100rpx"
height
=
"110rpx"
@
click
=
"login"
:
loading
=
"model.loading"
:
disabled
=
"model.loading"
size
=
"36"
shadow
/>
<
view
:
class
=
"['btn-wrapper', { disabled: !canLogin
}
]"
>
<
fui
-
button
text
=
"验证并登录"
background
=
"linear-gradient(90deg, #5DB66F 0%, #4CAF50 100%)"
radius
=
"100rpx"
height
=
"80rpx"
@
click
=
"login"
:
loading
=
"model.loading"
:
disabled
=
"!canLogin || model.loading"
color
=
"#ffffff"
disabled
-
color
=
"#888888"
disabled
-
background
=
"#f5f5f5"
size
=
"30"
shadow
><
/fui-button
>
<
/view
>
<
view
class
=
"secondary-links"
>
<
view
class
=
"reg-btn"
@
click
=
"goRegister"
>
新用户
注册
<
/view
>
<
view
class
=
"reg-btn"
@
click
=
"goRegister"
>
还没有账号?立即
注册
<
/view
>
<
/view
>
<
/view
>
<!--
隐私合规
-->
<
view
class
=
"policy-wrapper"
>
<
fui
-
checkbox
-
group
>
<
fui
-
label
class
=
"policy-label"
>
<
fui
-
checkbox
color
=
"#5DB66F"
:
scale
=
"0.7"
:
checked
=
"model.form.data.read"
@
change
=
"(e) => (model.form.data.read = e.checked)"
/>
<
view
class
=
"checkbox-align"
>
<
fui
-
checkbox
color
=
"#5DB66F"
:
scaleRatio
=
"0.7"
:
checked
=
"model.form.data.read"
@
change
=
"(e) => (model.form.data.read = e.checked)"
><
/fui-checkbox
>
<
/view
>
<
view
class
=
"policy-text"
>
同意
<
text
class
=
"link"
@
click
.
stop
=
"Link.to(Link.services, '服务协议')"
>
《服务协议》
<
/tex
t
>
与
<
text
class
=
"link"
@
click
.
stop
=
"Link.to(Link.privacy, '隐私政策')"
>
《隐私政策》
<
/tex
t
>
已阅读并同意
<
text
class
=
"link"
@
click
.
stop
=
"Link.to(Link.services, '服务协议')"
>
《服务协议》
<
/text>与<text class="link" @click.stop="Link.to
(
Link.privacy, '隐私政策'
)
">《隐私政策》</
text
>
,同意运营商对本人提供的手机号码进行验证。
<
/view
>
<
/fui-label
>
<
/fui-checkbox-group
>
...
...
@@ -322,45 +313,64 @@ clearInterval(countdownTimer)
<
/view
>
<
/view
>
<!--
装饰底部:泥土与天空的呼应
-->
<
view
class
=
"footer-aesthetic"
/>
<!--
功能弹窗
-->
<
fui
-
modal
:
show
=
"readConfirmShow"
title
=
"服务协议及隐私保护"
:
buttons
=
"[
{ text: '不同意', plain: true
}
,
{ text: '同意', plain: false, color: '#fff'
}
,
]"
@
click
=
"onReadConfirm"
>
<
view
class
=
"modal-notice"
>
点击同意即表示您已阅读并理解
<
text
class
=
"link-inline"
@
click
.
stop
=
"Link.to(Link.services, '服务协议')"
>
《服务协议》
<
/text
>
与
<
text
class
=
"link-inline"
@
click
.
stop
=
"Link.to(Link.privacy, '隐私政策')"
>
《隐私政策》
<
/text
>
<
view
class
=
"footer-aesthetic"
><
/view
>
<
view
class
=
"agree-mask"
v
-
if
=
"readConfirmShow"
@
click
.
self
=
"closeAgreeModal"
>
<
view
class
=
"agree-panel"
>
<
view
class
=
"agree-gradient-header"
><
/view
>
<
view
class
=
"agree-close"
@
click
=
"closeAgreeModal"
>
<
text
>
✕
<
/text
>
<
/view
>
<
view
class
=
"agree-title-wrap"
>
<
view
class
=
"agree-title"
>
服务协议及隐私保护
<
/view
>
<
view
class
=
"agree-title-line"
><
/view
>
<
/view
>
<
view
class
=
"agree-content"
>
<
view
class
=
"agree-text"
>
为了更好的保障您的合法权益,请您阅读并同意
<
text
class
=
"agree-link"
@
click
.
stop
=
"Link.to(Link.services, '用户协议')"
>
《用户协议》
<
/text
>
和
<
text
class
=
"agree-link"
@
click
.
stop
=
"Link.to(Link.privacy, '隐私协议')"
>
《隐私协议》
<
/text
>
。
<
/view
>
<
view
class
=
"agree-btn"
@
click
=
"onReadConfirm"
>
同意并继续
<
/view
>
<
/view
>
<
/view
>
<
/
fui-modal
>
<
/
view
>
<
SlideVerify
v
-
model
:
show
=
"showSlide"
@
success
=
"onSlideSuccess"
/>
<
view
class
=
"slide-mask"
v
-
if
=
"showSlideVerify"
@
click
.
self
=
"showSlideVerify = false"
>
<
view
class
=
"slide-panel"
>
<
view
class
=
"slide-gradient-header"
><
/view
>
<
view
class
=
"slide-close"
@
click
=
"showSlideVerify = false"
>
<
text
>
✕
<
/text
>
<
/view
>
<
view
class
=
"slide-title-wrap"
>
<
view
class
=
"slide-title"
>
安全验证
<
/view
>
<
view
class
=
"slide-title-line"
><
/view
>
<
/view
>
<
view
class
=
"slide-content"
>
<
view
class
=
"slide-desc"
>
按住滑块,拖动到最右侧
<
/view
>
<
fui
-
slide
-
verify
ref
=
"slideVerifyRef"
width
=
"540"
height
=
"80"
slider
-
width
=
"80"
background
=
"#f5f7f5"
active
-
bg
-
color
=
"#5DB66F"
pass
-
color
=
"#5DB66F"
arrow
-
color
=
"#5DB66F"
line
-
color
=
"#5DB66F"
color
=
"#999"
active
-
color
=
"#fff"
@
success
=
"onSlideSuccess"
><
/fui-slide-verify
>
<
/view
>
<
/view
>
<
/view
>
<
/view
>
<
/template
>
<
style
lang
=
"less"
scoped
>
@
keyframes
float
-
slow
{
0
%
,
100
%
{
transform
:
translateY
(
0
)
rotate
(
15
deg
);
}
50
%
{
transform
:
translateY
(
-
20
rpx
)
rotate
(
10
deg
);
}
}
.
login
-
page
{
min
-
height
:
100
vh
;
background
-
color
:
#
f7faf8
;
...
...
@@ -369,9 +379,8 @@ clearInterval(countdownTimer)
overflow
-
x
:
hidden
;
}
/* 顶部视觉层:农业生产全景 */
.
header
-
visual
{
height
:
60
0
rpx
;
height
:
55
0
rpx
;
position
:
relative
;
padding
-
top
:
var
(
--
status
-
bar
-
height
);
overflow
:
hidden
;
...
...
@@ -382,7 +391,7 @@ clearInterval(countdownTimer)
height
:
100
%
;
top
:
0
;
left
:
0
;
filter
:
brightness
(
0.
8
);
filter
:
brightness
(
0.
9
);
}
.
header
-
overlay
{
...
...
@@ -391,134 +400,134 @@ clearInterval(countdownTimer)
height
:
100
%
;
top
:
0
;
left
:
0
;
background
:
linear
-
gradient
(
180
deg
,
rgb
(
93
182
111
/
40
%
)
0
%
,
rgb
(
76
175
80
/
80
%
)
100
%
);
background
:
linear
-
gradient
(
180
deg
,
rgb
a
(
93
,
182
,
111
,
0.3
)
0
%
,
rgba
(
76
,
175
,
80
,
0.7
)
100
%
);
}
/* 漂浮装饰:增强数智感 */
.
float
-
icon
{
position
:
absolute
;
opacity
:
0.
6
;
filter
:
drop
-
shadow
(
0
4
rpx
10
rpx
rgb
(
0
0
0
/
20
%
));
opacity
:
0.
7
;
filter
:
drop
-
shadow
(
0
4
rpx
10
rpx
rgb
a
(
0
,
0
,
0
,
0.15
));
&
.
device
-
1
{
width
:
1
2
0
rpx
;
height
:
1
2
0
rpx
;
right
:
-
20
rpx
;
top
:
1
5
0
rpx
;
transform
:
rotate
(
1
5
deg
);
width
:
1
3
0
rpx
;
height
:
1
3
0
rpx
;
right
:
20
rpx
;
top
:
1
8
0
rpx
;
transform
:
rotate
(
1
0
deg
);
animation
:
float
-
slow
4
s
ease
-
in
-
out
infinite
;
}
&
.
device
-
2
{
width
:
100
rpx
;
height
:
100
rpx
;
left
:
40
rpx
;
bottom
:
120
rpx
;
transform
:
rotate
(
-
10
deg
);
width
:
110
rpx
;
height
:
110
rpx
;
left
:
30
rpx
;
bottom
:
100
rpx
;
animation
:
float
-
slow
3
s
ease
-
in
-
out
infinite
alternate
;
}
}
.
top
-
nav
-
bar
{
position
:
relative
;
z
-
index
:
10
;
display
:
flex
;
justify
-
content
:
flex
-
start
;
align
-
items
:
center
;
padding
:
20
rpx
40
rpx
;
}
.
brand
-
hero
{
position
:
relative
;
z
-
index
:
10
;
display
:
flex
;
flex
-
direction
:
column
;
align
-
items
:
center
;
margin
-
top
:
5
0
rpx
;
margin
-
top
:
8
0
rpx
;
.
logo
-
box
{
width
:
1
6
0
rpx
;
height
:
1
6
0
rpx
;
width
:
1
4
0
rpx
;
height
:
1
4
0
rpx
;
background
:
#
fff
;
border
-
radius
:
4
4
rpx
;
border
-
radius
:
4
0
rpx
;
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
center
;
box
-
shadow
:
0
1
6
rpx
32
rpx
rgb
(
0
0
0
/
15
%
);
box
-
shadow
:
0
1
2
rpx
24
rpx
rgba
(
0
,
0
,
0
,
0.1
);
.
logo
-
img
{
width
:
10
0
rpx
;
height
:
10
0
rpx
;
width
:
9
0
rpx
;
height
:
9
0
rpx
;
}
}
.
brand
-
info
{
margin
-
top
:
30
rpx
;
margin
-
top
:
24
rpx
;
text
-
align
:
center
;
color
:
#
fff
;
.
app
-
title
{
font
-
size
:
4
8
rpx
;
font
-
size
:
4
4
rpx
;
font
-
weight
:
800
;
letter
-
spacing
:
4
rpx
;
text
-
shadow
:
0
4
rpx
8
rpx
rgb
(
0
0
0
/
20
%
);
letter
-
spacing
:
2
rpx
;
background
:
linear
-
gradient
(
90
deg
,
#
fff
0
%
,
#
fff
40
%
,
rgba
(
255
,
255
,
255
,
0.3
)
50
%
,
#
fff
60
%
,
#
fff
100
%
);
background
-
size
:
300
%
100
%
;
-
webkit
-
background
-
clip
:
text
;
background
-
clip
:
text
;
-
webkit
-
text
-
fill
-
color
:
transparent
;
animation
:
shine
-
text
3
s
ease
-
in
-
out
infinite
;
}
.
app
-
slogan
{
margin
-
top
:
12
rpx
;
font
-
size
:
2
6
rpx
;
opacity
:
0.9
;
margin
-
top
:
8
rpx
;
font
-
size
:
2
4
rpx
;
opacity
:
0.9
5
;
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
center
;
.
weather
-
icon
{
width
:
40
rpx
;
height
:
40
rpx
;
margin
-
right
:
1
2
rpx
;
width
:
36
rpx
;
height
:
36
rpx
;
margin
-
right
:
1
0
rpx
;
}
}
.
app
-
slogan
text
{
background
:
linear
-
gradient
(
90
deg
,
rgba
(
255
,
255
,
255
,
0.9
)
0
%
,
rgba
(
255
,
255
,
255
,
0.9
)
40
%
,
rgba
(
255
,
255
,
255
,
0.4
)
50
%
,
rgba
(
255
,
255
,
255
,
0.9
)
60
%
,
rgba
(
255
,
255
,
255
,
0.9
)
100
%
);
background
-
size
:
300
%
100
%
;
-
webkit
-
background
-
clip
:
text
;
background
-
clip
:
text
;
-
webkit
-
text
-
fill
-
color
:
transparent
;
animation
:
shine
-
text
3
s
ease
-
in
-
out
infinite
0.5
s
;
}
}
}
}
/* 登录卡片层 */
.
main
-
card
-
container
{
padding
:
0
40
rpx
;
margin
-
top
:
-
10
0
rpx
;
margin
-
top
:
-
8
0
rpx
;
position
:
relative
;
z
-
index
:
20
;
}
.
aesthetic
-
card
{
background
:
#
fff
;
border
-
radius
:
5
6
rpx
;
padding
:
50
rpx
50
rpx
4
0
rpx
;
box
-
shadow
:
0
20
rpx
60
rpx
rgb
(
0
0
0
/
8
%
);
background
:
#
fff
fff
;
border
-
radius
:
5
0
rpx
;
padding
:
70
rpx
50
rpx
6
0
rpx
;
box
-
shadow
:
0
15
rpx
50
rpx
rgba
(
0
,
0
,
0
,
0.06
);
.
card
-
header
{
margin
-
bottom
:
36
rpx
;
margin
-
bottom
:
50
rpx
;
.
welcome
-
text
{
font
-
size
:
3
6
rpx
;
font
-
size
:
3
8
rpx
;
font
-
weight
:
bold
;
color
:
#
333
;
}
.
green
-
line
{
width
:
6
0
rpx
;
height
:
8
rpx
;
background
:
#
5
db66f
;
width
:
5
0
rpx
;
height
:
7
rpx
;
background
:
#
5
DB66F
;
border
-
radius
:
4
rpx
;
margin
-
top
:
1
2
rpx
;
margin
-
top
:
1
4
rpx
;
}
}
.
field
-
item
{
margin
-
bottom
:
3
0
rpx
;
margin
-
bottom
:
4
0
rpx
;
.
field
-
label
{
font
-
size
:
2
4
rpx
;
font
-
size
:
2
6
rpx
;
color
:
#
999
;
margin
-
left
:
4
rpx
;
}
...
...
@@ -530,9 +539,9 @@ clearInterval(countdownTimer)
.
sms
-
trigger
{
white
-
space
:
nowrap
;
font
-
size
:
2
6
rpx
;
color
:
#
5
db66f
;
padding
:
1
2
rpx
24
rpx
;
font
-
size
:
2
7
rpx
;
color
:
#
5
DB66F
;
padding
:
1
4
rpx
28
rpx
;
background
:
#
f1f9f2
;
border
-
radius
:
50
rpx
;
margin
-
left
:
20
rpx
;
...
...
@@ -548,64 +557,277 @@ clearInterval(countdownTimer)
}
.
action
-
footer
{
margin
-
top
:
50
rpx
;
margin
-
top
:
60
rpx
;
.
btn
-
wrapper
{
transition
:
opacity
0.3
s
;
}
.
secondary
-
links
{
margin
-
top
:
28
rpx
;
margin
-
top
:
40
rpx
;
text
-
align
:
center
;
.
auto
-
hint
{
font
-
size
:
24
rpx
;
color
:
#
ccc
;
margin
-
bottom
:
24
rpx
;
}
.
reg
-
btn
{
font
-
size
:
30
rpx
;
color
:
#
666
;
font
-
weight
:
500
;
display
:
inline
-
block
;
padding
:
10
rpx
40
rpx
;
border
:
1
rpx
solid
#
eee
;
border
-
radius
:
40
rpx
;
font
-
size
:
28
rpx
;
color
:
#
aaa
;
}
}
}
.
policy
-
wrapper
{
margin
-
top
:
5
0
rpx
;
margin
-
top
:
6
0
rpx
;
.
policy
-
label
{
display
:
flex
;
align
-
items
:
flex
-
start
;
justify
-
content
:
center
;
}
.
checkbox
-
align
{
line
-
height
:
1
;
display
:
flex
;
align
-
items
:
center
;
flex
-
shrink
:
0
;
margin
-
top
:
4
rpx
;
}
.
policy
-
text
{
font
-
size
:
24
rpx
;
color
:
#
bbb
;
margin
-
left
:
12
rpx
;
line
-
height
:
1.4
;
flex
:
1
;
line
-
height
:
1.6
;
word
-
break
:
break
-
all
;
}
.
link
{
color
:
#
5
db66f
;
color
:
#
5
DB66F
;
font
-
weight
:
500
;
}
}
.
modal
-
notice
{
.
footer
-
aesthetic
{
flex
:
1
;
background
:
linear
-
gradient
(
180
deg
,
#
f7faf8
0
%
,
#
edf3ef
100
%
);
}
.
agree
-
mask
{
position
:
fixed
;
inset
:
0
;
background
:
rgba
(
0
,
0
,
0
,
0.5
);
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
center
;
z
-
index
:
9999
;
}
.
agree
-
panel
{
width
:
640
rpx
;
background
:
#
ffffff
;
border
-
radius
:
50
rpx
;
display
:
flex
;
flex
-
direction
:
column
;
align
-
items
:
center
;
box
-
shadow
:
0
15
rpx
50
rpx
rgba
(
0
,
0
,
0
,
0.08
);
position
:
relative
;
overflow
:
hidden
;
}
.
agree
-
gradient
-
header
{
position
:
absolute
;
top
:
0
;
left
:
0
;
right
:
0
;
height
:
180
rpx
;
background
:
linear
-
gradient
(
180
deg
,
#
4
CAF50
0
%
,
#
5
DB66F
40
%
,
rgba
(
93
,
182
,
111
,
0.15
)
85
%
,
transparent
100
%
);
border
-
radius
:
50
rpx
50
rpx
0
0
;
}
.
agree
-
close
{
position
:
absolute
;
top
:
20
rpx
;
right
:
24
rpx
;
width
:
56
rpx
;
height
:
56
rpx
;
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
center
;
border
-
radius
:
50
%
;
background
:
rgba
(
255
,
255
,
255
,
0.25
);
z
-
index
:
2
;
text
{
font
-
size
:
32
rpx
;
color
:
#
ffffff
;
line
-
height
:
1
;
}
}
.
agree
-
title
-
wrap
{
display
:
flex
;
flex
-
direction
:
column
;
align
-
items
:
center
;
padding
-
top
:
48
rpx
;
margin
-
bottom
:
20
rpx
;
position
:
relative
;
z
-
index
:
1
;
}
.
agree
-
title
{
font
-
size
:
36
rpx
;
font
-
weight
:
700
;
color
:
#
ffffff
;
margin
-
bottom
:
10
rpx
;
text
-
shadow
:
0
2
rpx
8
rpx
rgba
(
0
,
0
,
0
,
0.12
);
}
.
agree
-
title
-
line
{
width
:
48
rpx
;
height
:
6
rpx
;
background
:
rgba
(
255
,
255
,
255
,
0.7
);
border
-
radius
:
3
rpx
;
}
.
agree
-
content
{
width
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
align
-
items
:
center
;
padding
:
36
rpx
50
rpx
48
rpx
;
position
:
relative
;
z
-
index
:
1
;
}
.
agree
-
text
{
font
-
size
:
28
rpx
;
color
:
#
666
;
line
-
height
:
1.8
;
text
-
align
:
center
;
margin
-
bottom
:
44
rpx
;
}
.
agree
-
link
{
color
:
#
5
DB66F
;
font
-
weight
:
600
;
}
.
agree
-
btn
{
width
:
100
%
;
height
:
90
rpx
;
background
:
linear
-
gradient
(
90
deg
,
#
5
DB66F
0
%
,
#
4
CAF50
100
%
);
border
-
radius
:
100
rpx
;
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
center
;
font
-
size
:
32
rpx
;
color
:
#
ffffff
;
font
-
weight
:
600
;
box
-
shadow
:
0
6
rpx
20
rpx
rgba
(
93
,
182
,
111
,
0.3
);
}
.
slide
-
mask
{
position
:
fixed
;
inset
:
0
;
background
:
rgba
(
0
,
0
,
0
,
0.5
);
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
center
;
z
-
index
:
9999
;
}
.
slide
-
panel
{
width
:
640
rpx
;
background
:
#
ffffff
;
border
-
radius
:
50
rpx
;
display
:
flex
;
flex
-
direction
:
column
;
align
-
items
:
center
;
box
-
shadow
:
0
15
rpx
50
rpx
rgba
(
0
,
0
,
0
,
0.08
);
position
:
relative
;
overflow
:
hidden
;
}
.
slide
-
gradient
-
header
{
position
:
absolute
;
top
:
0
;
left
:
0
;
right
:
0
;
height
:
180
rpx
;
background
:
linear
-
gradient
(
180
deg
,
#
4
CAF50
0
%
,
#
5
DB66F
40
%
,
rgba
(
93
,
182
,
111
,
0.15
)
85
%
,
transparent
100
%
);
border
-
radius
:
50
rpx
50
rpx
0
0
;
}
.
link
-
inline
{
color
:
#
5
db66f
;
font
-
weight
:
bold
;
.
slide
-
close
{
position
:
absolute
;
top
:
20
rpx
;
right
:
24
rpx
;
width
:
56
rpx
;
height
:
56
rpx
;
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
center
;
border
-
radius
:
50
%
;
background
:
rgba
(
255
,
255
,
255
,
0.25
);
z
-
index
:
2
;
text
{
font
-
size
:
32
rpx
;
color
:
#
ffffff
;
line
-
height
:
1
;
}
}
.
footer
-
aesthetic
{
flex
:
1
;
background
:
linear
-
gradient
(
180
deg
,
#
f7faf8
0
%
,
#
edf3ef
100
%
);
.
slide
-
title
-
wrap
{
display
:
flex
;
flex
-
direction
:
column
;
align
-
items
:
center
;
padding
-
top
:
48
rpx
;
margin
-
bottom
:
20
rpx
;
position
:
relative
;
z
-
index
:
1
;
}
.
slide
-
title
{
font
-
size
:
36
rpx
;
font
-
weight
:
700
;
color
:
#
ffffff
;
margin
-
bottom
:
10
rpx
;
text
-
shadow
:
0
2
rpx
8
rpx
rgba
(
0
,
0
,
0
,
0.12
);
}
.
slide
-
title
-
line
{
width
:
48
rpx
;
height
:
6
rpx
;
background
:
rgba
(
255
,
255
,
255
,
0.7
);
border
-
radius
:
3
rpx
;
}
.
slide
-
content
{
width
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
align
-
items
:
center
;
padding
:
36
rpx
50
rpx
48
rpx
;
position
:
relative
;
z
-
index
:
1
;
}
.
slide
-
desc
{
font
-
size
:
26
rpx
;
color
:
#
999
;
margin
-
bottom
:
36
rpx
;
letter
-
spacing
:
0.5
rpx
;
}
@
keyframes
float
-
slow
{
0
%
,
100
%
{
transform
:
translateY
(
0
)
rotate
(
5
deg
);
}
50
%
{
transform
:
translateY
(
-
15
rpx
)
rotate
(
-
5
deg
);
}
}
@
keyframes
shine
-
text
{
0
%
{
background
-
position
:
0
%
0
;
}
30
%
{
background
-
position
:
0
%
0
;
}
60
%
{
background
-
position
:
100
%
0
;
}
100
%
{
background
-
position
:
100
%
0
;
}
}
<
/style
>
src/pages/login/register.vue
浏览文件 @
dcb2c886
<
script
setup
lang=
"ts"
>
import
{
useUserStore
}
from
'@/store/modules/user'
import
*
as
API
from
'/@/api/model/userInfo'
import
SlideVerify
from
'@/components/slide-verify/SlideVerify.vue'
import
Link
from
'@/utils/const/link'
import
{
Message
}
from
'@/common'
const
defaultText
=
'湘农数智农服'
const
userStore
=
useUserStore
()
const
form
=
ref
()
const
slideVerifyRef
=
ref
()
const
showSlide
=
ref
(
false
)
const
slideType
=
ref
<
'sms'
|
'register'
>
(
'register'
)
const
slideAction
=
ref
<
'sms'
|
'submit'
>
(
'sms'
)
const
isForgotPwd
=
ref
(
false
)
onLoad
((
options
)
=>
{
if
(
options
?.
type
===
'forgot'
)
{
isForgotPwd
.
value
=
true
}
})
const
model
=
reactive
({
loading
:
false
,
countdown
:
0
,
countdown
:
0
,
form
:
{
rules
:
[
{
...
...
@@ -18,14 +30,25 @@
},
{
name
:
'code'
,
rule
:
[
'required'
],
msg
:
[
'请输入验证码'
],
rule
:
[
'required'
,
'minLength:6'
,
'maxLength:6'
],
msg
:
[
'请输入验证码'
,
'验证码为6位数字'
,
'验证码为6位数字'
],
},
{
name
:
'password'
,
rule
:
[
'required'
,
'minLength:6'
],
msg
:
[
'请输入密码'
,
'密码长度不能少于6位'
],
},
{
name
:
'confirmPassword'
,
rule
:
[
'required'
,
'minLength:6'
],
msg
:
[
'请确认密码'
,
'密码长度不能少于6位'
],
validator
:
[
{
msg
:
'两次输入的密码不一致'
,
method
:
(
value
:
string
)
=>
value
===
model
.
form
.
data
.
password
,
},
],
},
],
rulesPhone
:
[
{
...
...
@@ -37,6 +60,7 @@
data
:
{
phone
:
''
,
password
:
''
,
confirmPassword
:
''
,
code
:
''
,
read
:
false
,
},
...
...
@@ -45,7 +69,7 @@
let
countdownTimer
:
any
=
null
function
register
()
{
function
submit
()
{
if
(
!
model
.
form
.
data
.
read
)
{
Message
.
toast
(
'请阅读并同意服务协议及隐私政策'
)
return
...
...
@@ -53,13 +77,16 @@
form
?.
value
.
validator
(
model
.
form
.
data
,
model
.
form
.
rules
).
then
(
async
(
res
:
{
isPassed
:
boolean
})
=>
{
if
(
res
.
isPassed
)
{
slide
Type
.
value
=
'register
'
slide
Action
.
value
=
'submit
'
showSlide
.
value
=
true
slideVerifyRef
.
value
.
reset
()
}
else
{
Message
.
toast
(
res
.
errorMsg
)
}
})
}
function
do
Register
()
{
function
do
Submit
()
{
const
params
=
{
phone
:
model
.
form
.
data
.
phone
,
password
:
model
.
form
.
data
.
password
,
...
...
@@ -67,15 +94,22 @@
}
model
.
loading
=
true
API
.
sysRegister
(
params
)
.
then
(
async
(
body
)
=>
{
if
(
body
)
{
Message
.
toast
(
`注册成功, 请登录~`
)
setTimeout
(()
=>
{
goLogin
()
},
1500
)
if
(
body
?.
token
)
{
userStore
.
setToken
(
body
.
token
)
const
user
=
await
API
.
getUserInfo
()
userStore
.
setUserInfo
(
user
)
Message
.
toast
(
isForgotPwd
.
value
?
'密码重置成功'
:
'注册成功,欢迎加入~'
)
goHome
()
}
else
{
Message
.
toast
(
body
?.
message
||
(
isForgotPwd
.
value
?
'密码重置失败'
:
'注册失败'
))
}
})
.
catch
((
err
)
=>
{
Message
.
toast
(
err
?.
message
||
(
isForgotPwd
.
value
?
'密码重置失败,请稍后重试'
:
'注册失败,请稍后重试'
))
})
.
finally
(()
=>
{
model
.
loading
=
false
})
...
...
@@ -85,30 +119,33 @@
form
?.
value
.
validator
(
model
.
form
.
data
,
model
.
form
.
rulesPhone
).
then
(
async
(
res
:
{
isPassed
:
boolean
})
=>
{
if
(
res
.
isPassed
)
{
if
(
model
.
countdown
>
0
)
return
slide
Type
.
value
=
'sms'
slide
Action
.
value
=
'sms'
showSlide
.
value
=
true
slideVerifyRef
.
value
.
reset
()
}
else
{
Message
.
toast
(
res
.
errorMsg
)
}
})
}
function
onSlideSuccess
()
{
if
(
slideType
.
value
===
'sms'
)
{
// 开启乐观倒计时,不论接口成败,60s内不准重发
showSlide
.
value
=
false
if
(
slideAction
.
value
===
'submit'
)
{
doSubmit
()
}
else
{
startCountdown
()
const
params
=
{
mobile
:
model
.
form
.
data
.
phone
,
smsmode
:
2
,
// 2-注册
smsmode
:
isForgotPwd
.
value
?
3
:
2
,
}
API
.
sysSms
(
params
)
.
then
(
async
()
=>
{
Message
.
toast
(
'验证码已发送'
)
})
.
catch
((
err
)
=>
{
console
.
error
(
'
注册
短信发送失败:'
,
err
)
console
.
error
(
'短信发送失败:'
,
err
)
})
}
else
{
doRegister
()
}
}
...
...
@@ -155,30 +192,30 @@
<view
class=
"login-page"
>
<!-- 顶部视觉层:阳光农场与气象设备 -->
<view
class=
"header-visual"
>
<image
class=
"header-scene"
src=
"/static/images/login/farm_base_bg.png"
mode=
"aspectFill"
/
>
<image
class=
"header-scene"
src=
"/static/images/login/farm_base_bg.png"
mode=
"aspectFill"
></image
>
<view
class=
"header-overlay"
></view>
<!-- 气象站与农业设备:体现数智感 -->
<image
class=
"float-icon device-1"
src=
"/static/images/nongchang/device1.png"
mode=
"aspectFit"
/
>
<image
class=
"float-icon device-2"
src=
"/static/images/nongchang/device3.png"
mode=
"aspectFit"
/
>
<image
class=
"float-icon device-1"
src=
"/static/images/nongchang/device1.png"
mode=
"aspectFit"
></image
>
<image
class=
"float-icon device-2"
src=
"/static/images/nongchang/device3.png"
mode=
"aspectFit"
></image
>
<!-- 顶部导航 -->
<view
class=
"top-nav-bar"
>
<view
class=
"back-btn"
@
click=
"goLogin"
>
<fui-icon
name=
"arrowleft"
:size=
"48"
color=
"#fff"
/
>
<fui-icon
name=
"arrowleft"
:size=
"48"
color=
"#fff"
></fui-icon
>
</view>
</view>
<!-- 品牌标识 -->
<view
class=
"brand-hero"
>
<view
class=
"logo-box"
>
<image
class=
"logo-img"
src=
"/static/logo.png"
mode=
"aspectFit"
/
>
<image
class=
"logo-img"
src=
"/static/logo.png"
mode=
"aspectFit"
></image
>
</view>
<view
class=
"brand-info"
>
<text
class=
"app-title"
>
加入数智农服
</text>
<text
class=
"app-title"
>
{{
isForgotPwd
?
'重置密码'
:
'加入数智农服'
}}
</text>
<view
class=
"app-slogan"
>
<image
src=
"/static/images/weather/100.svg"
class=
"weather-icon"
/
>
<text>
开启您的智慧农业之旅
</text>
<image
src=
"/static/images/weather/100.svg"
class=
"weather-icon"
mode=
"aspectFit"
></image
>
<text>
{{
isForgotPwd
?
'设置新密码,安全便捷'
:
'开启您的智慧农业之旅'
}}
</text>
</view>
</view>
</view>
...
...
@@ -189,7 +226,7 @@
<view
class=
"aesthetic-card"
>
<fui-form
ref=
"form"
top=
"0"
:padding=
"['0', '0']"
background=
"transparent"
>
<view
class=
"card-header"
>
<text
class=
"welcome-text"
>
创建新账号
</text>
<text
class=
"welcome-text"
>
{{
isForgotPwd
?
'重置密码'
:
'创建新账号'
}}
</text>
<view
class=
"green-line"
></view>
</view>
...
...
@@ -198,15 +235,16 @@
<text
class=
"field-label"
>
手机号
</text>
<fui-input
v-model=
"model.form.data.phone"
:padding=
"['
24
rpx', '0']"
:padding=
"['
16
rpx', '0']"
placeholder=
"请输入手机号"
backgroundColor=
"transparent"
placeholderStyle=
"font-size:16px;"
borderColor=
"#F0F0F0"
borderBottom
height=
"
11
0rpx"
height=
"
8
0rpx"
size=
"34"
maxlength=
"11"
/
>
></fui-input
>
</view>
<view
class=
"field-item"
>
...
...
@@ -214,15 +252,17 @@
<view
class=
"code-inner"
>
<fui-input
v-model=
"model.form.data.code"
:padding=
"['
24
rpx', '0']"
:padding=
"['
16
rpx', '0']"
placeholder=
"请输入验证码"
backgroundColor=
"transparent"
placeholderStyle=
"font-size:16px;"
borderColor=
"#F0F0F0"
borderBottom
height=
"110rpx"
size=
"34"
height=
"80rpx"
size=
"30"
maxlength=
"6"
autocomplete=
"off"
/
>
></fui-input
>
<view
class=
"sms-trigger"
:class=
"
{ inactive: model.countdown > 0 }" @click="smsCode">
{{
model
.
countdown
>
0
?
`${model.countdown
}
s后重发`
:
'获取验证码'
}}
<
/view
>
...
...
@@ -234,33 +274,56 @@
<
fui
-
input
v
-
model
=
"model.form.data.password"
type
=
"password"
:
padding
=
"['24rpx', '0']"
placeholder
=
"请设置6位以上登录密码"
:
padding
=
"['16rpx', '0']"
placeholder
=
"请设置6位以上密码(建议字母+数字)"
backgroundColor
=
"transparent"
placeholderStyle
=
"font-size:16px;"
borderColor
=
"#F0F0F0"
borderBottom
height
=
"80rpx"
size
=
"34"
autocomplete
=
"new-password"
><
/fui-input
>
<
/view
>
<
view
class
=
"field-item"
>
<
text
class
=
"field-label"
>
确认密码
<
/text
>
<
fui
-
input
v
-
model
=
"model.form.data.confirmPassword"
type
=
"password"
:
padding
=
"['16rpx', '0']"
placeholder
=
"请再次输入密码"
backgroundColor
=
"transparent"
placeholderStyle
=
"font-size:16px;"
borderColor
=
"#F0F0F0"
borderBottom
height
=
"
11
0rpx"
height
=
"
8
0rpx"
size
=
"34"
autocomplete
=
"new-password"
/
>
><
/fui-input
>
<
/view
>
<
/view
>
<
view
class
=
"action-footer"
>
<
fui
-
button
text
=
"立即注册"
background
=
"linear-gradient(90deg, #5DB66F 0%, #4CAF50 100%)"
radius
=
"100rpx"
height
=
"110rpx"
@
click
=
"register"
:
loading
=
"model.loading"
:
disabled
=
"model.loading"
size
=
"36"
shadow
/>
<
view
class
=
"btn-wrapper"
>
<
fui
-
button
:
text
=
"isForgotPwd ? '确认重置' : '立即注册'"
background
=
"linear-gradient(90deg, #5DB66F 0%, #4CAF50 100%)"
radius
=
"100rpx"
height
=
"80rpx"
@
click
=
"submit"
:
loading
=
"model.loading"
:
disabled
=
"model.loading"
color
=
"#ffffff"
disabled
-
color
=
"#888888"
disabled
-
background
=
"#f5f5f5"
size
=
"30"
shadow
><
/fui-button
>
<
/view
>
<
view
class
=
"secondary-links"
>
<
view
class
=
"reg-btn"
@
click
=
"goLogin"
>
已有账号?去登录
<
/view
>
<
view
class
=
"reg-btn"
@
click
=
"goLogin"
>
{{
isForgotPwd
?
'返回登录'
:
'已有账号?去登录'
}}
<
/view
>
<
/view
>
<
/view
>
...
...
@@ -271,10 +334,10 @@
<
view
class
=
"checkbox-align"
>
<
fui
-
checkbox
color
=
"#5DB66F"
:
scale
=
"0.7"
:
scale
Ratio
=
"0.7"
:
checked
=
"model.form.data.read"
@
change
=
"(e) => (model.form.data.read = e.checked)"
/
>
><
/fui-checkbox
>
<
/view
>
<
view
class
=
"policy-text"
>
同意
<
text
class
=
"link"
@
click
.
stop
=
"Link.to(Link.services, '服务协议')"
>
《服务协议》
<
/text>与<text class="link" @click.stop="Link.to
(
Link.privacy, '隐私政策'
)
">《隐私政策》</
text
>
...
...
@@ -288,7 +351,35 @@
<
view
class
=
"footer-aesthetic"
><
/view
>
<
SlideVerify
v
-
model
:
show
=
"showSlide"
@
success
=
"onSlideSuccess"
/>
<
view
class
=
"slide-mask"
v
-
if
=
"showSlide"
@
click
.
self
=
"showSlide = false"
>
<
view
class
=
"slide-panel"
>
<
view
class
=
"slide-gradient-header"
><
/view
>
<
view
class
=
"slide-close"
@
click
=
"showSlide = false"
>
<
text
>
✕
<
/text
>
<
/view
>
<
view
class
=
"slide-title-wrap"
>
<
view
class
=
"slide-title"
>
安全验证
<
/view
>
<
view
class
=
"slide-title-line"
><
/view
>
<
/view
>
<
view
class
=
"slide-content"
>
<
view
class
=
"slide-desc"
>
按住滑块,拖动到最右侧
<
/view
>
<
fui
-
slide
-
verify
ref
=
"slideVerifyRef"
width
=
"540"
height
=
"80"
slider
-
width
=
"80"
background
=
"#f5f7f5"
active
-
bg
-
color
=
"#5DB66F"
pass
-
color
=
"#5DB66F"
arrow
-
color
=
"#5DB66F"
line
-
color
=
"#5DB66F"
color
=
"#999"
active
-
color
=
"#fff"
@
success
=
"onSlideSuccess"
><
/fui-slide-verify
>
<
/view
>
<
/view
>
<
/view
>
<
/view
>
<
/template
>
...
...
@@ -302,7 +393,7 @@
}
.
header
-
visual
{
height
:
55
0
rpx
;
height
:
46
0
rpx
;
position
:
relative
;
padding
-
top
:
var
(
--
status
-
bar
-
height
);
overflow
:
hidden
;
...
...
@@ -331,18 +422,18 @@
filter
:
drop
-
shadow
(
0
4
rpx
10
rpx
rgba
(
0
,
0
,
0
,
0.15
));
&
.
device
-
1
{
width
:
1
3
0
rpx
;
height
:
1
3
0
rpx
;
width
:
1
0
0
rpx
;
height
:
1
0
0
rpx
;
right
:
20
rpx
;
top
:
1
8
0
rpx
;
top
:
1
4
0
rpx
;
transform
:
rotate
(
10
deg
);
animation
:
float
-
slow
4
s
ease
-
in
-
out
infinite
;
}
&
.
device
-
2
{
width
:
11
0
rpx
;
height
:
11
0
rpx
;
width
:
9
0
rpx
;
height
:
9
0
rpx
;
left
:
30
rpx
;
bottom
:
10
0
rpx
;
bottom
:
8
0
rpx
;
animation
:
float
-
slow
3
s
ease
-
in
-
out
infinite
alternate
;
}
}
...
...
@@ -359,11 +450,11 @@
display
:
flex
;
flex
-
direction
:
column
;
align
-
items
:
center
;
margin
-
top
:
2
0
rpx
;
margin
-
top
:
0
rpx
;
.
logo
-
box
{
width
:
1
4
0
rpx
;
height
:
1
4
0
rpx
;
width
:
1
2
0
rpx
;
height
:
1
2
0
rpx
;
background
:
#
fff
;
border
-
radius
:
40
rpx
;
display
:
flex
;
...
...
@@ -372,25 +463,25 @@
box
-
shadow
:
0
12
rpx
24
rpx
rgba
(
0
,
0
,
0
,
0.1
);
.
logo
-
img
{
width
:
90
rpx
;
height
:
90
rpx
;
width
:
76
rpx
;
height
:
76
rpx
;
}
}
.
brand
-
info
{
margin
-
top
:
24
rpx
;
margin
-
top
:
16
rpx
;
text
-
align
:
center
;
color
:
#
fff
;
.
app
-
title
{
font
-
size
:
44
rpx
;
font
-
size
:
38
rpx
;
font
-
weight
:
800
;
letter
-
spacing
:
2
rpx
;
text
-
shadow
:
0
2
rpx
4
rpx
rgba
(
0
,
0
,
0
,
0.1
);
}
.
app
-
slogan
{
margin
-
top
:
8
rpx
;
margin
-
top
:
4
rpx
;
font
-
size
:
24
rpx
;
opacity
:
0.95
;
display
:
flex
;
...
...
@@ -417,33 +508,34 @@
.
aesthetic
-
card
{
background
:
#
ffffff
;
border
-
radius
:
50
rpx
;
padding
:
70
rpx
50
rpx
6
0
rpx
;
padding
:
36
rpx
50
rpx
3
0
rpx
;
box
-
shadow
:
0
15
rpx
50
rpx
rgba
(
0
,
0
,
0
,
0.06
);
.
card
-
header
{
margin
-
bottom
:
50
rpx
;
margin
-
bottom
:
24
rpx
;
.
welcome
-
text
{
font
-
size
:
3
8
rpx
;
font
-
size
:
3
4
rpx
;
font
-
weight
:
bold
;
color
:
#
333
;
}
.
green
-
line
{
width
:
50
rpx
;
height
:
7
rpx
;
width
:
44
rpx
;
height
:
6
rpx
;
background
:
#
5
DB66F
;
border
-
radius
:
4
rpx
;
margin
-
top
:
1
4
rpx
;
border
-
radius
:
3
rpx
;
margin
-
top
:
1
0
rpx
;
}
}
.
field
-
item
{
margin
-
bottom
:
4
0
rpx
;
margin
-
bottom
:
2
0
rpx
;
.
field
-
label
{
font
-
size
:
2
6
rpx
;
font
-
size
:
2
4
rpx
;
color
:
#
999
;
margin
-
left
:
4
rpx
;
margin
-
bottom
:
2
rpx
;
}
.
code
-
inner
{
...
...
@@ -453,9 +545,9 @@
.
sms
-
trigger
{
white
-
space
:
nowrap
;
font
-
size
:
2
7
rpx
;
font
-
size
:
2
4
rpx
;
color
:
#
5
DB66F
;
padding
:
1
4
rpx
28
rpx
;
padding
:
1
0
rpx
24
rpx
;
background
:
#
f1f9f2
;
border
-
radius
:
50
rpx
;
margin
-
left
:
20
rpx
;
...
...
@@ -471,28 +563,29 @@
}
.
action
-
footer
{
margin
-
top
:
70
rpx
;
margin
-
top
:
40
rpx
;
.
btn
-
wrapper
{
transition
:
opacity
0.3
s
;
}
.
secondary
-
links
{
margin
-
top
:
40
rpx
;
margin
-
top
:
38
rpx
;
text
-
align
:
center
;
.
reg
-
btn
{
font
-
size
:
28
rpx
;
color
:
#
888
;
padding
:
10
rpx
40
rpx
;
border
:
1
rpx
solid
#
f0f0f0
;
border
-
radius
:
40
rpx
;
color
:
#
aaa
;
}
}
}
.
policy
-
wrapper
{
margin
-
top
:
6
0
rpx
;
margin
-
top
:
6
2
rpx
;
.
policy
-
label
{
display
:
flex
;
align
-
items
:
center
;
/* 垂直居中 */
align
-
items
:
flex
-
start
;
justify
-
content
:
center
;
}
...
...
@@ -500,12 +593,14 @@
line
-
height
:
1
;
display
:
flex
;
align
-
items
:
center
;
flex
-
shrink
:
0
;
margin
-
top
:
4
rpx
;
}
.
policy
-
text
{
font
-
size
:
2
4
rpx
;
font
-
size
:
2
2
rpx
;
color
:
#
bbb
;
margin
-
left
:
1
2
rpx
;
margin
-
left
:
1
0
rpx
;
display
:
flex
;
align
-
items
:
center
;
}
...
...
@@ -521,6 +616,100 @@
background
:
linear
-
gradient
(
180
deg
,
#
f7faf8
0
%
,
#
edf3ef
100
%
);
}
.
slide
-
mask
{
position
:
fixed
;
inset
:
0
;
background
:
rgba
(
0
,
0
,
0
,
0.5
);
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
center
;
z
-
index
:
9999
;
}
.
slide
-
panel
{
width
:
640
rpx
;
background
:
#
ffffff
;
border
-
radius
:
50
rpx
;
display
:
flex
;
flex
-
direction
:
column
;
align
-
items
:
center
;
box
-
shadow
:
0
15
rpx
50
rpx
rgba
(
0
,
0
,
0
,
0.08
);
position
:
relative
;
overflow
:
hidden
;
}
.
slide
-
gradient
-
header
{
position
:
absolute
;
top
:
0
;
left
:
0
;
right
:
0
;
height
:
180
rpx
;
background
:
linear
-
gradient
(
180
deg
,
#
4
CAF50
0
%
,
#
5
DB66F
40
%
,
rgba
(
93
,
182
,
111
,
0.15
)
85
%
,
transparent
100
%
);
border
-
radius
:
50
rpx
50
rpx
0
0
;
}
.
slide
-
close
{
position
:
absolute
;
top
:
20
rpx
;
right
:
24
rpx
;
width
:
56
rpx
;
height
:
56
rpx
;
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
center
;
border
-
radius
:
50
%
;
background
:
rgba
(
255
,
255
,
255
,
0.25
);
z
-
index
:
2
;
text
{
font
-
size
:
32
rpx
;
color
:
#
ffffff
;
line
-
height
:
1
;
}
}
.
slide
-
title
-
wrap
{
display
:
flex
;
flex
-
direction
:
column
;
align
-
items
:
center
;
padding
-
top
:
48
rpx
;
margin
-
bottom
:
20
rpx
;
position
:
relative
;
z
-
index
:
1
;
}
.
slide
-
title
{
font
-
size
:
36
rpx
;
font
-
weight
:
700
;
color
:
#
ffffff
;
margin
-
bottom
:
10
rpx
;
text
-
shadow
:
0
2
rpx
8
rpx
rgba
(
0
,
0
,
0
,
0.12
);
}
.
slide
-
title
-
line
{
width
:
48
rpx
;
height
:
6
rpx
;
background
:
rgba
(
255
,
255
,
255
,
0.7
);
border
-
radius
:
3
rpx
;
}
.
slide
-
content
{
width
:
100
%
;
display
:
flex
;
flex
-
direction
:
column
;
align
-
items
:
center
;
padding
:
36
rpx
50
rpx
48
rpx
;
position
:
relative
;
z
-
index
:
1
;
}
.
slide
-
desc
{
font
-
size
:
26
rpx
;
color
:
#
999
;
margin
-
bottom
:
36
rpx
;
letter
-
spacing
:
0.5
rpx
;
}
@
keyframes
float
-
slow
{
0
%
,
100
%
{
transform
:
translateY
(
0
)
rotate
(
5
deg
);
}
50
%
{
transform
:
translateY
(
-
15
rpx
)
rotate
(
-
5
deg
);
}
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论