login.vue 8.7 KB

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