【私域操盘手】手机短信验证码登录

This commit is contained in:
eison
2025-03-16 19:24:37 +08:00
parent 1d7a87f29f
commit 5ce78d648e
39 changed files with 10304 additions and 13996 deletions

View File

@@ -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
View 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
View 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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -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')

View File

@@ -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'),
},
],
}

View 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)
}
}

View File

@@ -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)
}
/**
* 异常拦截处理器
*

View File

@@ -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>

View 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>