index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  1. <template>
  2. <view class="logo-v">
  3. <view class="login-bg">
  4. <image src="../../static/image/login-bg.jpg" mode="widthFix"></image>
  5. <view class="logoImg">
  6. <u-image :src="appIcon" mode="widthFix" :border-radius="20" width="160" height="160">
  7. <template #error>
  8. <u-image :src="logoImg" mode="widthFix" width="160" height="160">
  9. </u-image>
  10. </template>
  11. </u-image>
  12. </view>
  13. <view class="login-version">
  14. <view class="login-version-text">{{sysConfigInfo.sysVersion || define.sysVersion}}</view>
  15. </view>
  16. </view>
  17. <view class="logo-hd u-flex-col">
  18. <view class="loginSwitch u-flex-col">
  19. <view class="loginInputBox u-flex-col" v-show="!isSso && !ssoLoading">
  20. <u-form :model="formData" :rules="rules" ref="dataForm" :errorType="['toast']" label-position="left"
  21. label-width="150" label-align="left">
  22. <u-form-item prop="account" :borderBottom="false">
  23. <u-input input-align='left' v-model="formData.account" placeholder="请输入帐号" @focus="onFocus"
  24. @blur="onBlur" border border-color="#F0F1F3" placeholder-style="#9D9D9D">
  25. </u-input>
  26. </u-form-item>
  27. <u-form-item prop="password" :border-bottom="false">
  28. <u-input input-align='left' v-model="formData.password" type="password" placeholder="请输入密码"
  29. border border-color="#F0F1F3" placeholder-style="#9D9D9D">
  30. </u-input>
  31. </u-form-item>
  32. <u-form-item prop="code" required v-if="needCode">
  33. <view class="u-flex code-box">
  34. <u-input v-model="formData.code" placeholder="验证码" input-align='left'></u-input>
  35. <view class="code-img-box">
  36. <u-image :showLoading="true" :src="baseURL+imgUrl" width="130px" height="38px"
  37. @click="changeCode">
  38. </u-image>
  39. </view>
  40. </view>
  41. </u-form-item>
  42. </u-form>
  43. <view class="remember-wrap">
  44. <u-checkbox v-model="remember"><span class="remember-text">记住账号密码</span></u-checkbox>
  45. </view>
  46. <view class="loginBtnBox">
  47. <u-button @click="login" type="primary" :loading="loading">{{ loading ? "登录中...":"登录"}}
  48. </u-button>
  49. </view>
  50. <template v-if="socialsList.length">
  51. <u-divider margin-top='40' margin-bottom='40' half-width='100%'>其他登录方式</u-divider>
  52. <view class="other-list">
  53. <block v-for="(item,i) in socialsList" :key="i">
  54. <!--#ifdef H5 || MP-WEIXIN -->
  55. <view class="other-item" v-if="item.enname==='wechat_open'" :title="item.name"
  56. @click="wechatLogin()"><text :class="item.icon" />
  57. </view>
  58. <view class="other-item" v-else-if="item.enname==='qq'" :title="item.name"
  59. @click="otherslogin(item.enname,item.renderUrl)"><text :class="item.icon" />
  60. </view>
  61. <!-- #endif -->
  62. <!--#ifdef APP-->
  63. <view class="other-item" v-if="item.enname==='qq'" :title="item.name"
  64. @click="qqOtherlogin()"><text :class="item.icon" />
  65. </view>
  66. <!-- #endif -->
  67. <!-- #ifndef MP-WEIXIN -->
  68. <view class="other-item" v-else :title="item.name"
  69. @click="otherslogin(item.enname,item.renderUrl)"><text :class="item.icon" />
  70. </view>
  71. <!-- #endif -->
  72. </block>
  73. </view>
  74. </template>
  75. </view>
  76. <view class="sso-login-btn" v-show="isSso && !ssoLoading">
  77. <u-button @click="ssoLogin" type="primary" :loading="loading">{{ loading ? "登录中...":"登录"}}
  78. </u-button>
  79. </view>
  80. </view>
  81. </view>
  82. <u-popup v-model="show" mode="left" width="90%" height="100%">
  83. <view class="mian">
  84. <view class='top'>
  85. <view class='img-box'>
  86. <image class="img" src="/static/image/tabbar/contactsHL.png" mode="widthFix"></image>
  87. </view>
  88. <view class='title'>
  89. 请选择登录账号
  90. </view>
  91. </view>
  92. <view v-for="(item,i) in tenantUserInfo" :key="i">
  93. <view class='info' @click="socailsLogin(item)">
  94. <view class='user-name'>
  95. {{item.socialName}}
  96. </view>
  97. <view class='user-tenancy'>
  98. 租户名称: {{item.tenantName}}
  99. </view>
  100. <view class='user-tenancy'>
  101. 租户id:{{item.tenantId}}
  102. </view>
  103. <view class='user-tenancy'>
  104. 账号:{{item.accountName}}
  105. </view>
  106. </view>
  107. </view>
  108. </view>
  109. </u-popup>
  110. <view class="copyright" v-if="isKeyUp">{{copyright}}</view>
  111. </view>
  112. </template>
  113. <script>
  114. import {
  115. login,
  116. getConfig,
  117. getCallback,
  118. otherlogin,
  119. getLoginConfig,
  120. getSocialsUserList,
  121. socialsLogin,
  122. getTicket
  123. } from '@/api/common.js'
  124. import md5Libs from "@/uni_modules/vk-uview-ui/libs/function/md5";
  125. import resources from '@/libs/resources'
  126. import {
  127. useUserStore
  128. } from '@/store/modules/user'
  129. import logoImg from '@/static/logo.png'
  130. export default {
  131. data() {
  132. return {
  133. remember: false,
  134. logoImg,
  135. imgUrl: '',
  136. loading: false,
  137. formData: {
  138. account: "",
  139. password: "",
  140. code: "",
  141. origin: 'password'
  142. },
  143. needCode: false,
  144. codeLength: 4,
  145. isCode: false,
  146. rules: {
  147. account: [{
  148. required: true,
  149. message: '请输入账号',
  150. trigger: 'blur',
  151. }],
  152. password: [{
  153. required: true,
  154. message: '请输入密码',
  155. trigger: 'blur',
  156. }],
  157. },
  158. sysConfigInfo: {},
  159. appIcon: '',
  160. sysName: '',
  161. copyright: '',
  162. isCopyright: true,
  163. socialsList: [],
  164. show: false,
  165. tenantUserInfo: [],
  166. ssoLoading: true,
  167. isSso: false,
  168. ssoTicket: '',
  169. ssoUrl: '',
  170. preUrl: '',
  171. ticketParams: "",
  172. loginCode: '',
  173. isKeyUp: true
  174. }
  175. },
  176. watch: {
  177. remember: {
  178. handler(val) {
  179. let model = uni.getStorageSync('rememberAccount')
  180. if (!model) model = {
  181. account: '',
  182. password: ''
  183. }
  184. model.remember = val
  185. uni.setStorageSync('rememberAccount', model)
  186. },
  187. deep: true,
  188. }
  189. },
  190. computed: {
  191. baseURL() {
  192. return this.define.baseURL
  193. },
  194. },
  195. onReady() {
  196. this.$refs.dataForm.setRules(this.rules);
  197. },
  198. onLoad(options) {
  199. if (options?.JNPF_TICKET) {
  200. this.ssoTicket = options.JNPF_TICKET
  201. uni.navigateTo({
  202. url: `/pages/login/otherLogin?ssoTicket=${this.ssoTicket}`
  203. })
  204. }
  205. this.ssoTicket = uni.getStorageSync('ssoTicket')
  206. this.sysConfigInfo = uni.getStorageSync('sysConfigInfo')
  207. this.appIcon = !!this.sysConfigInfo.appIcon ? this.baseURL + this.sysConfigInfo.appIcon :
  208. logoImg
  209. this.sysName = !!this.sysConfigInfo.companyName ? this.sysConfigInfo.sysName :
  210. 'USKY快速开发平台'
  211. this.copyright = !!this.sysConfigInfo.copyright ? this.sysConfigInfo.copyright :
  212. this.define.copyright
  213. let needCode = uni.getStorageSync('app_loginNeedCode')
  214. this.isCode = needCode
  215. this.changeCode()
  216. this.getLoginConfig()
  217. this.formData.password = '';
  218. if (options.data) {
  219. this.tenantUserInfo = JSON.parse(options.data)
  220. if (this.tenantUserInfo) this.show = true
  221. }
  222. this.initAccount()
  223. // #ifndef H5
  224. uni.onKeyboardHeightChange(res => {
  225. this.isKeyUp = res.height == 0 ? true : false
  226. return this.isKeyUp
  227. })
  228. // #endif
  229. },
  230. methods: {
  231. initAccount() {
  232. let model = uni.getStorageSync('rememberAccount')
  233. if (model && model.remember) {
  234. if (model.account) {
  235. this.formData.account = this.jnpf.aesEncryption.decrypt(model.account)
  236. }
  237. if (model.password) {
  238. this.formData.password = this.jnpf.aesEncryption.decrypt(model.password)
  239. }
  240. }
  241. this.remember = model.remember
  242. },
  243. rememberAccount() {
  244. // 是否记住密码
  245. if (this.remember) {
  246. let model = {};
  247. model.remember = true
  248. model.account = this.jnpf.aesEncryption.encrypt(this.formData.account)
  249. model.password = this.jnpf.aesEncryption.encrypt(this.formData.password)
  250. uni.setStorageSync('rememberAccount', model)
  251. }
  252. },
  253. loginHandel() {
  254. uni.showLoading({
  255. title: '登录中'
  256. })
  257. userStore.getCurrentUser().then((res) => {
  258. uni.hideLoading()
  259. uni.reLaunch({
  260. url: '/pages/index/index'
  261. });
  262. }).catch(() => {
  263. uni.hideLoading()
  264. uni.reLaunch({
  265. url: '/pages/login/index'
  266. });
  267. })
  268. },
  269. loginToken(res) {
  270. const userStore = useUserStore()
  271. userStore.setToken(res.data.value)
  272. if (res.data.status != 2) {
  273. // 登录成功
  274. if (res.data.status == 1) return this.loginHandel()
  275. if (res.data.status == 6) {
  276. this.tenantUserInfo = JSON.parse(res.data.value)
  277. if (this.tenantUserInfo.length == 1) {
  278. this.loginHandel()
  279. } else {
  280. this.show = true
  281. }
  282. } else {
  283. this.show = false
  284. this.ssoUrl = ''
  285. uni.showToast({
  286. title: res.data.value || '操作超时,请重新点击登录',
  287. icon: 'none'
  288. })
  289. }
  290. }
  291. },
  292. wechatLogin() {
  293. // #ifdef MP-WEIXIN
  294. getTicket().then(res => {
  295. this.ssoTicket = res.data
  296. uni.login({
  297. provider: 'weixin',
  298. success: (loginRes) => {
  299. this.loginCode = loginRes.code
  300. }
  301. })
  302. uni.getUserProfile({
  303. desc: '获取你的昵称、头像、地区及性别',
  304. success: (info) => {
  305. let qurey = {
  306. encryptedData: info.encryptedData,
  307. iv: info.iv,
  308. signature: info.signature,
  309. code: this.loginCode,
  310. jnpf_ticket: this.ssoTicket,
  311. socialName: info.userInfo.nickName,
  312. source: 'wechat_applets'
  313. }
  314. socialsLogin(qurey).then(res => {
  315. this.loginToken(res)
  316. })
  317. }
  318. })
  319. })
  320. // #endif
  321. // #ifdef APP-PLUS
  322. getTicket().then(res => {
  323. this.ssoTicket = res.data
  324. uni.login({
  325. provider: 'weixin',
  326. success: (loginRes) => {
  327. // 登录成功
  328. uni.getUserProfile({
  329. provider: 'weixin',
  330. success: (info) => {
  331. let data = {
  332. source: 'wechat_open',
  333. uuid: info.userInfo.unionId,
  334. socialName: info.userInfo.nickName,
  335. jnpf_ticket: this.ssoTicket
  336. };
  337. socialsLogin(data).then(res => {
  338. this.loginToken(res)
  339. })
  340. },
  341. fail: function(err) {
  342. // 登录授权失败
  343. // err.code是错误码
  344. }
  345. })
  346. }
  347. });
  348. })
  349. // #endif
  350. },
  351. qqOtherlogin() {
  352. getTicket().then(res => {
  353. this.ssoTicket = res.data
  354. uni.login({
  355. provider: 'qq',
  356. success: (loginRes) => {
  357. // 登录成功
  358. uni.getUserInfo({
  359. provider: 'qq',
  360. success: (info) => {
  361. let data = {
  362. source: 'qq',
  363. jnpf_ticket: this.ssoTicket,
  364. socialName: info.userInfo.nickName,
  365. uuid: info.userInfonickName.unionid,
  366. };
  367. socialsLogin(data).then(res => {
  368. this.loginToken(res)
  369. }).catch((err) => {})
  370. // 获取用户信息成功, info.authResult保存用户信息
  371. }
  372. })
  373. }
  374. });
  375. })
  376. },
  377. socailsLogin(item) {
  378. const userStore = useUserStore()
  379. item.tenantLogin = true
  380. socialsLogin(item).then(res => {
  381. if (res.code == 200) {
  382. uni.showLoading({
  383. title: '登录中'
  384. })
  385. userStore.setToken(res.data.token)
  386. userStore.getCurrentUser().then((res) => {
  387. uni.hideLoading()
  388. uni.switchTab({
  389. url: '/pages/index/index'
  390. });
  391. this.show = false
  392. }).catch(() => {
  393. uni.hideLoading()
  394. uni.switchTab({
  395. url: '/pages/login/index'
  396. });
  397. })
  398. }
  399. }).catch(() => {
  400. uni.hideLoading()
  401. uni.switchTab({
  402. url: '/pages/login/index'
  403. });
  404. })
  405. },
  406. otherslogin(key, url) {
  407. if (key === 'wechat_open') {
  408. this.wechatLogin();
  409. } else {
  410. getTicket().then(res => {
  411. this.ssoTicket = res.data
  412. url = url.replace('JNPF_TICKET', this.ssoTicket)
  413. uni.setStorageSync('ssoUrl', url)
  414. uni.navigateTo({
  415. url: `/pages/login/otherLogin?ssoTicket=` + this.ssoTicket
  416. })
  417. }).catch(() => {})
  418. }
  419. },
  420. onFocus(e) {
  421. this.getCodeConfig(e)
  422. },
  423. onBlur(e) {
  424. this.getCodeConfig(e)
  425. },
  426. // 获取登陆配置
  427. getLoginConfig() {
  428. getLoginConfig().then(res => {
  429. this.isSso = res.data.redirect
  430. this.preUrl = res.data.url
  431. this.ticketParams = res.data.ticketParams
  432. let socialsList = res.data.socialsList || []
  433. this.socialsList = socialsList.filter(o => o.latest && o.enname !=
  434. 'github' && o
  435. .enname !=
  436. 'wechat_enterprise')
  437. this.ssoLoading = false
  438. }).catch(() => {
  439. this.isSso = false
  440. this.ssoLoading = false
  441. })
  442. },
  443. getCodeConfig(val) {
  444. if (!val) return
  445. getConfig(val).then(res => {
  446. this.needCode = !!res.data.enableVerificationCode
  447. if (this.needCode) {
  448. this.codeLength = res.data.verificationCodeNumber || 4
  449. this.changeCode()
  450. }
  451. })
  452. },
  453. changeCode() {
  454. let timestamp = Math.random()
  455. this.timestamp = timestamp
  456. this.imgUrl = `/api/oauth/ImageCode/${this.codeLength || 4}/${timestamp}`
  457. },
  458. login() {
  459. const userStore = useUserStore()
  460. this.$refs.dataForm.validate(valid => {
  461. if (valid) {
  462. this.loading = true
  463. const password = md5Libs.md5(this.formData.password);
  464. const encryptPassword = this.jnpf.aesEncryption.encrypt(password);
  465. let query = {
  466. account: this.formData.account,
  467. password: encryptPassword,
  468. timestamp: this.timestamp,
  469. code: this.formData.code,
  470. origin: this.formData.origin,
  471. jnpf_ticket: this.ssoTicket,
  472. grant_type: 'password',
  473. }
  474. // #ifdef APP-PLUS
  475. const clientId = plus.push.getClientInfo().clientid;
  476. query.clientId = clientId
  477. /* unipush2.0 */
  478. // query.Client_Id = uni.getStorageSync('cid')
  479. // #endif
  480. login(query).then(res => {
  481. let token = res.data.token
  482. userStore.setToken(token)
  483. this.rememberAccount()
  484. userStore.getCurrentUser().then(res => {
  485. this.loading = false
  486. uni.switchTab({
  487. url: '/pages/index/index'
  488. });
  489. }).catch(() => {
  490. this.loading = false
  491. })
  492. }).catch((err) => {
  493. this.getCodeConfig(this.formData.account)
  494. this.formData.code = ''
  495. this.changeCode()
  496. this.loading = false
  497. })
  498. }
  499. });
  500. },
  501. ssoLogin() {
  502. getTicket().then(res => {
  503. this.ssoTicket = res.data
  504. this.ssoUrl = this.preUrl + '?' + this.ticketParams + '=' + this.ssoTicket
  505. uni.setStorageSync('ssoUrl', this.ssoUrl)
  506. uni.navigateTo({
  507. url: `/pages/login/otherLogin?ssoTicket=${this.ssoTicket}`
  508. })
  509. })
  510. }
  511. }
  512. }
  513. </script>
  514. <style lang="scss">
  515. page {
  516. width: 100%;
  517. min-height: 100vh;
  518. }
  519. .remember-wrap {
  520. margin-top: 8px;
  521. & .remember-text {
  522. color: #9A9A9A;
  523. font-size: 13px;
  524. }
  525. }
  526. .logo-v {
  527. height: 100vh;
  528. display: flex;
  529. flex-direction: column;
  530. z-index: -1;
  531. .login-bg {
  532. height: 726rpx;
  533. position: relative;
  534. image {
  535. width: 100%;
  536. height: 100%;
  537. }
  538. .login-version {
  539. position: fixed;
  540. right: 0px;
  541. top: 0px;
  542. width: 120rpx;
  543. height: 120rpx;
  544. background: url('../../static/image/login_version.png') no-repeat center;
  545. background-size: 100%;
  546. .login-version-text {
  547. width: 120rpx;
  548. height: 120rpx;
  549. line-height: 70rpx;
  550. text-align: center;
  551. color: #fff;
  552. font-size: 28rpx;
  553. transform: rotate(45deg);
  554. }
  555. }
  556. .logoImg {
  557. width: 160rpx;
  558. height: 160rpx;
  559. margin: 0 auto;
  560. position: absolute;
  561. /* #ifdef APP-PLUS */
  562. bottom: -90rpx;
  563. /* #endif */
  564. /* #ifndef APP-PLUS */
  565. bottom: 0;
  566. /* #endif */
  567. left: 0;
  568. right: 0;
  569. top: 270rpx;
  570. .image {
  571. width: 100%;
  572. height: 100%;
  573. border-radius: 20%;
  574. }
  575. }
  576. }
  577. .logo-hd {
  578. width: 100%;
  579. margin-top: -240rpx;
  580. .introduce {
  581. justify-content: center;
  582. align-items: center;
  583. .text-one {
  584. height: 70rpx;
  585. font-weight: 700;
  586. }
  587. .text-two {
  588. color: #999999;
  589. }
  590. }
  591. .loginSwitch {
  592. margin-top: 40rpx;
  593. justify-content: center;
  594. align-items: center;
  595. .tabs {
  596. color: #999999;
  597. position: relative;
  598. &::after {
  599. content: "";
  600. width: 64rpx;
  601. height: 4rpx;
  602. background-color: #356efe;
  603. margin-top: 15rpx;
  604. position: absolute;
  605. left: 0;
  606. bottom: -15rpx;
  607. display: block;
  608. border-radius: 50rpx;
  609. }
  610. &.active2 {
  611. &::after {
  612. left: 70%;
  613. }
  614. }
  615. .tab {
  616. width: 50%;
  617. height: 80upx;
  618. text-align: center;
  619. color: #AEAFB5;
  620. font-size: 32upx;
  621. &.active {
  622. color: #3281ff;
  623. }
  624. }
  625. }
  626. .loginInputBox {
  627. width: 100%;
  628. /* #ifdef APP-PLUS */
  629. margin-top: 120rpx;
  630. /* #endif */
  631. /* #ifndef APP-PLUS */
  632. margin-top: 80rpx;
  633. /* #endif */
  634. padding: 0 64rpx;
  635. .code-box {
  636. width: 100%;
  637. .code-img-box {
  638. flex: 0.1;
  639. }
  640. }
  641. .loginBtnBox {
  642. margin-top: 156rpx;
  643. }
  644. .u-form-item {
  645. padding: 12rpx 0;
  646. }
  647. }
  648. }
  649. }
  650. .copyright {
  651. width: 100%;
  652. height: 32rpx;
  653. position: fixed;
  654. bottom: 102rpx;
  655. left: 50%;
  656. right: 0;
  657. text-align: center;
  658. color: #A2A7BE;
  659. font-size: 12px;
  660. font-family: PingFang SC;
  661. transform: translateX(-50%);
  662. }
  663. .sso-login-btn {
  664. width: 100%;
  665. padding: 0 64rpx;
  666. /* #ifdef APP-PLUS */
  667. margin-top: 404rpx;
  668. /* #endif */
  669. /* #ifndef APP-PLUS */
  670. margin-top: 364rpx;
  671. /* #endif */
  672. }
  673. }
  674. .other-list {
  675. display: flex;
  676. align-items: center;
  677. justify-content: space-around;
  678. .other-item {
  679. width: 30px;
  680. height: 30px;
  681. line-height: 30px;
  682. text-align: center;
  683. cursor: pointer;
  684. border-radius: 50%;
  685. text {
  686. font-size: 20px;
  687. color: #a0acb7;
  688. }
  689. }
  690. }
  691. .mian {
  692. background: url('/static/image/tenancy.png');
  693. height: 100%;
  694. .top {
  695. .img_box {
  696. margin-top: 20rpx;
  697. margin-left: 30%;
  698. .img {
  699. height: 50rpx;
  700. width: 50rpx;
  701. text-align: center;
  702. }
  703. }
  704. }
  705. }
  706. .title {
  707. margin-top: -55rpx;
  708. margin-left: 260rpx;
  709. font-size: 32rpx;
  710. }
  711. .info {
  712. margin: auto auto;
  713. width: 96%;
  714. height: 300rpx;
  715. background-color: #fff;
  716. margin-bottom: 20rpx;
  717. border-radius: 10rpx;
  718. border-left: 10rpx solid #9DC8FA;
  719. overflow: hidden;
  720. text-overflow: ellipsis;
  721. white-space: nowrap;
  722. }
  723. .user-name {
  724. font-weight: bold;
  725. font-size: 32rpx;
  726. margin-left: 20rpx;
  727. margin-bottom: 20rpx;
  728. margin-top: 30rpx;
  729. width: 100%;
  730. height: 60rpx;
  731. overflow: hidden;
  732. text-overflow: ellipsis;
  733. white-space: nowrap;
  734. }
  735. .user-tenancy {
  736. font-size: 28rpx;
  737. margin-left: 20rpx;
  738. margin-bottom: 20rpx;
  739. width: 100%;
  740. overflow: hidden;
  741. text-overflow: ellipsis;
  742. white-space: nowrap;
  743. }
  744. </style>