cli3解决 ie11语法错误 vue_基于 Vue + Koa2 + MongoDB + Redis 实现一个完整的登录注册...

项目地址:https://github.com/caochangkui/vue-element-responsive-demo/tree/login-register

通过 vue-cli3.0 + Element 构建项目前端,Node.js + Koa2 + MongoDB + Redis 实现数据库和接口设计,包括邮箱验证码、用户注册、用户登录、查看删除用户等功能。

1. 技术栈

  • 前端
    • 初始化项目:vue-cli3.0
    • 组件库:Element-ui
    • 路由控制/拦截:Vue-router
    • 状态管理:Vuex
  • 服务端
    • 运行环境:Node.js
    • 后台开发框架:Koa2
    • 路由中间件:Koa-router
    • 发送邮件: nodemailer
  • HTTP通讯
    • 接口请求/拦截:Axios
    • Token认证:jsonwebtoken
  • 数据库
    • MongoDB
    • 数据库操作:Mongoose
    • 缓存工具:Redis

2. 项目依赖:

"dependencies": {"axios": "^0.18.0","crypto-js": "^3.1.9-1","element-ui": "^2.4.5","js-cookie": "^2.2.0","jsonwebtoken": "^8.5.0","koa": "^2.7.0","koa-bodyparser": "^4.2.1","koa-generic-session": "^2.0.1","koa-json": "^2.0.2","koa-redis": "^3.1.3","koa-router": "^7.4.0","mongoose": "^5.4.19","nodemailer": "^5.1.1","nodemon": "^1.18.10","vue": "^2.5.21","vue-router": "^3.0.1","vuex": "^3.0.1"}

3. 前端实现步骤

3.1 登录注册页面

通过 vue-cli3.0 + Element 构建项目前端页面

登录页(@/view/users/Login.vue):

640b604a2aecc223abcb15ff2491b373.png

注册页(@/view/users/Register.vue):

发送验证码前需要验证用户名和邮箱,用户名必填,邮箱格式需正确。

78e380079a840871708f030ecdb7b155.png

用户设置页(@/view/users/setting/Setting.vue)

用户登录后,可以进入用户设置页查看用户和删除用户

3.2 Vuex 状态管理

通过 vuex 实现保存或删除用户 token,保存用户名等功能。

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter。

根目录下新建store文件夹,创建modules/user.js:

const user = {state: {token: localStorage.getItem('token'),username: localStorage.getItem('username')},mutations: {BIND_LOGIN: (state, data) => {localStorage.setItem('token', data)state.token = data},BIND_LOGOUT: (state) => {localStorage.removeItem('token')state.token = null},SAVE_USER: (state, data) => {localStorage.setItem('username', data)state.username = data}}
}export default user

创建文件 getters.js 对数据进行处理输出:

const getters = {sidebar: state => state.app.sidebar,device: state => state.app.device,token: state => state.user.token,username: state => state.user.username}
export default getters

创建文件 index.js 管理所有状态:

import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import getters from './getters'Vue.use(Vuex)const store = new Vuex.Store({modules: {user},getters
})export default store

3.3 路由控制/拦截

路由配置(router.js):

import Vue from 'vue'
import Router from 'vue-router'
const Login = () => import(/* webpackChunkName: "users" */ '@/views/users/Login.vue')
const Register = () => import(/* webpackChunkName: "users" */ '@/views/users/Register.vue')
const Setting = () => import(/* webpackChunkName: "tables" */ '@/views/setting/Setting.vue')Vue.use(Router)const router = new Router({base: process.env.BASE_URL,routes: [{path: '/login',name: 'Login',component: Login,meta: {title: '登录'}},{path: '/register',name: 'Register',component: Register,meta: {title: '注册'}},{path: '/setting',name: 'Setting',component: Setting,meta: {breadcrumb: '设置',requireLogin: true},}]
})

路由拦截:

关于vue 路由拦截参考:https://www.cnblogs.com/cckui/p/10319013.html

