This commit is contained in:
2023-03-21 17:56:02 +08:00
parent 995deef4d5
commit eac8674816
23 changed files with 663 additions and 63 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules/
.project
unpackage/
.DS_Store

16
.hbuilderx/launch.json Normal file
View File

@@ -0,0 +1,16 @@
{ // launch.json 配置了启动调试时相关设置configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
// launchtype项可配置值为local或remote, local代表前端连本地云函数remote代表前端连云端云函数
"version": "0.0",
"configurations": [{
"app-plus" :
{
"launchtype" : "local"
},
"default" :
{
"launchtype" : "local"
},
"type" : "uniCloud"
}
]
}

View File

@@ -14,15 +14,53 @@ uni-button:after {
/** iconfont */
@font-face {
font-family: 'iconfont';
src: url('@/static/iconfont/iconfont.woff2?t=1665454521636') format('woff2'),
url('@/static/iconfont/iconfont.woff?t=1665454521636') format('woff'),
url('@/static/iconfont/iconfont.ttf?t=1665454521636') format('truetype');
src: url('@/static/iconfont/iconfont.woff2') format('woff2'),
url('@/static/iconfont/iconfont.woff') format('woff'),
url('@/static/iconfont/iconfont.ttf') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 50rpx;
color: #000;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.delete_icon {
width: 40rpx;
height: 40rpx;
font-size: 40rpx;
line-height: 40rpx;
color: #bbb;
}
.delete_icon::after {
content: '\e632';
}
.eye_colse_icon {
width: 40rpx;
height: 40rpx;
font-size: 40rpx;
line-height: 40rpx;
color: #bbb;
}
.eye_colse_icon::after {
content: '\e7b2';
}
.eye_open_icon {
width: 40rpx;
height: 40rpx;
font-size: 40rpx;
line-height: 40rpx;
color: #bbb;
}
.eye_open_icon::after {
content: '\e7b2';
}
.scan_icon {
width: 80rpx;
height: 80rpx;
font-size: 70rpx;
line-height: 80rpx;
color: #D7592F;
}
.scan_icon::after {
content: '\e607';
}

View File

@@ -38,7 +38,8 @@ _fj(x=space-between,y=center,r=row,n=nowrap)
flex-wrap: n
//
_bis(url,w,h=auto,x=center,y=center)
_bis(c=transparent,url,w,h=auto,x=center,y=center)
background-color: c
background-position: x y
background-size: w h
background-image: url(url)

View File

@@ -1,11 +1,13 @@
import App from './App'
import store from '@/vuex/store.js'
// #ifndef VUE3
import Vue from 'vue'
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App
...App,
store
})
app.$mount()
// #endif
@@ -15,7 +17,8 @@ import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
app,
store
}
}
// #endif

5
package.json Normal file
View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"jsencrypt": "^3.3.2"
}
}

View File

@@ -1,9 +1,15 @@
{
"pages": [ //pages数组中第一项表示应用启动页参考https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "uni-app"
"navigationStyle": "custom"
}
},
{
"path": "pages/home/home",
"style": {
"navigationStyle": "custom"
}
}
],

110
pages/home/home.vue Normal file
View File

@@ -0,0 +1,110 @@
<template>
<view class="content">
<view class="header">
<view class="header-item">
<text class="header-user">登录人员{{userName}}</text>
</view>
<view class="header-item header-r">
<button class="header-button" @click="toExit">退&nbsp;&nbsp;</button>
</view>
</view>
<view class="nav-wrap">
<view class="nav-item"><text class="nav-text" @click="toJump('/pages/equipStatus/equipStatus')">设备状态修改</text></view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
userName: this.$store.getters.userInfo ? JSON.parse(this.$store.getters.userInfo).id : ''
}
},
onLoad() {
},
methods: {
toJump(path) {
uni.navigateTo({
url: path
});
},
toExit () {
this.$store.dispatch('delUserInfo', '')
uni.redirectTo({
url: '/pages/login/login'
})
}
}
}
</script>
<style lang="scss" scoped>
.content{
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
width: 100%;
height: 100%;
}
.header{
display: flex;
width: 100%;
height: 44px;
background-color: #d7592f;
}
.header-item {
width: calc(50% - 10px);
padding: 0 10px;
}
.header-user {
font-size: 16px;
line-height: 44px;
color: #ffffff;
}
.header-r{
display: flex;
justify-content: flex-end;
align-items: center
}
/deep/ .header-r uni-button{
margin-right: 0;
}
.header-button{
width: 80px;
height: 30px;
font-size: 16px;
color: #333333;
line-height: 30px;
}
.nav-wrap{
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-content: center;
width: calc(100% - 40%);
height: calc(100% - 44px);
padding: 0 20%;
}
.nav-item{
display: flex;
justify-content: center;
align-items: center;
width: 30%;
height: 100px;
background-color: #acbec6;
border-radius: 10px;
margin: 7.5px 5% 7.5px 0;
}
.nav-item:nth-child(3n){
margin-right: 0;
}
.nav-text{
font-size: 22px;
line-height: 30px;
color: #ffffff;
letter-spacing: 2px;
}
</style>

