Navbar.vue 19 KB

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