Vue3中使用Supabase Auth方法详解

目录
  • 引言
  • 安装Supabase
  • 设置Supabase
  • 创建一个AuthUser组合
  • 创建页面
    • 注册.vue
    • EmailConfirmation.vue
    • 登录.vu
    • ForgotPassword.vue
  • Me.vue
    • login()
    • loginWithSocialProvider()
    • logout()
    • isLoggedIn()
    • register()
    • update()
    • sendPasswordResetEmail()
  • 观察Auth状态的变化
  • 测试东西
    • 注销
    • 家庭作业
  • 总结

引言

Supabase是一个自称的 "开源Firebase替代品"。我对与Supbase合作已经有一段时间了,我想我将尝试使用他们的认证API来为Vue.js 3应用程序进行认证设置。

首先,你为什么要使用Supabase Auth?最重要的是,如果你使用Supabase作为你的数据存储,(它有一些非常甜蜜的好处),Supabase Auth是你可以管理对这些数据的访问的唯一方法。其次,虽然Supabase Auth也有许多不同的功能。

  • 没有中间件的用户权限(通过Postgres的行级安全)。
  • 神奇的电子邮件链接
  • 社交提供者的登录
  • 以及所有其他你期望的封装在一个简单的JavaScript SDK中的Auth功能

综上所述,值得一提的是,Supabase Auth不是一个独立的认证提供商,它只是为了与其他Supabase服务(数据、存储等)一起使用。

好了,让我们开始在我们的Vue.js 3应用程序中实施Supabase Auth。

安装Supabase

我假设你已经有了一个用Vue Router设置的Vue.js 3应用程序,如果没有,你可以下载这个模板代码来进行操作。我还假设你已经有了一个Supabase账户和项目设置。如果没有,你可以前往supabase.io,它将引导你完成第一步。

然后,为了与Supabase auth以及它的任何其他服务合作,我们需要安装Supabase JavaScript SDK。

npm install @supabase/supabase-js

设置Supabase

安装完Supabase后,我们需要经过几个步骤来设置它。我将创建一个名为UseSupabase.js 的组合,用来组织这个设置。

// UseSupabase.js
import { createClient } from "@supabase/supabase-js";
// these can come from an environment variable if desired
// not required however as they are 100% exposed on the client side anyway
// and that's ok, Supabase expects this (security is provided by Row Level Security)
const supabaseUrl = "";
const supabaseKey = "";
// setup client
const supabase = createClient(supabaseUrl, supabaseKey);
// expose supabase client
export default function useSupabase() {
  return { supabase };
}

supabaseUrl和supabaseKey可以在Supabase仪表板上的Settings > API

当你在Supabase界面时,你也可以到Autentication > Settings ,设置你的网站网址,以便Supabase知道如何正确地重定向确认邮件等。它的默认值是localhost:3000,但你可以根据需要进行修改。

创建一个AuthUser组合

设置好Supabase SDK后,我们现在可以开始使用它了。首先,我将创建一个AuthUser组合,以抽象出与AuthUser的一些交互,并存留所有我们需要填写的方法。这在我们将来想脱离Supabase时很有帮助,并帮助我们把所有的AuthUser功能集中到一个地方。

import { ref } from "vue";
// user is set outside of the useAuthUser function
// so that it will act as global state and always refer to a single user
const user = ref(null);
export default function useAuthUser() {
  /**
   * Login with email and password
   */
  const login = async ({ email, password }) => {};
  /**
   * Login with google, github, etc
   */
  const loginWithSocialProvider = (provider) => {};
  /**
   * Logout
   */
  const logout = async () => {};
  /**
   * Check if the user is logged in or not
   */
  const isLoggedIn = () => {};
  /**
   * Register
   */
  const register = async ({ email, password, ...meta }) => {};
  /**
   * Update user email, password, or meta data
   */
  const update = async (data) => {};
  /**
   * Send user an email to reset their password
   * (ie. support "Forgot Password?")
   */
  const sendPasswordRestEmail = async (email) => {};
  return {
    user,
    login,
    loginWithSocialProvider,
    isLoggedIn,
    logout,
    register,
    update,
    sendPasswordRestEmail,
    maybeHandleEmailConfirmation,
  };
}

创建页面

接下来,让我们来创建典型用户认证流程所需的页面。我使用Tailwind CSS做了一些样式设计,但可以根据你的需要自由调整这些样式。

