index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. <template>
  2. <div class="login-container">
  3. <el-card class="form-container">
  4. <el-scrollbar class="scrollbar">
  5. <div class="login-title">
  6. <h2>{{ defaultSettings.title }}</h2>
  7. <div @click="showJobNumber = !showJobNumber">
  8. {{ showJobNumber ? "账号密码登录" : "扫码登录" }}
  9. </div>
  10. </div>
  11. <el-form
  12. ref="loginFormRef"
  13. :model="loginData"
  14. :rules="loginRules"
  15. class="login-form"
  16. size="default"
  17. >
  18. <el-form-item v-if="showJobNumber" prop="jobNumber">
  19. <div class="flex-y-center w-full">
  20. <svg-icon class="mx-2" icon-class="document" />
  21. <el-input
  22. ref="username"
  23. v-model="loginData.jobNumber"
  24. class="h-[48px]"
  25. name="jobNumber"
  26. placeholder="请扫码或输入工号"
  27. size="large"
  28. />
  29. </div>
  30. </el-form-item>
  31. <div v-else>
  32. <el-form-item prop="userName">
  33. <div class="flex-y-center w-full">
  34. <svg-icon class="mx-2" icon-class="user" />
  35. <el-input
  36. ref="username"
  37. v-model="loginData.userName"
  38. class="h-[48px]"
  39. name="userName"
  40. placeholder="请输入用户名"
  41. size="large"
  42. />
  43. </div>
  44. </el-form-item>
  45. <el-tooltip
  46. :content="$t('login.capsLock')"
  47. :visible="isCapslock"
  48. placement="right"
  49. >
  50. <el-form-item prop="password">
  51. <div class="flex-y-center w-full">
  52. <svg-icon class="mx-2" icon-class="lock" />
  53. <el-input
  54. v-model="loginData.password"
  55. :placeholder="$t('login.password')"
  56. class="h-[48px] pr-2"
  57. name="password"
  58. show-password
  59. size="large"
  60. type="password"
  61. @keyup="checkCapslock"
  62. />
  63. </div>
  64. </el-form-item>
  65. </el-tooltip>
  66. </div>
  67. <el-form-item prop="orgId">
  68. <div class="flex-y-center w-full">
  69. <svg-icon class="mx-2" icon-class="captcha" />
  70. <el-select
  71. v-model="loginData.orgId"
  72. class="no-border"
  73. placeholder="请选择组织"
  74. size="large"
  75. >
  76. <el-option
  77. v-for="item in orgList"
  78. :key="item.id"
  79. :label="item.deptName"
  80. :value="item.id"
  81. />
  82. </el-select>
  83. </div>
  84. </el-form-item>
  85. <el-form-item prop="proCode">
  86. <div class="flex-y-center w-full">
  87. <svg-icon class="mx-2" icon-class="cascader" />
  88. <el-select
  89. v-model="loginData.proCode"
  90. class="no-border"
  91. placeholder="请选择产线"
  92. size="large"
  93. >
  94. <el-option
  95. v-for="item in productionList"
  96. :key="item.code"
  97. :label="item.name"
  98. :value="item.code"
  99. />
  100. </el-select>
  101. </div>
  102. </el-form-item>
  103. <el-form-item prop="stationId">
  104. <div class="flex-y-center w-full">
  105. <svg-icon class="mx-2" icon-class="client" />
  106. <el-select
  107. v-model="loginData.stationId"
  108. class="no-border"
  109. placeholder="请选择工位"
  110. size="large"
  111. @change="onStationChange"
  112. >
  113. <el-option
  114. v-for="item in stationList"
  115. :key="item.id"
  116. :label="item.name"
  117. :value="item.id"
  118. />
  119. </el-select>
  120. </div>
  121. </el-form-item>
  122. <el-button
  123. :loading="loading"
  124. class="login-btn"
  125. size="large"
  126. type="primary"
  127. @click.prevent="handleLogin"
  128. >{{ $t("login.login") }}
  129. </el-button>
  130. </el-form>
  131. </el-scrollbar>
  132. </el-card>
  133. <!-- ICP备案 -->
  134. <div v-show="icpVisible" class="absolute bottom-1 text-[10px] text-center">
  135. <p>Copyright © 2022-2025 jgai.com All Rights Reserved.</p>
  136. </div>
  137. <div>
  138. <svg-icon
  139. class="activeNotice"
  140. style="right: 20px; top: 20px; position: absolute"
  141. icon-class="shutdown"
  142. size="40"
  143. @click="toExitApp"
  144. />
  145. </div>
  146. </div>
  147. </template>
  148. <script lang="ts" setup>
  149. import { useSettingsStore, useUserStore } from "@/store";
  150. import {
  151. getCaptchaApi,
  152. getOrgListApi,
  153. getProductionList,
  154. stationListByCode,
  155. } from "@/api/auth";
  156. import { LoginData } from "@/api/auth/types";
  157. import { useRoute } from "vue-router";
  158. import defaultSettings from "@/settings";
  159. import { ThemeEnum } from "@/enums/ThemeEnum";
  160. import { watch } from "vue";
  161. // Stores
  162. const userStore = useUserStore();
  163. const settingsStore = useSettingsStore();
  164. // Internationalization
  165. const { t } = useI18n();
  166. // Reactive states
  167. const isDark = ref(settingsStore.theme === ThemeEnum.DARK);
  168. const icpVisible = ref(true);
  169. const showJobNumber = ref(false);
  170. const orgList = ref<any>([]);
  171. const productionList = ref<any>([]);
  172. const stationList = ref<any>([]);
  173. const loading = ref(false); // 按钮loading
  174. const isCapslock = ref(false); // 是否大写锁定
  175. const captchaBase64 = ref(); // 验证码图片Base64字符串
  176. const loginFormRef = ref(ElForm); // 登录表单ref
  177. const { height } = useWindowSize();
  178. const loginData = ref<LoginData>({
  179. userName: "",
  180. password: "",
  181. });
  182. const toExitApp = () => {
  183. if (window.openHarmonyBridge && window.openHarmonyBridge.exitApp) {
  184. window.openHarmonyBridge.exitApp("");
  185. }
  186. };
  187. const loginRules = computed?.(() => {
  188. return {
  189. userName: [
  190. {
  191. required: true,
  192. trigger: "blur",
  193. message: t("login.message.username.required"),
  194. },
  195. ],
  196. password: [
  197. {
  198. required: true,
  199. trigger: "blur",
  200. message: t("login.message.password.required"),
  201. },
  202. {
  203. min: 6,
  204. message: t("login.message.password.min"),
  205. trigger: "blur",
  206. },
  207. ],
  208. orgId: [
  209. {
  210. required: true,
  211. trigger: "blur",
  212. message: t("login.message.orgId.required"),
  213. },
  214. ],
  215. proCode: [
  216. {
  217. required: true,
  218. trigger: "blur",
  219. message: "请选择产线",
  220. },
  221. ],
  222. stationId: [
  223. {
  224. required: true,
  225. trigger: "blur",
  226. message: "请选择工位",
  227. },
  228. ],
  229. jobNumber: [
  230. {
  231. required: true,
  232. trigger: "blur",
  233. message: "请输入工号",
  234. },
  235. ],
  236. };
  237. });
  238. let stationType = ""; //工位类型
  239. let stationName = ""; //工位名称
  240. const onStationChange = (val: any) => {
  241. const stationArray = stationList.value.filter((item: any) => {
  242. return item.id === val;
  243. });
  244. stationType = stationArray[0].stationDictValue;
  245. stationName = stationArray[0].name;
  246. loginData.value.stationType = stationArray[0].stationDictValue;
  247. };
  248. /**
  249. * 获取验证码
  250. */
  251. function getCaptcha() {
  252. getCaptchaApi().then(({ data }) => {
  253. loginData.value.captchaKey = data.captchaKey;
  254. captchaBase64.value = data.captchaBase64;
  255. });
  256. }
  257. function getOrgList() {
  258. getOrgListApi().then((data: any) => {
  259. orgList.value = data.data;
  260. if (orgList.value) {
  261. loginData.value.orgId = orgList.value[0].id;
  262. }
  263. });
  264. }
  265. /**
  266. * 登录
  267. */
  268. const route = useRoute();
  269. const router = useRouter();
  270. function handleLogin() {
  271. loginFormRef.value.validate((valid: boolean) => {
  272. if (valid) {
  273. //保存用户名和密码
  274. localStorage.setItem("local_name", loginData.value.userName);
  275. loading.value = true;
  276. let numberP = {
  277. proCode: loginData.value.proCode,
  278. orgId: loginData.value.orgId,
  279. jobNumber: loginData.value.jobNumber,
  280. stationId: loginData.value.stationId,
  281. stationType: loginData.value.stationType,
  282. };
  283. let acountP = {
  284. userName: loginData.value.userName,
  285. password: loginData.value.password,
  286. proCode: loginData.value.proCode,
  287. orgId: loginData.value.orgId,
  288. stationId: loginData.value.stationId,
  289. stationType: loginData.value.stationType,
  290. };
  291. localStorage.setItem("local_stationId", loginData.value.stationId);
  292. userStore
  293. .login(showJobNumber.value ? numberP : acountP)
  294. .then(() => {
  295. userStore.user.station = stationName;
  296. userStore.user.proCode = loginData.value.proCode;
  297. // 捡选 装配 测试 维修站 预齐套
  298. // if (stationType == "5") {
  299. // router.replace({ name: "PrepareMain" });
  300. // } else {
  301. router.replace({ name: "ProcessMain" });
  302. // }
  303. })
  304. .catch(() => {
  305. // getCaptcha();
  306. console.log("catch");
  307. })
  308. .finally(() => {
  309. loading.value = false;
  310. });
  311. }
  312. });
  313. }
  314. /**
  315. * 主题切换
  316. */
  317. const toggleTheme = () => {
  318. //const newTheme = settingsStore.theme === ThemeEnum.DARK ? ThemeEnum.LIGHT : ThemeEnum.DARK;
  319. //settingsStore.changeTheme(newTheme);
  320. };
  321. /**
  322. * 根据屏幕宽度切换设备模式
  323. */
  324. watchEffect?.(() => {
  325. if (height.value < 600) {
  326. icpVisible.value = false;
  327. } else {
  328. icpVisible.value = true;
  329. }
  330. });
  331. watch(
  332. () => loginData.value.proCode,
  333. (newValue) => {
  334. if (newValue) {
  335. stationListByCode(newValue, 0).then((data: any) => {
  336. stationList.value = data.data;
  337. if (stationList.value && stationList.value.length > 0) {
  338. // 根据保存的工位id,获得工位信息
  339. let index = 0;
  340. for (let i = 0; i < stationList.value.length; i++) {
  341. if (
  342. localStorage.getItem("local_stationId") ===
  343. stationList.value[i].id
  344. ) {
  345. index = i;
  346. break;
  347. }
  348. }
  349. let station = stationList.value[index];
  350. loginData.value.stationId = station.id;
  351. stationType = station.stationDictValue;
  352. stationName = station.name;
  353. loginData.value.stationType = station.stationDictValue;
  354. }
  355. });
  356. }
  357. }
  358. );
  359. /**
  360. * 检查输入大小写
  361. */
  362. function checkCapslock(e: any) {
  363. isCapslock.value = e.getModifierState("CapsLock");
  364. }
  365. onMounted?.(() => {
  366. getOrgList();
  367. toggleTheme();
  368. getProductionList().then((data: any) => {
  369. productionList.value = data.data;
  370. if (productionList.value) {
  371. loginData.value.proCode = productionList.value[0].code;
  372. }
  373. });
  374. if (
  375. localStorage.getItem("local_name") &&
  376. localStorage.getItem("local_name") !== "null"
  377. ) {
  378. loginData.value.userName = localStorage.getItem("local_name");
  379. }
  380. });
  381. </script>
  382. <style lang="scss" scoped>
  383. .login-container {
  384. overflow-y: auto;
  385. background: url("@/assets/images/login-bg.png");
  386. background-position: center;
  387. background-size: cover;
  388. position: relative;
  389. @apply wh-full flex-center;
  390. .login-form {
  391. padding: 30px 10px;
  392. }
  393. .login-title {
  394. color: white;
  395. margin-left: 10px;
  396. display: flex;
  397. justify-content: space-between;
  398. align-items: center;
  399. }
  400. .form-container {
  401. background: #a0a8b2;
  402. border-radius: 16px;
  403. width: 500px;
  404. position: absolute;
  405. right: 10%;
  406. }
  407. .login-btn {
  408. width: 100%;
  409. height: 50px;
  410. border-radius: 25px;
  411. margin-top: 15px;
  412. }
  413. }
  414. .el-form-item {
  415. background: var(--el-input-bg-color);
  416. border: 1px solid var(--el-border-color);
  417. border-radius: 5px;
  418. }
  419. :deep(.el-input) {
  420. .el-input__wrapper {
  421. padding: 0;
  422. background-color: transparent;
  423. box-shadow: none;
  424. &.is-focus,
  425. &:hover {
  426. box-shadow: none !important;
  427. }
  428. input:-webkit-autofill {
  429. /* 通过延时渲染背景色变相去除背景颜色 */
  430. transition: background-color 1000s ease-in-out 0s;
  431. }
  432. }
  433. }
  434. :deep(.el-select__placeholder) {
  435. color: rgba(0, 0, 0, 0.9);
  436. }
  437. :deep(.el-select__placeholder.is-transparent) {
  438. color: rgba(0, 0, 0, 0.3);
  439. }
  440. :deep(.el-input__inner::placeholder) {
  441. color: rgba(0, 0, 0, 0.3);
  442. }
  443. :deep(.el-input__inner) {
  444. color: rgba(0, 0, 0, 0.9);
  445. }
  446. :deep(.el-select) {
  447. .el-select__wrapper {
  448. padding: 0;
  449. background-color: transparent;
  450. box-shadow: none;
  451. &.is-focus,
  452. &:hover {
  453. box-shadow: none !important;
  454. }
  455. input:-webkit-autofill {
  456. /* 通过延时渲染背景色变相去除背景颜色 */
  457. transition: background-color 1000s ease-in-out 0s;
  458. }
  459. }
  460. }
  461. @media (max-height: 540px) {
  462. /* 在页面高度低于500px时应用的样式 */
  463. .form-container {
  464. height: 100vh;
  465. }
  466. .scrollbar {
  467. height: 100vh;
  468. }
  469. }
  470. </style>