新增手机号短信发送&短信发送日志记录管理

This commit is contained in:
不做码农 2023-11-19 21:18:56 +08:00
parent 8151ba9d2f
commit 181ba7fa21
6 changed files with 348 additions and 30 deletions

View File

@ -94,3 +94,25 @@ export function verifyScan(data) {
data: data data: data
}) })
} }
/**
* 发送短信验证码
* @param {*} data
* @returns
*/
export function checkMobile(data) {
return request({
method: 'post',
data: data,
url: '/checkMobile'
})
}
// 登录方法
export function phoneLogin(data) {
return request({
url: '/phoneLogin',
method: 'POST',
data: data
})
}

View File

@ -0,0 +1,40 @@
import request from '@/utils/request'
import { downFile } from '@/utils/request'
/**
* 短信验证码记录分页查询
* @param {查询条件} data
*/
export function listSmscodeLog(query) {
return request({
url: 'system/SmscodeLog/list',
method: 'get',
params: query
})
}
/**
* 获取短信验证码记录详情
* @param {Id}
*/
export function getSmscodeLog(id) {
return request({
url: 'system/SmscodeLog/' + id,
method: 'get'
})
}
/**
* 删除短信验证码记录
* @param {主键} pid
*/
export function delSmscodeLog(pid) {
return request({
url: 'system/SmscodeLog/' + pid,
method: 'delete'
})
}
// 导出短信验证码记录
export async function exportSmscodeLog(query) {
await downFile('system/SmscodeLog/export', { ...query })
}

View File