注册.vue

我们需要的第一个页面是一个注册页面。它需要包含必要的字段,以便在Supabase中创建一个用户。这些字段是电子邮件和密码。我们也可以在Supabase中为我们的用户添加任意的元数据,这意味着我们也可以让用户的真实姓名成为注册的一部分。

// Register.vue
<script>...</script>
<template>
  <form class="max-w-lg m-auto" @submit.prevent="handleSubmit">
    <h1 class="text-3xl mb-5">Register</h1>
    <label>Name <input v-model="form.name" type="text" /></label>
    <label>Email <input v-model="form.email" type="email" /></label>
    <label>Password <input v-model="form.password" type="password" /></label>
    <button>Register</button>
  </form>
</template>

在脚本部分,我们可以使用一个反应式引用来跟上表单的数据,同时提供一个函数来处理表单的提交。在表单提交时,我们将从AuthUser组合中调用注册函数(它仍然是空的,但我们将在稍后用supabase的具体调用来填充它),然后重定向到一个页面,指示用户检查他们的电子邮件以确认注册。

// Register.vue
<script setup>
import { ref } from "vue";
import useAuthUser from "@/composables/UseAuthUser";
import { useRouter } from "vue-router";
// Use necessary composables
const router = useRouter();
const { register } = useAuthUser();
// Form reactive ref to keep up with the form data
const form = ref({
  name: "",
  email: "",
  password: "",
});
// function to hand the form submit
const handleSubmit = async () => {
  try {
        // use the register method from the AuthUser composable
    await register(form.value);
        // and redirect to a EmailConfirmation page the will instruct
        // the user to confirm they're email address
    router.push({
      name: "EmailConfirmation",
      query: { email: form.value.email },
    });
  } catch (error) {
    alert(error.message);
  }
};
</script>
<template>...</template>

最后,我们需要为该页面注册路由。

// router/index.js
const routes = [
  //...
    {
    name: "Register",
    path: "/register",
    component: () => import("@/pages/Register.vue"),
  },
]

如果你使用了模板代码,访问/register ,应该会得到一个看起来像这样的页面。

EmailConfirmation.vue

既然我们在注册后重定向到一个EmailConfirmation页面,我们当然需要让它存在。它将只是一个简单的模板和一条信息。当我们在上面重定向时,我们也提供了来自注册表单的电子邮件作为一个查询变量,所以我们可以在这里显示它。

<template>
  <div>
    <h1 class="text-3xl">Thanks for registering!</h1>
    <p>
      Please confirm your email to finishing registering:
      {{ $route.query.email }}
    </p>
  </div>
</template>

再一次,我们也需要注册这个路由。

// router/index.js
const routes = [
    //...
    {
    name: "EmailConfirmation",
    path: "/email-confirmation",
    component: () => import("@/pages/EmailConfirmation.vue"),
    },
]

现在访问带有电子邮件查询变量的/email-confirmation 路由将看起来像这样。

/[email protected]

登录.vu

在注册并确认了他们的电子邮件后,用户将不得不登录。让我们接下来创建这个页面。

该模板将包括一个带有电子邮件和密码字段的表单,以及一个指向忘记密码页面的链接,还有一个处理Github登录的链接。

<script>...</script>
<template>
  <div class="max-w-lg m-auto">
    <form @submit.prevent="handleLogin">
      <h1 class="text-3xl mb-5">Login</h1>
      <label>Email <input v-model="form.email" type="email" /></label>
      <label>Password <input v-model="form.password" type="password" /></label>
      <button>Login</button>
      <router-link to="/forgotPassword">Forgot Password?</router-link>
    </form>
    <div class="mt-5">
      <a @click.prevent="handleLogin('github')">Github</a>
    </div>
  </div>
</template>

在脚本部分,我们可以创建一个反应式引用来跟上表单的值,以及一个函数来处理表单的提交。

<script setup>
import { ref } from "vue";
import useAuthUser from "@/composables/UseAuthUser";
import { useRouter } from "vue-router";
// Use necessary composables
const router = useRouter();
const { login, loginWithSocialProvider } = useAuthUser();
// keep up with form data
const form = ref({
  email: "",
  password: "",
});
// call the proper login method from the AuthUser composable
// on the submit of the form
const handleLogin = async (provider) => {
  try {
    provider
      ? await loginWithSocialProvider(provider)
      : await login(form.value);
    router.push({ name: "Me" });
  } catch (error) {
    alert(error.message);
  }
};
</script>
<template>...</template>

