基于 vuestic-ui 实战教程 - 登录篇

1. 简介

登录做为一个系统的门面,也是阻挡外界的一道防线,那在vuestic-ui中如何做登录功能呢。在这里就之间沿用初始版本的Login页面,作为一个演示模板,后续需要改进的读者可以在此篇文章的基础上修改。

在这里插入图片描述

2. 登录接口相关api 与 type编写

在上一篇获取动态数据中 我们已经定义好了与ts整合的axios,实现发送异步请求与远程服务器交互(对于ts语法像是函数定义、基本数据类型还是有不懂的读者可以跳转到上一篇的2.1再学习学习)这里就直接引入登录接口的api编写, 具体位置如下我个人习惯创建一个api文件夹,里面专门存放一些与后端交互的api方法和类型定义(初始quickstart版本是写在上面的page中的,就看个人的编写习惯吧😁只要功能实现了就没问题)

在这里插入图片描述

对于index.ts中主要实现了三个基本的方法,登录登出和获取用户信息

这里没有实现注册功能,因为我实现该网站主要是做一个流量监控的系统,注册功能对于用户不多的情况下其实不太需要,管理员可以直接操作加入数据库中,要是对这快感兴趣的读者也可以自己尝试尝试注册模块的功能实现
本质就是add一个user到数据库中,不过需要注意的是添加验证码等防护措施,防止有不法分子大量注册短时间内打爆服务器!!

import { http } from '../../../utils/request'
import type { LoginData , UserInfoRes} from './types'
 
const requestContent = '/simple/cloud/access'
/**
 * 登录
 */
export function login(loginVo: LoginData) {
  return http.post<UserInfoRes>(`${requestContent}/login`, loginVo);
}
 
/**
 * 获取登录用户信息
 */
export function getUserInfo() {
  return http.post<UserInfoRes>(`${requestContent}/info`)
}  

/**
 * 退出登录
 */
export function logout() {
  return http.post<string>(`${requestContent}/logout`)
}  

假设访问的后端服务器使用URL - http:localhost:9001/simple/cloud/access/login 这里由于uri前缀是一样的都是 ‘/simple/cloud/access’ 所以把它提取出来做为一个常量简化编写。只需要在使用的地方通过变量占位符引入就好啦(注意不是 ‘’ ,刚开始也踩过这个坑在vscode中看到上面的requestContent 由灰色变成高亮则说明引用成功)

`${key}`

而对于api中引入的数据类型定义在types.ts中,其中的返回值类型就根据后端提供的接口方法来写,像是我后端返回的类型为一个Map<String,Object> 类型的对象如下图所示,那我就根据这个map中的key和value一一对应写出如下的接口UserInfoRes,然后使用export导出给外部使用
在这里插入图片描述

