login.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <template>
  2. <view class="login">
  3. <view class="login-top">
  4. <image class="back-img" src="../../static/login/login-back.png"></image>
  5. <view class="login-text">
  6. <view class="text">
  7. {{ $t('login.welcome') }}
  8. </view>
  9. <view class="text">
  10. {{ $t('app.appName') }}
  11. </view>
  12. </view>
  13. </view>
  14. <view class="login-form-wrap">
  15. <uni-forms class="login-form" ref="formRef" :modelValue="loginData" :rules="loginRules">
  16. <uni-forms-item name="username" class="margin-bt">
  17. <!-- type="number" -->
  18. <uni-easyinput
  19. class="login-input"
  20. v-model="loginData.username"
  21. :placeholder="$t('login.enterUsername')"
  22. :placeholderStyle="placeholderStyle"
  23. :styles="inputStyles" />
  24. </uni-forms-item>
  25. <uni-forms-item name="password" class="margin-bt">
  26. <uni-easyinput
  27. type="password"
  28. v-model="loginData.password"
  29. :placeholder="$t('login.enterPassword')"
  30. :placeholderStyle="placeholderStyle"
  31. :styles="inputStyles" />
  32. </uni-forms-item>
  33. </uni-forms>
  34. <button type="primary" @click="formSubmit(formRef)">
  35. {{ $t('login.login') }}
  36. </button>
  37. <view class="flex-row align-center justify-between">
  38. <view class="btn-text" @click="loginWithDingTalk">
  39. {{ $t('login.loginWithDingTalk') }}
  40. </view>
  41. <view class="btn-text" @click="openLanguagePopup">
  42. {{ $t('login.languageChange') }}
  43. </view>
  44. </view>
  45. </view>
  46. <!-- 引用语言选择组件 -->
  47. <language-popup ref="languagePopupRef" />
  48. <upgrade ref="upgradeRef" />
  49. </view>
  50. </template>
  51. <script setup>
  52. import { reactive, ref, onMounted, nextTick, getCurrentInstance } from 'vue';
  53. import { onLoad } from '@dcloudio/uni-app';
  54. // 引入接口api
  55. import { appLogin, dingTalkLogin, dingTalkLoginH5, getInfo, getTokenByUserId } from '@/api/login.js';
  56. // 引入配置文件
  57. import config from '@/utils/config';
  58. // 引入数据库操作
  59. import { saveUser } from '@/utils/appDb';
  60. // 引入本地存储操作
  61. import { setUserId, setToken, setDeptId, setUserInfo } from '@/utils/auth.js';
  62. // 引入组件
  63. import Upgrade from '@/components/upgrade.vue';
  64. import LanguagePopup from '@/components/language-popup.vue';
  65. // 引入钉钉JSAPI -- 仅在H5环境下使用
  66. let dd = null;
  67. // #ifdef H5
  68. import * as dingTalkJsApi from 'dingtalk-jsapi';
  69. dd = dingTalkJsApi;
  70. // #endif
  71. const { appContext } = getCurrentInstance();
  72. const t = appContext.config.globalProperties.$t;
  73. const languagePopupRef = ref(null);
  74. const openLanguagePopup = () => {
  75. languagePopupRef.value.open();
  76. };
  77. // 判断当前环境是否在钉钉环境
  78. const isDingTalk = () => {
  79. const ua = window.navigator.userAgent.toLowerCase();
  80. console.log('🚀 ~ 当前环境 ~ ua:', ua);
  81. return ua.includes('dingtalk') || ua.includes('dingtalkwork');
  82. };
  83. const dingTalkAutoLogin = async () => {
  84. // 判断是否在钉钉环境
  85. if (!isDingTalk()) {
  86. console.log('当前环境不是钉钉环境,无法自动登录');
  87. return;
  88. }
  89. // 执行钉钉微应用免登逻辑
  90. loginWithDingTalkH5();
  91. };
  92. // 钉钉登录
  93. const loginWithDingTalk = async () => {
  94. // #ifdef APP
  95. const plugin = uni.requireNativePlugin('DingTalk');
  96. // 钉钉登录,这里无法使用async,否则java端会报参数错误
  97. plugin.login(res => {
  98. console.log(res);
  99. if (res.success === 1) {
  100. dingTalkLogin({
  101. type: 20,
  102. code: res.code,
  103. state: res.state,
  104. }).then(res => {
  105. console.log(res);
  106. handleLoginSuccess(res);
  107. });
  108. } else if (res.success === 2) {
  109. uni.showToast({ title: t('login.dingTalkError'), icon: 'none' });
  110. console.error('APP端钉钉登录失败:', res);
  111. }
  112. });
  113. // #endif
  114. // #ifdef H5
  115. if (isDingTalk()) {
  116. if (!dd) {
  117. uni.showToast({ title: t('login.dingTalkJsapiMissing'), icon: 'none' });
  118. return;
  119. }
  120. loginWithDingTalkH5();
  121. } else {
  122. console.log('当前是普通 H5 环境,无法使用钉钉登录');
  123. uni.showToast({ title: t('login.h5DingTalk'), icon: 'none' });
  124. }
  125. // #endif
  126. };
  127. const loginWithDingTalkH5 = async () => {
  128. const corpId = config.default.corpId;
  129. console.log('🚀 ~ loginWithDingTalkH5 ~ corpId:', corpId);
  130. const clientId = config.default.clientId;
  131. console.log('🚀 ~ loginWithDingTalkH5 ~ clientId:', clientId);
  132. if (!corpId || !clientId) {
  133. console.error('缺少必要参数');
  134. return;
  135. }
  136. dd.requestAuthCode({
  137. corpId,
  138. clientId,
  139. success: async result => {
  140. console.log('🚀 ~ loginWithDingTalkH5 ~ result:', result);
  141. const { code } = result;
  142. dingTalkLoginH5({
  143. type: 10,
  144. state: new Date().getTime(),
  145. code: code,
  146. })
  147. .then(res => {
  148. console.log('🚀 ~ loginWithDingTalkH5 ~ res:', res);
  149. handleLoginSuccess(res);
  150. })
  151. .catch(err => {
  152. console.log('🚀 ~ loginWithDingTalkH5 ~ err:', err);
  153. });
  154. },
  155. fail: err => {
  156. console.log('🚀 ~ loginWithDingTalkH5 ~ err:', err);
  157. uni.showToast({
  158. title: '获取code失败:' + JSON.stringify(err),
  159. icon: 'none',
  160. });
  161. },
  162. });
  163. };
  164. onLoad(async options => {
  165. console.log('onLoad Login', uni.getLocale(), 11, uni.getStorageSync('language'));
  166. console.log(options);
  167. // 保存钉钉消息传递的参数
  168. if (options.userId) {
  169. uni.setStorageSync('dingTalkJson', JSON.stringify(options));
  170. const isLoggedIn = uni.getStorageSync('userId');
  171. if (!isLoggedIn) {
  172. const result = await getTokenByUserId(options.userId);
  173. await handleLoginSuccess(result);
  174. }
  175. }
  176. // #ifdef H5
  177. // 当前环境为H5时,判断是否是通过钉钉微应用打开的链接
  178. // 获取当前Url地址
  179. const url = window.location.href;
  180. console.log('当前环境为H5时 当前Url地址:', url);
  181. // 判断是否是通过钉钉微应用打开的链接
  182. if (url.includes('/deepoil')) {
  183. dingTalkAutoLogin();
  184. }
  185. // #endif
  186. });
  187. onMounted(() => {
  188. // console.log("onMounted");
  189. // 检查是否需要显示语言选择弹窗
  190. if (!uni.getStorageSync('language')) {
  191. nextTick(() => {
  192. openLanguagePopup();
  193. });
  194. }
  195. // 检查是否已登录
  196. const isLoggedIn = uni.getStorageSync('userId');
  197. // console.log("isLoggedIn", isLoggedIn);
  198. if (isLoggedIn) {
  199. uni.switchTab({
  200. url: '/pages/home/index',
  201. });
  202. }
  203. });
  204. const placeholderStyle = ref('color:#797979;font-weight:500;font-size:16px');
  205. const inputStyles = reactive({
  206. backgroundColor: '#F0F3FB',
  207. color: '#797979',
  208. });
  209. const loginData = reactive({
  210. username: '',
  211. password: '',
  212. });
  213. const loginRules = ref({
  214. username: {
  215. rules: [
  216. {
  217. required: true,
  218. errorMessage: t('login.enterUsername'),
  219. },
  220. ],
  221. },
  222. password: {
  223. rules: [
  224. {
  225. required: true,
  226. errorMessage: t('login.enterPassword'),
  227. },
  228. ],
  229. },
  230. });
  231. const formRef = ref();
  232. const formSubmit = async formEl => {
  233. if (!formEl) return;
  234. await formEl
  235. .validate()
  236. .then(res => {
  237. appLogin({
  238. ...loginData,
  239. // rememberMe: ,
  240. // tenantName: ""
  241. })
  242. .then(async result => {
  243. console.log('result,', result.data);
  244. if (result) {
  245. await saveUser({
  246. name: loginData.username,
  247. pwd: loginData.password,
  248. });
  249. await handleLoginSuccess(result);
  250. }
  251. })
  252. .finally(() => {});
  253. })
  254. .catch(err => {
  255. console.log('err', err);
  256. });
  257. };
  258. const handleLoginSuccess = async result => {
  259. if (result) {
  260. await setUserId(result.data.userId);
  261. await setToken(result.data);
  262. await getInfo().then(async res => {
  263. // console.log('useres', res)
  264. const data = JSON.stringify({
  265. user: res.data.user,
  266. roles: res.data.roles,
  267. });
  268. // console.log('data', data)
  269. await setUserInfo(data);
  270. await setDeptId(res.data.user.deptId);
  271. await uni.switchTab({
  272. url: '/pages/home/index',
  273. });
  274. });
  275. }
  276. };
  277. </script>
  278. <style lang="scss" scoped>
  279. .login-top {
  280. position: relative;
  281. width: 100%;
  282. height: 422rpx;
  283. }
  284. .back-img {
  285. width: 100%;
  286. height: 100%;
  287. }
  288. .login-text {
  289. width: 100%;
  290. height: 100%;
  291. box-sizing: border-box;
  292. position: absolute;
  293. top: 0;
  294. left: 0;
  295. color: #ffffff;
  296. font-size: 40rpx;
  297. font-family: 'Negreta,PingFang SC';
  298. font-weight: 600;
  299. padding: 0 56rpx;
  300. display: flex;
  301. justify-content: center;
  302. flex-direction: column;
  303. .text {
  304. width: 100%;
  305. margin-bottom: 6rpx;
  306. }
  307. }
  308. .margin-bt {
  309. margin-bottom: 25px;
  310. }
  311. .login-form-wrap {
  312. padding: 60rpx;
  313. }
  314. :deep(.uni-easyinput__content-input) {
  315. height: 45px;
  316. }
  317. :deep(.uni-input-input) {
  318. color: #999999 !important;
  319. }
  320. uni-button[type='primary'] {
  321. background: #004098;
  322. }
  323. .btn-text {
  324. color: #004098;
  325. margin-top: 20px;
  326. font-size: 14px;
  327. font-weight: 500;
  328. }
  329. </style>