注册完路由后,你可以访问/login 路由,看到一个漂亮的登录表单。

// router/index.js
const routes = [
    //...
    {
    name: "Login",
    path: "/login",
    component: () =&gt; import("@/pages/Login.vue"),
  },
]

ForgotPassword.vue

由于我们在上面实现了一个忘记密码的链接,让我们也把这个页面用脚手架搭出来。

在模板中,我们只需要一个带有单个电子邮件字段的表单,以便收集我们应该发送重置链接的电子邮件。

<script>...</script>
<template>
  <form class="max-w-lg m-auto" @submit.prevent="handlePasswordReset()">
    <h1 class="text-3xl mb-5">Forgot Password?</h1>
    <label>Email <input v-model="email" type="email" /></label>
    <button>Send Reset Email</button>
  </form>
</template>

在脚本部分,我们将创建一个电子邮件反应式参考,以跟上表单中的电子邮件输入,并定义一个函数,在表单提交时调用AuthUser可组合的sendPasswordRestEmail 函数。

<script setup>
import useAuthUser from "@/composables/UseAuthUser";
import { ref } from "vue";
// use necessary composables
const { sendPasswordRestEmail } = useAuthUser();
// keep up with email
const email = ref("");
// function to call on submit of the form
// triggers sending the reset email to the user
const handlePasswordReset = async () => {
  await sendPasswordRestEmail(email.value);
  alert(`Password reset email sent to: ${email.value}`);
};
</script>

最后,我们将注册这个路由。

// router/index.js
const routes = [
    //...
    {
    name: "ForgotPassword",
    path: "/forgotPassword",
    component: () => import("@/pages/ForgotPassword.vue"),
  },
]

现在,点击登录页面上的 "忘记密码?"链接将把你带到这里。

Me.vue

我们需要的最后一个页面是一个个人资料页面,在用户登录后显示他们的秘密信息。我们将它称为/me

// router/index.js
const routes = [
    //...
    {
    name: "Me",
    path: "/me",
    component: () =&gt; import("@/pages/Me.vue"),
  },
]

我们还将添加路由中间件,让我们的程序知道这应该是一个受保护的路由,只有通过认证的用户才能访问。

{
  name: "Me",
    meta: {
        requiresAuth: true,
    },
    //...
},

然后,为了让这个中间件真正发挥作用,我们需要这样来实现它。

const router = createRouter({
  history: createWebHistory(),
  routes,
});
router.beforeEach((to) => {
    // here we check it the user is logged in
    // if they aren't and the route requries auth we redirect to the login page
  const { isLoggedIn } = useAuthUser();
  if (!isLoggedIn() && to.meta.requiresAuth) {
    return { name: "Login" };
  }
});
export default router;

页面本身将是一个简单的问候用户的信息。

<script setup>
import useAuthUser from "@/composables/UseAuthUser";
const { user } = useAuthUser();
</script>
<template>
  <div v-if="user">
        <!--user_metadata is the key supabase nests all arbitrary meta data under-->
    <div>Hello {{ user.user_metadata.name }}</div>
  </div>
</template>

我们现在还不会尝试查看这个页面,因为我们还没有实现登录。

https://github.com/danielkellyio/supabase-auth-example

现在,页面和AuthUser组合已经到位,我们可以开始通过调用Supabase SDK来填充AuthUser组合函数的内容。

我们需要做的第一件事是访问我们的Supabase客户端。我们可以通过导入UseSupabase可组合函数并从中解构Supabase客户端来做到这一点。

import useSupabase from "@/composables/UseSupabase";
export default function useAuthUser() {
    const { supabase } = useSupabase();
    //...
}

现在我们可以通过我们的空函数,一个一个地填充它们。

login()

为了登录Supabase,我们需要调用supabase.auth.signIn 。我们也可以等待响应,如果有错误就抛出一个新的错误,否则就返回登录的用户。

const login = async ({ email, password }) => {
    const { user, error } = await supabase.auth.signIn({ email, password });
    if (error) throw error;
    return user;
};

loginWithSocialProvider()

loginWithSocialProvider也同样简单。只需要将提供者传递给signIn方法。

const loginWithSocialProvider = async (token) => {
    const { user, error } = await supabase.auth.signIn({ provider });
    if (error) throw error;
    return user;
};

