login.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  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
  16. class="login-form"
  17. ref="formRef"
  18. :modelValue="loginData"
  19. :rules="loginRules">
  20. <uni-forms-item name="username" class="margin-bt">
  21. <!-- type="number" -->
  22. <uni-easyinput
  23. class="login-input"
  24. v-model="loginData.username"
  25. :placeholder="$t('login.enterUsername')"
  26. :placeholderStyle="placeholderStyle"
  27. :styles="inputStyles" />
  28. </uni-forms-item>
  29. <uni-forms-item name="password" class="margin-bt">
  30. <uni-easyinput
  31. type="password"
  32. v-model="loginData.password"
  33. :placeholder="$t('login.enterPassword')"
  34. :placeholderStyle="placeholderStyle"
  35. :styles="inputStyles" />
  36. </uni-forms-item>
  37. </uni-forms>
  38. <button type="primary" @click="formSubmit(formRef)">
  39. {{ $t("login.login") }}
  40. </button>
  41. <view class="flex-row align-center justify-between">
  42. <view class="btn-text" @click="loginWithDingTalk">
  43. {{ $t("login.loginWithDingTalk") }}
  44. </view>
  45. <view class="btn-text" @click="openLanguagePopup">
  46. {{ $t("login.languageChange") }}
  47. </view>
  48. </view>
  49. </view>
  50. <!-- <view class="uni-padding-wrap">
  51. <view>
  52. <checkbox-group @change="handleChange">
  53. <label>
  54. <checkbox :value="true" :checked="isChecked" style="transform: scale(0.6)" />
  55. </label>
  56. </checkbox-group>
  57. </view>
  58. <view class="uni-title">
  59. 已阅读并同意
  60. <text style="text-decoration: underline" @click="goPrivacy">《隐私政策》</text>
  61. <text style="text-decoration: underline" @click="goAgreement">《用户服务协议》</text>
  62. </view>
  63. <uni-popup ref="privacyRef">
  64. <scroll-view scroll-y="true" class="privacy" style="height: 500px; border-radius: 20rpx">
  65. <view style="background-color: #fff; padding: 0 50rpx; border-radius: 20rpx">
  66. <Privacy />
  67. </view>
  68. </scroll-view>
  69. </uni-popup>
  70. <uni-popup ref="aggRef">
  71. <scroll-view scroll-y="true" class="privacy" style="height: 500px; border-radius: 20rpx">
  72. <view style="background-color: #fff; padding: 0 50rpx; border-radius: 20rpx">
  73. <Agg />
  74. </view>
  75. </scroll-view>
  76. </uni-popup>
  77. </view> -->
  78. <!-- 引用语言选择组件 -->
  79. <language-popup ref="languagePopupRef" />
  80. <upgrade ref="upgradeRef" />
  81. </view>
  82. </template>
  83. <script setup>
  84. import { reactive, ref, onMounted, nextTick, getCurrentInstance } from "vue";
  85. import { onLoad } from "@dcloudio/uni-app";
  86. // 引入接口api
  87. import {
  88. appLogin,
  89. dingTalkLogin,
  90. dingTalkLoginH5,
  91. getInfo,
  92. getTokenByUserId,
  93. } from "@/api/login.js";
  94. // 引入配置文件
  95. import config from "@/utils/config";
  96. // 引入数据库操作
  97. import { saveUser } from "@/utils/appDb";
  98. // 引入本地存储操作
  99. import { setUserId, setToken, setDeptId, setUserInfo } from "@/utils/auth.js";
  100. // 引入组件
  101. import Upgrade from "@/components/upgrade.vue";
  102. import LanguagePopup from "@/components/language-popup.vue";
  103. // 引入钉钉JSAPI -- 仅在H5环境下使用
  104. let dd = null;
  105. // #ifdef H5
  106. import * as dingTalkJsApi from "dingtalk-jsapi";
  107. dd = dingTalkJsApi;
  108. // #endif
  109. const { appContext } = getCurrentInstance();
  110. const t = appContext.config.globalProperties.$t;
  111. const languagePopupRef = ref(null);
  112. const openLanguagePopup = () => {
  113. languagePopupRef.value.open();
  114. };
  115. let isChecked = ref(false);
  116. let my_value = ref(false);
  117. const handleChange = (val) => {
  118. my_value.value = val.detail.value[0];
  119. };
  120. // let privacyRef = ref(null);
  121. // let aggRef = ref(null);
  122. // const goPrivacy = () => {
  123. // privacyRef.value.open();
  124. // };
  125. // const goAgreement = () => {
  126. // aggRef.value.open();
  127. // };
  128. // 判断当前环境是否在钉钉环境
  129. const isDingTalk = () => {
  130. const ua = window.navigator.userAgent.toLowerCase();
  131. console.log("🚀 ~ 当前环境 ~ ua:", ua);
  132. return ua.includes("dingtalk") || ua.includes("dingtalkwork");
  133. };
  134. const dingTalkAutoLogin = async () => {
  135. // 判断是否在钉钉环境
  136. if (!isDingTalk()) {
  137. console.log("当前环境不是钉钉环境,无法自动登录");
  138. return;
  139. }
  140. // 执行钉钉微应用免登逻辑
  141. loginWithDingTalkH5();
  142. };
  143. // 钉钉登录
  144. const loginWithDingTalk = async () => {
  145. // #ifdef APP
  146. const plugin = uni.requireNativePlugin("DingTalk");
  147. // 钉钉登录,这里无法使用async,否则java端会报参数错误
  148. plugin.login((res) => {
  149. console.log(res);
  150. if (res.success === 1) {
  151. dingTalkLogin({
  152. type: 20,
  153. code: res.code,
  154. state: res.state,
  155. }).then((res) => {
  156. console.log(res);
  157. handleLoginSuccess(res);
  158. });
  159. } else if (res.success === 2) {
  160. uni.showToast({ title: t("login.dingTalkError"), icon: "none" });
  161. console.error("APP端钉钉登录失败:", res);
  162. }
  163. });
  164. // #endif
  165. // #ifdef H5
  166. if (isDingTalk()) {
  167. if (!dd) {
  168. uni.showToast({ title: t("login.dingTalkJsapiMissing"), icon: "none" });
  169. return;
  170. }
  171. loginWithDingTalkH5();
  172. } else {
  173. console.log("当前是普通 H5 环境,无法使用钉钉登录");
  174. uni.showToast({ title: t("login.h5DingTalk"), icon: "none" });
  175. }
  176. // #endif
  177. };
  178. const loginWithDingTalkH5 = async () => {
  179. const corpId = config.default.corpId;
  180. console.log("🚀 ~ loginWithDingTalkH5 ~ corpId:", corpId);
  181. const clientId = config.default.clientId;
  182. console.log("🚀 ~ loginWithDingTalkH5 ~ clientId:", clientId);
  183. if (!corpId || !clientId) {
  184. console.error("缺少必要参数");
  185. return;
  186. }
  187. dd.requestAuthCode({
  188. corpId,
  189. clientId,
  190. success: async (result) => {
  191. console.log("🚀 ~ loginWithDingTalkH5 ~ result:", result);
  192. const { code } = result;
  193. dingTalkLoginH5({
  194. type: 10,
  195. state: new Date().getTime(),
  196. code: code,
  197. })
  198. .then((res) => {
  199. console.log("🚀 ~ loginWithDingTalkH5 ~ res:", res);
  200. handleLoginSuccess(res);
  201. })
  202. .catch((err) => {
  203. console.log("🚀 ~ loginWithDingTalkH5 ~ err:", err);
  204. });
  205. },
  206. fail: (err) => {
  207. console.log("🚀 ~ loginWithDingTalkH5 ~ err:", err);
  208. uni.showToast({
  209. title: "获取code失败:" + JSON.stringify(err),
  210. icon: "none",
  211. });
  212. },
  213. });
  214. };
  215. onLoad(async (options) => {
  216. console.log(
  217. "onLoad Login",
  218. uni.getLocale(),
  219. 11,
  220. uni.getStorageSync("language")
  221. );
  222. console.log(options);
  223. // 保存钉钉消息传递的参数
  224. if (options.userId) {
  225. uni.setStorageSync("dingTalkJson", JSON.stringify(options));
  226. const isLoggedIn = uni.getStorageSync("userId");
  227. if (!isLoggedIn) {
  228. const result = await getTokenByUserId(options.userId);
  229. await handleLoginSuccess(result);
  230. }
  231. }
  232. // #ifdef H5
  233. // 当前环境为H5时,判断是否是通过钉钉微应用打开的链接
  234. // 获取当前Url地址
  235. const url = window.location.href;
  236. console.log("当前环境为H5时 当前Url地址:", url);
  237. // 判断是否是通过钉钉微应用打开的链接
  238. if (url.includes("/deepoil")) {
  239. dingTalkAutoLogin();
  240. }
  241. // #endif
  242. });
  243. onMounted(() => {
  244. // console.log("onMounted");
  245. // 检查是否需要显示语言选择弹窗
  246. if (!uni.getStorageSync("language")) {
  247. nextTick(() => {
  248. openLanguagePopup();
  249. });
  250. }
  251. // 检查是否已登录
  252. const isLoggedIn = uni.getStorageSync("userId");
  253. // console.log("isLoggedIn", isLoggedIn);
  254. if (isLoggedIn) {
  255. uni.switchTab({
  256. url: "/pages/home/index",
  257. });
  258. }
  259. });
  260. const placeholderStyle = ref("color:#797979;font-weight:500;font-size:16px");
  261. const inputStyles = reactive({
  262. backgroundColor: "#F0F3FB",
  263. color: "#797979",
  264. });
  265. const loginData = reactive({
  266. username: "",
  267. password: "",
  268. });
  269. const loginRules = ref({
  270. username: {
  271. rules: [
  272. {
  273. required: true,
  274. errorMessage: t("login.enterUsername"),
  275. },
  276. ],
  277. },
  278. password: {
  279. rules: [
  280. {
  281. required: true,
  282. errorMessage: t("login.enterPassword"),
  283. },
  284. ],
  285. },
  286. });
  287. const formRef = ref();
  288. const formSubmit = async (formEl) => {
  289. // if (!my_value.value) {
  290. // uni.showToast({
  291. // title: '请阅读并同意隐私政策和用户服务协议',
  292. // icon: 'none', // 可选 success/error/loading/none
  293. // duration: 2000, // 持续时间,单位ms
  294. // mask: true // 是否显示透明蒙层,防止触摸穿透
  295. // });
  296. // return;
  297. // }
  298. if (!formEl) return;
  299. await formEl
  300. .validate()
  301. .then((res) => {
  302. appLogin({
  303. ...loginData,
  304. // rememberMe: ,
  305. // tenantName: ""
  306. })
  307. .then(async (result) => {
  308. console.log("result,", result.data);
  309. if (result) {
  310. await saveUser({
  311. name: loginData.username,
  312. pwd: loginData.password,
  313. });
  314. await handleLoginSuccess(result);
  315. }
  316. })
  317. .finally(() => {});
  318. })
  319. .catch((err) => {
  320. console.log("err", err);
  321. });
  322. };
  323. const handleLoginSuccess = async (result) => {
  324. if (result) {
  325. await setUserId(result.data.userId);
  326. await setToken(result.data);
  327. await getInfo().then(async (res) => {
  328. // console.log('useres', res)
  329. const data = JSON.stringify({
  330. user: res.data.user,
  331. roles: res.data.roles,
  332. });
  333. // console.log('data', data)
  334. await setUserInfo(data);
  335. await setDeptId(res.data.user.deptId);
  336. await uni.switchTab({
  337. url: "/pages/home/index",
  338. });
  339. });
  340. }
  341. };
  342. </script>
  343. <style lang="scss" scoped>
  344. .privacy {
  345. height: 60vh;
  346. width: 85vw;
  347. overflow: hidden;
  348. }
  349. .uni-padding-wrap {
  350. display: flex;
  351. z-index: 999;
  352. justify-content: center;
  353. align-items: center;
  354. padding: 0 10rpx;
  355. }
  356. .uni-title {
  357. font-size: 12px;
  358. }
  359. .login-top {
  360. position: relative;
  361. width: 100%;
  362. height: 422rpx;
  363. }
  364. .back-img {
  365. width: 100%;
  366. height: 100%;
  367. }
  368. .login-text {
  369. width: 100%;
  370. height: 100%;
  371. box-sizing: border-box;
  372. position: absolute;
  373. top: 0;
  374. left: 0;
  375. color: #ffffff;
  376. font-size: 40rpx;
  377. font-family: "Negreta,PingFang SC";
  378. font-weight: 600;
  379. padding: 0 56rpx;
  380. display: flex;
  381. justify-content: center;
  382. flex-direction: column;
  383. .text {
  384. width: 100%;
  385. margin-bottom: 6rpx;
  386. }
  387. }
  388. .margin-bt {
  389. margin-bottom: 25px;
  390. }
  391. .login-form-wrap {
  392. padding: 60rpx;
  393. }
  394. :deep(.uni-easyinput__content-input) {
  395. height: 45px;
  396. }
  397. :deep(.uni-input-input) {
  398. color: #999999 !important;
  399. }
  400. uni-button[type="primary"] {
  401. background: #004098;
  402. }
  403. .btn-text {
  404. color: #004098;
  405. margin-top: 20px;
  406. font-size: 14px;
  407. font-weight: 500;
  408. }
  409. </style>