【私域操盘手】手机短信验证码登录
This commit is contained in:
@@ -1,7 +1,14 @@
|
||||
# 环境标识
|
||||
NODE_ENV=development
|
||||
VUE_APP_PREVIEW=false
|
||||
VUE_APP_API_BASE_URL=http://localhost:8000
|
||||
VUE_APP_WWW_BASE_URL=http://localhost:8000
|
||||
VUE_APP_WEB_SOCKET_URL=ws://localhost:2348
|
||||
VUE_APP_WEBSITE_NAME="医师管理系统开发环境"
|
||||
VUE_APP_TITLE=医师管理系统
|
||||
# 应用名称
|
||||
VUE_APP_WEBSITE_NAME=医师管理系统
|
||||
|
||||
# API基础URL
|
||||
VUE_APP_API_BASE_URL=http://yishi.com
|
||||
|
||||
# 前端网站
|
||||
VUE_APP_WWW_BASE_URL=http://yishi.com
|
||||
|
||||
# WebSocket URL
|
||||
VUE_APP_WEB_SOCKET_URL=wss://api.yishi.com/ws
|
||||
14
Backend/.env.production
Normal file
14
Backend/.env.production
Normal file
@@ -0,0 +1,14 @@
|
||||
# 环境标识
|
||||
NODE_ENV=production
|
||||
|
||||
# 应用名称
|
||||
VUE_APP_WEBSITE_NAME=医师管理系统
|
||||
|
||||
# API基础URL
|
||||
VUE_APP_API_BASE_URL=https://api.yishi.com
|
||||
|
||||
# 前端网站URL
|
||||
VUE_APP_WWW_BASE_URL=https://www.yishi.com
|
||||
|
||||
# WebSocket URL
|
||||
VUE_APP_WEB_SOCKET_URL=wss://api.yishi.com/ws
|
||||
14
Backend/.env.test
Normal file
14
Backend/.env.test
Normal file
@@ -0,0 +1,14 @@
|
||||
# 环境标识
|
||||
NODE_ENV=production
|
||||
|
||||
# 应用名称
|
||||
VUE_APP_WEBSITE_NAME=医师管理系统(测试)
|
||||
|
||||
# API基础URL
|
||||
VUE_APP_API_BASE_URL=https://test-api.yishi.com
|
||||
|
||||
# 前端网站URL
|
||||
VUE_APP_WWW_BASE_URL=https://test.yishi.com
|
||||
|
||||
# WebSocket URL
|
||||
VUE_APP_WEB_SOCKET_URL=wss://test-api.yishi.com/ws
|
||||
@@ -85,4 +85,82 @@ server {
|
||||
#### 联系方式
|
||||
QQ : 837215079
|
||||
|
||||
### 如果你觉得还不错,请 Star , Fork 给作者鼓励一下。
|
||||
### 如果你觉得还不错,请 Star , Fork 给作者鼓励一下。
|
||||
|
||||
## 环境变量配置
|
||||
|
||||
本项目使用Vue CLI的环境变量配置功能,可以在不同环境下使用不同的配置。
|
||||
|
||||
### 配置文件
|
||||
|
||||
- `.env` - 所有环境的默认配置
|
||||
- `.env.development` - 开发环境配置
|
||||
- `.env.test` - 测试环境配置
|
||||
- `.env.production` - 生产环境配置
|
||||
|
||||
### 环境变量
|
||||
|
||||
项目中使用的主要环境变量:
|
||||
|
||||
- `VUE_APP_WEBSITE_NAME` - 网站名称
|
||||
- `VUE_APP_API_BASE_URL` - API基础URL
|
||||
- `VUE_APP_WWW_BASE_URL` - 前端网站URL
|
||||
- `VUE_APP_WEB_SOCKET_URL` - WebSocket URL
|
||||
|
||||
### 使用方法
|
||||
|
||||
在代码中可以通过以下方式访问环境变量:
|
||||
|
||||
```js
|
||||
// 直接访问
|
||||
console.log(process.env.VUE_APP_API_BASE_URL)
|
||||
|
||||
// 通过配置文件访问
|
||||
import config from '@/config/config'
|
||||
console.log(config.BASE_API_URL)
|
||||
```
|
||||
|
||||
### 运行与构建
|
||||
|
||||
开发环境:
|
||||
```bash
|
||||
# 使用开发环境配置运行
|
||||
npm run serve:dev
|
||||
|
||||
# 使用开发环境配置构建
|
||||
npm run build:dev
|
||||
```
|
||||
|
||||
测试环境:
|
||||
```bash
|
||||
# 使用测试环境配置运行
|
||||
npm run serve:test
|
||||
|
||||
# 使用测试环境配置构建
|
||||
npm run build:test
|
||||
```
|
||||
|
||||
生产环境:
|
||||
```bash
|
||||
# 使用生产环境配置运行
|
||||
npm run serve:prod
|
||||
|
||||
# 使用生产环境配置构建
|
||||
npm run build:prod
|
||||
```
|
||||
|
||||
## 项目设置
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 启动开发服务器
|
||||
npm run serve
|
||||
|
||||
# 构建生产版本
|
||||
npm run build
|
||||
|
||||
# 代码检查
|
||||
npm run lint
|
||||
```
|
||||
20427
Backend/package-lock.json
generated
20427
Backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,24 @@
|
||||
{
|
||||
"name": "YiShi",
|
||||
"version": "0.1.0",
|
||||
"name": "yishi-admin",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"serve:dev": "vue-cli-service serve --mode development",
|
||||
"serve:test": "vue-cli-service serve --mode test",
|
||||
"serve:prod": "vue-cli-service serve --mode production",
|
||||
"build": "vue-cli-service build",
|
||||
"build:dev": "vue-cli-service build --mode development",
|
||||
"build:test": "vue-cli-service build --mode test",
|
||||
"build:prod": "vue-cli-service build --mode production",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.21.0",
|
||||
"axios": "^0.21.1",
|
||||
"babel-plugin-prismjs": "^2.0.1",
|
||||
"core-js": "^3.6.5",
|
||||
"element-ui": "^2.14.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
"element-ui": "^2.15.6",
|
||||
"js-audio-recorder": "^1.0.6",
|
||||
"js-base64": "^2.5.1",
|
||||
"mavon-editor": "^2.9.0",
|
||||
@@ -22,12 +29,14 @@
|
||||
"vue-contextmenujs": "^1.3.13",
|
||||
"vue-cropper": "^0.5.5",
|
||||
"vue-prism-editor": "^0.5.1",
|
||||
"vue-router": "^3.4.9",
|
||||
"vuex": "^3.5.1"
|
||||
"vue-router": "^3.2.0",
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-plugin-import": "^1.13.1",
|
||||
|
||||
@@ -1,10 +1,34 @@
|
||||
import { post, get } from '@/utils/request'
|
||||
|
||||
// 登录服务接口
|
||||
export const ServeLogin = data => {
|
||||
/**
|
||||
* 用户登录
|
||||
* @param {Object} data 登录数据
|
||||
* @param {string} data.username 用户名
|
||||
* @param {string} data.password 密码
|
||||
* @param {boolean} data.is_encrypted 密码是否已加密
|
||||
* @returns {Promise} 登录结果
|
||||
*/
|
||||
export function ServeLogin(data) {
|
||||
return post('/api/auth/login', data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号验证码登录
|
||||
* @param {Object} data 登录数据
|
||||
* @param {string} data.mobile 手机号
|
||||
* @param {string} data.code 验证码
|
||||
* @param {boolean} data.is_encrypted 验证码是否已加密
|
||||
* @returns {Promise} 登录结果
|
||||
*/
|
||||
export function ServeMobileLogin(data) {
|
||||
return post('/api/auth/mobile-login', data)
|
||||
}
|
||||
|
||||
// 发送验证码
|
||||
export const ServeSendCode = data => {
|
||||
return post('/api/auth/code', data)
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
export const ServeGetUser = () => {
|
||||
return get('/api/auth/info')
|
||||
|
||||
@@ -12,5 +12,13 @@ export default {
|
||||
},
|
||||
component: () => import('@/views/auth/login'),
|
||||
},
|
||||
{
|
||||
path: '/auth/mobile-login',
|
||||
meta: {
|
||||
title: '手机号登录',
|
||||
needLogin: false,
|
||||
},
|
||||
component: () => import('@/views/auth/mobile-login'),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
36
Backend/src/utils/crypto.js
Normal file
36
Backend/src/utils/crypto.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import CryptoJS from 'crypto-js'
|
||||
|
||||
/**
|
||||
* 密码加密工具类
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* MD5加密
|
||||
* @param {string} text 需要加密的文本
|
||||
* @returns {string} 加密后的文本
|
||||
*/
|
||||
md5(text) {
|
||||
return CryptoJS.MD5(text).toString()
|
||||
},
|
||||
|
||||
/**
|
||||
* SHA256加密
|
||||
* @param {string} text 需要加密的文本
|
||||
* @returns {string} 加密后的文本
|
||||
*/
|
||||
sha256(text) {
|
||||
return CryptoJS.SHA256(text).toString()
|
||||
},
|
||||
|
||||
/**
|
||||
* 密码加密
|
||||
* 使用SHA256加密,可以根据需要修改为其他算法
|
||||
* @param {string} password 原始密码
|
||||
* @returns {string} 加密后的密码
|
||||
*/
|
||||
encryptPassword(password) {
|
||||
// 可以添加自定义的盐值增加安全性
|
||||
const salt = 'yishi_salt_2024'
|
||||
return this.sha256(password + salt)
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,12 @@ const request = axios.create({
|
||||
timeout: 20000,
|
||||
})
|
||||
|
||||
// 输出当前环境和API基础URL(仅在开发环境)
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('当前环境:', process.env.NODE_ENV)
|
||||
console.log('API基础URL:', config.BASE_API_URL)
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常拦截处理器
|
||||
*
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
>立即登录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<div class="login-options">
|
||||
<span @click="toMobileLogin">手机号登录</span>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -37,6 +40,7 @@
|
||||
<script>
|
||||
import { setToken, setUserInfo } from '@/utils/auth'
|
||||
import { ServeLogin } from '@/api/user'
|
||||
import CryptoUtil from '@/utils/crypto'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -76,9 +80,13 @@ export default {
|
||||
},
|
||||
|
||||
login() {
|
||||
// 对密码进行加密
|
||||
const encryptedPassword = CryptoUtil.encryptPassword(this.form.password)
|
||||
|
||||
ServeLogin({
|
||||
username: this.form.username,
|
||||
password: this.form.password,
|
||||
password: encryptedPassword,
|
||||
is_encrypted: true // 标记密码已加密
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code == 200) {
|
||||
@@ -112,6 +120,10 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
toMobileLogin() {
|
||||
this.$router.push('/auth/mobile-login')
|
||||
},
|
||||
|
||||
toLink(url) {
|
||||
this.$router.push({
|
||||
path: url,
|
||||
@@ -122,4 +134,12 @@ export default {
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import '~@/assets/css/page/login-auth.less';
|
||||
|
||||
.login-options {
|
||||
text-align: right;
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
color: #409EFF;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
239
Backend/src/views/auth/mobile-login.vue
Normal file
239
Backend/src/views/auth/mobile-login.vue
Normal file
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<div id="login-box">
|
||||
<div class="header">手机号登录</div>
|
||||
<div class="main">
|
||||
<el-form ref="form" :model="form" :rules="rules">
|
||||
<el-form-item prop="mobile">
|
||||
<el-input
|
||||
v-model="form.mobile"
|
||||
placeholder="手机号"
|
||||
class="cuborder-radius"
|
||||
maxlength="11"
|
||||
@keyup.enter.native="onSubmit('form')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code">
|
||||
<div class="code-input">
|
||||
<el-input
|
||||
v-model="form.code"
|
||||
placeholder="验证码"
|
||||
class="cuborder-radius"
|
||||
maxlength="6"
|
||||
@keyup.enter.native="onSubmit('form')"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="code-btn"
|
||||
:disabled="codeBtnDisabled"
|
||||
@click="sendCode"
|
||||
>{{ codeBtnText }}</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="submit-btn"
|
||||
:loading="loginLoading"
|
||||
@click="onSubmit('form')"
|
||||
>立即登录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<div class="login-options">
|
||||
<span @click="toAccountLogin">账号密码登录</span>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { setToken, setUserInfo } from '@/utils/auth'
|
||||
import { ServeMobileLogin, ServeSendCode } from '@/api/user'
|
||||
import CryptoUtil from '@/utils/crypto'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
// 手机号验证规则
|
||||
const validateMobile = (rule, value, callback) => {
|
||||
if (!value) {
|
||||
return callback(new Error('请输入手机号'))
|
||||
}
|
||||
if (!/^1[3-9]\d{9}$/.test(value)) {
|
||||
return callback(new Error('手机号格式不正确'))
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
return {
|
||||
loginLoading: false,
|
||||
form: {
|
||||
mobile: '',
|
||||
code: '',
|
||||
},
|
||||
rules: {
|
||||
mobile: [
|
||||
{ required: true, message: '手机号不能为空', trigger: 'blur' },
|
||||
{ validator: validateMobile, trigger: 'blur' }
|
||||
],
|
||||
code: [
|
||||
{ required: true, message: '验证码不能为空', trigger: 'blur' },
|
||||
{ min: 4, max: 6, message: '验证码长度必须在4-6位之间', trigger: 'blur' }
|
||||
],
|
||||
},
|
||||
codeBtnText: '获取验证码',
|
||||
codeBtnDisabled: false,
|
||||
countdown: 60,
|
||||
timer: null
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 组件销毁前清除定时器
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSubmit(formName) {
|
||||
if (this.loginLoading) return false
|
||||
|
||||
this.$refs[formName].validate(valid => {
|
||||
if (!valid) return false
|
||||
this.loginLoading = true
|
||||
this.login()
|
||||
})
|
||||
},
|
||||
|
||||
login() {
|
||||
// 不再对验证码进行加密,直接使用明文
|
||||
ServeMobileLogin({
|
||||
mobile: this.form.mobile,
|
||||
code: this.form.code,
|
||||
is_encrypted: false // 标记验证码未加密
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code == 200) {
|
||||
let result = res.data
|
||||
|
||||
// 保存授权信息到本地缓存
|
||||
setToken(result.token, result.token_expired - Math.floor(Date.now() / 1000))
|
||||
|
||||
// 保存用户信息到本地缓存
|
||||
setUserInfo(result.member)
|
||||
|
||||
this.$store.commit('UPDATE_USER_INFO', result.member)
|
||||
this.$store.commit('UPDATE_LOGIN_STATUS', true)
|
||||
|
||||
this.$notify.success({
|
||||
title: '成功',
|
||||
message: '登录成功',
|
||||
})
|
||||
|
||||
// 跳转到首页
|
||||
this.toLink('/')
|
||||
} else {
|
||||
this.$notify.error({
|
||||
title: '错误',
|
||||
message: res.msg,
|
||||
})
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loginLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
// 发送验证码
|
||||
sendCode() {
|
||||
if (this.codeBtnDisabled) return
|
||||
|
||||
// 验证手机号
|
||||
this.$refs.form.validateField('mobile', (errorMsg) => {
|
||||
if (errorMsg) return
|
||||
|
||||
this.codeBtnDisabled = true
|
||||
|
||||
// 发送验证码请求
|
||||
ServeSendCode({
|
||||
mobile: this.form.mobile,
|
||||
type: 'login'
|
||||
}).then(res => {
|
||||
if (res.code === 200) {
|
||||
this.$notify.success({
|
||||
title: '成功',
|
||||
message: '验证码发送成功',
|
||||
})
|
||||
|
||||
// 开始倒计时
|
||||
this.startCountdown()
|
||||
|
||||
// 测试环境下,自动填充验证码
|
||||
if (res.data && res.data.code) {
|
||||
this.form.code = res.data.code
|
||||
}
|
||||
} else {
|
||||
this.$notify.error({
|
||||
title: '错误',
|
||||
message: res.msg,
|
||||
})
|
||||
this.codeBtnDisabled = false
|
||||
}
|
||||
}).catch(() => {
|
||||
this.codeBtnDisabled = false
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 开始倒计时
|
||||
startCountdown() {
|
||||
this.countdown = 60
|
||||
this.codeBtnText = `${this.countdown}秒后重新获取`
|
||||
|
||||
this.timer = setInterval(() => {
|
||||
if (this.countdown > 1) {
|
||||
this.countdown--
|
||||
this.codeBtnText = `${this.countdown}秒后重新获取`
|
||||
} else {
|
||||
clearInterval(this.timer)
|
||||
this.codeBtnDisabled = false
|
||||
this.codeBtnText = '获取验证码'
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
// 跳转到账号密码登录
|
||||
toAccountLogin() {
|
||||
this.$router.push('/auth/login')
|
||||
},
|
||||
|
||||
toLink(url) {
|
||||
this.$router.push({
|
||||
path: url,
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import '~@/assets/css/page/login-auth.less';
|
||||
|
||||
.code-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.el-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.code-btn {
|
||||
margin-left: 10px;
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-options {
|
||||
text-align: right;
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
color: #409EFF;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user