【私域操盘手】账号密码登录

This commit is contained in:
eison
2025-03-16 17:43:30 +08:00
parent f4e36f1921
commit 1d7a87f29f
30 changed files with 1474 additions and 97 deletions

View File

@@ -3,4 +3,6 @@ VUE_APP_PREVIEW=false
VUE_APP_API_BASE_URL=http://yishi.com
VUE_APP_WWW_BASE_URL=http://yishi.com
VUE_APP_WEB_SOCKET_URL=ws://yishi.com:2348
VUE_APP_WEBSITE_NAME="管理后台"
VUE_APP_WEBSITE_NAME="管理后台"
VUE_APP_TITLE=医师管理系统
VUE_APP_API_URL=http://yishi.com

View File

@@ -1,6 +1,7 @@
NODE_ENV=development
VUE_APP_PREVIEW=true
VUE_APP_API_BASE_URL=http://yishi.com
VUE_APP_WWW_BASE_URL=http://yishi.com
VUE_APP_WEB_SOCKET_URL=ws://yishi.com:2348
VUE_APP_WEBSITE_NAME="管理后台"
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=医师管理系统

View File

@@ -1,12 +1,18 @@
import { post } from '@/utils/request'
import { post, get } from '@/utils/request'
// 登录服务接口
export const ServeLogin = data => {
return post('/backend/user/login', data)
return post('/api/auth/login', data)
}
// 获取用户信息
export const ServeGetUser = () => {
return post('/backend/user/get')
return get('/api/auth/info')
}
// 刷新token
export const ServeRefreshToken = () => {
return post('/api/auth/refresh')
}
export const ServeSetUserPassword = (data) => {
@@ -14,8 +20,9 @@ export const ServeSetUserPassword = (data) => {
}
// 退出登录服务接口
export const ServeLogout = data => {
return post('/backend/user/logout', data)
export const ServeLogout = () => {
// JWT不需要服务端登出直接清除本地token即可
return Promise.resolve({ code: 200, msg: '退出成功' })
}
export const UserIndex = data => {

View File

@@ -7,7 +7,7 @@ export default {
{
path: '/auth/login',
meta: {
title: '账号登录',
title: '账号登录',
needLogin: false,
},
component: () => import('@/views/auth/login'),

View File

@@ -6,6 +6,8 @@ import SystemRouter from './system'
import ProductRouter from '@/router/product';
import TaskRouter from '@/router/task';
import DeviceRouter from '@/router/device';
import { isLogin } from '@/utils/auth'
import store from '@/store'
const originalPush = Router.prototype.push
Router.prototype.push = function push(location) {
@@ -46,7 +48,33 @@ const routes = [
},
]
export default new Router({
const router = new Router({
routes,
mode: 'hash',
})
// 全局前置守卫
router.beforeEach((to, from, next) => {
// 设置页面标题
document.title = to.meta.title ? to.meta.title : '医视管理系统'
// 检查路由是否需要登录
if (to.meta.needLogin) {
// 检查登录状态
if (isLogin()) {
next()
} else {
// 未登录,重定向到登录页
next({ path: '/auth/login' })
}
} else {
// 如果是访问登录页且已登录,则跳转到首页
if (to.path === '/auth/login' && isLogin()) {
next({ path: '/' })
} else {
next()
}
}
})
export default router

View File

@@ -1,56 +1,55 @@
import { setUserInfo, getUserInfo, removeAll, getToken } from '@/utils/auth'
import { setUserInfo, getUserInfo, removeAll, getToken, isLogin } from '@/utils/auth'
import { ServeLogout } from '@/api/user'
import { ServeLogout, ServeRefreshToken } from '@/api/user'
let state = {
// 用户ID
id: 0,
// 渠道ID
channel_id: 0,
// 渠道名称
channel_name: '',
// 角色
role: '',
// 权限
permissions: [],
// 账号
username: '',
// 姓名
name: '',
// 手机号
mobile: '',
// 登录时间
login_time: '',
// 登录次数
login_count: 0,
// 登录IP
login_ip: '',
// 创建时间
create_time: '',
// 个性头像
avatar: require('@/assets/image/detault-avatar.jpg'),
// 角色
roles: [],
// 当前登录状态
loginStatus: false,
// 是否启动游戏模块
// 原有字段保持不变
channel_id: 0,
channel_name: '',
mobile: '',
login_time: '',
login_count: 0,
login_ip: '',
create_time: '',
mod_game: false,
}
// 判断用户是否登录
if (getToken()) {
if (isLogin()) {
let userInfo = getUserInfo()
state.id = userInfo.id
state.channel_id = userInfo.channel_id
state.channel_name = userInfo.channel_name
state.username = userInfo.username
state.name = userInfo.name
state.mobile = userInfo.mobile
state.login_time = userInfo.login_time
state.login_count = userInfo.login_count
state.login_ip = userInfo.login_ip
state.create_time = userInfo.create_time
state.roles = userInfo.roles ? userInfo.roles: []
//state.avatar = userInfo.avatar ? userInfo.avatar : state.avatar
// 更新状态
state.id = userInfo.id
state.username = userInfo.username
state.name = userInfo.name
state.role = userInfo.role
state.permissions = userInfo.permissions || []
state.loginStatus = true
state.mod_game = userInfo.mod_game
// 兼容原有字段
state.channel_id = userInfo.channel_id || 0
state.channel_name = userInfo.channel_name || ''
state.mobile = userInfo.mobile || ''
state.login_time = userInfo.login_time || ''
state.login_count = userInfo.login_count || 0
state.login_ip = userInfo.login_ip || ''
state.create_time = userInfo.create_time || ''
state.mod_game = userInfo.mod_game || false
state.roles = userInfo.roles || []
console.log('userInfo: ', userInfo)
}
@@ -60,24 +59,28 @@ const User = {
mutations: {
// 用户退出登录
USER_LOGOUT(state) {
state.id = 0
state.channel_id = 0
state.channel_name = ''
state.username = ''
state.name = ''
state.mobile = ''
state.login_time = ''
state.login_count = 0
state.login_ip = ''
state.create_time = ''
state.roles = []
state.id = 0
state.username = ''
state.name = ''
state.role = ''
state.permissions = []
state.loginStatus = false
state.mod_game = false
// 原有字段重置
state.channel_id = 0
state.channel_name = ''
state.mobile = ''
state.login_time = ''
state.login_count = 0
state.login_ip = ''
state.create_time = ''
state.roles = []
state.mod_game = false
},
// 设置用户登录状态
UPDATE_LOGIN_STATUS(state) {
state.loginStatus = true
UPDATE_LOGIN_STATUS(state, status) {
state.loginStatus = status
},
// 更新用户信息
@@ -91,18 +94,20 @@ const User = {
// 保存用户信息到缓存
setUserInfo({
id: state.id,
channel_id: state.channel_id,
channel_name: state.channel_name,
username: state.username,
name: state.name,
role: state.role,
permissions: state.permissions || [],
// 兼容原有字段
channel_id: state.channel_id,
channel_name: state.channel_name,
mobile: state.mobile,
login_time: state.login_time,
login_count: state.login_count,
login_ip: state.login_ip,
create_time: state.create_time,
roles: state.roles ? state.roles: [],
roles: state.roles || [],
mod_game: state.mod_game,
//avatar: state.avatar,
})
},
},
@@ -115,6 +120,25 @@ const User = {
location.reload()
})
},
// 刷新令牌
ACT_REFRESH_TOKEN({ commit }) {
return new Promise((resolve, reject) => {
ServeRefreshToken()
.then(res => {
if (res.code === 200) {
// 更新token
setToken(res.data.token, res.data.token_expired - Math.floor(Date.now() / 1000))
resolve(res)
} else {
reject(res)
}
})
.catch(error => {
reject(error)
})
})
},
},
}

View File

@@ -7,8 +7,8 @@ const USER_SETTING = 'MANAGE_SETTING'
/**
* 设置用户授权token
*
* @param {String} token
* @param {Number} expires
* @param {String} token - JWT令牌
* @param {Number} expires - 过期时间戳(秒)
*/
export function setToken(token, expires) {
expires = new Date().getTime() + expires * 1000
@@ -23,6 +23,7 @@ export function setToken(token, expires) {
/**
* 获取授权token
* @returns {String} token
*/
export function getToken() {
const result = JSON.parse(
@@ -33,9 +34,24 @@ export function getToken() {
})
)
// 检查token是否过期
if (result.expires > 0 && result.expires < new Date().getTime()) {
// token已过期清除token
removeAll()
return ''
}
return result.token
}
/**
* 检查用户是否登录
* @returns {Boolean}
*/
export function isLogin() {
return !!getToken()
}
/**
* 设置用户信息
*

View File

@@ -39,7 +39,8 @@ const errorHandler = error => {
request.interceptors.request.use(config => {
const token = getToken()
if (token) {
config.headers['token'] = token
// 设置JWT认证头
config.headers['Authorization'] = 'Bearer ' + token
}
return config

View File

@@ -35,7 +35,7 @@
</div>
</template>
<script>
import { setToken } from '@/utils/auth'
import { setToken, setUserInfo } from '@/utils/auth'
import { ServeLogin } from '@/api/user'
export default {
@@ -85,16 +85,24 @@ export default {
let result = res.data
// 保存授权信息到本地缓存
setToken(result.token, result.token_expired)
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')
this.$store.commit('UPDATE_LOGIN_STATUS', true)
// 登录成功后连接 WebSocket 服务器
this.$notify.success({
title: '成功',
message: '登录成功',
})
// 跳转到首页
this.toLink('/')
} else {
this.$notify.info({
title: '提示',
this.$notify.error({
title: '错误',
message: res.msg,
})
}

View File

@@ -0,0 +1,247 @@
<template>
<el-container v-loading="loading" style="height: 100%;">
<el-header style="display: flex; align-items: center; padding: 0;">
<div style="margin-left: 20px;">
<el-page-header @back="goBack" :content="'设备ID: ' + deviceId"></el-page-header>
</div>
</el-header>
<el-main>
<el-card v-if="deviceInfo" class="device-card">
<div slot="header" class="card-header">
<span>设备详情</span>
<el-tag :type="getStatusType(deviceInfo.status)" class="status-tag">
{{ getStatusText(deviceInfo.status) }}
</el-tag>
</div>
<el-row :gutter="20">
<el-col :span="12">
<div class="info-item">
<span class="label">设备ID</span>
<span class="value">{{ deviceInfo.id }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<span class="label">IMEI</span>
<span class="value">{{ deviceInfo.imei }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="info-item">
<span class="label">设备手机号</span>
<span class="value">{{ deviceInfo.phone || '--' }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<span class="label">电量</span>
<span class="value">
<el-progress
:percentage="deviceInfo.battery"
:color="getBatteryColor(deviceInfo.battery)"
:format="() => `${deviceInfo.battery}%`"
style="width: 200px;">
</el-progress>
</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="info-item">
<span class="label">微信ID</span>
<span class="value">{{ deviceInfo.wechat_id || '--' }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<span class="label">微信号</span>
<span class="value">{{ deviceInfo.wechat_name || '--' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="info-item">
<span class="label">分组名称</span>
<span class="value">{{ deviceInfo.group_name || '--' }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<span class="label">保管者</span>
<span class="value">{{ deviceInfo.username || '--' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<div class="info-item">
<span class="label">备注</span>
<span class="value">{{ deviceInfo.remark || '--' }}</span>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="info-item">
<span class="label">创建时间</span>
<span class="value">{{ formatTime(deviceInfo.create_time) }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="info-item">
<span class="label">更新时间</span>
<span class="value">{{ formatTime(deviceInfo.update_time) }}</span>
</div>
</el-col>
</el-row>
</el-card>
<el-empty v-else description="未找到设备信息"></el-empty>
</el-main>
</el-container>
</template>
<script>
// 引入API请求
import request from '@/utils/request'
export default {
name: 'DeviceDetail',
data() {
return {
// 设备ID
deviceId: null,
// 设备信息
deviceInfo: null,
// 加载状态
loading: false
}
},
created() {
// 获取路由参数中的设备ID
this.deviceId = this.$route.params.id
// 加载设备详情
this.getDeviceDetail()
},
methods: {
// 返回列表页
goBack() {
this.$router.push('/device/index')
},
// 获取设备详情
getDeviceDetail() {
if (!this.deviceId) {
this.$message.error('设备ID不能为空')
return
}
this.loading = true
request({
url: `/api/devices/detail/${this.deviceId}`,
method: 'get'
}).then(res => {
this.loading = false
if (res.code === 200) {
this.deviceInfo = res.data
} else {
this.$message.error(res.msg || '获取设备详情失败')
}
}).catch(() => {
this.loading = false
})
},
// 格式化时间
formatTime(timestamp) {
if (!timestamp) return '--'
const date = new Date(timestamp * 1000)
const year = date.getFullYear()
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
const hour = date.getHours().toString().padStart(2, '0')
const minute = date.getMinutes().toString().padStart(2, '0')
const second = date.getSeconds().toString().padStart(2, '0')
return `${year}-${month}-${day} ${hour}:${minute}:${second}`
},
// 获取状态对应的类型
getStatusType(status) {
const statusMap = {
'0': 'info', // 离线
'1': 'success', // 在线
'2': 'danger', // 故障
'3': 'warning' // 维修中
}
return statusMap[status] || 'info'
},
// 获取状态对应的文本
getStatusText(status) {
const statusMap = {
'0': '离线',
'1': '在线',
'2': '故障',
'3': '维修中'
}
return statusMap[status] || status
},
// 获取电量对应的颜色
getBatteryColor(battery) {
if (battery < 20) {
return '#F56C6C' // 红色,电量低
} else if (battery < 50) {
return '#E6A23C' // 黄色,电量中等
} else {
return '#67C23A' // 绿色,电量充足
}
}
}
}
</script>
<style scoped>
.el-header {
border-bottom: 1px solid #e6e6e6;
background-color: #fff;
}
.device-card {
margin-top: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.status-tag {
margin-left: 10px;
}
.info-item {
margin-bottom: 15px;
display: flex;
align-items: center;
}
.label {
color: #606266;
font-weight: bold;
margin-right: 10px;
min-width: 80px;
display: inline-block;
}
.value {
color: #303133;
}
</style>