mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	feat(ui): 登录新增验证码及记住密码功能
This commit is contained in:
		@@ -32,4 +32,11 @@ export const loginLog = (params?: Record<string, unknown>) => {
 | 
				
			|||||||
    method: "get",
 | 
					    method: "get",
 | 
				
			||||||
    params
 | 
					    params
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const captcha = () => {
 | 
				
			||||||
 | 
					  return http({
 | 
				
			||||||
 | 
					    url: "/api/admin/login/captcha",
 | 
				
			||||||
 | 
					    method: "get",
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,24 +1,82 @@
 | 
				
			|||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { reactive } from "vue";
 | 
					import { onMounted, reactive } from "vue";
 | 
				
			||||||
import { useRoute } from "vue-router";
 | 
					 | 
				
			||||||
import { useAuthStore } from "@/stores/auth";
 | 
					import { useAuthStore } from "@/stores/auth";
 | 
				
			||||||
 | 
					import { captcha } from "@/http/login";
 | 
				
			||||||
 | 
					import useState from "@/composables/useState";
 | 
				
			||||||
import useRequest from "@/composables/useRequest";
 | 
					import useRequest from "@/composables/useRequest";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const route = useRoute();
 | 
					// 表单
 | 
				
			||||||
const authStore = useAuthStore();
 | 
					function useFormData() {
 | 
				
			||||||
 | 
					  const formData = reactive({
 | 
				
			||||||
const [loginRequest, _, loading] = useRequest(authStore.login);
 | 
					    username: "",
 | 
				
			||||||
const formData = reactive({
 | 
					    password: "",
 | 
				
			||||||
  username: "",
 | 
					    captcha: "",
 | 
				
			||||||
  password: "",
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function handleSubmit({ errors, values }: any) {
 | 
					 | 
				
			||||||
  if (errors) return;
 | 
					 | 
				
			||||||
  await loginRequest({
 | 
					 | 
				
			||||||
    ...values,
 | 
					 | 
				
			||||||
    ...route.query,
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					  const rules = {
 | 
				
			||||||
 | 
					    username: [{ required: true, message: "请输入您的账号" }],
 | 
				
			||||||
 | 
					    password: [{ required: true, message: "请输入您的密码" }],
 | 
				
			||||||
 | 
					    captcha: [{ required: true, message: "请输入验证码" }],
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  const authStore = useAuthStore();
 | 
				
			||||||
 | 
					  const [loginRequest, _, submitting] = useRequest(authStore.login);
 | 
				
			||||||
 | 
					  return { formData, loginRequest, submitting, rules };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 验证码
 | 
				
			||||||
 | 
					function useCaptcha() {
 | 
				
			||||||
 | 
					  const captchaImage = reactive({
 | 
				
			||||||
 | 
					    pic_path: "",
 | 
				
			||||||
 | 
					    captcha_id: "",
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  const getCaptchaImage = async () => {
 | 
				
			||||||
 | 
					    const { data } = await captcha();
 | 
				
			||||||
 | 
					    Object.assign(captchaImage, data);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  onMounted(getCaptchaImage);
 | 
				
			||||||
 | 
					  return { captchaImage, getCaptchaImage };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 记住密码
 | 
				
			||||||
 | 
					function useRemeberPWD(formData) {
 | 
				
			||||||
 | 
					  const storageKey = "r-f";
 | 
				
			||||||
 | 
					  const [isRemember, setIsRemember] = useState(false);
 | 
				
			||||||
 | 
					  const onIsRememberChange = (v) => {
 | 
				
			||||||
 | 
					    if (v) {
 | 
				
			||||||
 | 
					      const value = {
 | 
				
			||||||
 | 
					        username: formData.username,
 | 
				
			||||||
 | 
					        password: formData.password,
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      localStorage.setItem(storageKey, JSON.stringify(value));
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      localStorage.removeItem(storageKey);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    setIsRemember(v);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  onMounted(() => {
 | 
				
			||||||
 | 
					    const getter = localStorage.getItem(storageKey);
 | 
				
			||||||
 | 
					    if (getter) {
 | 
				
			||||||
 | 
					      setIsRemember(true);
 | 
				
			||||||
 | 
					      Object.assign(formData, JSON.parse(getter));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					  return { isRemember, onIsRememberChange };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { formData, loginRequest, submitting, rules } = useFormData();
 | 
				
			||||||
 | 
					const { captchaImage, getCaptchaImage } = useCaptcha();
 | 
				
			||||||
 | 
					const { isRemember, onIsRememberChange } = useRemeberPWD(formData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 表单提交
 | 
				
			||||||
 | 
					async function handleSubmit({ errors }: any) {
 | 
				
			||||||
 | 
					  if (errors) return;
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    await loginRequest({
 | 
				
			||||||
 | 
					      ...formData,
 | 
				
			||||||
 | 
					      captcha_id: captchaImage.captcha_id,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  } catch (err) {
 | 
				
			||||||
 | 
					    getCaptchaImage();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
@@ -33,44 +91,59 @@ async function handleSubmit({ errors, values }: any) {
 | 
				
			|||||||
        <div class="form-box">
 | 
					        <div class="form-box">
 | 
				
			||||||
          <div class="title">ChatGPT Plus Admin</div>
 | 
					          <div class="title">ChatGPT Plus Admin</div>
 | 
				
			||||||
          <a-form
 | 
					          <a-form
 | 
				
			||||||
              ref="formRef"
 | 
					            ref="formRef"
 | 
				
			||||||
              :model="formData"
 | 
					            :model="formData"
 | 
				
			||||||
              class="form"
 | 
					            class="form"
 | 
				
			||||||
              size="medium"
 | 
					            size="medium"
 | 
				
			||||||
              auto-label-width
 | 
					            auto-label-width
 | 
				
			||||||
              @submit="handleSubmit"
 | 
					            :label-col-props="{ span: 0 }"
 | 
				
			||||||
 | 
					            :wrapper-col-props="{ span: 24 }"
 | 
				
			||||||
 | 
					            :rules="rules"
 | 
				
			||||||
 | 
					            @submit="handleSubmit"
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <a-space direction="vertical" style="width: 100%">
 | 
					            <a-space direction="vertical" style="width: 100%">
 | 
				
			||||||
              <a-form-item
 | 
					              <a-form-item field="username" label="账号">
 | 
				
			||||||
                  field="username"
 | 
					 | 
				
			||||||
                  label="账号"
 | 
					 | 
				
			||||||
                  hide-label
 | 
					 | 
				
			||||||
                  hide-asterisk
 | 
					 | 
				
			||||||
                  :rules="[{ required: true, message: '请输入您的账号' }]"
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                <a-input
 | 
					                <a-input
 | 
				
			||||||
                    v-model="formData.username"
 | 
					                  v-model="formData.username"
 | 
				
			||||||
                    placeholder="请输入您的账号"
 | 
					                  placeholder="请输入您的账号"
 | 
				
			||||||
                    class="input"
 | 
					                  class="input"
 | 
				
			||||||
                ></a-input>
 | 
					                ></a-input>
 | 
				
			||||||
              </a-form-item>
 | 
					              </a-form-item>
 | 
				
			||||||
              <a-form-item
 | 
					              <a-form-item field="password" label="密码">
 | 
				
			||||||
                  field="password"
 | 
					 | 
				
			||||||
                  label="密码"
 | 
					 | 
				
			||||||
                  hide-label
 | 
					 | 
				
			||||||
                  hide-asterisk
 | 
					 | 
				
			||||||
                  :rules="[{ required: true, message: '请输入您的密码' }]"
 | 
					 | 
				
			||||||
              >
 | 
					 | 
				
			||||||
                <a-input-password
 | 
					                <a-input-password
 | 
				
			||||||
                    v-model="formData.password"
 | 
					                  v-model="formData.password"
 | 
				
			||||||
                    placeholder="请输入您的密码"
 | 
					                  placeholder="请输入您的密码"
 | 
				
			||||||
                    class="input"
 | 
					                  class="input"
 | 
				
			||||||
                >
 | 
					                >
 | 
				
			||||||
                </a-input-password>
 | 
					                </a-input-password>
 | 
				
			||||||
              </a-form-item>
 | 
					              </a-form-item>
 | 
				
			||||||
 | 
					              <a-form-item field="captcha" label="验证码">
 | 
				
			||||||
 | 
					                <a-input v-model="formData.captcha" placeholder="请输入验证码" class="input">
 | 
				
			||||||
 | 
					                  <template #append>
 | 
				
			||||||
 | 
					                    <img
 | 
				
			||||||
 | 
					                      class="captcha-image"
 | 
				
			||||||
 | 
					                      :src="captchaImage.pic_path"
 | 
				
			||||||
 | 
					                      alt="验证码"
 | 
				
			||||||
 | 
					                      title="点击刷新验证码"
 | 
				
			||||||
 | 
					                      @click="getCaptchaImage()"
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                  </template>
 | 
				
			||||||
 | 
					                </a-input>
 | 
				
			||||||
 | 
					              </a-form-item>
 | 
				
			||||||
 | 
					              <a-form-item hide-label>
 | 
				
			||||||
 | 
					                <a-checkbox :model-value="isRemember" @change="onIsRememberChange"
 | 
				
			||||||
 | 
					                  >记住密码</a-checkbox
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					              </a-form-item>
 | 
				
			||||||
            </a-space>
 | 
					            </a-space>
 | 
				
			||||||
            <a-form-item hide-label>
 | 
					            <a-form-item hide-label>
 | 
				
			||||||
              <a-button :disabled="loading" html-type="submit" long type="primary" class="sign-in-btn">
 | 
					              <a-button
 | 
				
			||||||
 | 
					                :loading="submitting"
 | 
				
			||||||
 | 
					                html-type="submit"
 | 
				
			||||||
 | 
					                long
 | 
				
			||||||
 | 
					                type="primary"
 | 
				
			||||||
 | 
					                class="sign-in-btn"
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
                登录
 | 
					                登录
 | 
				
			||||||
              </a-button>
 | 
					              </a-button>
 | 
				
			||||||
            </a-form-item>
 | 
					            </a-form-item>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { computed } from "vue";
 | 
					import { computed, ref } from "vue";
 | 
				
			||||||
import type { UploadInstance, FileItem } from "@arco-design/web-vue";
 | 
					import type { UploadInstance, FileItem } from "@arco-design/web-vue";
 | 
				
			||||||
import { uploadUrl } from "@/http/config";
 | 
					import { uploadUrl } from "@/http/config";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -10,6 +10,8 @@ defineProps({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const emits = defineEmits(["update:modelValue"]);
 | 
					const emits = defineEmits(["update:modelValue"]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const uploadRef = ref();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const uploadProps = computed<UploadInstance["$props"]>(() => ({
 | 
					const uploadProps = computed<UploadInstance["$props"]>(() => ({
 | 
				
			||||||
  action: uploadUrl,
 | 
					  action: uploadUrl,
 | 
				
			||||||
  name: "file",
 | 
					  name: "file",
 | 
				
			||||||
@@ -19,19 +21,23 @@ const uploadProps = computed<UploadInstance["$props"]>(() => ({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const handleChange = (_, file: FileItem) => {
 | 
					const handleChange = (_, file: FileItem) => {
 | 
				
			||||||
  console.log(file.response);
 | 
					  console.log(file.response);
 | 
				
			||||||
 | 
					  console.log(file);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
  <a-space>
 | 
					  <a-input-group style="width: 100%">
 | 
				
			||||||
    <a-input-group>
 | 
					    <a-input
 | 
				
			||||||
      <a-input :model-value="modelValue" :placeholder="placeholder" readonly />
 | 
					      :model-value="modelValue"
 | 
				
			||||||
      <a-upload v-bind="uploadProps" @change="handleChange">
 | 
					      :placeholder="placeholder"
 | 
				
			||||||
        <template #upload-button>
 | 
					      readonly
 | 
				
			||||||
          <a-button type="primary">
 | 
					      @dblclick="uploadRef?.$el?.click()"
 | 
				
			||||||
            <icon-cloud />
 | 
					    />
 | 
				
			||||||
          </a-button>
 | 
					    <a-upload ref="uploadRef" v-bind="uploadProps" @change="handleChange">
 | 
				
			||||||
        </template>
 | 
					      <template #upload-button>
 | 
				
			||||||
      </a-upload>
 | 
					        <a-button type="primary" style="width: 100px">
 | 
				
			||||||
    </a-input-group>
 | 
					          <icon-cloud />
 | 
				
			||||||
  </a-space>
 | 
					        </a-button>
 | 
				
			||||||
 | 
					      </template>
 | 
				
			||||||
 | 
					    </a-upload>
 | 
				
			||||||
 | 
					  </a-input-group>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user