logout()

现在,为了注销,我们需要调用Supabase的signOut方法。由于用户不再可用,所以没有什么可以从注销中返回。

const logout = async () => {
  const { error } = await supabase.auth.signOut();
  if (error) throw error;
};

isLoggedIn()

对于isLoggedIn函数,我们只需检查reactive ref user是否有一个值。

const isLoggedIn = () => {
    return !!user.value;
};

如果你在想,我们在登录用户时从来没有设置过这个值,你是完全正确的,但我们将利用另一个小的supabase方法来帮助我们解决这个问题,就在一分钟之内。

register()

register函数看起来几乎和login函数一样,接收电子邮件和密码。然而,它也需要接受其他用户信息(即元数据)。另外,我们将重定向到个人资料页面,并附上一些查询变量,其中包含一些有用的信息。

const register = async ({ email, password, ...meta }) => {
  const { user, error } = await supabase.auth.signUp(
    { email, password },
    {
            //arbitrary meta data is passed as the second argument under a data key
            // to the Supabase signUp method
            data: meta,
            // the to redirect to after the user confirms their email
            // window.location wouldn't be available if we were rendering server side
            // but since we're all on the client it will work fine
      redirectTo: `${window.location.origin}/me?fromEmail=registrationConfirmation"`,
        }
  );
  if (error) throw error;
  return user;
};

请注意这个很酷的小技巧,我们把meta:...meta 。这允许我们在调用函数时,在传递给函数的对象中提供同一层次的元数据,但在函数中单独访问它。

// for example
register({email: '[email&nbsp;protected]', password: 'password123', name: 'Daniel Kelly', favoriteFood: 'Spaghetti'})
// meta will be {name: 'Daniel Kelly', favoriteFood: 'Spaghetti'}

update()

虽然我们实际上没有提供更新用户的接口,但我们可以去实现这个函数,因为Supabase让它变得如此简单。

const update = async (data) => {
    const { user, error } = await supabase.auth.update(data);
    if (error) throw error;
    return user;
};

sendPasswordResetEmail()

最后一个要实现的函数是sendPasswordResetEmail 。再一次,supabase有一个简单的解决方案。

const sendPasswordRestEmail = async (email) => {
    const { user, error } = await supabase.auth.api.resetPasswordForEmail(email);
    if (error) throw error;
    return user;
};

观察Auth状态的变化

在这一点上,我们几乎已经准备好开始使用我们的接口了,但还有一个关键步骤需要执行。我们需要知道用户何时登录或注销,并相应地更新AuthUser组合中的反应式ref。

你的第一个想法可能是在登录和注销方法中完成这个任务,这在某些时候是可行的。然而,如果用户因为会话过期而注销了呢?或者如果用户在Supabase那边被更新或删除了呢?在这两种情况下,我们的登录和注销方法都不会被调用。

为了解决这个问题,Supabase提供了一个叫做onAuthStateChange 的函数。

我们可以在我们的supabase组合中调用这个函数,让它监听auth状态的所有变化,然后相应地设置我们的用户reactive ref。

// UseSupabase.js
import { createClient } from "@supabase/supabase-js";
import useAuthUser from "@/composables/UseAuthUser";
// config
const supabaseUrl = "";
const supabaseKey = "";
// setup client
const supabase = createClient(supabaseUrl, supabaseKey);
//  setup auth state listener
supabase.auth.onAuthStateChange((event, session) => {
    // the "event" is a string indicating what trigger the state change (ie. SIGN_IN, SIGN_OUT, etc)
    // the session contains info about the current session most importanly the user dat
  const { user } = useAuthUser();
    // if the user exists in the session we're logged in
    // and we can set our user reactive ref
  user.value = session?.user || null;
});
// expose supabase client
export default function useSupabase() {
  return { supabase };
}

我选择在UseSupabase.js中的函数调用之外做这件事,这样它就只被调用一次,并与其他Supabase设置代码组织在一起。

测试东西

现在到了关键时刻。我们应该有大部分的工作。(尽管你马上就会看到,我们还需要再做一些调整)。在你的浏览器中导航到注册页面并注册。

之后,你应该成功地被重定向到EmailConfirmation页面,你的电子邮件地址显示在信息中。

另外,如果你检查你的收件箱,你会像预期那样收到电子邮件。