@ -1,4 +1,4 @@
import { login, logout, getInfo, oauthCallback } from '@/api/system/login' import { login, logout, getInfo, oauthCallback, phoneLogin } from '@/api/system/login'
import { getToken, setToken, removeToken } from '@/utils/auth' import { getToken, setToken, removeToken } from '@/utils/auth'
import useTagsViewStore from './tagsView' import useTagsViewStore from './tagsView'
import defAva from '@/assets/images/profile.jpg' import defAva from '@/assets/images/profile.jpg'
@ -84,6 +84,25 @@ const useUserStore = defineStore('user', {
resolve(data.token) //then处理 resolve(data.token) //then处理
}) })
}, },
// 手机号登录
phoneNumLogin(userInfo) {
return new Promise((resolve, reject) => {
phoneLogin(userInfo)
.then((res) => {
if (res.code == 200) {
setToken(res.data)
this.token = res.data
resolve() //then处理
} else {
console.log('login error ', res)
reject(res) //catch处理
}
})
.catch((error) => {
reject(error)
})
})
},
// 获取用户信息 // 获取用户信息
getInfo() { getInfo() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@ -26,7 +26,7 @@ service.interceptors.request.use(
//将token放到请求头发送给服务器,将tokenkey放在请求头中 //将token放到请求头发送给服务器,将tokenkey放在请求头中
config.headers['Authorization'] = 'Bearer ' + getToken() config.headers['Authorization'] = 'Bearer ' + getToken()
config.headers['userid'] = useUserStore().userId config.headers['userid'] = useUserStore().userId
config.headers['userName'] = useUserStore().userName config.headers['userName'] = encodeURIComponent(useUserStore().userName)
} }
const method = config?.method || 'get' const method = config?.method || 'get'
const header = config?.headers['Content-Type'] ?? '' const header = config?.headers['Content-Type'] ?? ''
@ -93,7 +93,7 @@ service.interceptors.response.use(
let { message, response } = error let { message, response } = error
if (response.status == 403) { if (response.status == 403) {
window.location.href = '/401' window.location.href = import.meta.env.VITE_APP_ROUTER_PREFIX + '401'
} else if (message == 'Network Error') { } else if (message == 'Network Error') {
message = '后端接口连接异常' message = '后端接口连接异常'
} else if (message.includes('timeout')) { } else if (message.includes('timeout')) {

View File

@ -7,6 +7,16 @@
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="code" v-if="captchaOnOff != 'off'" :style="{ 'margin-top': captchaOnOff == 'off' ? '40px' : '' }">
<el-input v-model="loginForm.code" auto-complete="off" :placeholder="$t('login.captcha')" style="width: 63%" @keyup.enter="handleLogin">
<template #prefix>
<svg-icon name="validCode" class="input-icon" />
</template>
</el-input>
<div class="login-code">
<el-image :src="codeUrl" @click="getCode" class="login-code-img" />
</div>
</el-form-item>
<el-form-item prop="phoneCode"> <el-form-item prop="phoneCode">
<el-input v-model="loginForm.phoneCode" type="number" auto-complete="off" :placeholder="$t('login.phoneCode')" @keyup.enter="handleLogin"> <el-input v-model="loginForm.phoneCode" type="number" auto-complete="off" :placeholder="$t('login.phoneCode')" @keyup.enter="handleLogin">
<template #prefix> <template #prefix>
@ -18,16 +28,6 @@
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="code" v-if="captchaOnOff != 'off'">
<el-input v-model="loginForm.code" auto-complete="off" :placeholder="$t('login.captcha')" style="width: 63%" @keyup.enter="handleLogin">
<template #prefix>
<svg-icon name="validCode" class="input-icon" />
</template>
</el-input>
<div class="login-code">
<el-image :src="codeUrl" @click="getCode" class="login-code-img" />
</div>
</el-form-item>
<el-form-item style="width: 100%" :style="{ 'margin-top': captchaOnOff == 'off' ? '40px' : '' }"> <el-form-item style="width: 100%" :style="{ 'margin-top': captchaOnOff == 'off' ? '40px' : '' }">
<el-button :loading="loading" size="default" round type="primary" style="width: 100%" @click.prevent="handleLogin"> <el-button :loading="loading" size="default" round type="primary" style="width: 100%" @click.prevent="handleLogin">
@ -39,15 +39,14 @@
</template> </template>
<script setup name="phonelogin"> <script setup name="phonelogin">
import { getCodeImg } from '@/api/system/login' import { getCodeImg, checkMobile } from '@/api/system/login'
// import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
const userStore = useUserStore()
const route = useRoute() const route = useRoute()
const router = useRouter()
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const loginForm = ref({ const loginForm = ref({
password: '',
rememberMe: false,
code: '', code: '',
uuid: '', uuid: '',
phoneCode: '', phoneCode: '',
@ -67,16 +66,6 @@ const captchaOnOff = ref('')
const redirect = ref() const redirect = ref()
redirect.value = route.query.redirect redirect.value = route.query.redirect
function handleLogin() {
proxy.$refs.loginRef.validate((valid) => {
if (valid) {
loading.value = true
// cookie
proxy.$modal.msg('敬请期待')
}
})
}
function getCode() { function getCode() {
getCodeImg().then((res) => { getCodeImg().then((res) => {
codeUrl.value = 'data:image/gif;base64,' + res.data.img codeUrl.value = 'data:image/gif;base64,' + res.data.img
@ -87,9 +76,53 @@ function getCode() {
const showCounddown = ref(false) const showCounddown = ref(false)
const countdownValue = ref(0) const countdownValue = ref(0)
function handleSendCode() { function handleSendCode() {
const updateArr = ['phoneNum', 'code']
proxy.$refs.loginRef.validateField(updateArr, async (valid) => {
//
if (!valid) {
// proxy.$modal.msg('')
return
} else {
checkMobile(loginForm.value)
.then((res) => {
if (res.code == 200) {
showCounddown.value = true showCounddown.value = true
countdownValue.value = Date.now() + 1000 * 60 countdownValue.value = Date.now() + 1000 * 60
} }
})
.catch((err) => {
console.log(err)
// proxy.$modal.msgError(err.msg)
//
if (captchaOnOff.value) {
getCode()
}
})
}
})
}
function handleLogin() {
proxy.$refs.loginRef.validate((valid) => {
if (valid) {
loading.value = true
userStore
.phoneNumLogin(loginForm.value)
.then(() => {
proxy.$modal.msgSuccess(proxy.$t('login.loginSuccess'))
router.push({ path: redirect.value || '/' })
})
.catch((error) => {
console.error(error)
// proxy.$modal.msgError(error.msg)
loading.value = false
})
}
})
}
function handleFinish() { function handleFinish() {
showCounddown.value = false showCounddown.value = false
} }

View File

@ -0,0 +1,204 @@
<!--
* @Descripttion: (短信验证码记录/smsCode_log)
* @Author: (zz)
* @Date: (2023-11-19)
-->
<template>
<div>
<el-form :model="queryParams" label-position="right" inline ref="queryRef" v-show="showSearch" @submit.prevent>
<el-form-item label="用户id" prop="userid">
<el-input v-model.number="queryParams.userid" placeholder="请输入用户id" />
</el-form-item>
<el-form-item label="手机号" prop="phoneNum">
<el-input v-model.number="queryParams.phoneNum" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="发送时间">
<el-date-picker
v-model="dateRangeAddTime"
type="datetimerange"
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD HH:mm:ss"
:default-time="defaultTime"
:shortcuts="dateOptions">
</el-date-picker>
</el-form-item>
<el-form-item label="发送类型" prop="sendType">
<el-select clearable v-model="queryParams.sendType" placeholder="请选择发送类型">
<el-option v-for="item in options.sendTypeOptions" :key="item.dictValue" :label="item.dictLabel" :value="item.dictValue">
<span class="fl">{{ item.dictLabel }}</span>
<span class="fr" style="color: var(--el-text-color-secondary)">{{ item.dictValue }}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button icon="search" type="primary" @click="handleQuery">{{ $t('btn.search') }}</el-button>
<el-button icon="refresh" @click="resetQuery">{{ $t('btn.reset') }}</el-button>
</el-form-item>
</el-form>
<!-- 工具区域 -->
<el-row :gutter="15" class="mb10">
<el-col :span="1.5">
<el-button type="warning" plain icon="download" @click="handleExport" v-hasPermi="['smscodelog:export']">
{{ $t('btn.export') }}
</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
</el-row>
<el-table
:data="dataList"
v-loading="loading"
ref="table"
border
header-cell-class-name="el-table-header-cell"
highlight-current-row
@sort-change="sortChange">
<el-table-column prop="id" label="Id" align="center" width="170" v-if="columns.showColumn('id')" />
<el-table-column prop="userid" label="用户id" align="center" v-if="columns.showColumn('userid')" />
<el-table-column prop="userIP" label="用户IP" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('userIP')" />
<el-table-column prop="location" label="位置" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('userIP')" />
<el-table-column prop="phoneNum" label="手机号" align="center" v-if="columns.showColumn('phoneNum')" />
<el-table-column prop="smsCode" label="短信验证码" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('smsCode')" />
<el-table-column prop="smsContent" label="短信内容" align="center" :show-overflow-tooltip="true" v-if="columns.showColumn('smsContent')" />
<el-table-column prop="addTime" label="发送时间" :show-overflow-tooltip="true" v-if="columns.showColumn('addTime')" />
<el-table-column prop="sendType" label="发送类型" align="center" v-if="columns.showColumn('sendType')">
<template #default="scope">
<dict-tag :options="options.sendTypeOptions" :value="scope.row.sendType" />
</template>
</el-table-column>
<el-table-column label="操作" width="60">
<template #default="scope">
<el-button
type="danger"
size="small"
icon="delete"
title="删除"
v-hasPermi="['smscodelog:delete']"
@click="handleDelete(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
<pagination :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</div>
</template>
<script setup name="smscodelog">
import { listSmscodeLog, delSmscodeLog } from '@/api/system/smscodelog.js'
const { proxy } = getCurrentInstance()
const ids = ref([])
const loading = ref(false)
const showSearch = ref(true)
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
sort: 'Id',
sortType: 'desc',
userid: undefined,
phoneNum: undefined,
addTime: undefined,
sendType: undefined
})
const columns = ref([
{ visible: true, prop: 'id', label: 'Id' },
{ visible: true, prop: 'smsCode', label: '短信验证码' },
{ visible: true, prop: 'userid', label: '用户id' },
{ visible: true, prop: 'phoneNum', label: '手机号' },
{ visible: true, prop: 'smsContent', label: '短信内容' },
{ visible: true, prop: 'addTime', label: '发送时间' },
{ visible: true, prop: 'userIP', label: '用户IP' },
{ visible: true, prop: 'sendType', label: '发送类型' }
])
const total = ref(0)
const dataList = ref([])
const queryRef = ref()
const defaultTime = ref([new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)])
//
const dateRangeAddTime = ref([])
var dictParams = []
function getList() {
proxy.addDateRange(queryParams, dateRangeAddTime.value, 'AddTime')
loading.value = true
listSmscodeLog(queryParams).then((res) => {
const { code, data } = res
if (code == 200) {
dataList.value = data.result
total.value = data.totalNum
loading.value = false
}
})
}
//
function handleQuery() {
queryParams.pageNum = 1
getList()
}
//
function resetQuery() {
//
dateRangeAddTime.value = []
proxy.resetForm('queryRef')
handleQuery()
}
//
function sortChange(column) {
var sort = undefined
var sortType = undefined
if (column.prop != null && column.order != null) {
sort = column.prop
sortType = column.order
}
queryParams.sort = sort
queryParams.sortType = sortType
handleQuery()
}
/*************** form操作 ***************/
const state = reactive({
single: true,
multiple: true,
form: {},
options: {
// eg:{ dictLabel: '', dictValue: '0'}
sendTypeOptions: [{ dictLabel: '登录', dictValue: '1' }]
}
})
const { form, options } = toRefs(state)
//
function handleDelete(row) {
const Ids = row.id || ids.value
proxy
.$confirm('是否确认删除参数编号为"' + Ids + '"的数据项?')
.then(function () {
return delSmscodeLog(Ids)
})
.then(() => {
getList()
proxy.$modal.msgSuccess('删除成功')
})
}
//
function handleExport() {
proxy
.$confirm('是否确认导出短信验证码记录数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
await proxy.downFile('/system/SmscodeLog/export', { ...queryParams })
})
}
handleQuery()
</script>