index.vue 7.31 KB
<template>
  <view class="login-page">
    <!-- 顶部标题区 - 加高 + 左下大圆角 + 底部蓝色过滤阴影 + 从浅到深 蓝色渐变背景 -->
    <view class="top-title">
      <text class="welcome-text">你好,欢迎光临</text>
      <text class="platform-name">全域智能运营管理平台</text>
    </view>

    <!-- 登录表单区域 - 往上偏移 盖住top-title一部分 -->
    <view class="login-form">
      <!-- 登录标题 -->
      <view class="login-title">账户登录</view>

      <!-- ✅ 核心:和工单页同款 up-form 表单校验容器 -->
      <up-form
          label-position="left"
          :model="form"
          ref="loginFormRef"
          labelWidth="0"
      >
        <!-- 账号输入框 + 校验 -->
        <up-form-item
            prop="account"
        >
          <up-input
              v-model="form.account"
              border="surround"
              clearable
              maxlength="30"
              input-align="left"
              fontSize="16px"
              :disabled="isLoading"
              shape="circle"
              placeholder="请输入账户"
              selectionStart="15"
              selectionEnd="15"
              @blur="() => loginFormRef.validateField('account')"
          />
        </up-form-item>

        <!-- 密码输入框 + 校验 -->
        <up-form-item
            prop="password"
        >
          <up-input
              v-model="form.password"
              placeholder="请输入密码"
              maxlength="20"
              border="surround"
              clearable
              input-align="left"
              type="password"
              :disabled="isLoading"
              shape="circle"
              selectionStart="5"
              selectionEnd="5"
              @blur="() => loginFormRef.validateField('password')"
          />
        </up-form-item>
      </up-form>

      <!-- 登录按钮 -->
      <up-button
          class="login-btn"
          type="primary"
          size="large"
          :loading="isLoading"
          @click="handleLogin"
          shape="circle"
      >
        登录
      </up-button>
    </view>

    <!-- 版权信息 -->
    <view class="copyright">蓟城山水集团版权所有</view>
  </view>
</template>

<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue';
import { useUserStore } from '@/pinia/user';
import globalConfig from '@/common/config/global';

// ========== 【全局实例 & 基础状态】 和工单页写法一致 ==========
const userStore = useUserStore();
const loginFormRef = ref(null); // 表单ref 用于校验
const isLoading = ref(false);   // 登录加载状态

// ========== 【表单数据】 和工单页一致的 reactive 声明 ==========
const form = reactive({
  account: '',  // 账号
  password: ''  // 密码
});

// ========== 【表单校验规则】 ✅核心 和工单页1:1同款校验规则写法 ==========
const loginFormRules = reactive({
  // 账号校验:必填 + 长度限制 2-30位(合理账号长度,可自行调整)
  account: [
    { type: 'string', required: true, message: '请输入登录账号', trigger: ['change', 'blur'] },
    { type: 'string', min: 2, max: 30, message: '账号长度为2-30个字符', trigger: ['change', 'blur'] }
  ],
  // 密码校验:必填 + 长度限制 6-20位(行业通用密码长度,可自行调整)
  password: [
    { type: 'string', required: true, message: '请输入登录密码', trigger: ['change', 'blur'] },
    { type: 'string', min: 6, max: 20, message: '密码长度为6-20个字符', trigger: ['change', 'blur'] }
  ]
});

// ========== 【生命周期】 nextTick 设置校验规则 和工单页一致 ==========
onMounted(() => {
  // 检查登录态
  checkLoginStatus();
  // nextTick确保表单挂载完成再赋值规则,修复校验不生效bug,和工单页写法一致
  nextTick(() => {
    loginFormRef.value?.setRules(loginFormRules);
  });
});

// ========== 【检查登录状态】 原有逻辑不变 ==========
const checkLoginStatus = () => {
  try {
    // 已登录则直接跳首页
    if (userStore.isLogin) {
      uni.switchTab({
        url: '/pages/workbench/index',
        fail: () => {
          uni.reLaunch({ url: '/pages/workbench/index' });
        }
      });
      return;
    }
  } catch (err) {
    console.warn('检查登录状态失败:', err);
  }
};

// ========== 【核心登录方法】 ✅ 完整表单校验 + 登录逻辑,和工单页submit提交逻辑一致 ==========
const handleLogin = async () => {
  try {
    // ✅ 第一步:先执行表单整体校验,通过后再执行登录逻辑(和工单页submitWorkOrder写法一致)
    await loginFormRef.value.validate();

    isLoading.value = true;
    // 执行登录请求
    await userStore.login({
      username: form.account,
      password: form.password
    });

    uni.showToast({ title: '登录成功', icon: 'success', duration: 1000 });

    // 登录成功后跳转首页
    setTimeout(() => {
      uni.switchTab({
        url: globalConfig.router.tabBarList[1].path,
        fail: (err) => {
          console.warn('tabBar跳转失败,切换为普通跳转:', err);
          uni.reLaunch({ url: '/pages/workbench/index' });
        }
      });
    }, 1000);
  } catch (err) {
    // ✅ 校验失败/登录失败 统一捕获提示,和工单页异常处理一致
    if (!Array.isArray(err)) {
      console.error('登录失败:', err);
      uni.showToast({
        title: err.message || '账号或密码错误,请重试',
        icon: 'none',
        duration: 2000
      });
    }
  } finally {
    isLoading.value = false;
  }
};
</script>

<style scoped lang="scss">
// 核心布局
.login-page {
  min-height: 100vh;
  padding: 0;
  box-sizing: border-box;
  overflow: hidden;
  position: relative;
  background: #f5f7fa;
}

// 顶部:从左到右 由浅到深 线性渐变 过渡自然柔和 无突兀色块
.top-title {
  background: linear-gradient(120deg, #4299e1 0%, #2970e8 50%, #2563eb 100%);
  color: #fff;
  padding: 240rpx 30rpx 80rpx;
  text-align: left;
  border-bottom-left-radius: 120rpx;
  position: relative;
  z-index: 1;
  box-shadow: 0 25rpx 40rpx -15rpx rgba(37, 99, 235, 0.3);

  .welcome-text {
    font-size: 46rpx;
    display: block;
    margin-bottom: 10px;
    font-weight: 500;
  }

  .platform-name {
    margin-bottom: 10px;
    font-size: 46rpx;
    display: block;
    opacity: 0.95;
  }
}

.login-form {
  background-color: #fff;
  padding: 40rpx 30rpx;
  border-radius: 16rpx;
  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
  margin: -60rpx 40rpx 0;
  position: relative;
  z-index: 10;
  box-sizing: border-box;

  .login-title {
    font-size: 38rpx;
    font-weight: 600;
    color: #666;
    margin-bottom: 30rpx;
    letter-spacing: 1rpx;
  }
}

// 适配表单校验的间距
:deep(.u-form-item) {
  margin-bottom: 20rpx;
  position: relative;
}

// 登录按钮:和顶部完全同款 从左到右 由浅到深 线性渐变 + 保留所有原尺寸
.login-btn {
  margin-top: 40rpx;
  width: 100%;
  height: 88rpx;
  line-height: 88rpx;
  border-radius: 8rpx;
  font-size: 32rpx;
  background: linear-gradient(120deg, #4299e1 0%, #2970e8 50%, #2563eb 100%) !important;
  border: none !important;
  &::after { border: none !important; }
}

.copyright {
  width: 100%;
  text-align: center;
  font-size: 24rpx;
  color: #999;
  position: fixed;
  bottom: 100px;
}
</style>