顺便提一下,如果你想定制这封电子邮件的样子,你可以在Supabase仪表板上的Authentication > Templates

另外,如果你在Authentication > Users ,你就可以看到你的新注册用户的状态是:Waiting for Verication!

很好,现在去点击电子邮件中的那个链接吧。哦,天哪!我们被重新定向到了一个新的网站。我们被重定向到了登录页面......这不对。然而,请注意,标题右上方的链接确实写着 "注销"

如果我们点击到me 页面,它将让我们访问它,并正确显示我们在注册表格中提供的名字。

问题是,在我们点击页面的那一瞬间,我们的中间件正在运行,我们还没有完全登录,因此authStateChange还没有发生,还没有设置我们的用户反应式。

让我们在我们的中间件中做一个例外,如果查询变量fromEmail存在,我们就继续让导航通过,因为我们知道,从确认邮件中来,用户将立即登录。

router.beforeEach((to) => {
  const { isLoggedIn } = useAuthUser();
  if (
    !isLoggedIn() &&
    to.meta.requiresAuth &&
    !Object.keys(to.query).includes("fromEmail")
  ) {
    return { name: "Login" };
  }
});

还要注意,这不是一个安全问题。如果有人在没有登录的情况下随意在查询字符串中包括fromEmail ,由于用户不存在,反正什么都不会显示出来,也不会从Supabase收到关于用户的信息。

注销

现在除了注销链接外,一切都应该正常了。我们可以通过定义注销路由并直接向其添加一个路由保护来使其工作。

{
    name: "Logout",
    path: "/logout",
    beforeEnter: async () => {
      const { logout } = useAuthUser();
      await logout();
      return { name: "Home" };
    },
  },

注销后,如果你愿意,你可以尝试用不同的电子邮件再次注册,以确认我们上面的修复对直接导航到个人资料页面起作用。同样,当注销后,检查一下登录页面。你应该也能用现有的用户成功登录!

家庭作业

最后,如果你使用忘记密码的链接,Supabase确实会向你发送重置密码的电子邮件,然而你仍然需要实现该表格。我敢说,你可以利用你在文章中所学到的知识来研究这个问题,并利用AuthUser可组合的update 方法。

总结

Supabase是Firebase的一个新的和即将到来的替代品。它有很多诱人的特点,比如开源,使用PostgreSQL作为数据库,并提供流行的服务,如数据存储、认证和文件存储。用他们的认证服务来认证你的Vue.js 3应用程序是相当简单的,因为他们的认证api很简单。

如果你想看看本文讨论的完整代码,你可以查看Github repo

一旦认证完成,你就可以开始向你的Supabase实例发出请求,并创建规则来限制哪些用户可以访问哪些数据。

因此,去制作真实的Supabase应用程序吧。

https://vueschool.io/courses/vuejs-3-fundamentals?ref=blog

以上就是Vue3中使用Supabase Auth方法详解的详细内容,更多关于Vue3中使用Supabase Auth的资料请关注我们其它相关文章!

(0)

