register.vue 6.66 KB
<template>
  <view class="page">
    <view
      class="header"
      :style="{ paddingTop: statusBarHeight + 'px' }"
    >
      <image
        class="header-bg"
        :src="headerBg"
        mode="aspectFill"
      />
      <view class="header-mask" />
      <view class="header-inner">
        <text class="greet">创建账号</text>
        <text class="welcome">手机号注册</text>
      </view>
    </view>

    <view class="sheet">
      <view v-if="loading" class="loading">
        <text>提交中…</text>
      </view>

      <template v-else>
        <view class="field">
          <text class="label">手机号</text>
          <input
            v-model="phone"
            class="input"
            type="number"
            maxlength="11"
            placeholder="请输入11位手机号"
            placeholder-class="ph"
          />
          <view class="line" />
        </view>

        <view class="field">
          <text class="label">密码</text>
          <input
            v-model="password"
            class="input"
            password
            placeholder="至少6位"
            placeholder-class="ph"
          />
          <view class="line" />
        </view>

        <view class="field">
          <text class="label">确认密码</text>
          <input
            v-model="password2"
            class="input"
            password
            placeholder="再次输入密码"
            placeholder-class="ph"
          />
          <view class="line" />
        </view>

        <button class="btn btn-main" @click="onSubmit">注册</button>
        <text class="to-login" @click="goLogin">已有账号?去登录</text>

        <text v-if="errMsg" class="err">{{ errMsg }}</text>
      </template>
    </view>
  </view>
</template>

<script setup lang="ts">
import { onShow } from "@dcloudio/uni-app";
import { ref } from "vue";
import { apiEnabled, appendLoopbackConnectHint } from "@/config/api";
import { registerWithPhone } from "@/utils/api/mpSession";
import { staticAsset } from "@/utils/staticAsset";

const statusBarHeight = ref(20);
const headerBg = staticAsset("login-header.png");
const loading = ref(false);
const errMsg = ref("");
const phone = ref("");
const password = ref("");
const password2 = ref("");

function goHome() {
  uni.switchTab({ url: "/pages/home/home" });
}

function goLogin() {
  uni.navigateBack({ fail: () => uni.redirectTo({ url: "/pages/login/login" }) });
}

onShow(() => {
  statusBarHeight.value = uni.getSystemInfoSync().statusBarHeight || 20;
  if (!apiEnabled()) {
    uni.showToast({ title: "当前为离线模式,无法注册", icon: "none" });
  }
});

function pickErr(e: unknown, fallback: string): string {
  if (e && typeof e === "object" && "msg" in e && typeof (e as { msg: string }).msg === "string") {
    return (e as { msg: string }).msg;
  }
  if (e && typeof e === "object" && "errMsg" in e && typeof (e as { errMsg: string }).errMsg === "string") {
    const m = (e as { errMsg: string }).errMsg.trim();
    if (m) return m;
  }
  if (e instanceof Error && e.message.trim()) {
    return e.message;
  }
  return fallback;
}

async function onSubmit() {
  errMsg.value = "";
  if (!apiEnabled()) {
    uni.showToast({ title: "API 未配置", icon: "none" });
    return;
  }
  const p = phone.value.trim();
  if (!/^1\d{10}$/.test(p)) {
    errMsg.value = "请输入11位手机号";
    uni.showToast({ title: errMsg.value, icon: "none" });
    return;
  }
  if (password.value.length < 6) {
    errMsg.value = "密码至少6位";
    uni.showToast({ title: errMsg.value, icon: "none" });
    return;
  }
  if (password.value !== password2.value) {
    errMsg.value = "两次密码不一致";
    uni.showToast({ title: errMsg.value, icon: "none" });
    return;
  }

  loading.value = true;
  try {
    await registerWithPhone(p, password.value, password2.value);
    uni.showToast({ title: "注册成功", icon: "success" });
    setTimeout(() => goHome(), 400);
  } catch (e: unknown) {
    const msg = appendLoopbackConnectHint(pickErr(e, "注册失败"));
    errMsg.value = msg;
    uni.showToast({
      title: msg.length > 28 ? "网络失败:请改用电脑局域网 IP(见底部红字)" : msg,
      icon: "none",
      duration: 3200,
    });
  } finally {
    loading.value = false;
  }
}
</script>

<style scoped>
.page {
  display: flex;
  flex-direction: column;
  height: 100vh;
  box-sizing: border-box;
  overflow: hidden;
  background: #f1f5f9;
}

.header {
  position: relative;
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  overflow: hidden;
  min-height: 430rpx;
  padding-bottom: 56rpx;
  box-sizing: border-box;
}

.header-bg {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 100%;
}

.header-mask {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(0, 0, 0, 0.12) 0%,
    rgba(0, 0, 0, 0.35) 100%
  );
  pointer-events: none;
}

.header-inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex: 1;
  flex-direction: column;
  justify-content: center;
  padding: 0 48rpx;
  min-height: 0;
}

.greet {
  display: block;
  margin-bottom: 8rpx;
  font-size: 44rpx;
  font-weight: 600;
  color: #fff;
  letter-spacing: 0.02em;
  text-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.25);
}

.welcome {
  display: block;
  font-size: 44rpx;
  font-weight: 700;
  color: #fff;
  letter-spacing: 0.02em;
  text-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.25);
}

.sheet {
  flex: 1;
  min-height: 0;
  margin-top: 0;
  padding: 36rpx 48rpx calc(24rpx + env(safe-area-inset-bottom));
  border-radius: 24rpx 24rpx 0 0;
  background: #fff;
  box-shadow: 0 -8rpx 40rpx rgba(0, 0, 0, 0.06);
}

.field {
  margin-bottom: 8rpx;
}

.label {
  display: block;
  margin-bottom: 16rpx;
  font-size: 30rpx;
  font-weight: 600;
  color: #111827;
}

.input {
  display: block;
  width: 100%;
  height: 72rpx;
  padding: 0 4rpx;
  font-size: 30rpx;
  color: #111827;
  border: none;
  background: transparent;
}

.ph {
  color: #c4c4c4;
  font-size: 28rpx;
}

.line {
  height: 1rpx;
  margin-top: 8rpx;
  background: #eeeeee;
}

.loading {
  padding: 80rpx 0;
  font-size: 28rpx;
  color: #9ca3af;
  text-align: center;
}

.btn {
  display: flex;
  width: 100%;
  height: 88rpx;
  align-items: center;
  justify-content: center;
  margin-top: 28rpx;
  margin-bottom: 20rpx;
  padding: 0;
  font-size: 32rpx;
  font-weight: 500;
  border: none;
  border-radius: 999rpx;
}

.btn::after {
  display: none;
  border: none;
}

.btn-main {
  color: #fff;
  background: #2185ff;
}

.to-login {
  display: block;
  font-size: 26rpx;
  color: #2185ff;
  text-align: center;
}

.err {
  display: block;
  margin-top: 24rpx;
  font-size: 24rpx;
  line-height: 1.4;
  color: #ef4444;
  text-align: center;
}
</style>