注意编写的过程中只用指定ts基本的类型(java的List对应的就是ts中的数组 - 使用 [ ] 进行初始化 ),而要是需要返回一个User类型的对象,那就需要重新定义一个UserInfoInterface ,或者在user对应的api处定义types.ts 再在该文件下在引入(我是更推荐这种做法👍

/* 登录接口参数类型 */
export interface LoginData {
  email: string,
  password: string,
}
  
/* 用户信息接口返回值类型 */
export interface UserInfoRes {
  routers: [],
  buttons: [],
  roles: [],
  name: string,
  token: string,
}

3. 修改Login.vue

定义好与后端交互的方法api后,我们就可以回到前面的Login.vue处修改具体登录逻辑啦,由于初始版本使用的全是静态数据,所以很多功能其实都是不用的,具体删除修改后的模板如下(只保留了一个忘记密码的选项,该功能后续再完善😭先把主要的逻辑跑通先,感兴趣的读者可以先占个坑,后续我一定会回来填坑的!)

<template>
  <VaForm ref="form" @submit.prevent="submit">
    <h1 class="font-semibold text-4xl mb-4">Log in</h1>
    
    <VaInput
      v-model="formData.email"
      :rules="[validators.required, validators.email]"
      class="mb-4"
      label="Email"
      type="email"
    />
    <VaValue v-slot="isPasswordVisible" :default-value="false">
      <VaInput
        v-model="formData.password"
        :rules="[validators.required]"
        :type="isPasswordVisible.value ? 'text' : 'password'"
        class="mb-4"
        label="Password"
        @clickAppendInner.stop="isPasswordVisible.value = !isPasswordVisible.value"
      >
        <template #appendInner>
          <VaIcon
            :name="isPasswordVisible.value ? 'mso-visibility_off' : 'mso-visibility'"
            class="cursor-pointer"
            color="secondary"
          />
        </template>
      </VaInput>
    </VaValue>

    <div class="auth-layout__options flex flex-col sm:flex-row items-start sm:items-center justify-between">
      <RouterLink :to="{ name: 'recover-password' }" class="mt-2 sm:mt-0 sm:ml-1 font-semibold text-primary">
        Forgot password?
      </RouterLink>
    </div>

    <div class="flex justify-center mt-4">
      <VaButton class="w-full" @click="submit"> Login</VaButton>
    </div>
  </VaForm>
</template>

重写绑定的submit点击事件逻辑

const submit = () => {
	if (validate()) {
	  login(formData)
	    .then((data: UserInfoRes) => {
	      if (data) {
	        // 在这里添加需要执行的操作
	        const token = data.token;
	
	        // 将token存储到authStore中
	        const authStore = useAuthStore()
	        authStore.setToken(token)
	        authStore.setIsAuthenticated(true)
	         window.sessionStorage.setItem('isAuthenticated', 'true')
	        authStore.setName(data.name)
	        authStore.setButtons(data.buttons)
	        authStore.setRoles(data.roles)
	        authStore.setRouters(data.routers)
	
	        init({ message: "logged in success", color: 'success' });
	        // 登陆成功后就重定向到主页面dashboard
	        push({ name: 'dashboard' })
	
	      }
	    })
	    .catch(() => {
	      init({ message: "logged in fail , please check carefully!", color: '#FF0000' });
	    });
	
	}else{
	  Message.error('error submit!!')
	  return false
	}
}

看到这里我相信你肯定会疑惑,为什么我需要获取到数据又存储到store中,那这个store又在哪里定义的呢,作者也没讲啊😡
别急别急,请听我细细道来

4. store实现

在Vue应用程序中,当需要管理共享状态时,通常会使用Vuex库,而store就是Vuex中用于存储这些状态的地方,而我们登录后自然需要围护当前登录角色的一些关键信息(权限,姓名等等)需要的时候就直接到store中拿去,而不是反复的去数据库中查找,废话不多说下面就来定义一个store ,在初始版本中就已经定义好了store,只不过这个store里面是没东西的,如下图所示

在这里插入图片描述

那我们就可以在原有的基础上添加修改,下面的代码都是在index.ts中实现的,如下代码就是一个模板,对应pinia库的描述如下
Pinia是Vue的另一种状态管理方案,与Vuex类似,但设计上更简洁、更易于上手。以下是关于Pinia的一些详细说明:

  • 简单易用:Pinia的目标是提供一个更简单的状态管理解决方案,它的API设计非常直观,使得开发者可以快速上手并有效地管理状态。
  • 独立模块:与Vuex不同,Pinia中的每个store都是一个独立的模块,它们可以单独导入和导出,这有助于更好地组织和维护代码。
  • 响应式:Pinia中的状态是响应式的,当状态发生变化时,依赖于这些状态的组件会自动更新。
  • Devtools支持:Pinia具有良好的Devtools支持,可以帮助开发者更方便地跟踪和调试状态变化。
  • 插件化:Pinia被设计为一个插件,可以轻松地集成到现有的Vue应用中。
  • 与Vuex兼容:虽然Pinia是一个全新的状态管理库,但它也允许与Vuex共存于同一个项目中,方便开发者逐步迁移。

本次项目中store就基于Pinia实现,首先通过defineStore方法定义一个全局可供调用的store, 其中包括了一些属性像是

  1. id (自己设定,但是要保证全局唯一)
  2. state (定义的所有状态)
  3. getters (获取状态的方法)
  4. actions (有获取肯定就有设置的方法啦)
// store.ts
import { createPinia, defineStore } from 'pinia'
 
export const useAuthStore = defineStore({
  id: 'auth',
  state: () => ({

  }),
  getters: {

  },
  actions: {
    
  },
})

export default createPinia()

4.1 state

在state中定义的状态就是在一个浏览器会话内需要存储的用户信息(登录后赋值,登出或者会话结束就销毁)根据第2点中types定义的UserInfoRes 可以设计出来, 由于ts不像js一样是弱语言,ts是有类型的上一讲也提到过,所以为了能在后续的get set中拿到指定和设置其中的属性值,我们需要通过as 参数类型的方式来指定

isAuthenticated 本意是为了阻止用户登录前就访问其他的页面(会被驳回,重定向到登录页面)后面发现存到浏览器缓存中也是可以的,这里就做个备选,看读者喜欢哪一种方式

state: () => ({
    token : '',
    isAuthenticated : false,
    routers : [] as RouterVo[],
    buttons : [] as string[],
    name : '',
    roles : [] as RoleData[],
  }),

这里的RoleDta和RouterVo就分别对应了角色和菜单列表,具体实现如下(编写在types.ts文件中,具体位置看下边4.4的总体代码)


/* sysUser参数类型 */
export interface RoleData {
  id: number,
  roleName: string,
  roleCode: string,
  description: string
}

 /* RouterVo参数类型 */
export interface RouterVo {
  path: string,
  hidden: boolean,
  alwaysShow: boolean,
  meta: MetaVo,
  children: RouterVo[],
}

4.2 getters

根据如下的指定格式获取存在store中的参数

getters: {
  getButtons: (state) => state.buttons,
  getToken: (state) => state.token,
  getIsAuthenticated: (state) => state.isAuthenticated,
  getRouters: (state) => state.routers,
  getName: (state) => state.name,
  getRoles: (state) => state.roles,
},

4.3 actions

actions中定义了一系列set方法,可以发现这里()内的参数都是指定类型的,如果我们在定义的时候不指定类型这就会报错!!

actions: {
 setRoles(roles : RoleData[]) {
    this.roles = roles
  },
  
  setButtons(buttons : string[]) {
    this.buttons = buttons
  },
  
  setRouters(routers : RouterVo[]) {
    this.routers = routers
  },
  
  setName(name : string) {
    this.name = name
  },
  
  setToken(token : string) {
    this.token = token
  },
  
  setIsAuthenticated(isAuthenticated : boolean){
    this.isAuthenticated = isAuthenticated
  },
	// 登出后的资源重置
  reset(){
    this.roles = []
    this.name = ''
    this.buttons = []
    this.routers = []
    this.isAuthenticated = false
    this.token = ''
  },
},

4.4 总体代码

// store.ts
import { createPinia, defineStore } from 'pinia'
import { RoleData } from '@/api/system/sysRole/types'
import { RouterVo } from '@/api/system/sysMenu/types'
 
export const useAuthStore = defineStore({
  id: 'auth',
  state: () => ({
    token : '',
    isAuthenticated : false,
    routers : [] as RouterVo[],
    buttons : [] as string[],
    name : '',
    roles : [] as RoleData[],
  }),
  getters: {
    getButtons: (state) => state.buttons,
    getToken: (state) => state.token,
    getIsAuthenticated: (state) => state.isAuthenticated,
    getRouters: (state) => state.routers,
    getName: (state) => state.name,
    getRoles: (state) => state.roles,
  },
  actions: {
    setRoles(roles : RoleData[]) {
      this.roles = roles
    },
    
    setButtons(buttons : string[]) {
      this.buttons = buttons
    },
    
    setRouters(routers : RouterVo[]) {
      this.routers = routers
    },
    
    setName(name : string) {
      this.name = name
    },
    
    setToken(token : string) {
      this.token = token
    },
    
    setIsAuthenticated(isAuthenticated : boolean){
      this.isAuthenticated = isAuthenticated
    },

    reset(){
      this.roles = []
      this.name = ''
      this.buttons = []
      this.routers = []
      this.isAuthenticated = false
      this.token = ''
    },
  },
})
// 记得要导出,不在就白定义了 外部通过调用createPinia() 获取示例
export default createPinia()

4.5 main.ts中App引入

在Vue中引入App是因为App.vue通常作为项目的主组件和页面入口文件,负责构建定义及页面组件的归集和切换。定义的组件自然要添加到其中,在初始化的时候就一同创建。在文件原有基础上添加如下代码

import stores from './stores'
import { createPinia } from 'pinia'

app.use(createPinia)
app.use(stores)

最后保存就好啦,到这里在回看第3点的submit方法是不是就一目了然
这里提炼出使用store的核心代码,有需要的读者可以直接复制使用😁

// 导入刚刚定义的方法
import { useAuthStore } from '@/stores'

// 外部调用创建一个示例(唯一的)
const authStore = useAuthStore()
// 在对应的操作方法里面使用我们在getters和actions中定义的方法
// set
authStore.setToken(token)
// get
const token = authStore.getToken

5. vue限制实现不登录无法进入其他页面

这个模块可用的方法有很多网上也是有各种各样的教程,在这里使用的是设置路由守卫的方法,在router/index.ts下修改,具体做三种判断

  1. 防止重复登录: 登录后的用户不能在登录了,只能主动退出或者关闭浏览器(token失效也是一个,这个后面讲)
  2. 白名单直接放行:对于可以供给全部用户访问的一些静态资源、页面(比如登录页面,和一些docs帮助文档是可以直接访问的)
  3. 没有登录:对于没有登录的用户无法访问系统的资源,为了提防有些通过导航栏修改URL的方法访问
// 设置哪些页面是属于白名单的
const witheList = ["/auth/login"];
 
function isWitheRoute(path : string) {
  return witheList.includes(path);
}
 
// 全局前置守卫
router.beforeEach((to, from, next) => {
  const isAuthenticated =  window.sessionStorage.getItem('isAuthenticated');

  //防止重复登录
  if (isAuthenticated && (to.path === "/auth/login"))  {
    Message.info("You have successfully logged in. Please avoid logging in repeatedly! (You can log out if you wish)");
    return next({ path: from.path ? from.path : "/" });
  }

  // 判断如果是白名单就直接放行
  if (isWitheRoute(to.path)) {
    next();
    return;
  }
  // 没有登录,强制跳转到登录页面
  if (!isAuthenticated && to.path != "/auth/login") {
    Message.info("Please logging first");
    next({ path: "/auth/login" });
    return;
  }  
  next()
});

5.1. 浏览器缓存

上边埋了一个坑,可以使用浏览器缓存的方法实现该功能,上边代码也看到了window.sessionStorage. 那么这到底是嘛玩意,作用范围生命周期又是什么呢?下面将一一解答:

  1. sessionStorage为Web开发者提供了一种在用户的浏览器中临时存储数据的方式。这种存储方式特定于用户打开的特定窗口或标签页,并且数据只在这个特定的窗口或标签页有效。当用户关闭这个窗口或标签页时,存储在sessionStorage中的所有数据将被清除。这就意味着不同的浏览器窗口或标签页,即使是打开相同的网页,它们之间的sessionStorage数据是不共享的。
  2. sessionStorage的生命周期与用户打开的窗口或标签页的持续时间同步。只要窗口或标签页保持打开状态,即便是进行页面刷新或切换到同源的其他页面,sessionStorage中的数据都将持续存在。然而,一旦窗口或标签页被关闭,sessionStorage中的所有数据将立即失效并被清除。

可以见得通过该方法保存用户的登录状态也是不错之选,而且非正常退出时候也不用担心数据泄露(会自动销毁,后端的数据就需要通过勾子函数回调,或者直接设置redis过期时间就等它自动过期)下边就是三个常用的方法:

对于我们的登录功能来说,在登录成功后设置为true,此时路由守卫判断时候就能获取到该值,而在登出的时候就删除掉该数据,这样就能保证统一

//设置对应的key-value
window.sessionStorage.setItem('isAuthenticated', 'true');

//通过getItem获取 (取不到时为null)
const isAuthenticated =  window.sessionStorage.getItem('isAuthenticated');

//去除浏览器缓存
window.sessionStorage.removeItem('isAuthenticated')

6. 登出功能实现

登出功能本质上是跟登录没什么区别的,就是后端清除存储的数据token , reids中权限数据等,前端清除login获取到的所有数据(回到出厂设置的感觉)在初始版本中是没有登出这个按钮的,经常登录网页的朋友都知道,登出的按钮一般是在右上角,那这里我们就遵循惯例先找找最上边的栏目是在哪一个vue页面里面(最笨的方法就是一个一个去搜索是否有相应的字眼)

那么我就以我的理解来告诉大家如何快速找到相应的模块。首先要知道的是所有的组件都是放在src/components文件夹下的,那我们就去下边找,一展开就很明显看到navbar的字眼(导航栏嘛,也就是我们要找的上边栏所在位置)点开后发现又有个components(根据上面的知识不用我说都知道这是放组件的吧)点开就看到GitHubButton 这不就是我们要找的上边栏上的github按钮吗,说明我们找对地方了,最终就锁定范围在这两个vue文件中,是不是一下子节省很多工作量😁 , 具体示例文件所在处如下图所示
在这里插入图片描述

找到这个文件后我们预期的效果是跟下图这样加一个Logout 按钮 用户点击就可以退出登录
在这里插入图片描述
在AppNavbarActions这个文件中点开就发现其实实现起来很简单,就是依葫芦画瓢,照抄原来有的button组件就好啦,具体代码如下

<VaButton
  v-if="!isMobile"
  preset="secondary"
  @click="logoutOper" <!--自定义点击事件-->
  target="_blank"
  color="textPrimary"
  class="app-navbar-actions__item flex-shrink-0 mx-0"
>
  {{ t('Logout') }}
</VaButton>

因为我们绑定了点击事件,自然要实现的啦(如下代码在script中原有的基础上添加)

import { logout } from '@/api/system/auth/index'
import { useAuthStore } from '@/stores'
import { useToast } from 'vuestic-ui'
import { useRouter } from 'vue-router'
const { push } = useRouter()
const { init } = useToast()

const logoutOper = () => {
  logout().then(() => {
    const store = useAuthStore() // 获取store实例
    store.reset() // 重置store

    //去除浏览器缓存
    window.sessionStorage.removeItem('isAuthenticated')

    //跳转路由
    init({ message: "logout success", color: 'success' });
    push({ name: 'login' })

  }).catch(() => {
    init({ message: "logged out fail , please contact administration", color: '#FF0000' });
  });
}

7. 每次请求时带上token访问服务器

由于加入了权限认证功能,所以登录后的每一次请求都必须携带上token(这里的token就遵循OAuth2的规范以"Bearer "开头),不然会认为没有登录跳转的登录页面重新登录,在每一次请求中添加请求头是不是就是定义一个全局filter,也就是在上一讲中提到的axios请求拦截器,那如下代码就在utils/request.ts下修改(还没有的请看上一讲)

import { useAuthStore } from '@/stores'

/* 请求拦截器 */
service.interceptors.request.use((config: InternalAxiosRequestConfig) => {  
    const authStore = useAuthStore()
    if (authStore != undefined) {
      //获取token
      const token = authStore.getToken
      config.headers.Authorization = `Bearer ${token}`;
    } else {
      // 如果不存在 token,则拒绝请求并跳转到登录页面
      window.location.href = '/auth/login';

      //去除浏览器缓存
      window.sessionStorage.removeItem('isAuthenticated')
      return Promise.reject('Authenticated fail');
    }

  return config;  

}, (error: AxiosError) => {
  Message.error(error.message);
  return Promise.reject(error)
})

终于讲完啦,这篇内容挺多的,给看到这里的读者点赞👍,希望能够对你们有所帮助(本篇主要实现前端的功能,后续会结合权限管理给出后端认证授权功能实现,敬请期待…)


各位读者我回来填坑啦,对于上面的后端实现我又写了点自己的想法,感兴趣的读者可以点击查阅后端认证授权功能实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/656690.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

扔掉 MacBook,挑战带OrangePi出差!

背景 由于工作需要&#xff0c;博主经常会到各大企业的自建机房中私有化部署公司的软件产品。 在某些企业自建机房中&#xff0c;有时给到全新的机器&#xff0c;没有基础环境&#xff0c;甚至有的还无法互联网&#xff0c;而且因为近几年CentOS的停止更新&#xff0c;服务器…

go-zero 实战(2)

go-zero 实战&#xff08;1&#xff09; 中&#xff0c;使用了go-zero 创建了order 和 user 两个微服务。而order作为grpc的客户端&#xff0c;user 作为grpc的服务端&#xff0c;打通了 order 到 user的调用。接下来&#xff0c;我们在user中&#xff0c;加入mysql组件。确保数…

开源与闭源:AI模型发展的两条路径

目录 前言1 数据隐私保护与用户数据安全1.1 开源大模型的透明性与挑战1.2 闭源大模型的控制与责任 2 商业应用的优劣比较2.1 开源大模型的灵活性与创新2.2 闭源大模型的可靠性与服务质量 3 社区参与与合作的差异3.1 开源大模型的社区驱动与协作3.2 闭源大模型的企业主导与保密性…

四川古力未来科技抖音小店安全靠谱,购物新体验

在数字化浪潮席卷而来的今天&#xff0c;电商行业蓬勃发展&#xff0c;各种线上购物平台如雨后春笋般涌现。其中&#xff0c;抖音小店凭借其独特的短视频直播购物模式&#xff0c;迅速赢得了广大消费者的青睐。而四川古力未来科技抖音小店&#xff0c;更是以其安全靠谱、品质保…

Python OCR 文字检测使用模型:读光-文字检测-DBNet行检测模型-中英-通用领域

介绍 什么是OCR&#xff1f; OCR是“Optical Character Recognition”的缩写&#xff0c;中文意为“光学字符识别”。它是一种技术&#xff0c;可以识别和转换打印在纸张或图像上的文字和字符为机器可处理的格式&#xff0c;如计算机文本文件。通过使用OCR技术&#xff0c;可…

Qt Creator(2)【如何在Qt Creator中创建新工程】

阅读导航 引言一、Qt Creator开始界面介绍二、如何在Qt Creator中创建新工程1. 新建项目2. 选择项目模板3. 选择项目路径4. 选择构建系统5. 填写类信息设置界面6. 选择语言和翻译文件7. 选择Qt套件8. 选择版本控制系统9. 最终效果 三、认识Qt Creator项目内容界面1. 基本界面2.…

基于Vue的神影视频APP

需求说明:使用Vue脚手架进行搭建,页面简洁、精致,和一些常见的电影网站类似,例如支付宝中的“淘票票电影”。在项目中使用页面布局技术(表格,vue.js框架,DIV+CSS或者混合使用)进行页面设计,使网站功能齐全,界面美观大方,有一定的交互性。 功能分析:系统主要分为七…

翻译《The Old New Thing》- What did MakeProcInstance do?

What did MakeProcInstance do? - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20080207-00/?p23533 Raymond Chen 2008年02月07日 MakeProcInstance 做了什么&#xff1f; MakeProcInstance 宏实际上什么也不做。 #define MakeProcInst…

四川农业大学Java实训项目圆满收官,汇智知了堂引领学子实践创新

近日&#xff0c;四川农业大学与汇智知了堂共同举办的Java实训项目正式迎来了项目汇报阶段。本次实训是汇智知了堂在高等教育领域深化校企合作、推动产教融合的一次重要实践&#xff0c;旨在为广大学子提供一个将理论知识与实际操作相结合的平台。 在实训过程中&#xff0c;汇…

(三)MySQL 索引

欢迎访问 什么是索引&#xff1f; 提高查询效率的一种数据结构&#xff0c;索引是数据的目录 索引的分类 按「数据结构」分类&#xff1a;Btree索引、Hash索引、Full-text索引。按「物理存储」分类&#xff1a;聚簇索引、二级索引。按「字段特性」分类&#xff1a;主键索引…

【Linux学习】进程间通信 (3) —— System V (1)

下面是有关进程通信中 System V 的相关介绍&#xff0c;希望对你有所帮助&#xff01; 小海编程心语录-CSDN博客 目录 1. System V IPC 1. 消息队列 msg 消息队列的使用方法 1.1 消息队列的创建 1.2 向消息队列发送消息 1.3 从消息队列接收消息 1.4 使用msgctl函数显式地…

拉格朗日插值及牛顿差商方法的实现(Matlab)

一、问题描述 拉格朗日插值及牛顿差商方法的实现。 二、实验目的 掌握拉格朗日插值和牛顿差商方法的原理&#xff0c;能够编写代码实现两种方法&#xff1b;能够分析多项式插值中的误差。 三、实验内容及要求 利用拉格朗日插值及牛顿差商方法估计1980 年的人口&#xff0c;并…

监控员工电脑的软件有哪些,不得不说这几款电脑监控软件太好用了

监控员工电脑的软件在市场上种类繁多&#xff0c;以下是几款备受好评的电脑监控软件&#xff0c;它们各自具有独特的功能和优势&#xff0c;选择前必须了解一下才能做成正确决定。 1.安企神&#xff1a; 这款软件支持7天试用测试&#xff0c;获取测试版请移驾 ↓↓↓ 安企神…

常见的数据分析方法

1.周期性分析法 一个指标的观察时间拉长,看它是否有周期变化规律。周期性分析常见的有两者:自然周期和生命周期。自然周期,指业务指标会随着时间自然变化,如节假日用户/业绩出现下滑、产品销售额随季节变动等;生命周期,譬如“商品生命周期”、“APP生命周期”、“用户生…

【论文阅读】Rank-DETR(NIPS‘23)

paper:https://arxiv.org/abs/2310.08854 code:https://github.com/LeapLabTHU/Rank-DETR

二叉树——基础知识详解

前言&#xff1a; 经过前面的学习&#xff0c;我们接下来要开始二叉树的学习&#xff0c;因二叉树有难度&#xff0c;为了方便讲解以及各位的理解&#xff0c;本节知识会分成不同的小节进行学习&#xff0c;在本阶段只学习初阶的二叉树&#xff08;堆&#xff0c;二叉数基本知识…

项目9-网页聊天室3(主界面之用户信息)

1.前端页面 CSS: 如何让img里的图片自适应div&#xff0c;且不变形_img自适应div大小 铺满且不变形-CSDN博客 JavaScript/jQuery 如何改变一个img元素的src属性|极客教程 (geek-docs.com) 2.要求 左上角显示用户的昵称和头像. 3.后端代码 3.1 添加拦截器 3.2 注册拦截器 …

信息学奥赛初赛天天练-14-阅读程序-字符数组、唯一分解定理应用

更多资源请关注纽扣编程微信公众号 1 2019 CSP-J 阅读程序1 (程序输入不超过数组或字符串定义的范围&#xff1b;判断题正确填√,错误填&#xff1b;除特殊说明外&#xff0c;判断题1.5分&#xff0c;选择题3分&#xff0c;共计40分) 1 输入的字符串只能由小写字母或大写字母组…

vue/uniapp 企业微信H5使用JS-SDK

企业微信H5需要我们使用一些SDK方法如获取外部联系人userid 获取当前外部联系人userid 使用SDK前提是如何通过config接口注入权限验证配置 使用说明 - 接口文档 - 企业微信开发者中心 当前项目是vue项目&#xff0c;不好直接使用 引入JS文件&#xff0c;但我们可以安装依赖…

python使用多种方法计算列表元素平方的技巧

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、使用列表推导式进行元素平方 二、使用map函数进行元素平方 三、循环遍历列表进行元素平…