layout-components-Sidebar-FixiOSBug.js
export default {
computed: {
device() {
return this.$store.state.app.device
},
},
mounted() {
this.fixBugIniOS()
},
methods: {
fixBugIniOS() {
const $subMenu = this.$refs.subMenu
if ($subMenu) {
const handleMouseleave = $subMenu.handleMouseleave
$subMenu.handleMouseleave = e => {
if (this.device === 'mobile') {
return
}
handleMouseleave(e)
}
}
},
},
}
layout-components-Sidebar-index.vue
<template>
<div>
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="$route.path"
:collapse="isCollapse"
:background-color="variables.menuBg"
:text-color="variables.menuText"
:unique-opened="false"
:active-text-color="variables.menuActiveText"
:collapse-transition="false"
mode="vertical"
>
<sidebar-item v-for="route in routes" :key="route.path" :item="route" :base-path="route.path" />
</el-menu>
</el-scrollbar>
</div>
</template>
<script>
import variables from '@/styles/variables.scss'
import { UserInfo } from '@/utils/auth'
import SidebarItem from './SidebarItem'
import { routerChange } from '@/router'
import { mapGetters } from 'vuex'
export default {
components: { SidebarItem },
data() {
return {
platName: UserInfo.value('platName'),
options: [],
}
},
computed: {
...mapGetters(['sidebar']),
routes() {
const routes = this.options
return routes.find(route => route.path === '/')?.children || []
},
activeMenu() {
const route = this.$route
const { meta, path } = route
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
variables() {
return variables
},
isCollapse() {
return !this.sidebar.opened
// return false
},
},
created() {
routerChange(routers => {
this.options = routers
})
},
}
</script>
<style lang="scss" scoped>
.slider-title {
font-size: 16px;
height: 55px;
line-height: 55px;
padding-left: 20px;
position: relative;
&::after {
content: ' ';
position: absolute;
width: 100%;
height: 2px;
left: 0;
bottom: 0;
background-color: #f5f6f7;
z-index: 5;
}
}
</style>
Item.vue
<script>
export default {
name: 'MenuItem',
functional: true,
props: {
icon: {
type: String,
default: ''
},
title: {
type: String,
default: ''
}
},
render(h, context) {
const { icon, title } = context.props;
const vnodes = [];
if (icon) {
if (icon.includes('el-icon')) {
vnodes.push(<i class={[icon, 'sub-el-icon']} />);
} else {
vnodes.push(<svg-icon icon-class={icon}/>);
}
}
if (title) {
vnodes.push(<span slot='title'>{(title)}</span>);
}
return vnodes;
}
};
</script>
<style scoped>
.sub-el-icon {
color: currentColor;
width: 1em;
height: 1em;
}
</style>
Link.vue
<template>
<component :is="type" v-bind="linkProps(to)">
<slot />
</component>
</template>
<script>
import { isExternal } from '@/utils/validate';
export default {
props: {
to: {
type: String,
required: true
}
},
computed: {
isExternal() {
return isExternal(this.to);
},
type() {
if (this.isExternal) {
return 'a';
}
return 'router-link';
}
},
methods: {
linkProps(to) {
if (this.isExternal) {
return {
href: to,
target: '_blank',
rel: 'noopener'
};
}
return {
to: to
};
}
}
};
</script>
Logo.vue
<template>
<component :is="type" v-bind="linkProps(to)">
<slot />
</component>
</template>
<script>
import { isExternal } from '@crm-pro/utils'
export default {
props: {
to: {
type: String,
required: true,
},
},
computed: {
isExternal() {
return isExternal(this.to)
},
type() {
if (this.isExternal) {
return 'a'
}
return 'router-link'
},
},
methods: {
linkProps(to) {
if (this.isExternal) {
return {
href: to,
target: '_blank',
rel: 'noopener',
}
}
return {
to: to,
}
},
},
}
</script>
SidebarItem.vue
<template>
<div v-if="!item.hidden">
<template
v-if="
hasOneShowingChild(item.children, item) &&
(!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
!item.alwaysShow
"
>
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
<item :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" :title="onlyOneChild.meta.title" />
</el-menu-item>
</app-link>
</template>
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
<template slot="title">
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
</template>
<sidebar-item
v-for="child in item.children"
:key="child.path"
:is-nest="true"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
</el-submenu>
</div>
</template>
<script>
import path from 'path'
import { isExternal } from '@/utils/validate'
import Item from './Item'
import AppLink from './Link'
import FixiOSBug from './FixiOSBug'
export default {
name: 'SidebarItem',
components: { Item, AppLink },
mixins: [FixiOSBug],
props: {
// route object
item: {
type: Object,
required: true,
},
isNest: {
type: Boolean,
default: false,
},
basePath: {
type: String,
default: '',
},
},
data() {
this.onlyOneChild = null
return {}
},
methods: {
hasOneShowingChild(children = [], parent) {
const showingChildren = children.filter(item => {
if (item.hidden) {
return false
} else {
// Temp set(will be used if only has one showing child)
this.onlyOneChild = item
return true
}
})
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
this.onlyOneChild = { ...parent, path: '', noShowingChildren: true }
return true
}
return false
},
resolvePath(routePath) {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(this.basePath)) {
return this.basePath
}
return path.resolve(this.basePath, routePath)
},
},
}
</script>
AppMain.vue
<template>
<section class="app-main">
<transition name="fade-transform" mode="out-in">
<router-view />
</transition>
</section>
</template>
<script>
export default {
name: 'AppMain'
};
</script>
<style lang="scss" scoped>
.app-main {
height: 100%;
margin-left: 0px;
position: relative;
background-color: #fff;
padding: 20px;
}
.fixed-header+.app-main {
padding-top: 50px;
}
</style>
<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
.fixed-header {
padding-right: 15px;
}
}
</style>
mixin-ResizeHandler.js
import store from '@/store';
const { body } = document;
const WIDTH = 992; // refer to Bootstrap's responsive design
export default {
watch: {
$route(route) {
if (this.device === 'mobile' && this.sidebar.opened) {
store.dispatch('app/closeSideBar', { withoutAnimation: false });
}
}
},
beforeMount() {
window.addEventListener('resize', this.$_resizeHandler);
},
beforeDestroy() {
window.removeEventListener('resize', this.$_resizeHandler);
},
mounted() {
const isMobile = this.$_isMobile();
if (isMobile) {
store.dispatch('app/toggleDevice', 'mobile');
store.dispatch('app/closeSideBar', { withoutAnimation: true });
}
},
methods: {
// use $_ for mixins properties
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
$_isMobile() {
const rect = body.getBoundingClientRect();
return rect.width - 1 < WIDTH;
},
$_resizeHandler() {
if (!document.hidden) {
const isMobile = this.$_isMobile();
store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop');
if (isMobile) {
store.dispatch('app/closeSideBar', { withoutAnimation: true });
}
}
}
}
};
layout-index.vue
<template>
<div :class="classObj" class="app-wrapper">
<el-container>
<el-header height="50px" :style="{ display: 'flex' }">
<div
v-if="sidebar.opened"
@click="handleClickOutside"
:style="{
width: '50px',
height: '100%',
fontSize: '20px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
color: '#0046ff',
}"
>
<i class="el-icon-s-fold"></i>
</div>
<div
v-if="!sidebar.opened"
@click="toggleSideBar"
:style="{
width: '50px',
height: '100%',
fontSize: '20px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
color: '#0046ff',
}"
>
<i class="el-icon-s-unfold"></i>
</div>
<topbar />
</el-header>
<el-container>
<el-aside :width="elAsideConWidth" class="elAside-con">
<app-side-bar class="sidebar-container" />
</el-aside>
<el-main>
<app-main :class="appMainClass" />
</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
import Topbar from '@/components/Topbar'
import { AppMain, AppSideBar } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'
export default {
name: 'Layout',
components: {
Topbar,
AppMain,
AppSideBar,
},
mixins: [ResizeMixin],
data() {
return {
isShowSidebar: true,
elAsideConWidth: '200px', // 55px
}
},
computed: {
...mapState({
sidebar: state => state.app.sidebar,
device: state => state.app.device,
showSettings: state => state.settings.showSettings,
needTagsView: state => state.settings.tagsView,
fixedHeader: state => state.settings.fixedHeader,
}),
classObj() {
return {
openSidebar: this.sidebar.opened,
hideSidebar: !this.sidebar.opened,
}
},
appMainClass() {
const pathClass = this.$route.path.substr(1).replace(/\//g, '-')
return {
[pathClass]: true,
}
},
},
watch: {
$route(to, from) {
// 监听路由发生变化判断是否显示侧边栏
this.handleShowSide()
},
},
mounted() {
// 判断路由是否是帐号管理并显示侧边栏
this.handleShowSide()
// console.log('this.sidebar.opened ===>',JSON.stringify(this.sidebar.opened));
if (this.sidebar.opened === true || this.sidebar.opened === 1) {
// this.toggleSideBar()
this.elAsideConWidth = '200px'
}
if (this.sidebar.opened === false || this.sidebar.opened === 0) {
// this.handleClickOutside()
this.elAsideConWidth = '55px'
}
},
methods: {
handleShowSide() {
this.isShowSidebar = true
},
toggleSideBar() {
this.elAsideConWidth = '200px'
this.$store.dispatch('app/toggleSideBar')
},
handleClickOutside() {
this.elAsideConWidth = '55px'
this.$store.dispatch('app/closeSideBar', { withoutAnimation: false })
},
},
}
</script>
<style lang="scss" scoped>
@import '~@/styles/mixin.scss';
@import '~@/styles/variables.scss';
.app-wrapper {
@include clearfix;
position: relative;
height: 100vh;
width: 100%;
overflow: hidden;
&.openSidebar {
position: fixed;
top: 0;
}
.elAside-con {
// position: relative;
.elAside-switch-1 {
position: absolute;
z-index: 10000000;
left: 200px;
top: 50px;
color: black;
}
.elAside-switch-2 {
position: absolute;
z-index: 10000000;
left: 55px;
top: 50px;
color: black;
}
}
}
.el-container {
min-height: 100%;
}
.el-header {
background-color: $white;
line-height: 50px;
position: fixed;
left: 0;
right: 0;
top: 0;
height: 50px;
z-index: 99;
box-shadow: 0 1px 2px 0 rgba(41, 48, 64, 0.2);
padding: 0 20px 0 0;
}
.el-aside {
z-index: 9;
}
.el-footer {
background-color: $navBg;
line-height: 50px;
}
.el-main {
height: 100vh;
padding: 66px 16px 0;
background-color: #f5f6f7;
margin: 0;
}
.app-side-bar {
width: 240px;
}
.app-main {
flex: 1;
word-break: break-all;
overflow: auto;
}
.performance-totalOverview {
background-color: transparent;
padding: 0;
}
.hideSidebar {
.sidebar-container {
width: 54px !important;
}
.main-container {
margin-left: 54px;
}
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
.el-tooltip {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
}
}
.el-submenu {
overflow: hidden;
& > .el-submenu__title {
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
.sub-el-icon {
margin-left: 19px;
}
.el-submenu__icon-arrow {
display: none;
}
}
}
.el-menu--collapse {
.el-submenu {
& > .el-submenu__title {
& > span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
}
}
</style>
import { UserInfo } from '@/utils/auth.js'文件
import Cookies from 'js-cookie'
// 本地存储的key值,要保证各系统中的一致性
const StoreKeys = {
token: '',
userinfo: '',
}
const UserStore = {
get: key => JSON.parse(window.localStorage.getItem(key)),
set: (key, value) => window.localStorage.setItem(key, JSON.stringify(value)),
remove: key => window.localStorage.removeItem(key),
}
// 设置配置参数
export function setup(...param) {
const { token, userinfo } = UserStore.get('cc-app-config') || {}
console.log('setup 0 ==>', param, token, userinfo)
if (!token || !userinfo) {
return goHome()
}
StoreKeys.token = token
StoreKeys.userinfo = userinfo
console.log('setup 1==>', StoreKeys)
}
setup()
// local run
// Cookies.set(StoreKeys.token, 'ss111');
// UserStore.set({ token: 'ss111' });
/**
* 生成登录用户实例,包含以下属性
companyName: null
deptName: null
displayName: "系统管理员"
email: null
mobile: null
token: "7fb2be19f6d947db8caaa7ce1148e0c3"
expire: 3600 // 有效期s
userId: "1348933878131302402"
userName: "cdp-admin"
userRoleNameList: null
userType: "1",
menuPermission: [],
* @param {Object} store 用户信息存储实例
* @returns 用户信息
*/
function createUserInfo(store) {
const sKey = StoreKeys.userinfo
let curInfo = store.get(sKey) || {}
const userInfo = {
// 获取、设置属性值
value: (key, value) => {
if (value === undefined) {
return curInfo[key]
}
curInfo[key] = value
// store.set(sKey, curInfo); // 值有变化,更新到store中
return userInfo
},
remove: key => {
delete curInfo[key]
return userInfo
},
save: () => {
store.set(sKey, curInfo)
},
clear: () => {
curInfo = {}
Cookies.remove(sKey)
},
}
return userInfo
}
// 当前登录用户信息
export const UserInfo = createUserInfo(UserStore)
export function setView(resource) {
const resourceKeys = [
'productGroupId',
'productGroupName',
'projectId',
'projectName',
'pmProjectId',
'pmProjectName',
]
// 各资源之前为互斥操作,在设置之前先全部清除
resourceKeys.forEach(key => {
UserInfo.remove(key)
})
// 设置对应的参数
resourceKeys.filter(key => resource[key] !== undefined).forEach(key => UserInfo.value(key, resource[key]))
// 持久化
UserInfo.save()
goHome(false)
}
export function goHome(redirect, ...param) {
console.log('获取首页 0 ==>', redirect, location, param)
if (window.location.pathname === '/') {
return
}
let url = '/?redirect=' + encodeURIComponent(location.pathname + location.hash)
if (redirect === false) {
url = '/'
}
console.log('获取首页 1 ==>', redirect, url, location)
window.location = url
}
function createToken() {
const sKey = StoreKeys.token
let curToken = Cookies.get(sKey)
return {
get: () => {
// console.log('进入这里0', curToken);
if (!curToken) {
return undefined
}
// console.log('进入这里1', curToken !== UserInfo.value('token'));
if (curToken !== UserInfo.value('token')) {
// console.log('###########', sKey)
// 异常,用户token和保存的不一致,可能是被篡改
Cookies.remove(sKey)
return undefined
}
return curToken
},
set: value => {
curToken = value
Cookies.set(sKey, value)
},
clear: () => {
curToken = ''
Cookies.remove(sKey)
},
}
}
// Token信息
export const Token = createToken()
export default {
clear: () => {
Token.clear()
UserInfo.clear()
},
}
import { isExternal } from '@/utils/validate.js';文件
export function isExternal(path) {
return /^(https?:|mailto:|tel:)/.test(path);
}
import Topbar from '@/components/Topbar.vue'
<template>
<div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
<item :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" :title="onlyOneChild.meta.title" />
</el-menu-item>
</app-link>
</template>
<el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
<template slot="title">
<item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
</template>
<sidebar-item v-for="child in item.children" :key="child.path" :is-nest="true" :item="child" :base-path="resolvePath(child.path)" class="nest-menu" />
</el-submenu>
</div>
</template>
<script>
import path from 'path'
import { isExternal } from '@crm-pro/utils'
import Item from './Item'
import AppLink from './Link'
import FixiOSBug from './FixiOSBug'
export default {
name: 'SidebarItem',
components: { Item, AppLink },
mixins: [FixiOSBug],
props: {
// route object
item: {
type: Object,
required: true,
},
isNest: {
type: Boolean,
default: false,
},
basePath: {
type: String,
default: '',
},
},
data() {
this.onlyOneChild = null
return {}
},
methods: {
hasOneShowingChild(children = [], parent) {
const showingChildren = children.filter((item) => {
if (item.hidden) {
return false
} else {
this.onlyOneChild = item
return true
}
})
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
this.onlyOneChild = { ...parent, path: '', noShowingChildren: true }
return true
}
return false
},
resolvePath(routePath) {
if (isExternal(routePath)) {
return routePath
}
if (isExternal(this.basePath)) {
return this.basePath
}
return path.resolve(this.basePath, routePath)
},
},
}
</script>
import Log from '@/utils/log'
import { parseTime } from './time'
const Config = {
debug: true || /(&|\?)debug=true/.test(window.location.search),
log: true,
error: true,
}
window.console.setLog = (sType, bValue) => {
Config[sType] = bValue
}
function showMsg(isShow, ...msg) {
if (!isShow) {
return
}
console.log(parseTime(new Date()), ...msg)
}
function MyLog(sModuleName) {
return {
debug: (...msg) => {
// 调试信息,在发矶版本中会被关闭
const title = `[debug]${sModuleName}`
showMsg(Config.debug, title, ...msg)
},
log: (...msg) => {
// 一般性信息闭
const title = `[log]${sModuleName}`
showMsg(Config.log, title, ...msg)
},
error: (...msg) => {
// 错误信息在不强制关闭的情况下都要显示闭
const title = `[error]${sModuleName}`
showMsg(Config.error !== false, title, ...msg)
},
}
}
export default MyLog
import { parseTime } from './time'
export function parseTime(time, cFormat) {
if (arguments.length === 0 || !time) {
return null
}
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if (typeof time === 'string') {
if (/^[0-9]+$/.test(time)) {
// support "1548221490638"
time = parseInt(time)
} else {
time = time.replace(new RegExp(/-/gm), '/')
}
}
if (typeof time === 'number' && time.toString().length === 10) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay(),
}
const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
const value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') {
return ['日', '一', '二', '三', '四', '五', '六'][value]
}
return value.toString().padStart(2, '0')
})
return time_str
}
/**
* @param {number} time
* @param {string} option
* @returns {string}
*/
export function formatTime(time, option) {
if (('' + time).length === 10) {
time = parseInt(time) * 1000
} else {
time = +time
}
const d = new Date(time)
const now = Date.now()
const diff = (now - d) / 1000
if (diff < 30) {
return '刚刚'
} else if (diff < 3600) {
// less 1 hour
return Math.ceil(diff / 60) + '分钟前'
} else if (diff < 3600 * 24) {
return Math.ceil(diff / 3600) + '小时前'
} else if (diff < 3600 * 24 * 2) {
return '1天前'
}
if (option) {
return parseTime(time, option)
} else {
return d.getMonth() + 1 + '月' + d.getDate() + '日' + d.getHours() + '时' + d.getMinutes() + '分'
}
}
import settings from '@/settings'
const defSettings = {};
module.exports = {
...defSettings,
/**
* @type {boolean} true | false
* @description Whether fix the header
*/
fixedHeader: false,
/**
* @type {boolean} true | false
* @description Whether show the logo in sidebar
*/
sidebarLogo: false,
subProductVersion: ''
};
router-index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
import Log from '@/utils/log'
import constantRoutes from './datas.js'
import store from '@/store'
import { get } from '../utils/request'
const log = Log('router')
function filterRouter(routers) {
function _filter(nodes) {
return nodes.filter(node => {
// 不支持的跳由,直接过滤掉
if (node.supported === false) {
log.debug(node.name, 'is not supported')
return false
}
// 有子路由,且没有符合条件的子路由时,过滤掉
if (node.children) {
node.children = _filter(node.children)
if (node.children.length === 0) {
log.debug(node.name, 'has no children')
return false
}
}
// 默认保留
return true
})
}
// console.log(_filter(routers))
return _filter(routers)
}
export function getMenuPermission() {
return get('base/user/getMenuPermission').then(({ data }) => {
const optList = data.operationList.reduce((result, item) => {
result[item] = true
return result
}, {})
store.dispatch('user/resetOptList', optList)
resetRouter(router, data.menuNameList)
})
}
const router = new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: filterRouter(constantRoutes),
})
export default function createRouter() {
return getMenuPermission().then(() => router)
}
// 路由通知
const onRouterChange = {
data: null,
fn: [],
}
function resetRouter(router, menuList) {
function checkExist(item, menuList) {
if (item.name && !menuList.includes(item.name)) {
return false
}
if (item.children) {
item.children = item.children.filter(item1 => checkExist(item1, menuList))
return item.children.length
}
return true
}
// 把过滤好的路由缓存,并通知订阅者
const routes = router.options.routes.filter(item => checkExist(item, menuList))
onRouterChange.data = routes
onRouterChange.fn.forEach(cb => cb(routes))
}
export function routerChange(cb) {
log.debug('onRouterChange')
if (onRouterChange.data) {
cb(onRouterChange.data)
}
onRouterChange.fn.push(cb)
}
router-data.js
export default [
{
path: '/',
component: () => import('@/layout'),
redirect: '/workbench',
children: [
{
path: 'workbench',
// hidden: true,
component: () => import('@/views/workbench/index'),
meta: { title: '他', icon: 'el-icon-postcard', activeMenu: 'workbench' },
},
{
path: 'www',
component: () => import('@/views/index'),
name: '',
meta: { title: '12', icon: 'el-icon-s-cooperation', activeMenu: 'www' },
children: [
{
path: '/www/list',
component: () => import('@/views/www/qqq'),
meta: { title: '123', icon: '', parent: '12', activeMenu: '/www/list' },
},
{
path: 'ccc',
component: () => import('@/views/index'),
name: '123',
meta: { title: '123', icon: '', parent: '12', activeMenu: 'ccc' },
children: [
{
path: '/ccc/list',
name: '88',
component: () => import('@/views/ggg/index.vue'),
meta: {
title: '88',
icon: '',
parent: '123',
activeMenu: '/ccc/listt',
},
},
],
},
],
},
{
path: '/404',
component: () => import('@/views/404'),
hidden: true,
},
],
},
]
import { get } from '../utils/request'
import axios from 'axios'
import { Message, Loading } from 'ccxd-ux'
import auth, { Token, goHome } from '@/utils/auth'
import qs from 'qs'
import cacheRequest from './requestCache'
let loadingInstance1 = null
const loadingList = [
'/baidu',
]
const Config = {
baseUrl: '/api/v1/',
}
// 补全URL路径
// 除以下两种情况,都会增加Config.baseUrl前缀
// 1。 以http:// or https://开头
// 2。 以 / 开头
function getUrl(url, params) {
let sUrl = url
Object.keys(params || {}).forEach(name => {
sUrl = sUrl.replace(`{${name}}`, params[name])
})
// 以 / 开头的路径不需增加前缀
if (sUrl.charAt(0) === '/') {
return sUrl
}
// 以http:// or https://开头的路径不需增加前缀
if (/^https?:\/\//.test(sUrl)) {
return sUrl
}
// 所有的相对路径都要增加前缀
return `${Config.baseUrl}${sUrl}`
}
function showError(errMsg) {
const sMsg = errMsg || 'Error'
Message({
message: sMsg,
type: 'error',
duration: 5000,
})
return Promise.reject(new Error(sMsg))
}
// create an axios instance
const service = axios.create({
// baseURL: Config.baseUrl,
// withCredentials: true, // send cookies when cross-domain requests
timeout: 150000, // request timeout
paramsSerializer: function (params) {
return qs.stringify(params, { arrayFormat: 'brackets' })
},
})
// request interceptor
service.interceptors.request.use(
config => {
// do something before request is sent
if (config.method === 'get') {
// 如果是get请求,且params是数组类型如arr=[1,2],则转换成arr=1&arr=2
config.paramsSerializer = function (params) {
return qs.stringify(params, { arrayFormat: 'repeat' })
}
}
// 控制台视图中必须是登录后访问,因此肯定存在token
config.headers.common['Authorization'] = Token.get() || sessionStorage.getItem('bToken')
// 产品视图时,header中需要增加projectId
config.headers.common['ResourceView'] = `null:null`
if (Object.prototype.toString.call(sessionStorage.getItem('loginType')) !== '[object Null]') {
config.headers.common['type'] = sessionStorage.getItem('loginType')
}
// loadingInstance1 = Loading.service({ fullscreen: true })
if (loadingList.indexOf(config.url) >= 0 || loadingList.length == 0) {
loadingInstance1 = Loading.service({
lock: true,
text: '数据正在加载,请稍后...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.01)',
})
}
return config
},
error => {
// do something with request error
loadingInstance1 && loadingInstance1.close()
console.log(error) // for debug
return Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* Determine the request status by custom code
* Here is just an example
* You can also judge the status by HTTP Status Code
*/
response => {
//如果接口返回不是'application/json'类型,就走下载
loadingInstance1 && loadingInstance1.close()
// if (response.headers['content-type'] !== 'application/json') {
if (!response.headers['content-type'] || response.headers['content-type'].indexOf('json') < 0) {
let name = ''
if (response.headers['content-disposition']) {
name =
response.headers['content-disposition']
.split(';')
.map(item => {
const [key, value] = item.split('=')
return { key, value }
})
.filter(item => item.key === 'filename')
.map(item => item.value)[0] || 'file'
} else {
name = response.config.url.split('/').pop()
}
let blob = new Blob([response.data])
let url = window.URL.createObjectURL(blob)
let aLink = document.createElement('a')
aLink.style.display = 'none'
aLink.href = url
aLink.setAttribute('download', decodeURI(name))
document.body.appendChild(aLink)
aLink.click()
document.body.removeChild(aLink) // 下载完成移除元素
window.URL.revokeObjectURL(url) // 释放掉blob对象
return response.data
} else {
const res = response.data
if (
res.code === 20000 ||
res.code === 0 ||
res.code === 3110200005 ||
res.respCode === '1000' ||
res.respCode === 1000
) {
return res
}
// 状态码202xx为无效token,重新登录
if (res.code >= 20200 && res.code < 20300) {
auth.clear()
goHome(true, 0)
}
// if the custom code is not 20000, it is judged as an error.
return showError(res.message)
}
},
error => {
// loadingInstance1 && loadingInstance1.close()
console.log('err' + error) // for debug
return showError(error.message)
}
)
export default service
export function get(url, params, config = {}) {
const sUrl = getUrl(url, params)
const fnRequest = () => service.get(sUrl, { params, ...config })
return cacheRequest(sUrl, fnRequest)
}
export function post(sUrl, data, config = {}) {
return service.post(getUrl(sUrl, data), data, config)
}