|
@@ -1,42 +1,47 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <view class="login-container">
|
|
|
|
|
- <view class="login-form">
|
|
|
|
|
- <view class="logo-section">
|
|
|
|
|
- <view class="logo-text">自助洗车</view>
|
|
|
|
|
- <view class="logo-subtitle">运营管理平台</view>
|
|
|
|
|
|
|
+ <view class="login-page">
|
|
|
|
|
+ <view class="login-content">
|
|
|
|
|
+ <!-- Logo -->
|
|
|
|
|
+ <view class="brand">
|
|
|
|
|
+ <view class="brand-mark"></view>
|
|
|
|
|
+ <text class="brand-name">自助洗车</text>
|
|
|
|
|
+ <text class="brand-sub">运营管理平台</text>
|
|
|
</view>
|
|
</view>
|
|
|
-
|
|
|
|
|
- <view class="input-group">
|
|
|
|
|
- <view class="input-label">
|
|
|
|
|
- <AppIcon name="smartphone" size="20" color="#C6171E" />
|
|
|
|
|
- <text class="label">手机号</text>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Form -->
|
|
|
|
|
+ <view class="form">
|
|
|
|
|
+ <view class="field">
|
|
|
|
|
+ <view class="field-header">
|
|
|
|
|
+ <AppIcon name="smartphone" size="18" color="#999999" />
|
|
|
|
|
+ <text class="field-label">手机号</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <input
|
|
|
|
|
+ v-model="loginForm.mobilePhone"
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ placeholder="请输入手机号"
|
|
|
|
|
+ maxlength="11"
|
|
|
|
|
+ class="field-input"
|
|
|
|
|
+ />
|
|
|
</view>
|
|
</view>
|
|
|
- <input
|
|
|
|
|
- v-model="loginForm.mobilePhone"
|
|
|
|
|
- type="number"
|
|
|
|
|
- placeholder="请输入手机号"
|
|
|
|
|
- maxlength="11"
|
|
|
|
|
- class="input-field"
|
|
|
|
|
- />
|
|
|
|
|
- </view>
|
|
|
|
|
-
|
|
|
|
|
- <view class="input-group">
|
|
|
|
|
- <view class="input-label">
|
|
|
|
|
- <AppIcon name="lock" size="20" color="#C6171E" />
|
|
|
|
|
- <text class="label">密码</text>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <view class="field">
|
|
|
|
|
+ <view class="field-header">
|
|
|
|
|
+ <AppIcon name="lock" size="18" color="#999999" />
|
|
|
|
|
+ <text class="field-label">密码</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <input
|
|
|
|
|
+ v-model="loginForm.password"
|
|
|
|
|
+ type="password"
|
|
|
|
|
+ placeholder="请输入密码"
|
|
|
|
|
+ @confirm="handleLogin"
|
|
|
|
|
+ class="field-input"
|
|
|
|
|
+ />
|
|
|
</view>
|
|
</view>
|
|
|
- <input
|
|
|
|
|
- v-model="loginForm.password"
|
|
|
|
|
- type="password"
|
|
|
|
|
- placeholder="请输入密码"
|
|
|
|
|
- @confirm="handleLogin"
|
|
|
|
|
- class="input-field"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <button class="submit-btn" @click="handleLogin" :disabled="loading">
|
|
|
|
|
+ {{ loading ? '登录中...' : '登录' }}
|
|
|
|
|
+ </button>
|
|
|
</view>
|
|
</view>
|
|
|
-
|
|
|
|
|
- <button class="login-btn" @click="handleLogin" :disabled="loading">
|
|
|
|
|
- {{ loading ? '登录中...' : '登录' }}
|
|
|
|
|
- </button>
|
|
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
</template>
|
|
</template>
|
|
@@ -54,62 +59,52 @@ const loginForm = ref({
|
|
|
password: ''
|
|
password: ''
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
-// RSA加密函数(与admin-web保持一致)
|
|
|
|
|
const encryptData = (str) => {
|
|
const encryptData = (str) => {
|
|
|
let encryptor = new JSEncrypt()
|
|
let encryptor = new JSEncrypt()
|
|
|
- // 设置公钥(从后端配置文件获取,与admin-web相同)
|
|
|
|
|
let publicKey = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNc4Zrvk3E0mUkO8NOeNYOOaPI4uLoBAuDt9Rp0urX7y0wq7vvQzytvwzXXeM9Xp89j7g4ZLR7qBLBCj3QNPH0SUjE1yy9KVBKdjkPre7WT+plS74s2rJz/hygKiJ3Vxa+Z15v6JEHy/3/+i9gW3p/bCLaMQtvGemNvDXwCTwINQIDAQAB'
|
|
let publicKey = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNc4Zrvk3E0mUkO8NOeNYOOaPI4uLoBAuDt9Rp0urX7y0wq7vvQzytvwzXXeM9Xp89j7g4ZLR7qBLBCj3QNPH0SUjE1yy9KVBKdjkPre7WT+plS74s2rJz/hygKiJ3Vxa+Z15v6JEHy/3/+i9gW3p/bCLaMQtvGemNvDXwCTwINQIDAQAB'
|
|
|
encryptor.setPublicKey(publicKey)
|
|
encryptor.setPublicKey(publicKey)
|
|
|
- // 加密数据
|
|
|
|
|
return encryptor.encrypt(str)
|
|
return encryptor.encrypt(str)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 加载字典数据
|
|
|
|
|
const loadDictionaries = async () => {
|
|
const loadDictionaries = async () => {
|
|
|
await loadDicts()
|
|
await loadDicts()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const handleLogin = async () => {
|
|
const handleLogin = async () => {
|
|
|
- // 表单验证
|
|
|
|
|
const phone = loginForm.value.mobilePhone.trim()
|
|
const phone = loginForm.value.mobilePhone.trim()
|
|
|
if (!phone) {
|
|
if (!phone) {
|
|
|
showToast('请输入手机号')
|
|
showToast('请输入手机号')
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- // 手机号格式验证
|
|
|
|
|
|
|
+
|
|
|
if (!/^1[3-9]\d{9}$/.test(phone)) {
|
|
if (!/^1[3-9]\d{9}$/.test(phone)) {
|
|
|
showToast('请输入正确的手机号')
|
|
showToast('请输入正确的手机号')
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if (!loginForm.value.password) {
|
|
if (!loginForm.value.password) {
|
|
|
showToast('请输入密码')
|
|
showToast('请输入密码')
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
loading.value = true
|
|
loading.value = true
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
- // RSA加密密码
|
|
|
|
|
const encryptedPassword = encryptData(loginForm.value.password)
|
|
const encryptedPassword = encryptData(loginForm.value.password)
|
|
|
const res = await login(phone, encryptedPassword)
|
|
const res = await login(phone, encryptedPassword)
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if (res && res.code === 200) {
|
|
if (res && res.code === 200) {
|
|
|
- // 保存token和用户信息
|
|
|
|
|
storage.set('token', res.data.accessToken)
|
|
storage.set('token', res.data.accessToken)
|
|
|
storage.set('userInfo', {
|
|
storage.set('userInfo', {
|
|
|
id: res.data.id,
|
|
id: res.data.id,
|
|
|
name: res.data.username || phone,
|
|
name: res.data.username || phone,
|
|
|
mobilePhone: phone
|
|
mobilePhone: phone
|
|
|
})
|
|
})
|
|
|
-
|
|
|
|
|
- // 登录成功后加载字典数据(与admin-web保持一致)
|
|
|
|
|
|
|
+
|
|
|
await loadDictionaries()
|
|
await loadDictionaries()
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
showToast('登录成功', 'success')
|
|
showToast('登录成功', 'success')
|
|
|
-
|
|
|
|
|
- // 跳转到首页
|
|
|
|
|
|
|
+
|
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
|
uni.switchTab({
|
|
uni.switchTab({
|
|
|
url: '/pages/index/index'
|
|
url: '/pages/index/index'
|
|
@@ -128,114 +123,120 @@ const handleLogin = async () => {
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
|
-/* 登录容器 */
|
|
|
|
|
-.login-container {
|
|
|
|
|
- background: #C6171E;
|
|
|
|
|
|
|
+.login-page {
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ background: #F5F7FA;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
- min-height: 100vh;
|
|
|
|
|
- padding: 60rpx 30rpx;
|
|
|
|
|
|
|
+ padding: 60rpx 40rpx;
|
|
|
box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* 登录表单卡片 */
|
|
|
|
|
-.login-form {
|
|
|
|
|
- background-color: #FFFFFF;
|
|
|
|
|
- border-radius: 32rpx;
|
|
|
|
|
- padding: 80rpx 60rpx;
|
|
|
|
|
|
|
+.login-content {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
- max-width: 650rpx;
|
|
|
|
|
- box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.15);
|
|
|
|
|
|
|
+ max-width: 600rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* Logo区域 */
|
|
|
|
|
-.logo-section {
|
|
|
|
|
- text-align: center;
|
|
|
|
|
|
|
+/* Brand */
|
|
|
|
|
+.brand {
|
|
|
margin-bottom: 80rpx;
|
|
margin-bottom: 80rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.logo-text {
|
|
|
|
|
- font-size: 56rpx;
|
|
|
|
|
- font-weight: 700;
|
|
|
|
|
- color: #C6171E;
|
|
|
|
|
- margin-bottom: 16rpx;
|
|
|
|
|
|
|
+.brand-mark {
|
|
|
|
|
+ width: 8rpx;
|
|
|
|
|
+ height: 40rpx;
|
|
|
|
|
+ background: #C6171E;
|
|
|
|
|
+ border-radius: 4rpx;
|
|
|
|
|
+ margin-bottom: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.brand-name {
|
|
|
display: block;
|
|
display: block;
|
|
|
- letter-spacing: 4rpx;
|
|
|
|
|
|
|
+ font-size: 48rpx;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #1A1A1A;
|
|
|
|
|
+ letter-spacing: 2rpx;
|
|
|
|
|
+ line-height: 1.2;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.logo-subtitle {
|
|
|
|
|
- font-size: 28rpx;
|
|
|
|
|
- color: #999999;
|
|
|
|
|
|
|
+.brand-sub {
|
|
|
display: block;
|
|
display: block;
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+ color: #999999;
|
|
|
|
|
+ margin-top: 12rpx;
|
|
|
font-weight: 400;
|
|
font-weight: 400;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* 输入组 */
|
|
|
|
|
-.input-group {
|
|
|
|
|
- margin-bottom: 40rpx;
|
|
|
|
|
|
|
+/* Form */
|
|
|
|
|
+.form {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 32rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.input-label {
|
|
|
|
|
|
|
+.field {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- align-items: center;
|
|
|
|
|
- margin-bottom: 20rpx;
|
|
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 16rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.input-label .app-icon {
|
|
|
|
|
- margin-right: 12rpx;
|
|
|
|
|
|
|
+.field-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10rpx;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.label {
|
|
|
|
|
- color: #1A1A1A;
|
|
|
|
|
- font-size: 28rpx;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
|
|
+.field-label {
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ color: #666666;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* 输入框 */
|
|
|
|
|
-.input-field {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- height: 90rpx;
|
|
|
|
|
- padding: 0 30rpx;
|
|
|
|
|
- border: 2rpx solid #E8E8E8;
|
|
|
|
|
- border-radius: 16rpx;
|
|
|
|
|
|
|
+.field-input {
|
|
|
|
|
+ height: 96rpx;
|
|
|
|
|
+ padding: 0 28rpx;
|
|
|
|
|
+ background: #F0F0F0;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ border-radius: 12rpx;
|
|
|
font-size: 28rpx;
|
|
font-size: 28rpx;
|
|
|
color: #1A1A1A;
|
|
color: #1A1A1A;
|
|
|
box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
|
- background-color: #F5F7FA;
|
|
|
|
|
- transition: all 0.3s;
|
|
|
|
|
|
|
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.input-field:focus {
|
|
|
|
|
- border-color: #C6171E;
|
|
|
|
|
- background-color: #FFFFFF;
|
|
|
|
|
|
|
+.field-input:focus {
|
|
|
|
|
+ background: #FFFFFF;
|
|
|
|
|
+ box-shadow: 0 0 0 3px rgba(198, 23, 30, 0.2);
|
|
|
outline: none;
|
|
outline: none;
|
|
|
- box-shadow: 0 0 0 4rpx rgba(198, 23, 30, 0.1);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* 登录按钮 */
|
|
|
|
|
-.login-btn {
|
|
|
|
|
|
|
+.field-input::placeholder {
|
|
|
|
|
+ color: #B0B0B0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* Button */
|
|
|
|
|
+.submit-btn {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
- height: 90rpx;
|
|
|
|
|
|
|
+ height: 96rpx;
|
|
|
background: #C6171E;
|
|
background: #C6171E;
|
|
|
color: #FFFFFF;
|
|
color: #FFFFFF;
|
|
|
border: none;
|
|
border: none;
|
|
|
- border-radius: 16rpx;
|
|
|
|
|
- font-size: 32rpx;
|
|
|
|
|
|
|
+ border-radius: 48rpx;
|
|
|
|
|
+ font-size: 30rpx;
|
|
|
font-weight: 600;
|
|
font-weight: 600;
|
|
|
- margin-top: 60rpx;
|
|
|
|
|
- box-shadow: 0 8rpx 24rpx rgba(198, 23, 30, 0.4);
|
|
|
|
|
- transition: all 0.3s;
|
|
|
|
|
|
|
+ margin-top: 16rpx;
|
|
|
|
|
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.login-btn:active {
|
|
|
|
|
- transform: translateY(2rpx);
|
|
|
|
|
- box-shadow: 0 6rpx 20rpx rgba(198, 23, 30, 0.25);
|
|
|
|
|
|
|
+.submit-btn:active {
|
|
|
|
|
+ background: #A81212;
|
|
|
|
|
+ transform: scale(0.97);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.login-btn:disabled {
|
|
|
|
|
|
|
+.submit-btn:disabled {
|
|
|
background: #CCCCCC;
|
|
background: #CCCCCC;
|
|
|
- box-shadow: none;
|
|
|
|
|
opacity: 0.6;
|
|
opacity: 0.6;
|
|
|
|
|
+ transform: none;
|
|
|
}
|
|
}
|
|
|
-</style>
|
|
|
|
|
|
|
+</style>
|