Files
jnpf_app/pages/login/index.vue

904 lines
22 KiB
Vue
Raw Normal View History

2026-01-04 11:09:06 +08:00
<template>
<view class="logo-v">
<view class="login-bg">
<image src="../../static/image/login-bg.jpg" mode="widthFix"></image>
<view class="logoImg">
2026-01-19 17:34:15 +08:00
<image src="../../static/image/logoT.png" mode="widthFix"></image>
<!-- <u-image src="../../static/image/logo.png" mode="widthFix" :border-radius="20" width="160" height="160">
2026-01-04 11:09:06 +08:00
<template #error>
<u-image :src="logoImg" mode="widthFix" width="160" height="160">
</u-image>
</template>
2026-01-19 17:34:15 +08:00
</u-image> -->
2026-01-04 11:09:06 +08:00
</view>
2026-01-19 17:34:15 +08:00
<!-- <view class="login-version">
2026-01-04 11:09:06 +08:00
<view class="login-version-text">{{sysConfigInfo.sysVersion || define.sysVersion}}</view>
2026-01-19 17:34:15 +08:00
</view> -->
2026-01-04 11:09:06 +08:00
</view>
<view class="logo-hd u-flex-col">
<view class="loginSwitch u-flex-col">
2026-01-25 20:22:58 +08:00
<view class="loginInputBox u-flex-col" v-show=" true || !isSso && !ssoLoading">
2026-01-04 11:09:06 +08:00
<u-form :model="formData" :rules="rules" ref="dataForm" :errorType="['toast']" label-position="left"
label-width="150" label-align="left">
2026-01-19 17:34:15 +08:00
<u-form-item v-show="false" prop="account" :borderBottom="false">
<u-input input-align='left' v-model="formData.tenantName" placeholder="请输入租户名称" @focus="onFocus"
@blur="onBlur" border border-color="#F0F1F3" placeholder-style="#9D9D9D">
</u-input>
</u-form-item>
<u-form-item prop="username" :borderBottom="false">
<u-input input-align='left' v-model="formData.username" placeholder="请输入帐号" @focus="onFocus"
2026-01-04 11:09:06 +08:00
@blur="onBlur" border border-color="#F0F1F3" placeholder-style="#9D9D9D">
</u-input>
</u-form-item>
<u-form-item prop="password" :border-bottom="false">
<u-input input-align='left' v-model="formData.password" type="password" placeholder="请输入密码"
border border-color="#F0F1F3" placeholder-style="#9D9D9D">
</u-input>
</u-form-item>
<u-form-item prop="code" required v-if="needCode">
<view class="u-flex code-box">
<u-input v-model="formData.code" placeholder="验证码" input-align='left'></u-input>
<view class="code-img-box">
<u-image :showLoading="true" :src="baseURL+imgUrl" width="130px" height="38px"
@click="changeCode">
</u-image>
</view>
</view>
</u-form-item>
</u-form>
<view class="remember-wrap">
<u-checkbox v-model="remember"><span class="remember-text">记住账号密码</span></u-checkbox>
</view>
2026-01-25 20:22:58 +08:00
<!-- <view class="remember-wrap">
2026-01-23 11:09:28 +08:00
<u-checkbox v-model="isCertify"><span class="remember-text">是否认证</span></u-checkbox>
2026-01-25 20:22:58 +08:00
</view> -->
2026-01-04 11:09:06 +08:00
<view class="loginBtnBox">
<u-button @click="login" type="primary" :loading="loading">{{ loading ? "登录中...":"登录"}}
</u-button>
</view>
<template v-if="socialsList.length">
<u-divider margin-top='40' margin-bottom='40' half-width='100%'>其他登录方式</u-divider>
<view class="other-list">
<block v-for="(item,i) in socialsList" :key="i">
<!--#ifdef H5 || MP-WEIXIN -->
<view class="other-item" v-if="item.enname==='wechat_open'" :title="item.name"
@click="wechatLogin()"><text :class="item.icon" />
</view>
<view class="other-item" v-else-if="item.enname==='qq'" :title="item.name"
@click="otherslogin(item.enname,item.renderUrl)"><text :class="item.icon" />
</view>
<!-- #endif -->
<!--#ifdef APP-->
<view class="other-item" v-if="item.enname==='qq'" :title="item.name"
@click="qqOtherlogin()"><text :class="item.icon" />
</view>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view class="other-item" v-else :title="item.name"
@click="otherslogin(item.enname,item.renderUrl)"><text :class="item.icon" />
</view>
<!-- #endif -->
</block>
</view>
</template>
</view>
<view class="sso-login-btn" v-show="isSso && !ssoLoading">
<u-button @click="ssoLogin" type="primary" :loading="loading">{{ loading ? "登录中...":"登录"}}
</u-button>
</view>
</view>
</view>
<u-popup v-model="show" mode="left" width="90%" height="100%">
<view class="mian">
<view class='top'>
<view class='img-box'>
<image class="img" src="/static/image/tabbar/contactsHL.png" mode="widthFix"></image>
</view>
<view class='title'>
请选择登录账号
</view>
</view>
<view v-for="(item,i) in tenantUserInfo" :key="i">
<view class='info' @click="socailsLogin(item)">
<view class='user-name'>
{{item.socialName}}
</view>
<view class='user-tenancy'>
租户名称 {{item.tenantName}}
</view>
<view class='user-tenancy'>
租户id{{item.tenantId}}
</view>
<view class='user-tenancy'>
账号{{item.accountName}}
</view>
</view>
</view>
</view>
</u-popup>
2026-01-19 17:34:15 +08:00
<view class="copyright">{{copyright}}</view>
2026-01-04 11:09:06 +08:00
</view>
</template>
<script>
import {
login,
2026-01-23 11:09:28 +08:00
socialLogin,
2026-01-19 17:34:15 +08:00
getPermissionInfo,
getByName,
2026-01-04 11:09:06 +08:00
getCallback,
otherlogin,
getLoginConfig,
getSocialsUserList,
socialsLogin,
2026-01-25 20:22:58 +08:00
getTicket,
socialAuthRedirect
2026-01-04 11:09:06 +08:00
} from '@/api/common.js'
import md5Libs from "@/uni_modules/vk-uview-ui/libs/function/md5";
import resources from '@/libs/resources'
import {
useUserStore
} from '@/store/modules/user'
import logoImg from '@/static/logo.png'
2026-01-23 11:09:28 +08:00
let unique = 0
2026-01-04 11:09:06 +08:00
export default {
data() {
return {
remember: false,
2026-01-23 11:09:28 +08:00
isCertify: false,
2026-01-04 11:09:06 +08:00
logoImg,
imgUrl: '',
loading: false,
formData: {
2026-01-19 17:34:15 +08:00
tenantName: "000000",
username: "",
2026-01-04 11:09:06 +08:00
password: "",
code: "",
origin: 'password'
},
needCode: false,
codeLength: 4,
isCode: false,
rules: {
2026-01-19 17:34:15 +08:00
username: [{
2026-01-04 11:09:06 +08:00
required: true,
message: '请输入账号',
trigger: 'blur',
}],
password: [{
required: true,
message: '请输入密码',
trigger: 'blur',
}],
},
sysConfigInfo: {},
appIcon: '',
2026-01-25 20:22:58 +08:00
sysName: '综合监控系统',
2026-01-19 17:34:15 +08:00
copyright: '综合监控系统',
2026-01-04 11:09:06 +08:00
socialsList: [],
show: false,
tenantUserInfo: [],
ssoLoading: true,
isSso: false,
ssoTicket: '',
ssoUrl: '',
preUrl: '',
ticketParams: "",
loginCode: '',
2026-01-25 20:22:58 +08:00
isKeyUp: true,
ssoOptions: {} // 端点登录参数
2026-01-04 11:09:06 +08:00
}
},
watch: {
remember: {
handler(val) {
let model = uni.getStorageSync('rememberAccount')
if (!model) model = {
account: '',
password: ''
}
model.remember = val
uni.setStorageSync('rememberAccount', model)
},
deep: true,
}
},
computed: {
baseURL() {
return this.define.baseURL
},
},
onReady() {
this.$refs.dataForm.setRules(this.rules);
},
onLoad(options) {
2026-01-25 20:22:58 +08:00
this.ssoOptions = options
if(options.code){
this.exchangeToken(options)
}else if(options.socialLogin){
this.getLogin()
2026-01-04 11:09:06 +08:00
}
2026-01-25 20:22:58 +08:00
else{
this.certifyLogin()
}
// if(JSON.stringify(options) == '{}'){
// // 端点登录
// this.certifyLogin()
// }else {
// this.getLogin()
// }
// if (options?.JNPF_TICKET) {
// this.ssoTicket = options.JNPF_TICKET
// uni.navigateTo({
// url: `/pages/login/otherLogin?ssoTicket=${this.ssoTicket}`
// })
// }
// this.ssoTicket = uni.getStorageSync('ssoTicket')
// this.sysConfigInfo = uni.getStorageSync('sysConfigInfo')
// console.log(this.define,'define-------------')
// this.appIcon = !!this.sysConfigInfo.appIcon ? this.baseURL + this.sysConfigInfo.appIcon :
// logoImg
// this.sysName = !!this.sysConfigInfo.companyName ? this.sysConfigInfo.sysName :
// '综合监控系统'
// // this.copyright = !!this.sysConfigInfo.copyright ? this.sysConfigInfo.copyright :
// // this.define.copyright
// // this.copyright = !!this.sysConfigInfo.copyright && this.sysConfigInfo.copyright
2026-01-04 11:09:06 +08:00
uni.setNavigationBarTitle({
title: this.sysName
})
2026-01-25 20:22:58 +08:00
// let needCode = uni.getStorageSync('app_loginNeedCode')
// this.isCode = needCode
// this.changeCode()
2026-01-19 17:34:15 +08:00
// this.getLoginConfig()
2026-01-25 20:22:58 +08:00
// this.formData.password = '';
// if (options.data) {
// this.tenantUserInfo = JSON.parse(options.data)
// // if (this.tenantUserInfo) this.show = true
// }
// this.initAccount()
// // #ifndef H5
// uni.onKeyboardHeightChange(res => {
// this.isKeyUp = res.height == 0 ? true : false
// return this.isKeyUp
// })
// // #endif
2026-01-19 17:34:15 +08:00
2026-01-25 20:22:58 +08:00
// this.initLoginConfig()
2026-01-04 11:09:06 +08:00
},
methods: {
initAccount() {
let model = uni.getStorageSync('rememberAccount')
if (model && model.remember) {
if (model.account) {
this.formData.account = this.jnpf.aesEncryption.decrypt(model.account)
}
if (model.password) {
this.formData.password = this.jnpf.aesEncryption.decrypt(model.password)
}
}
this.remember = model.remember
},
rememberAccount() {
// 是否记住密码
if (this.remember) {
let model = {};
model.remember = true
model.account = this.jnpf.aesEncryption.encrypt(this.formData.account)
model.password = this.jnpf.aesEncryption.encrypt(this.formData.password)
uni.setStorageSync('rememberAccount', model)
}
},
loginHandel() {
uni.showLoading({
title: '登录中'
})
userStore.getCurrentUser().then((res) => {
uni.hideLoading()
uni.reLaunch({
url: '/pages/index/index'
});
}).catch(() => {
uni.hideLoading()
uni.reLaunch({
url: '/pages/login/index'
});
})
},
loginToken(res) {
const userStore = useUserStore()
userStore.setToken(res.data.value)
if (res.data.status != 2) {
// 登录成功
if (res.data.status == 1) return this.loginHandel()
if (res.data.status == 6) {
this.tenantUserInfo = JSON.parse(res.data.value)
if (this.tenantUserInfo.length == 1) {
this.loginHandel()
} else {
2026-01-19 17:34:15 +08:00
// this.show = true
2026-01-04 11:09:06 +08:00
}
} else {
this.show = false
this.ssoUrl = ''
uni.showToast({
title: res.data.value || '操作超时,请重新点击登录',
icon: 'none'
})
}
}
},
wechatLogin() {
// #ifdef MP-WEIXIN
getTicket().then(res => {
this.ssoTicket = res.data
uni.login({
provider: 'weixin',
success: (loginRes) => {
this.loginCode = loginRes.code
}
})
uni.getUserProfile({
desc: '获取你的昵称、头像、地区及性别',
success: (info) => {
let qurey = {
encryptedData: info.encryptedData,
iv: info.iv,
signature: info.signature,
code: this.loginCode,
jnpf_ticket: this.ssoTicket,
socialName: info.userInfo.nickName,
source: 'wechat_applets'
}
socialsLogin(qurey).then(res => {
this.loginToken(res)
})
}
})
})
// #endif
// #ifdef APP-PLUS
getTicket().then(res => {
this.ssoTicket = res.data
uni.login({
provider: 'weixin',
success: (loginRes) => {
// 登录成功
uni.getUserProfile({
provider: 'weixin',
success: (info) => {
let data = {
source: 'wechat_open',
uuid: info.userInfo.unionId,
socialName: info.userInfo.nickName,
jnpf_ticket: this.ssoTicket
};
socialsLogin(data).then(res => {
this.loginToken(res)
})
},
fail: function(err) {
// 登录授权失败
// err.code是错误码
}
})
}
});
})
// #endif
},
qqOtherlogin() {
getTicket().then(res => {
this.ssoTicket = res.data
uni.login({
provider: 'qq',
success: (loginRes) => {
// 登录成功
uni.getUserInfo({
provider: 'qq',
success: (info) => {
let data = {
source: 'qq',
jnpf_ticket: this.ssoTicket,
socialName: info.userInfo.nickName,
uuid: info.userInfonickName.unionid,
};
socialsLogin(data).then(res => {
this.loginToken(res)
}).catch((err) => {})
// 获取用户信息成功, info.authResult保存用户信息
}
})
}
});
})
},
socailsLogin(item) {
const userStore = useUserStore()
item.tenantLogin = true
socialsLogin(item).then(res => {
if (res.code == 200) {
uni.showLoading({
title: '登录中'
})
userStore.setToken(res.data.token)
userStore.getCurrentUser().then((res) => {
uni.hideLoading()
uni.switchTab({
url: '/pages/index/index'
});
this.show = false
}).catch(() => {
uni.hideLoading()
uni.switchTab({
url: '/pages/login/index'
});
})
}
}).catch(() => {
uni.hideLoading()
uni.switchTab({
url: '/pages/login/index'
});
})
},
otherslogin(key, url) {
if (key === 'wechat_open') {
this.wechatLogin();
} else {
getTicket().then(res => {
this.ssoTicket = res.data
url = url.replace('JNPF_TICKET', this.ssoTicket)
uni.setStorageSync('ssoUrl', url)
uni.navigateTo({
url: `/pages/login/otherLogin?ssoTicket=` + this.ssoTicket
})
}).catch(() => {})
}
},
onFocus(e) {
this.getCodeConfig(e)
},
onBlur(e) {
this.getCodeConfig(e)
},
// 获取登陆配置
getLoginConfig() {
getLoginConfig().then(res => {
this.isSso = res.data.redirect
this.preUrl = res.data.url
this.ticketParams = res.data.ticketParams
let socialsList = res.data.socialsList || []
this.socialsList = socialsList.filter(o => o.latest && o.enname !=
'github' && o
.enname !=
'wechat_enterprise')
this.ssoLoading = false
}).catch(() => {
this.isSso = false
this.ssoLoading = false
})
},
2026-01-19 17:34:15 +08:00
initLoginConfig() {
// 1. 强制关闭SSO登录显示账号密码登录
this.isSso = false
// 2. 关闭SSO加载状态让页面正常显示
this.ssoLoading = false
// 3. 可选:配置第三方登录列表(如果需要显示微信/QQ登录
// 如需显示第三方登录,取消下面注释并调整配置;不需要则留空数组
this.socialsList = [
// 示例微信登录配置根据实际项目的icon/class调整
// {
// enname: 'wechat_open',
// name: '微信登录',
// icon: 'icon-ym icon-ym-wechat' // 替换成项目真实的微信图标类名
// },
// 示例QQ登录配置
// {
// enname: 'qq',
// name: 'QQ登录',
// icon: 'icon-ym icon-ym-qq' // 替换成项目真实的QQ图标类名
// }
]
// 4. 可选SSO相关配置用不到可以不赋值
this.preUrl = ''
this.ticketParams = ''
},
getCodeConfig() {
const userStore = useUserStore()
getByName(this.formData.tenantName).then(res => {
const data = res.data
userStore.setTenantId(data)
2026-01-04 11:09:06 +08:00
})
},
changeCode() {
let timestamp = Math.random()
this.timestamp = timestamp
this.imgUrl = `/api/oauth/ImageCode/${this.codeLength || 4}/${timestamp}`
},
login() {
2026-01-25 20:22:58 +08:00
if(this.ssoOptions.socialLogin){
this.$refs.dataForm.validate(valid => {
if (valid) {
this.loading = true
const password = md5Libs.md5(this.formData.password);
const encryptPassword = this.jnpf.aesEncryption.encrypt(password);
2026-01-23 11:09:28 +08:00
this.getLogin()
}
2026-01-25 20:22:58 +08:00
});
return
}
this.certifyLogin()
2026-01-23 11:09:28 +08:00
},
// 普通登录
getLogin(){
2026-01-23 11:12:57 +08:00
const userStore = useUserStore()
2026-01-23 11:09:28 +08:00
let query = {
2026-01-19 17:34:15 +08:00
tenantName: this.formData.tenantName,
username: this.formData.username,
password: this.formData.password,
rememberMe : false
2026-01-04 11:09:06 +08:00
}
// #ifdef APP-PLUS
const clientId = plus.push.getClientInfo().clientid;
query.clientId = clientId
/* unipush2.0 */
// query.Client_Id = uni.getStorageSync('cid')
// #endif
login(query).then(res => {
2026-01-25 20:22:58 +08:00
const {accessToken,refreshToken} = res.data
userStore.setToken(accessToken)
userStore.setRefreshToken(refreshToken)
2026-01-04 11:09:06 +08:00
this.rememberAccount()
2026-01-19 17:34:15 +08:00
if(res.code == 0){
getPermissionInfo().then(res=>{
if(res.code == 0){
userStore.setUserInfo(res.data)
uni.switchTab({
url: '/pages/index/indexWork'
2026-01-04 11:09:06 +08:00
});
2026-01-19 17:34:15 +08:00
}
2026-01-04 11:09:06 +08:00
})
2026-01-19 17:34:15 +08:00
}
// setTimeout(()=>{
// userStore.getCurrentUser().then(res => {
// this.loading = false
// uni.switchTab({
// url: '/pages/index/index'
// });
// }).catch(() => {
// this.loading = false
// })
// },1000)
// getPermissionInfo
2026-01-04 11:09:06 +08:00
}).catch((err) => {
uni.showToast({
title: err,
icon: 'none'
})
this.getCodeConfig(this.formData.account)
this.formData.code = ''
this.changeCode()
this.loading = false
})
2026-01-23 11:09:28 +08:00
},
// 认证登录
2026-01-25 20:22:58 +08:00
async certifyLogin(){
let type = '100'
let redirectUri = location.origin + '/pages/login/index?' + encodeURIComponent(`type=${type}&redirect=/`)
const res = await socialAuthRedirect(type,encodeURIComponent(redirectUri))
console.log(res,'res-------')
window.location.href = res.data
2026-01-23 11:09:28 +08:00
},
2026-01-25 20:22:58 +08:00
async exchangeToken(options) {
2026-01-23 11:09:28 +08:00
try {
2026-01-25 20:22:58 +08:00
const type = '100'
const code = '1222' || options?.code
const state = this.uuid() || options?.state
2026-01-23 11:09:28 +08:00
const res = await socialLogin(type, code, state)
2026-01-25 20:22:58 +08:00
const {accessToken,refreshToken} = res.data
2026-01-23 11:09:28 +08:00
const userStore = useUserStore()
// 存储Token和用户信息
2026-01-25 20:22:58 +08:00
userStore.setToken(accessToken)
userStore.setRefreshToken(refreshToken)
2026-01-23 11:09:28 +08:00
uni.switchTab({
url: '/pages/index/indexWork'
2026-01-04 11:09:06 +08:00
});
2026-01-23 11:09:28 +08:00
} catch (err) {
console.error('换取Token失败', err)
uni.showToast({
title: '登录失败,请重试',
icon: 'none'
})
} finally {
this.loading = false
}
},
uuid() {
const time = Date.now()
const random = Math.floor(Math.random() * 1000000000)
unique++
return 'qrcode_' + random + unique + String(time)
2026-01-04 11:09:06 +08:00
},
ssoLogin() {
getTicket().then(res => {
this.ssoTicket = res.data
this.ssoUrl = this.preUrl + '?' + this.ticketParams + '=' + this.ssoTicket
uni.setStorageSync('ssoUrl', this.ssoUrl)
uni.navigateTo({
url: `/pages/login/otherLogin?ssoTicket=${this.ssoTicket}`
})
})
}
}
}
</script>
<style lang="scss">
page {
width: 100%;
min-height: 100vh;
}
.remember-wrap {
margin-top: 8px;
& .remember-text {
color: #9A9A9A;
font-size: 13px;
}
}
.logo-v {
height: 100vh;
display: flex;
flex-direction: column;
z-index: -1;
.login-bg {
height: 726rpx;
position: relative;
image {
width: 100%;
height: 100%;
}
.login-version {
position: fixed;
right: 0px;
top: 0px;
width: 120rpx;
height: 120rpx;
background: url('../../static/image/login_version.png') no-repeat center;
background-size: 100%;
.login-version-text {
width: 120rpx;
height: 120rpx;
line-height: 70rpx;
text-align: center;
color: #fff;
font-size: 28rpx;
transform: rotate(45deg);
}
}
.logoImg {
2026-01-19 17:34:15 +08:00
width: 260rpx;
height: 260rpx;
2026-01-04 11:09:06 +08:00
margin: 0 auto;
position: absolute;
/* #ifdef APP-PLUS */
bottom: -90rpx;
/* #endif */
/* #ifndef APP-PLUS */
bottom: 0;
/* #endif */
left: 0;
right: 0;
top: 270rpx;
.image {
width: 100%;
height: 100%;
border-radius: 20%;
}
}
}
.logo-hd {
width: 100%;
margin-top: -240rpx;
.introduce {
justify-content: center;
align-items: center;
.text-one {
height: 70rpx;
font-weight: 700;
}
.text-two {
color: #999999;
}
}
.loginSwitch {
margin-top: 40rpx;
justify-content: center;
align-items: center;
.tabs {
color: #999999;
position: relative;
&::after {
content: "";
width: 64rpx;
height: 4rpx;
background-color: #356efe;
margin-top: 15rpx;
position: absolute;
left: 0;
bottom: -15rpx;
display: block;
border-radius: 50rpx;
}
&.active2 {
&::after {
left: 70%;
}
}
.tab {
width: 50%;
height: 80upx;
text-align: center;
color: #AEAFB5;
font-size: 32upx;
&.active {
color: #3281ff;
}
}
}
.loginInputBox {
width: 100%;
/* #ifdef APP-PLUS */
margin-top: 120rpx;
/* #endif */
/* #ifndef APP-PLUS */
margin-top: 80rpx;
/* #endif */
padding: 0 64rpx;
.code-box {
width: 100%;
.code-img-box {
flex: 0.1;
}
}
.loginBtnBox {
margin-top: 156rpx;
}
.u-form-item {
padding: 12rpx 0;
}
}
}
}
.copyright {
width: 100%;
height: 32rpx;
position: fixed;
bottom: 102rpx;
left: 50%;
right: 0;
text-align: center;
color: #A2A7BE;
font-size: 12px;
font-family: PingFang SC;
transform: translateX(-50%);
}
.sso-login-btn {
width: 100%;
padding: 0 64rpx;
/* #ifdef APP-PLUS */
margin-top: 404rpx;
/* #endif */
/* #ifndef APP-PLUS */
margin-top: 364rpx;
/* #endif */
}
}
.other-list {
display: flex;
align-items: center;
justify-content: space-around;
.other-item {
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
cursor: pointer;
border-radius: 50%;
text {
font-size: 20px;
color: #a0acb7;
}
}
}
.mian {
background: url('/static/image/tenancy.png');
height: 100%;
.top {
.img_box {
margin-top: 20rpx;
margin-left: 30%;
.img {
height: 50rpx;
width: 50rpx;
text-align: center;
}
}
}
}
.title {
margin-top: -55rpx;
margin-left: 260rpx;
font-size: 32rpx;
}
.info {
margin: auto auto;
width: 96%;
height: 300rpx;
background-color: #fff;
margin-bottom: 20rpx;
border-radius: 10rpx;
border-left: 10rpx solid #9DC8FA;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.user-name {
font-weight: bold;
font-size: 32rpx;
margin-left: 20rpx;
margin-bottom: 20rpx;
margin-top: 30rpx;
width: 100%;
height: 60rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.user-tenancy {
font-size: 28rpx;
margin-left: 20rpx;
margin-bottom: 20rpx;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>