相关推荐

  • Vue.js使用$.ajax和vue-resource实现OAuth的注册、登录、注销和API调用

    概述 上一篇我们介绍了如何使用vue resource处理HTTP请求,结合服务端的REST API,就能够很容易地构建一个增删查改应用. 这个应用始终遗留了一个问题,Web App在访问REST API时,没有经过任何认证,这使得服务端的REST API是不安全的,只要有人知道api地址,就可以调用API对服务端的资源进行修改和删除. 今天我们就来探讨一下如何结合Web API来限制资源的访问. 本文的主要内容如下: 介绍传统的Web应用和基于REST服务的Web应用 介绍OAuth认证流程和

  • nuxt 自定义 auth 中间件实现令牌的持久化操作

    核心点就是在process.server下,把之前存在 cookie 中的数据再用store.commit一下 auth.js /* eslint-disable no-unused-vars */ /* eslint-disable no-useless-return */ export const TokenKey = 'Admin-token' /** * 解析服务端拿到的cookie * @param {*} cookie * @param {*} key */ export funct

  • Vue3中使用Supabase Auth方法详解

    目录 引言 安装Supabase 设置Supabase 创建一个AuthUser组合 创建页面 注册.vue EmailConfirmation.vue 登录.vu ForgotPassword.vue Me.vue login() loginWithSocialProvider() logout() isLoggedIn() register() update() sendPasswordResetEmail() 观察Auth状态的变化 测试东西 注销 家庭作业 总结 引言 Supabase是

  • Vue3中使用defineCustomElement 定义组件详解

    目录 使用 Vue 构建自定义元素 跳过组件解析 传递 DOM 属性 defineCustomElement() 生命周期 Props 事件 插槽 依赖注入 将 SFC 编译为自定义元素 基于 Vue 构建自定义元素库 defineComponent() defineAsyncComponent() 使用 Vue 构建自定义元素 Web Components 是一组 web 原生 API 的统称,允许开发者创建可复用的自定义元素 (custom elements). 自定义元素的主要好处是,它们

  • python编程之requests在网络请求中添加cookies参数方法详解

    哎,好久没有学习爬虫了,现在想要重新拾起来.发现之前学习爬虫有些粗糙,竟然连requests中添加cookies都没有掌握,惭愧.废话不宜多,直接上内容. 我们平时使用requests获取网络内容很简单,几行代码搞定了,例如: import requests res=requests.get("https://cloud.flyme.cn/browser/index.jsp") print res.content 你没有看错,真的只有三行代码.但是简单归简单,问题还是不少的. 首先,这

  • Android通过json向MySQL中读写数据的方法详解【读取篇】

    本文实例讲述了Android通过json向MySQL中读取数据的方法.分享给大家供大家参考,具体如下: 首先 要定义几个解析json的方法parseJsonMulti,代码如下: private void parseJsonMulti(String strResult) { try { Log.v("strResult11","strResult11="+strResult); int index=strResult.indexOf("[");

  • java 中enum的使用方法详解

    java 中enum的使用方法详解 enum 的全称为 enumeration, 是 JDK 1.5 中引入的新特性,存放在 java.lang 包中. 下面是我在使用 enum 过程中的一些经验和总结. 原始的接口定义常量 public interface IConstants { String MON = "Mon"; String TUE = "Tue"; String WED = "Wed"; String THU = "Thu

  • Android 中RxPermissions 的使用方法详解

    Android 中RxPermissions 的使用方法详解 以请求拍照.读取位置权限为例 module的build.gradle: compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.4@aar' compile 'io.reactivex.rxjava2:rxjava:2.0.5' AndroidManifest.xml: <uses-permission android:name="android.permission.AC

  • Android中XUtils3框架使用方法详解(一)

    xUtils简介 xUtils 包含了很多实用的android工具. xUtils 支持大文件上传,更全面的http请求协议支持(10种谓词),拥有更加灵活的ORM,更多的事件注解支持且不受混淆影响... xUitls 最低兼容android 2.2 (api level 8) 今天给大家带来XUtils3的基本介绍,本文章的案例都是基于XUtils3的API语法进行的演示.相信大家对这个框架也都了解过, 下面简单介绍下XUtils3的一些基本知识. XUtils3一共有4大功能:注解模块,网络

  • Android 中Context的使用方法详解

    Android 中Context的使用方法详解 概要: Context字面意思是上下文,位于framework package的android.content.Context中,其实该类为LONG型,类似Win32中的Handle句柄.很多方法需要通过 Context才能识别调用者的实例:比如说Toast的第一个参数就是Context,一般在Activity中我们直接用this代替,代表调用者的实例为Activity,而到了一个button的onClick(View view)等方法时,我们用t

  • Android通过json向MySQL中读写数据的方法详解【写入篇】

    本文实例讲述了Android通过json向MySQL中写入数据的方法.分享给大家供大家参考,具体如下: 先说一下如何通过json将Android程序中的数据上传到MySQL中: 首先定义一个类JSONParser.Java类,将json上传数据的方法封装好,可以直接在主程序中调用该类,代码如下 public class JSONParser { static InputStream is = null; static JSONObject jObj = null; static String j

  • jQueryUI中的datepicker使用方法详解

    jQuery UI很强大,其中的日期选择插件Datepicker是一个配置灵活的插件,我们可以自定义其展示方式,包括日期格式.语言.限制选择日期范围.添加相关按钮以及其它导航等. 之前做的一个排班考勤系统,跟时间打交道较多,对时间控件做过一些对比,觉得jqueryUI里的这个datepicker更为实用,下面抽点时间给大家整理,方便以后查阅,同时也希望能帮助到大家! 1,引入js,css <link rel="stylesheet" href="http://code.

随机推荐