AccountBindPanel.vue 6.27 KB
<template>
  <div class="account-bind-panel">
    <div class="settings-content-header">
      <h1>{{ t("settings.accountBind.title") }}</h1>
      <p>{{ t("settings.accountBind.desc") }}</p>
    </div>

    <div class="bind-status-section">
      <div class="section-title">
        {{ t("settings.accountBind.bindStatus") }}
      </div>
      <div class="section-desc">
        {{ t("settings.accountBind.currentWechatStatus")
        }}<strong>{{ bindStatusText }}</strong>
        <template v-if="wechatNickname"
          >({{ t("settings.accountBind.nickname")
          }}{{ wechatNickname }})</template
        >
      </div>
      <el-button class="refresh-btn" @click="refreshBindStatus" plain>
        {{ t("settings.accountBind.syncStatus") }}
      </el-button>
    </div>

    <el-divider />

    <div class="wechat-bind-section" v-if="!wechatBound">
      <div class="section-title">
        {{ t("settings.accountBind.bindToWechat") }}
      </div>
      <div class="section-desc">
        {{ t("settings.accountBind.bindToWechatDesc") }}
      </div>
      <el-button class="save-btn" @click="goWechatBind">{{
        t("settings.accountBind.goWechatBind")
      }}</el-button>
    </div>

    <div class="section" v-if="!hasEmail">
      <div class="section-title">
        {{ t("settings.accountBind.bindExistingEmail") }}
      </div>
      <div class="form-group">
        <label class="form-label">{{ t("settings.accountBind.email") }}</label>
        <input
          class="input"
          type="email"
          v-model="emailToBind"
          :placeholder="t('settings.accountBind.emailPlaceholder')"
        />
      </div>
      <div class="form-group">
        <label class="form-label">{{
          t("settings.accountBind.password")
        }}</label>
        <input
          class="input"
          type="password"
          v-model="passwordToBind"
          :placeholder="t('settings.accountBind.passwordPlaceholder')"
        />
      </div>
      <el-button class="save-btn" :loading="binding" @click="mergeEmail">{{
        t("settings.accountBind.bindEmail")
      }}</el-button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted } from "vue";
import { useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import { ElMessage } from "element-plus";
import { useAuthStore } from "@/stores/auth";
import { getWechatBindStatus, mergeEmailToWechatUser } from "@/api/wechat";
import { getUserProfile } from "@/api/user";

const { t } = useI18n();
const router = useRouter();
const authStore = useAuthStore();

const wechatBound = ref(false);
const wechatNickname = ref("");
const binding = ref(false);
const hasEmail = ref(false);
const emailToBind = ref("");
const passwordToBind = ref("");

const bindStatusText = computed(() =>
  wechatBound.value
    ? t("settings.accountBind.bound")
    : t("settings.accountBind.unbound"),
);

const refreshBindStatus = async () => {
  try {
    const { data } = await getWechatBindStatus();
    wechatBound.value = !!data.bound;
    wechatNickname.value = data.wechatNickname || "";
  } catch (e) {
    // ignore
  }
};

const goWechatBind = () => {
  router.push({ path: "/wechat-login", query: { mode: "bind" } });
};

const mergeEmail = async () => {
  if (!emailToBind.value || !passwordToBind.value) {
    ElMessage.error(t("settings.accountBind.enterEmailAndPassword"));
    return;
  }
  binding.value = true;
  try {
    const { data } = await mergeEmailToWechatUser({
      email: emailToBind.value,
      password: passwordToBind.value,
    });
    if (data && data.userId) {
      ElMessage.success(t("settings.accountBind.bindSuccess"));
      await authStore.login({
        email: emailToBind.value,
        password: passwordToBind.value,
      });
      await authStore.fetchProfile();
      await refreshBindStatus();
    } else {
      ElMessage.success(t("settings.accountBind.requestSubmitted"));
      await refreshBindStatus();
    }
  } catch (e: any) {
    const msg =
      e?.response?.data?.message ||
      e?.message ||
      t("settings.accountBind.bindFailed");
    ElMessage.error(msg);
  } finally {
    binding.value = false;
  }
};

onMounted(() => {
  refreshBindStatus();
  getUserProfile().then((res) => {
    if (res.email) {
      hasEmail.value = true;
    }
  });
});
</script>

<style scoped lang="scss">
.account-bind-panel {
  color: var(--color-text);
  background: var(--color-bg);
  width: 100%;
  margin-left: 0;
  margin-right: auto;
  padding: 0 0 40px 0;
}

.settings-content-header {
  margin-bottom: 20px;
  text-align: left;

  h1 {
    font-size: 28px;
    font-weight: 800;
    color: var(--color-text);
    margin-bottom: 8px;
  }

  p {
    font-size: 14px;
    color: var(--color-secondary);
  }
}

.bind-status-section,
.wechat-bind-section {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}

.section {
  width: 100%;
  margin-bottom: 36px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 22px;
}

.section-title {
  font-size: 18px;
  font-weight: 700;
  color: var(--color-text);
  margin-bottom: 8px;
}

.section-desc {
  font-size: 14px;
  color: var(--color-secondary);
  margin-bottom: 12px;
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
}

.form-label {
  font-size: 14px;
  font-weight: 600;
  color: var(--color-secondary);
}

.input {
  background: var(--color-card);
  color: var(--color-text-secondary);
  border: 1.5px solid var(--color-border);
  border-radius: 8px;
  padding: 12px 16px;
  font-size: 15px;
  outline: none;
  width: 100%;
  box-sizing: border-box;
  transition: border-color 0.2s;

  &:focus {
    border-color: var(--color-primary);
    box-shadow: 0 0 0 2px rgba(51, 153, 255, 0.1);
  }

  &::placeholder {
    color: var(--color-placeholder);
  }
}

.save-btn {
  background: var(--color-primary);
  color: var(--color-bg);
  border: none;
  padding: 10px 20px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 14px;
  font-weight: 600;
  transition: all 0.2s;

  &:hover {
    background: var(--color-btn-hover);
  }
}

.divider {
  width: calc(100% + 48px);
  height: 1px;
  background: var(--color-border);
  margin: 24px 0 32px -48px;
  opacity: 0.7;
}

@media (max-width: 1024px) {
  .account-bind-panel {
    max-width: 100%;
    padding: 0 4px 20px 4px;
  }
}
</style>