Navbar.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. <template>
  2. <div class="navbar">
  3. <!-- <hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> -->
  4. <router-link
  5. key="collapse"
  6. style="display: flex; align-items: center; padding-left: 30px"
  7. to="/"
  8. >
  9. <img
  10. src="@/assets/images/base/logo.png"
  11. class="sidebar-logo"
  12. style="width: 129px; height: 37px"
  13. />
  14. </router-link>
  15. <!-- <breadcrumb class="breadcrumb-container" /> -->
  16. <div class="indexlayout-top-menu">
  17. <!-- :class="{'active': getTopMenuActive === route.path}" -->
  18. <!-- el-scrollbar -->
  19. <el-scrollbar
  20. class="horizontal-scrollbar"
  21. style="overflow: hidden; height: 100%"
  22. >
  23. <template v-for="route in permission_routes">
  24. <app-link
  25. v-if="!route.hidden"
  26. :to="route.path"
  27. :key="route.id"
  28. class="indexlayout-top-menu-li"
  29. :class="{ active: getTopMenuActive === route.path }"
  30. >
  31. <span>{{ route.meta.title }}</span>
  32. </app-link>
  33. </template>
  34. </el-scrollbar>
  35. </div>
  36. <div class="right-menu">
  37. <el-popover
  38. placement="bottom"
  39. trigger="hover"
  40. style="display: flex; height: 89px"
  41. >
  42. <div class="popover-container" style="text-align: center">OA审批</div>
  43. <div
  44. style="
  45. display: flex;
  46. align-items: center;
  47. justify-content: center;
  48. height: 89px;
  49. "
  50. class="msginfo"
  51. @click="gotoOa"
  52. slot="reference"
  53. >
  54. <img
  55. src="@/assets/images/base/icon_oa.png"
  56. width="24px"
  57. height="24px"
  58. />
  59. <!-- <div class="active"></div> -->
  60. </div>
  61. </el-popover>
  62. <el-popover
  63. v-if="isShowIns"
  64. placement="bottom"
  65. trigger="hover"
  66. style="display: flex; height: 89px"
  67. >
  68. <div class="popover-container" style="text-align: center">操作手册</div>
  69. <div
  70. style="
  71. display: flex;
  72. align-items: center;
  73. justify-content: center;
  74. height: 89px;
  75. "
  76. class="msginfo"
  77. @click="openIns"
  78. slot="reference"
  79. >
  80. <img
  81. src="@/assets/images/base/instruction-icon.png"
  82. width="24px"
  83. height="24px"
  84. />
  85. <!-- <div class="active"></div> -->
  86. </div>
  87. </el-popover>
  88. <el-popover
  89. placement="bottom"
  90. trigger="hover"
  91. style="display: flex; height: 89px"
  92. >
  93. <div class="popover-container" style="text-align: center">系统日志</div>
  94. <div
  95. style="
  96. display: flex;
  97. align-items: center;
  98. justify-content: center;
  99. height: 89px;
  100. "
  101. class="msginfo"
  102. v-permission="'/journal'"
  103. @click="gotoRecode"
  104. slot="reference"
  105. >
  106. <img
  107. src="@/assets/images/base/base-bell.png"
  108. width="24px"
  109. height="24px"
  110. />
  111. <!-- <div class="active"></div> -->
  112. </div>
  113. </el-popover>
  114. <div class="left-menu">
  115. <el-popover
  116. placement="top-start"
  117. width="300"
  118. trigger="hover"
  119. style="display: flex; height: 89px"
  120. >
  121. <div class="popover-container">
  122. <el-tag
  123. class="navbar_tag"
  124. type="info"
  125. v-for="item in organNameList"
  126. :key="item"
  127. >{{ item }}</el-tag
  128. >
  129. </div>
  130. <span
  131. slot="reference"
  132. class="msginfo"
  133. style="
  134. display: flex;
  135. align-items: center;
  136. justify-content: center;
  137. height: 89px;
  138. "
  139. >
  140. <!-- {{ organName.length > 10 ? organName.substr(0, 10) + "..." : organName }} -->
  141. <!-- <i class="el-icon-s-home" style="font-size: 23px; color: #1A1A1A;"></i> -->
  142. <img
  143. src="@/assets/images/base/base-home.png"
  144. width="24px"
  145. height="24px"
  146. />
  147. </span>
  148. </el-popover>
  149. </div>
  150. <el-dropdown class="avatar-container" trigger="click">
  151. <div class="avatar-wrapper">
  152. <img
  153. v-if="$store.getters.avatar"
  154. :src="$store.getters.avatar"
  155. class="user-avatar"
  156. />
  157. <img
  158. v-else
  159. class="user-avatar"
  160. src="@/assets/images/base/placehorder-icon.png"
  161. />
  162. <!-- <i class="el-icon-caret-bottom" /> -->
  163. <span>{{ username }}</span>
  164. </div>
  165. <el-dropdown-menu slot="dropdown" class="user-dropdown">
  166. <!-- divided -->
  167. <el-dropdown-item v-if="tenantStatus">
  168. <span style="display: block" @click="onTenantChange">{{ tenantName }} <i class="el-icon-sort"></i></span>
  169. </el-dropdown-item>
  170. <el-dropdown-item>
  171. <span style="display: block" @click="resetPassWord">修改密码</span>
  172. </el-dropdown-item>
  173. <el-dropdown-item>
  174. <span style="display: block" @click="accountSetting">账号设置</span>
  175. </el-dropdown-item>
  176. <el-dropdown-item>
  177. <span style="display: block" @click="logout">安全退出</span>
  178. </el-dropdown-item>
  179. </el-dropdown-menu>
  180. </el-dropdown>
  181. </div>
  182. <el-dialog
  183. title="修改密码"
  184. width="500px"
  185. append-to-body
  186. :visible.sync="resetVisible"
  187. >
  188. <el-form
  189. :model="resetForm"
  190. label-position="right"
  191. label-width="100px"
  192. ref="pwdForm"
  193. >
  194. <el-form-item label="手机号" prop="phone">
  195. <div>{{ this.$store.getters.phone }}</div>
  196. </el-form-item>
  197. <el-form-item
  198. label="新密码"
  199. :rules="[
  200. { required: true, message: '密码不能为空', trigger: 'blur' },
  201. {
  202. pattern: /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$/,
  203. message: '密码为6-20位数字和字母组合',
  204. trigger: 'blur',
  205. },
  206. ]"
  207. prop="password"
  208. >
  209. <el-input
  210. v-model.trim="resetForm.password"
  211. type="password"
  212. style="width: 180px"
  213. autocomplete="off"
  214. ></el-input>
  215. </el-form-item>
  216. <el-form-item
  217. label="再次输入"
  218. :rules="[
  219. { required: true, message: '密码不能为空', trigger: 'blur' },
  220. {
  221. pattern: /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$/,
  222. message: '密码为6-20位数字和字母组合',
  223. trigger: 'blur',
  224. },
  225. ]"
  226. prop="password2"
  227. >
  228. <el-input
  229. v-model.trim="resetForm.password2"
  230. type="password"
  231. style="width: 180px"
  232. autocomplete="off"
  233. ></el-input>
  234. </el-form-item>
  235. <el-form-item
  236. label="验证码"
  237. :rules="[
  238. { required: true, message: '验证码不能为空', trigger: 'blur' },
  239. ]"
  240. prop="authCode"
  241. style=""
  242. >
  243. <el-input
  244. v-model.trim="resetForm.authCode"
  245. style="width: 180px"
  246. autocomplete="off"
  247. ></el-input>
  248. <el-button :disabled="isDisable" @click="getCode">{{
  249. btnName
  250. }}</el-button>
  251. </el-form-item>
  252. </el-form>
  253. <div slot="footer" class="dialog-footer">
  254. <el-button @click="resetVisible = false">取 消</el-button>
  255. <el-button type="primary" @click="submitResetPassWord">确 定</el-button>
  256. </div>
  257. </el-dialog>
  258. <el-dialog
  259. title="切换机构"
  260. width="500px"
  261. append-to-body
  262. v-if="tenantVisible"
  263. :visible.sync="tenantVisible"
  264. >
  265. <el-form
  266. :model="tenantForm"
  267. label-position="right"
  268. label-width="100px"
  269. ref="tenantForm"
  270. >
  271. <el-form-item
  272. label="选择机构"
  273. :rules="[
  274. { required: true, message: '请选择机构', trigger: 'change' }
  275. ]"
  276. prop="tenantId"
  277. >
  278. <el-select
  279. v-model.trim="tenantForm.tenantId"
  280. filterable
  281. placeholder="请选择机构"
  282. clearable
  283. style="width: 100% !important"
  284. >
  285. <el-option v-for="(item, index) in tenantList" :key="index" :label="item.name" :value="item.id"></el-option>
  286. </el-select>
  287. </el-form-item>
  288. </el-form>
  289. <div slot="footer" class="dialog-footer">
  290. <el-button @click="tenantVisible = false">取 消</el-button>
  291. <el-button type="primary" @click="submitTenant">确 定</el-button>
  292. </div>
  293. </el-dialog>
  294. <portal-target name="AppMain" ref="target">
  295. <instructions ref="instructions" @checkShow="checkShow" />
  296. </portal-target>
  297. </div>
  298. </template>
  299. <script>
  300. import qs from "qs";
  301. import Logo from "./Sidebar/Logo";
  302. import { mapGetters } from "vuex";
  303. // import Breadcrumb from '@/components/Breadcrumb'
  304. // import Hamburger from '@/components/Hamburger'
  305. import { resetPassword } from "@/api/buildTeam";
  306. import AppLink from "./Sidebar/Link";
  307. import { getBelongTopMenuPath } from "@/utils/permission";
  308. import instructions from "./instructions";
  309. import { validOaUrl } from '@/utils/validate'
  310. import { tenantInfoQueryPage } from '@/views/organManager/api'
  311. import Cookies from 'js-cookie'
  312. import axios from 'axios'
  313. export default {
  314. data() {
  315. let tenantConfig = sessionStorage.getItem('tenantConfig')
  316. tenantConfig = tenantConfig ? JSON.parse(tenantConfig) : {}
  317. return {
  318. username: "",
  319. organName: this.$store.getters.organName,
  320. organNameList: [],
  321. resetVisible: false,
  322. resetForm: {
  323. phone: "",
  324. authCode: "",
  325. password: "",
  326. password2: "",
  327. },
  328. isDisable: false, // 是否允许发送验证码
  329. timerCount: 60,
  330. btnName: "获取验证码",
  331. isShowIns: false,
  332. tenantVisible: false,
  333. tenantName: tenantConfig.tenantName || null,
  334. tenantForm: {
  335. tenantId: Number(tenantConfig.tenantId) || null,
  336. },
  337. tenantList: []
  338. };
  339. },
  340. components: {
  341. AppLink,
  342. instructions,
  343. // Breadcrumb,
  344. // Hamburger
  345. },
  346. computed: {
  347. ...mapGetters(["sidebar", "avatar", "permission_routes"]),
  348. getTopMenuActive() {
  349. let route = this.$route;
  350. // (route, getBelongTopMenuPath(route))
  351. return getBelongTopMenuPath(route);
  352. },
  353. tenantStatus() { // 判断是否是平台账号 true 是
  354. const baseTenantId = sessionStorage.getItem('baseTenantId')
  355. return baseTenantId < 0 ? true : false
  356. },
  357. },
  358. mounted() {
  359. // 手动加入
  360. this.toggleSideBar();
  361. this.username = this.$store.getters.name;
  362. this.organNameList = this.organName.split(",") || [];
  363. },
  364. methods: {
  365. toggleSideBar() {
  366. this.$store.dispatch("app/toggleSideBar");
  367. },
  368. async logout() {
  369. await this.$store.dispatch("user/logout");
  370. localStorage.removeItem("firstMenuUrl");
  371. // await this.$store.dispatch("permission/removePermission")
  372. this.$router.push(`/login`);
  373. window.location.reload();
  374. },
  375. async onTenantChange() {
  376. try {
  377. const res = await tenantInfoQueryPage({ page: 1, rows: 999, payState: 1 }, )
  378. this.tenantList = res.data?.rows || []
  379. this.tenantVisible = true
  380. } catch(e) {}
  381. },
  382. accountSetting() {
  383. // 账号设置
  384. this.$router.push('/tenantSetting/tenantInfoSetting')
  385. },
  386. submitTenant() {
  387. this.$refs['tenantForm'].validate((res) => {
  388. if(res) {
  389. const tenantForm = this.tenantForm
  390. let tenantName = null
  391. this.tenantList.forEach(item => {
  392. if(item.id == tenantForm.tenantId) {
  393. tenantName = item.name
  394. }
  395. });
  396. let tenantConfig = sessionStorage.getItem('tenantConfig')
  397. tenantConfig = tenantConfig ? JSON.parse(tenantConfig) : {}
  398. if(tenantConfig.tenantId != tenantForm.tenantId) {
  399. //判断是否是当前路由
  400. // if(url == this.$route.path) {
  401. tenantConfig.tenantId = tenantForm.tenantId
  402. tenantConfig.tenantName = tenantName
  403. sessionStorage.setItem('tenantConfig', JSON.stringify(tenantConfig))
  404. this.$router.push({
  405. path: '/redirect',
  406. query: this.$route.fullPath
  407. })
  408. // } else {
  409. // this.$router.push({
  410. // path: url
  411. // })
  412. // }
  413. }
  414. }
  415. })
  416. },
  417. gotoRecode() {
  418. this.$router.push("/journal/journal");
  419. },
  420. resetPassWord() {
  421. this.resetVisible = true;
  422. },
  423. submitResetPassWord() {
  424. if (this.resetForm.password !== this.resetForm.password2) {
  425. this.$message.error("两次密码必须相同");
  426. return;
  427. }
  428. this.$refs["pwdForm"].validate((res) => {
  429. if (res) {
  430. // 发请求
  431. resetPassword({
  432. authCode: this.resetForm.authCode,
  433. mobile: this.$store.getters.phone,
  434. newPassword: this.resetForm.password,
  435. }).then((res) => {
  436. if (res.code == 200) {
  437. // 修改成功
  438. this.$message.success("修改成功");
  439. this.logout();
  440. }
  441. });
  442. }
  443. });
  444. },
  445. getCode() {
  446. // 获取验证码
  447. if (!this.$store.getters.phone) {
  448. this.$message.error("请输入正确的手机号");
  449. return;
  450. }
  451. if (!this.isDisable) {
  452. this.isDisable = true;
  453. // 发请求成功后开启定时器
  454. // 发送验证码
  455. axios
  456. .post(
  457. "/api-web/code/sendSms",
  458. qs.stringify({ mobile: this.$store.getters.phone })
  459. )
  460. .then((res) => {
  461. if (res.data.code == 200) {
  462. let timer = setInterval((res) => {
  463. if (this.timerCount <= 0) {
  464. clearInterval(timer);
  465. this.isDisable = false;
  466. this.btnName = "获取验证码";
  467. this.timerCount = 60;
  468. } else {
  469. this.timerCount--;
  470. this.btnName = `${this.timerCount}s后重试`;
  471. }
  472. }, 1000);
  473. }
  474. });
  475. }
  476. },
  477. openIns() {
  478. this.$refs.instructions.showInstructions();
  479. },
  480. checkShow(val) {
  481. this.isShowIns = val;
  482. },
  483. gotoOa(){
  484. // console.log(validOaUrl())
  485. // const Token = Cookies.get('cross-Token')
  486. // console.log(Token, validOaUrl().split('//')[1])
  487. // Cookies.set('Admin-Token', Token, { domain: `.${validOaUrl().split('//')[1]}`, path: '/' })
  488. // document.cookie = `Adminoken=${Token};domain=oadev.dayaedu.com;`
  489. window.open(validOaUrl())
  490. }
  491. },
  492. watch: {
  493. resetVisible(val) {
  494. if (!val) {
  495. this.resetForm = {
  496. phone: "",
  497. authCode: "",
  498. password: "",
  499. password2: "",
  500. };
  501. }
  502. },
  503. },
  504. };
  505. </script>
  506. <style lang="scss" scoped>
  507. .navbar_tag {
  508. margin: 0 5px 8px;
  509. }
  510. .indexlayout-top-menu {
  511. padding-left: 57px;
  512. height: 90px;
  513. line-height: 88px;
  514. flex: 1;
  515. display: flex;
  516. overflow: hidden;
  517. /* overflow-x: auto; */
  518. .indexlayout-top-menu-li {
  519. display: inline-block;
  520. padding: 0 5px;
  521. height: 90px;
  522. text-decoration: none;
  523. color: #f2f2f2;
  524. font-size: 16px;
  525. transition: all 0.3s ease;
  526. span {
  527. // display: block;
  528. transition: all 0.3s ease;
  529. padding: 10px 20px;
  530. }
  531. &:hover,
  532. &.active {
  533. span {
  534. font-weight: 500;
  535. // border-radius: 6px;
  536. }
  537. }
  538. // &.active span {
  539. // font-weight: bold;
  540. // }
  541. }
  542. .breadcrumb {
  543. line-height: 90px;
  544. margin-left: 10px;
  545. .el-breadcrumb__item {
  546. display: inline-block;
  547. float: none;
  548. }
  549. }
  550. }
  551. .popover-container {
  552. max-height: 350px;
  553. overflow-y: scroll;
  554. }
  555. .navbar {
  556. display: flex;
  557. flex-direction: row;
  558. justify-content: space-between;
  559. height: 90px;
  560. overflow: hidden;
  561. position: relative;
  562. z-index: 2000;
  563. box-shadow: 0px 8px 20px 0px rgba(0, 0, 0, 0.1);
  564. h2 {
  565. font-size: 18px;
  566. line-height: 90px;
  567. margin: 0 0 0 30px;
  568. display: inline-block;
  569. }
  570. .hamburger-container {
  571. line-height: 90px;
  572. height: 100%;
  573. float: left;
  574. cursor: pointer;
  575. transition: background 0.3s;
  576. -webkit-tap-highlight-color: transparent;
  577. &:hover {
  578. background: rgba(0, 0, 0, 0.025);
  579. }
  580. }
  581. .breadcrumb-container {
  582. float: left;
  583. }
  584. .left-menu {
  585. line-height: 90px;
  586. // padding-right: 22px;
  587. font-size: 16px;
  588. .topIcon {
  589. width: 20px;
  590. height: 25px;
  591. }
  592. }
  593. .right-menu {
  594. min-width: 154px;
  595. float: right;
  596. height: 100%;
  597. line-height: 90px;
  598. display: flex;
  599. flex-direction: row;
  600. justify-content: flex-start;
  601. &:focus {
  602. outline: none;
  603. }
  604. .msginfo.ins {
  605. img {
  606. width: 18px;
  607. height: 23px;
  608. }
  609. }
  610. .msginfo {
  611. display: flex;
  612. flex-direction: row;
  613. justify-content: flex-start;
  614. align-items: center;
  615. padding-right: 25px;
  616. position: relative;
  617. cursor: pointer;
  618. img {
  619. width: 24px;
  620. height: 24px;
  621. }
  622. .active {
  623. position: absolute;
  624. width: 7px;
  625. height: 7px;
  626. background-color: #f97215;
  627. border-radius: 50%;
  628. top: 20px;
  629. right: -4px;
  630. }
  631. }
  632. .right-menu-item {
  633. display: inline-block;
  634. padding: 0 8px;
  635. height: 100%;
  636. font-size: 14px;
  637. color: #5a5e66;
  638. vertical-align: text-bottom;
  639. &.hover-effect {
  640. cursor: pointer;
  641. transition: background 0.3s;
  642. &:hover {
  643. background: rgba(0, 0, 0, 0.025);
  644. }
  645. }
  646. }
  647. .avatar-container {
  648. height: 90px;
  649. margin-right: 42px;
  650. cursor: pointer;
  651. .avatar-wrapper {
  652. position: relative;
  653. display: flex;
  654. flex-direction: row;
  655. justify-content: flex-start;
  656. align-items: center;
  657. span {
  658. margin-left: 8px;
  659. font-size: 14px;
  660. font-weight: 500;
  661. // color: rgba(68, 68, 68, 1);
  662. color: #fff;
  663. }
  664. .user-avatar {
  665. cursor: pointer;
  666. width: 32px;
  667. height: 32px;
  668. border: 2px solid #f0f2f5;
  669. border-radius: 50%;
  670. }
  671. .el-icon-caret-bottom {
  672. cursor: pointer;
  673. position: absolute;
  674. right: -20px;
  675. top: 25px;
  676. font-size: 14px;
  677. }
  678. }
  679. }
  680. }
  681. }
  682. </style>