// 页面刷新时,重新赋值token
if (localStorage.getItem('token')) {store.commit('BIND_LOGIN', localStorage.getItem('token'))
}// 全局导航钩子
router.beforeEach((to, from, next) => {if (to.meta.title) { // 路由发生变化修改页面titledocument.title = to.meta.title}if (to.meta.requireLogin) {if (store.getters.token) {if (Object.keys(from.query).length === 0) { // 判断路由来源是否有query,处理不是目的跳转的情况next()} else {let redirect = from.query.redirect // 如果来源路由有queryif (to.path === redirect) { // 避免 next 无限循环next()} else {next({ path: redirect }) // 跳转到目的路由}}} else {next({path: '/login',query: { redirect: to.fullPath } // 将跳转的路由path作为参数,登录成功后跳转到该路由})}} else {next()}
})export default router

3.4 Axios 封装

封装 Axios

// axios 配置
import axios from 'axios'
import store from './store'
import router from './router'//创建 axios 实例
let instance = axios.create({timeout: 5000, // 请求超过5秒即超时返回错误headers: { 'Content-Type': 'application/json;charset=UTF-8' },
})instance.interceptors.request.use(config => {if (store.getters.token) { // 若存在token,则每个Http Header都加上tokenconfig.headers.Authorization = `token ${store.getters.token}`console.log('拿到token')}console.log('request请求配置', config)return config},err => {return Promise.reject(err)})// http response 拦截器
instance.interceptors.response.use(response => {console.log('成功响应:', response)return response},error => {if (error.response) {switch (error.response.status) {case 401:// 返回 401 (未授权) 清除 token 并跳转到登录页面store.commit('BIND_LOGOUT')router.replace({path: '/login',query: {redirect: router.currentRoute.fullPath}})breakdefault:console.log('服务器出错,请稍后重试!')alert('服务器出错,请稍后重试!')}}return Promise.reject(error.response) // 返回接口返回的错误信息}
)export default {// 发送验证码userVerify (data) {return instance.post('/api/verify', data)},// 注册userRegister (data) {return instance.post('/api/register', data)},// 登录userLogin (data) {return instance.post('/api/login', data)},// 获取用户列表getAllUser () {return instance.get('/api/alluser')},// 删除用户delUser (data) {return instance.post('/api/deluser', data)}
}

4. 服务端和数据库实现

在根目录下创建 server 文件夹,存放服务端和数据库相关代码。

4.1 MongoDB和Redis

创建 /server/dbs/config.js ,进行数据库和邮箱配置

// mongo 连接地址
const dbs = 'mongodb://127.0.0.1:27017/[数据库名称]'// redis 地址和端口
const redis = {get host() {return '127.0.0.1'},get port() {return 6379}
}// qq邮箱配置
const smtp = {get host() {return 'smtp.qq.com'},get user() {return '1********@qq.com' // qq邮箱名},get pass() {return '*****************' // qq邮箱授权码},// 生成邮箱验证码get code() {return () => {return Math.random().toString(16).slice(2, 6).toUpperCase()}},// 定义验证码过期时间rules,5分钟get expire() {return () => {return new Date().getTime() + 5 * 60 * 1000}}
}module.exports = {dbs,redis,smtp
}

使用 qq 邮箱发送验证码,需要在“设置/账户”中打开POP3/SMTP服务和MAP/SMTP服务。

4.2 Mongo 模型

创建 /server/dbs/models/users.js:

// users模型,包括四个字段
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const UserSchema = new Schema({username: {type: String,unique: true,required: true},password: {type: String,required: true},email: {type: String,required: true},token: {type: String,required: true}
})module.exports = {Users: mongoose.model('User', UserSchema)
}

4.3 接口实现

创建 /server/interface/user.js:

const Router = require('koa-router')
const Redis = require('koa-redis') // key-value存储系统, 存储用户名,验证每个用户名对应的验证码是否正确
const nodeMailer = require('nodemailer') // 通过node发送邮件
const User = require('../dbs/models/users').Users
const Email = require('../dbs/config')// 创建和验证token, 参考4.4
const createToken = require('../token/createToken.js') // 创建token
const checkToken = require('../token/checkToken.js') // 验证token// 创建路由对象
const router = new Router({prefix: '/api' // 接口的统一前缀
})// 获取redis的客户端
const Store = new Redis().client// 接口 - 测试
router.get('/test', async ctx => {ctx.body = {code: 0,msg: '测试',}
})// 发送验证码 的接口
router.post('/verify', async (ctx, next) => {const username = ctx.request.body.usernameconst saveExpire = await Store.hget(`nodemail:${username}`, 'expire') // 拿到过期时间console.log(ctx.request.body)console.log('当前时间:', new Date().getTime())console.log('过期时间:', saveExpire)// 检验已存在的验证码是否过期,以限制用户频繁发送验证码if (saveExpire && new Date().getTime() - saveExpire < 0) {ctx.body = {code: -1,msg: '发送过于频繁,请稍后再试'}return}// QQ邮箱smtp服务权限校验const transporter = nodeMailer.createTransport({/***  端口465和587用于电子邮件客户端到电子邮件服务器通信 - 发送电子邮件。*  端口465用于smtps SSL加密在任何SMTP级别通信之前自动启动。*  端口587用于msa*/host: Email.smtp.host,port: 587,secure: false, // 为true时监听465端口,为false时监听其他端口auth: {user: Email.smtp.user,pass: Email.smtp.pass}})// 邮箱需要接收的信息const ko = {code: Email.smtp.code(),expire: Email.smtp.expire(),email: ctx.request.body.email,user: ctx.request.body.username}// 邮件中需要显示的内容const mailOptions = {from: `"认证邮件" <${Email.smtp.user}>`, // 邮件来自to: ko.email, // 邮件发往subject: '邀请码', // 邮件主题 标题html: `您正在注册****,您的邀请码是${ko.code}` // 邮件内容}// 执行发送邮件await transporter.sendMail(mailOptions, (err, info) => {if (err) {return console.log('error')} else {Store.hmset(`nodemail:${ko.user}`, 'code', ko.code, 'expire', ko.expire, 'email', ko.email)}})ctx.body = {code: 0,msg: '验证码已发送,请注意查收,可能会有延时,有效期5分钟'}
})// 接口 - 注册
router.post('/register', async ctx => {const { username, password, email, code } = ctx.request.body// 验证验证码if (code) {const saveCode = await Store.hget(`nodemail:${username}`, 'code') // 拿到已存储的真实的验证码const saveExpire = await Store.hget(`nodemail:${username}`, 'expire') // 过期时间console.log(ctx.request.body)console.log('redis中保存的验证码:', saveCode)console.log('当前时间:', new Date().getTime())console.log('过期时间:', saveExpire)// 用户提交的验证码是否等于已存的验证码if (code === saveCode) {if (new Date().getTime() - saveExpire > 0) {ctx.body = {code: -1,msg: '验证码已过期,请重新申请'}return}} else {ctx.body = {code: -1,msg: '请填写正确的验证码'}return}} else {ctx.body = {code: -1,msg: '请填写验证码'}return}// 用户名是否已经被注册const user = await User.find({ username })if (user.length) {ctx.body = {code: -1,msg: '该用户名已被注册'}return}// 如果用户名未被注册,则写入数据库const newUser = await User.create({username,password,email,token: createToken(this.username) // 生成一个token 存入数据库})// 如果用户名被成功写入数据库,则返回注册成功if (newUser) {ctx.body = {code: 0,msg: '注册成功',}} else {ctx.body = {code: -1,msg: '注册失败'}}
})// 接口 - 登录
router.post('/login', async (ctx, next) => {const { username, password } = ctx.request.bodylet doc = await User.findOne({ username })if (!doc) {ctx.body = {code: -1,msg: '用户名不存在'}} else if (doc.password !== password) {ctx.body = {code: -1,msg: '密码错误'}} else if (doc.password === password) {console.log('密码正确')let token = createToken(username) // 生成tokendoc.token = token // 更新mongo中对应用户名的tokentry {await doc.save() // 更新mongo中对应用户名的tokenctx.body = {code: 0,msg: '登录成功',username,token}} catch (err) {ctx.body = {code: -1,msg: '登录失败,请重新登录'}}}
})// 接口 - 获取所有用户 需要验证 token
router.get('/alluser', checkToken, async (ctx, next) => {try {let result = []let doc = await User.find({})doc.map((val, index) => {result.push({email: val.email,username: val.username,})})ctx.body = {code: 0,msg: '查找成功',result}} catch (err) {ctx.body = {code: -1,msg: '查找失败',result: err}}
})// 接口 - 删除用户 需要验证 token
router.post('/deluser', checkToken, async (ctx, next) => {const { username } = ctx.request.bodytry {await User.findOneAndRemove({username: username})ctx.body = {code: 0,msg: '删除成功',}} catch (err) {ctx.body = {code: -1,msg: '删除失败',}}
})module.exports = {router
}

上面实现了五个接口:

  • 发送验证码至邮箱: router.post('/verify')
  • 注册:router.post('/register')
  • 登录:router.post('/login')
  • 获取用户列表:router.get('/alluser')
  • 删除数据库中的某个用户:router.post('/deluser')

分别对应了前面 3.4 中 axios 中的5个请求地址

4.4 JSON Web Token 认证

JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。详情参考:http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html

分别创建 /server/token/createToken.js 和 /server/token/checkToken.js

// 创建token
const jwt = require('jsonwebtoken')module.exports = function (id) {const token = jwt.sign({id: id},'cedric1990',{expiresIn: '300s'})return token
}
// 验证token
const jwt = require('jsonwebtoken')// 检查 token
module.exports = async (ctx, next) => {// 检验是否存在 token// axios.js 中设置了 authorizationconst authorization = ctx.get('Authorization')if (authorization === '') {ctx.throw(401, 'no token detected in http headerAuthorization')}const token = authorization.split(' ')[1]// 检验 token 是否已过期try {await jwt.verify(token, 'cedric1990')} catch (err) {ctx.throw(401, 'invalid token')}await next()
}

4.5 服务端入口

根目录创建 server.js:

// server端启动入口
const Koa = require('koa')
const app =  new Koa();
const mongoose = require('mongoose')
const bodyParser = require('koa-bodyparser')
const session = require('koa-generic-session')
const Redis = require('koa-redis')
const json = require('koa-json') // 美化json格式化
const dbConfig = require('./server/dbs/config')const users = require('./server/interface/user.js').router// 一些session和redis相关配置
app.keys = ['keys', 'keyskeys']
app.proxy = true
app.use(session({store: new Redis()})
)app.use(bodyParser({extendTypes: ['json', 'form', 'text']
}))app.use(json())// 连接数据库
mongoose.connect(dbConfig.dbs,{ useNewUrlParser: true }
)mongoose.set('useNewUrlParser', true)
mongoose.set('useFindAndModify', false)
mongoose.set('useCreateIndex', true)const db = mongoose.connection
mongoose.Promise = global.Promise // 防止Mongoose: mpromise 错误db.on('error', function () {console.log('数据库连接出错')
})db.on('open', function () {console.log('数据库连接成功')
})// 路由中间件
app.use(users.routes()).use(users.allowedMethods())app.listen(8888, () => {console.log('This server is running at http://localhost:' + 8888)
})

5. 跨域处理

详情参考:https://www.cnblogs.com/cckui/p/10331432.html

vue 前端启动端口9527 和 koa 服务端启动端口8888不同,需要做跨域处理,打开vue.config.js:

devServer: {port: 9527,https: false,hotOnly: false,proxy: {'/api': {target: 'http://127.0.0.1:8888/', // 接口地址changeOrigin: true,ws: true,pathRewrite: {'^/': ''}}}}

6. 接口对接

import axios from '../../axios.js'
import CryptoJS from 'crypto-js' // 用于MD5加密处理

发送验证码:

// 用户名不能为空,并且验证邮箱格式
sendCode() {let email = this.ruleForm2.emailif (this.checkEmail(email) && this.ruleForm2.username) {axios.userVerify({username: encodeURIComponent(this.ruleForm2.username),email: this.ruleForm2.email}).then((res) => {if (res.status === 200 && res.data && res.data.code === 0) {this.$notify({title: '成功',message: '验证码发送成功,请注意查收。有效期5分钟',duration: 1000,type: 'success'})let time = 300this.buttonText = '已发送'this.isDisabled = trueif (this.flag) {this.flag = false;let timer = setInterval(() => {time--;this.buttonText = time + ' 秒'if (time === 0) {clearInterval(timer);this.buttonText = '重新获取'this.isDisabled = falsethis.flag = true;}}, 1000)}} else {this.$notify({title: '失败',message: res.data.msg,duration: 1000,type: 'error'})}})}
}

注册:

submitForm(formName) {this.$refs[formName].validate(valid => {if (valid) {axios.userRegister({username: encodeURIComponent(this.ruleForm2.username),password: CryptoJS.MD5(this.ruleForm2.pass).toString(),email: this.ruleForm2.email,code: this.ruleForm2.smscode}).then((res) => {if (res.status === 200) {if (res.data && res.data.code === 0) {this.$notify({title: '成功',message: '注册成功。',duration: 2000,type: 'success'})setTimeout(() => {this.$router.push({path: '/login'})}, 500)} else {this.$notify({title: '错误',message: res.data.msg,duration: 2000,type: 'error'})}} else {this.$notify({title: '错误',message: `服务器请求出错, 错误码${res.status}`,duration: 2000,type: 'error'})}})} else {console.log("error submit!!");return false;}})
},

登录:

login(formName) {this.$refs[formName].validate(valid => {if (valid) {axios.userLogin({username: window.encodeURIComponent(this.ruleForm.name),password: CryptoJS.MD5(this.ruleForm.pass).toString()}).then((res) => {if (res.status === 200) {if (res.data && res.data.code === 0) {this.bindLogin(res.data.token)this.saveUser(res.data.username)this.$notify({title: '成功',message: '恭喜,登录成功。',duration: 1000,type: 'success'})setTimeout(() => {this.$router.push({path: '/'})}, 500)} else {this.$notify({title: '错误',message: res.data.msg,duration: 1000,type: 'error'})}} else {this.$notify({title: '错误',message: '服务器出错,请稍后重试',duration: 1000,type: 'error'})}})}})
},

7. 启动项目 测试接口

7.1 vue端:

$ npm run serve

7.2 启动mogod:

$ mongod

7.3 启动Redis:

$ redis-server

7.4 启动服务端server.js:

安装 nodemon 热启动辅助工具:

$ npm i nodemon
$ nodemon server.js

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

gwt 嵌入html_GWT和HTML5画布演示

gwt 嵌入html这是我对GWT和HTML5 Canvas的第一个实验。 我的第一个尝试是创建矩形&#xff0c;仅用几行代码就得出了这样的内容&#xff1a; 码&#xff1a; public class GwtHtml5 implements EntryPoint {static final String canvasHolderId "canvasholder";sta…

tez什么意思_传统数仓和大数据数仓的区别是什么?

概念与容器为什么先说这个&#xff0c;其实很简单&#xff1a;因为绝大多数人都把这两个概念混为一谈。然后就会出现各种各样的问题&#xff1a;oracle不是数据库么&#xff0c;怎么又是数据仓库&#xff1f;Hive不是数据仓库么&#xff1f;怎么又是数据库&#xff1f;数据仓库…

华为M2平板打不开云课堂_能运行PC应用的安卓生产力平板:华为MatePad Pro 5G登场...

当手机屏幕无法承担更复杂的工作任务&#xff0c;当PC重量不能满足更随性的移动办公&#xff0c;拥有全面屏和便携优势的平板电脑或是中间值&#xff0c;但前提是其必须拥有足够强大的生产力。5月27日华为在国内上市的其首款5G平板——华为MatePad Pro 5G&#xff0c;就是5G时代…

soa示例_SOA示例应用程序

soa示例SOA描述了一组用于创建松散耦合的&#xff0c;基于标准的&#xff0c;与业务相关的服务的模式&#xff0c;由于描述&#xff0c;实现和绑定之间的关注点分离&#xff0c;因此提供了新的灵活性。 近年来&#xff0c;至少在参与大多数信息技术活动的人们中&#xff0c;面向…

body curl 设置post_curl 命令详解

常用参数常用参数分类# 调试类-v, --verbose 输出信息-q, --disable 在第一个参数位置设置后 .curlrc 的设置直接失效&#xff0c;这个参数会影响到 -K, --config -A, --user-agent -e, --referer-K, --config FILE …

NetBeans Java EE技巧7:忽略的Java类和XHTML编辑器快捷方式

有时&#xff0c;最被忽略的是IDE最有用的功能。 在本文中&#xff0c;我将概述在开发Java EE应用程序时可以使用的五个NetBeans Java和XHTML编辑器快捷方式。 &#xff03;1 –轻松修复命名空间和类 也许您已经向尚未声明名称空间的视图中添加了新的JSF标记&#xff0c;或者…

python学习第三十二节(进程间通信、进程池、协程)

当多线程创建完毕之后&#xff0c;start并没有了立刻运行&#xff0c;依旧需要和其他线程抢CPU的资格&#xff0c;只是时间很短。进程之间的通信分为两种&#xff0c;queue和pipe 1 import multiprocessing2 def foo(q):3 q.put([1,hello,True])4 if __name____main__:5 …

备份ad_IT管理公开课——备份恢复解决方案

时间&#xff1a;2020年5月28日 14:00内容&#xff1a;调查显示&#xff0c;44&#xff05;的客户面临意外删除或修改数据&#xff0c;如果没有提前备份&#xff0c;这些数据是很难恢复的。RecoveryManager Plus是一款针对AD域&#xff0c;Exchange&#xff0c;Sharepoint以及O…

Ajax中的url使用规则

Ajax中的url使用规则Ajax中的url使用规则如下&#xff1a; 先封装项目访问地址&#xff1a; String basePath request.getScheme() "://" request.getServerName() ":" request.getServerPort() request.getContextPath();然后在js中定义&#xff1…

[网络管理]全双工与半双工的差别

[网络管理]全双工与半双工的差别 同事说新办公室的网络一直不稳定&#xff0c;常常掉线延迟。检查进口线路和更换转接网线。都无法解决这个问题。预计是不是进口网线中一根或者2根短路&#xff0c;那就改动下网卡属性吧。 把自适应改成全双工10M模式&#xff0c;測试OK。全双工…

echarts大屏模板_完整的可视化大屏分享,科技感十足,各行业直接就能用

你的老板有没有要求过你做一个可视化大屏&#xff1f;或许在你看来&#xff0c;这就是一个无理的需求&#xff0c;很简单啊&#xff0c;做几个动态图表&#xff0c;直接投影到屏幕上不就行了&#xff1f;就算做出来能用数据增长业务吗&#xff1f;不懂为什么要拍脑袋做大屏&…

Java面试基础知识(1)

1、final, finally, finalize的区别final&#xff1a;修饰符&#xff08;关键字&#xff09;如果一个类被声明为final&#xff0c;没有子类也不能被继承。因此一个类不能既被声明为 abstract的&#xff0c;又被声明为final的。将变量或方法声明为final&#xff0c;可以保证它们…

bird 报表_轻松完成Birt报告

bird 报表这是使用Birt插件在Eclipse中构建报告的完整指南。 Birt或Business Intelligence and Reporting工具是一种无需编写太多Java代码即可生成报告的工具。 如果您使用的是ireport&#xff0c;那么您知道我在说什么&#xff1a;&#xff09;&#xff08;晶体报告..毫无意义…

通过kubeadm安装kubernetes 1.7文档记录[docker容器方式]

参照了网上N多文档&#xff0c;不一一列表&#xff0c;共享精神永存&#xff01;&#xff01;&#xff01;&#xff01; 获取所有安装包 安装包分为两类&#xff0c;rpm安装包和docker镜像 rpm安装包 rpm为以下四个 kubeadm-1.7.0-0.x86_64.rpm kubectl-1.7.0-0.x86_64.rpm ku…

san框架计数的textarea

san框架计数的textarea<template><div style"height: 100%;width:100%;"><div style"border-bottom: 1px solid #e7e7e7"><div style"height:230px;overflow: hidden;"><t-textarea id"cxfssy" value"…

京东五星电器送扫地机器人_京东五星电器联手打造互联网小家电“孵化器”

出新迭代迅速、件均价较低的生活小家电复购率高&#xff0c;易“种草”&#xff0c;是最具活力的家电品类。11月24日&#xff0c;京东小家电事业部联合京东五星电器召开“小家电发展战略沟通会”&#xff0c;美的、苏泊尔、九阳、小熊、雀巢、德龙、戴森、飞利浦、科沃斯等众多…

异或运算性质

异或性质**异或运算的性质**

星痕 轻松实现大屏数据可视化_数据美的历程有多难?大屏可视化轻松帮你实现...

看到这个数据可视化大屏&#xff0c;我们如何实现美感呢&#xff1f;正确的姿势必不可少&#xff01;当我们满怀激动地开始数据可视化时&#xff0c;请不要马上钻入某个细节里&#xff0c;不要急着考虑用什么酷炫的图表来展现&#xff0c;也不要纠结于用什么颜色、什么字体。而…

卡方检验检验水准矫正_【2008.】趋势性卡方检验专题讨论

学员提问学员老师&#xff0c;在SPSS中怎样实现线性趋势卡方检验呢&#xff1f;很多学员都为这个问题疑惑&#xff0c;下面我来详细介绍一下。老师1、线性趋势卡方检验的常用方法&#xff1a;线性趋势检验最常用的方法是&#xff1a;Cochran-Armitage Test for Trend(也就是the…

前端布局的一些收获

前端布局的一些收获这段时间做前端样式的改造&#xff0c;有一些收获&#xff0c;来记录一下(吐槽一句IE垃圾) 1.calc()函数 定义与用法 calc() 函数用于动态计算长度值。 需要注意的是&#xff0c;运算符前后都需要保留一个空格&#xff0c;例如&#xff1a;width: calc(10…