View File

@@ -1,52 +0,0 @@
<template>
<view class="content">
<image class="logo" src="/static/logo.png"></image>
<view class="text-area">
<text class="title">{{title}}</text>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: 'Hello'
}
},
onLoad() {
},
methods: {
}
}
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>

215
pages/login/login.vue Normal file
View File

@@ -0,0 +1,215 @@
<template>
<view class="login_bg">
<view class="login_wrap">
<view class="login_tab">
<view class="login_tab_line drift" :style="{'left': drift+'%'}"></view>
<view class="login_tab_item" @tap="_tabChange(0)">登录</view>
<view class="login_tab_item" @tap="_tabChange(50)">配置</view>
</view>
<view class="login_cnt drift" :style="{'left': '-'+drift*2+'%'}">
<view class="login_card">
<view class="card_wrap">
<view class="inputOuter">
<input type="text" placeholder="用户名" v-model="loginname" class="inputStyle">
<text v-show="showBtn3" class="iconfont login_icon delete_icon" @tap="clearData(3)"></text>
</view>
<view class="inputOuter">
<input :type="inputType" placeholder="密码" v-model="password" class="inputStyle">
<text class="iconfont login_icon" :class="eyeOpen ? 'eye_colse_icon' : 'eye_open_icon'" @click="passwordShow"></text>
</view>
</view>
<view class="submit">
<button class="primary-button" :disabled="disabled" @click="toLogin">&nbsp;&nbsp;</button>
</view>
<view class="scanBox">
<view class="iconfont scan_icon"></view>
<text class="san_text">扫码登录</text>
</view>
</view>
<view class="login_card">
<view class="card_wrap">
<view class="inputOuter">
<input type="text" placeholder="域名地址" v-model="baseUrl" class="inputStyle">
<text v-show="showBtn1" class="iconfont login_icon delete_icon" @tap="clearData(1)"></text>
</view>
<view class="inputOuter">
<input type="text" placeholder="图片域名地址" v-model="imgBaseUrl" class="inputStyle">
<text v-show="showBtn2" class="iconfont login_icon delete_icon" @tap="clearData(2)"></text>
</view>
</view>
<view class="submit">
<button class="primary-button" :disabled="disabled" @tap="toConfig">&nbsp;&nbsp;</button>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { RSAencrypt } from '@/utils/jsencrypt.js'
import {handLogin} from '@/utils/getData2.js'
export default {
data() {
return {
loginname: '',
password: '',
baseUrl: this.$store.getters.baseUrl,
imgBaseUrl: this.$store.getters.imgBaseUrl,
inputType: 'password',
eyeOpen: true,
drift: 0,
disabled: false,
jobnum: '',
qrcode: '',
logintype: ''
};
},
computed: {
showBtn1 () {
return this.baseUrl !== ''
},
showBtn2 () {
return this.imgBaseUrl !== ''
},
showBtn3 () {
return this.loginname !== ''
}
},
methods: {
_tabChange (num) {
this.drift = num
},
clearData (t) {
switch (t) {
case 1:
this.baseUrl = ''
break
case 2:
this.imgBaseUrl = ''
break
case 3:
this.loginname = ''
break
}
},
passwordShow () {
this.eyeOpen = !this.eyeOpen
if (this.eyeOpen) {
this.inputType = 'password'
} else {
this.inputType = 'text'
}
},
toConfig () {
let obj = {
baseUrl: this.baseUrl,
imgBaseUrl: this.imgBaseUrl
}
this.$store.dispatch('setConfig', obj)
this._tabChange(0)
},
async toLogin() {
this.disabled = true
if (this.loginname === '') {
uni.showToast({
title: '用户名不能为空',
icon: 'none'
})
this.disabled = false
return
}
if (this.password === '') {
uni.showToast({
title: '密码不能为空',
icon: 'none'
})
this.disabled = false
return
}
try {
let res = await handLogin(this.loginname, RSAencrypt(this.password))
this.$store.dispatch('saveUserInfo', JSON.stringify(res.user.user))
this.$store.dispatch('saveToken', res.token)
uni.redirectTo({
url: '/pages/home/home'
})
this.disabled = false
} catch (e) {
this.disabled = false
}
}
}
}
</script>
<style lang="stylus">
@import '../../common/style/mixin.styl';
.login_bg
_wh(100%, 100%)
_bis(#fff,'../../static/images/bg_01.png', 100%,,bottom)
.login_wrap
position fixed
left 50%
top 50%
width 44%
transform translate3d(-50%, -50%, 0)
border-radius 5px
overflow hidden
.login_tab
position relative
height 100rpx
border-bottom 1px solid #E2E2E2
margin-bottom 40rpx
.login_tab_item
float left
width 50%
_font(32rpx,100rpx,#444444,,center)
cursor pointer
.login_tab_line
position absolute
width 50%
height 2px
background-color #D7592F
left 0
bottom -1px
.login_cnt
position relative
width 200%
overflow hidden
.login_card
width 50%
float left
.card_wrap
overflow hidden
.inputOuter
position relative
width 100%
margin 40rpx auto
.inputStyle
_wh(100%,80rpx)
_font(30rpx,80rpx,#999,,)
text-indent 20rpx
border-bottom 1px solid #E2E2E2
box-sizing border-box
.login_icon
position absolute
top 20rpx
right 20rpx
.submit
width 100%
margin 40rpx auto
text-align center
.primary-button
_wh(100%, 80rpx)
background-color #d7592f
border-radius 80rpx
_font(32rpx,80rpx,#fff,,center)
.scanBox
_fj()
flex-direction column
.san_text
_font(32rpx,1,#D7592F,,center)
.drift
transition left .3s linear
</style>

BIN
static/.DS_Store vendored

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
static/images/bg_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

0
utils/getData1.js Normal file
View File

10
utils/getData2.js Normal file
View File

@@ -0,0 +1,10 @@
import request from './request.js'
// 登录
export const handLogin = (user, password) => request({
url:'mobile/auth/login',
data: {
username: user,
password: password
}
})

12
utils/jsencrypt.js Normal file
View File

@@ -0,0 +1,12 @@
import JSEncrypt from '../node_modules/jsencrypt/bin/jsencrypt.js'
let publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANL378k3RiZHWx5AfJqdH9xRNBmD9wGD\n' +
'2iRe41HdTNF8RUhNnHit5NpMNtGL0NPTSSpPjjI1kJfVorRvaQerUgkCAwEAAQ==';
function RSAencrypt(pas){
//实例化jsEncrypt对象
let jse = new JSEncrypt();
//设置公钥
jse.setPublicKey(publicKey);
return jse.encrypt(pas);
}
export {RSAencrypt}

127
utils/request.js Normal file
View File

@@ -0,0 +1,127 @@
// import qs from 'qs' // 处理data
import store from '@/vuex/store'
const request = (params) => {
let _self = this;
let url = params.url;
let method = params.method || 'POST';
let data = params.data || {};
// data.token = "default-access_token"
// if (!params.token) {
// let token = uni.getStorageSync('token');
// if (!token) {
// uni.navigateTo({
// url: '/pages/login/login'
// });
// } else {
// data.token = '179509245-9c91827e0224bdc18d0b118b8be1b5af';
// }
// }
let token = ''
if (store.getters.saveToken !== '') {
token = store.getters.saveToken
}
let defaultOpot = {
// 'Content-Type': 'application/x-www-form-urlencoded',
'Terminal-Type': 'innerH5',
'Content-Type': 'application/json;charset=UTF-8',
}
let header = {}
method = method.toUpperCase()
if (method == 'POST') {
header = {
'Content-Type': 'application/json;charset=UTF-8',
'Authorization': token
}
// data = qs.stringify(data)
}
const requestUrl = `${store.getters.baseUrl}/` + url;
uni.showLoading({
title: '加载中...'
});
return new Promise((resolve, reject) => {
uni.request({
url: requestUrl,
method: method,
header: Object.assign({}, defaultOpot, header),
data: data,
dataType: 'json',
})
.then(res => { // 成功
if (res.length === 1) {
uni.showModal({
content: 'request:fail',
showCancel: false
})
reject('request:fail')
} else if (res[1] && res[1].statusCode === 400) {
uni.showModal({
content: res[1].data.message,
showCancel: false
})
reject(res[1].data.message)
} else if (res[1] && res[1].statusCode === 401) {
uni.showModal({
content: res[1].data.message,
showCancel: false
})
store.dispatch('delUserInfo')
uni.redirectTo({
url: '/pages/login/login'
})
} else if (res[1] && res[1].statusCode === 200) {
let {
data: dataType
} = res[1]
resolve(dataType)
// switch (dataType.code * 1) { // 拦截返回参数
// case 0:
// resolve(dataType)
// break;
// case 1003:
// uni.showModal({
// title: '登录已过期',
// content: '很抱歉,登录已过期,请重新登录',
// confirmText: '重新登录',
// success: function(res) {
// if (res.confirm) {
// uni.navigateTo({
// // 切记这儿需要哈pages.json保持一致不能有.vue后缀
// url: '/pages/login/login'
// });
// } else if (res.cancel) {
// console.log('用户点击取消');
// }
// }
// })
// break;
// case -1:
// uni.showModal({
// title: '请求数据失败',
// content: '获取数据失败!',
// confirmText: '确定',
// showCancel: false,
// success: function(res) {
// if (res.confirm) {} else if (res.cancel) {
// console.log('用户点击取消');
// }
// }
// })
// break
// }
}else {
uni.showModal({
content: res[1].data.message,
showCancel: false
})
reject(res[1].data.message)
}
})
.catch(err => { // 错误
reject(err)
})
.finally(() => {
uni.hideLoading();
})
})
}
export default request

77
vuex/modules/user.js Normal file
View File

@@ -0,0 +1,77 @@
import * as types from '../types'
const baseUrl = process.env.NODE_ENV === 'development' ? 'http://192.168.81.252:8010' : 'http://192.168.81.252:8010'
const imgBaseUrl = process.env.NODE_ENV === 'development' ? 'http://192.168.81.252:8010' : 'http://192.168.81.252:8010'
const state = {
baseUrl: uni.getStorageSync('baseUrl') || baseUrl,
imgBaseUrl: uni.getStorageSync('imgBaseUrl') || imgBaseUrl,
setTime: uni.getStorageSync('setTime') || 5000,
loginName: uni.getStorageSync('loginName') ? uni.getStorageSync('loginName') : '',
userInfo: uni.getStorageSync('userInfo') ? uni.getStorageSync('userInfo') : '',
saveToken: uni.getStorageSync('saveToken') || ''
}
const getters = {
baseUrl: state => state.baseUrl,
imgBaseUrl: state => state.imgBaseUrl,
setTime: state => state.setTime,
loginName: state => state.loginName,
userInfo: state => state.userInfo,
saveToken: state => state.saveToken
}
const actions = {
setConfig ({commit}, res) {
uni.setStorageSync('baseUrl', res.baseUrl)
uni.setStorageSync('imgBaseUrl', res.imgBaseUrl)
// uni.setStorageSync('setTime', res.setTime)
commit(types.COM_CONFIG, res)
},
saveLoginName({commit}, res) {
uni.setStorageSync('loginName', res)
commit(types.SAVE_LOGIN_NAME, res)
},
delLoginName({commit}, res) {
uni.clearStorageSync('loginName')
commit(types.DEL_LOGIN_NAME, res)
},
saveUserInfo({commit}, res) {
uni.setStorageSync('userInfo', res)
commit(types.SAVE_USER_INFO, res)
},
delUserInfo({commit}, res) {
uni.clearStorageSync('userInfo')
commit(types.DEL_USER_INFO, res)
},
saveToken({commit}, res) {
uni.setStorageSync('saveToken', res)
commit(types.SAVE_TOKEN, res)
}
}
const mutations = {
[types.COM_CONFIG] (state, res) {
state.baseUrl = res.baseUrl
state.imgBaseUrl = res.imgBaseUrl
// state.setTime = res.setTime
},
[types.SAVE_LOGIN_NAME] (state, res) {
state.loginName = res
},
[types.DEL_LOGIN_NAME] (state, res) {
state.loginName = res
},
[types.SAVE_USER_INFO] (state, res) {
state.userInfo = res
},
[types.DEL_USER_INFO] (state, res) {
state.userInfo = res
},
[types.SAVE_TOKEN] (state, res) {
state.saveToken = res
}
}
export default {
state,
getters,
actions,
mutations
}

12
vuex/store.js Normal file
View File

@@ -0,0 +1,12 @@
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
user
}
})

8
vuex/types.js Normal file
View File

@@ -0,0 +1,8 @@
/**
* user
*/
export const SAVE_LOGIN_NAME = 'SAVE_LOGIN_NAME'
export const DEL_LOGIN_NAME = 'DEL_LOGIN_NAME'
export const COM_CONFIG = 'COM_CONFIG'
export const SAVE_USER_INFO = 'SAVE_USER_INFO'
export const DEL_USER_INFO = 'DEL_USER_INFO'

8
yarn.lock Normal file
View File

@@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
jsencrypt@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/jsencrypt/-/jsencrypt-3.3.2.tgz#b0f1a2278810c7ba1cb8957af11195354622df7c"
integrity sha512-arQR1R1ESGdAxY7ZheWr12wCaF2yF47v5qpB76TtV64H1pyGudk9Hvw8Y9tb/FiTIaaTRUyaSnm5T/Y53Ghm/A==