abstract-dialog移除了top属性

代码优化
移除无用代码
master
toesbieya 6 years ago
parent 7d47e9c0ec
commit 875ad55d90
  1. 4
      vue/src/component/OrgTree/index.vue
  2. 7
      vue/src/component/abstract/Dialog/index.vue
  3. 6
      vue/src/component/form/Anchor/index.vue
  4. 4
      vue/src/filter/index.js
  5. 4
      vue/src/layout/component/Aside/index.vue
  6. 10
      vue/src/layout/component/Header/component/Navbar/component/Bell.vue
  7. 10
      vue/src/layout/component/Header/component/Navbar/component/HeadMenu.vue
  8. 7
      vue/src/layout/component/Header/component/Navbar/index.vue
  9. 33
      vue/src/layout/component/Header/component/TagsView/index.vue
  10. 5
      vue/src/layout/mixin/menu.js
  11. 2
      vue/src/layout/mixin/tagsViewPersistent.js
  12. 80
      vue/src/layout/store/tagsView.js
  13. 30
      vue/src/layout/布局设计.md
  14. 2
      vue/src/mixin/docDetailMixin.js
  15. 2
      vue/src/store/module/user.js
  16. 18
      vue/src/util/auth.js
  17. 99
      vue/src/util/browser.js
  18. 39
      vue/src/util/color.js
  19. 4
      vue/src/util/element-ui/elTree.js
  20. 26
      vue/src/util/excel/common.js
  21. 28
      vue/src/util/excel/exceljs.js
  22. 28
      vue/src/util/route.js
  23. 16
      vue/src/util/secret.js
  24. 62
      vue/src/util/storage.js
  25. 47
      vue/src/util/tree.js

@ -70,7 +70,7 @@ export default {
this.flatData[data[this.props.id]] = data
},
/**
* @description 工具方法用于遍历树状数据的每个节点 fn为在该节点做的操作其有一个参数即当前节点数据
* 工具方法用于遍历树状数据的每个节点 fn为在该节点做的操作其有一个参数即当前节点数据
*/
_mapData(data, fn) {
fn(data)
@ -78,7 +78,7 @@ export default {
children && children.forEach(child => this._mapData(child, fn))
},
/**
* @description 用来便利所有节点数据将树状数据扁平化存放到flatData用于数据更新后展开状态的恢复
* 用来便利所有节点数据将树状数据扁平化存放到flatData用于数据更新后展开状态的恢复
*/
_updateExpandStatus() {
this._mapData(this.dataCloned, this._setFlatData)

@ -1,5 +1,5 @@
<script type="text/jsx">
import variables from '@/asset/style/variables.scss'
import cssVariables from '@/asset/style/variables.scss'
import LoadingMask from './LoadingMask'
export default {
@ -11,12 +11,11 @@ export default {
value: Boolean,
title: String,
loading: {type: Boolean, default: true},
top: {type: String, default: variables['dialogTop']},
width: {type: String, default: '30%'}
},
render(h, context) {
const {data, children, listeners, props: {value, title, loading, top, width}} = context
const {data, children, listeners, props: {value, title, loading, width}} = context
const onClose = function () {
return listeners.close ? () => listeners.close(false) : () => ({})
@ -27,7 +26,7 @@ export default {
v-drag-dialog
visible={value}
custom-class="abstract-dialog"
top={top}
top={cssVariables['dialogTop']}
width={width}
on-close={onClose}
{...data}

@ -14,7 +14,7 @@
<script>
import {debounce} from "@/util"
import {getScroll, getOffsetTop, scrollTo} from "@/util/browser"
import {getScroll, getTopDistance, scrollTo} from "@/util/browser"
export default {
name: "FormAnchor",
@ -44,7 +44,7 @@ export default {
for (const {ref} of this.data) {
const target = this.getDomFromRef(ref)
if (!target) continue
const top = getOffsetTop(target, container)
const top = getTopDistance(target, container)
if (top <= offsetTop + bounds) {
sections.push({ref, top})
}
@ -74,7 +74,7 @@ export default {
const container = this.getContainer()
const scrollTop = getScroll(container, true)
const elOffsetTop = getOffsetTop(targetElement, container)
const elOffsetTop = getTopDistance(targetElement, container)
const y = scrollTop + elOffsetTop - this.offsetTop
//100ms

@ -44,7 +44,7 @@ export function numberFormatter(num, digits = 2) {
/**
* 10000 => "10,000"
* @param {number} num
* @param num {number}
*/
export function toThousandFilter(num) {
if (isEmpty(num)) return ''
@ -53,7 +53,7 @@ export function toThousandFilter(num) {
/**
* Upper case first char
* @param {String} string
* @param string {string}
*/
export function uppercaseFirst(string) {
if (isEmpty(string)) return ''

@ -5,7 +5,7 @@ import {getters as asideGetters, mutations as asideMutations} from "@/layout/sto
import Logo from './Logo'
import NavMenuItem from '@/component/menu/NavMenu/item'
import {getSidebarMenus, getActiveMenuByRoute} from "@/layout/util"
import {getOffsetTop} from "@/util/browser"
import {getTopDistance} from "@/util/browser"
export default {
name: 'Aside',
@ -186,7 +186,7 @@ export default {
//
if (scrollHeight <= menuHeight) return
const elHeight = el.offsetHeight, between = getOffsetTop(el, container)
const elHeight = el.offsetHeight, between = getTopDistance(el, container)
//undefined
let distance

@ -5,6 +5,8 @@
</template>
<script>
import {refreshPage} from "@/util/route"
export default {
name: "Bell",
@ -16,9 +18,11 @@ export default {
methods: {
jump() {
let target = '/message/user'
if (this.$route.path === target) target = `/redirect${target}`
this.$router.replace(target)
const target = '/message/user'
this.$route.path === target
? refreshPage()
: this.$router.push(target)
}
},

@ -146,10 +146,10 @@ export default {
})
},
renderVisibleMenus(h, visibleMenus) {
renderVisibleMenus(visibleMenus) {
return visibleMenus.map(m => <nav-menu-item menu={m} show-icon-max-depth={1}/>)
},
renderHiddenMenus(h, hiddenMenus, hideAll) {
renderHiddenMenus(hiddenMenus, hideAll) {
if (hiddenMenus.length <= 0) return
const content = hideAll
@ -168,7 +168,7 @@ export default {
this.createResizeObserver()
},
render(h) {
render() {
const {lastVisibleIndex, menus} = this
if (!Array.isArray(menus) || menus.length <= 0 || menus.length === 1 && !this.alwaysShow) {
return
@ -195,8 +195,8 @@ export default {
default-active={this.activeMenu}
on-select={this.onSelect}
>
{this.renderVisibleMenus(h, visibleMenus)}
{this.renderHiddenMenus(h, hiddenMenus, hiddenMenus.length === menus.length)}
{this.renderVisibleMenus(visibleMenus)}
{this.renderHiddenMenus(hiddenMenus, hiddenMenus.length === menus.length)}
</el-menu>
)
}

@ -9,6 +9,7 @@ import {getters as appGetters} from "@/layout/store/app"
import {getters as asideGetters, mutations as asideMutations} from "@/layout/store/aside"
import {getSidebarMenus} from "@/layout/util"
import {elConfirm} from "@/util/message"
import {refreshPage} from "@/util/route"
export default {
name: 'navbar',
@ -22,10 +23,6 @@ export default {
computed: mapState('user', ['avatar', 'name', 'prepareLogout']),
methods: {
refresh() {
this.$router.replace({path: `/redirect${this.$route.fullPath}`})
},
command(command) {
switch (command) {
case 'user':
@ -67,7 +64,7 @@ export default {
return [
<bell class="navbar-item"/>,
<div class="navbar-item" title="刷新" on-click={this.refresh}>
<div class="navbar-item" title="刷新" on-click={() => refreshPage()}>
<i class="el-icon-refresh-right navbar-icon"/>
</div>,

@ -2,11 +2,11 @@
import shortcutMixin from '@/layout/mixin/tagsViewShortcut'
import decideRouterTransitionMixin from '@/layout/mixin/decideRouterTransition'
import persistenceMixin from '@/layout/mixin/tagsViewPersistent'
import {route as routeConfig} from '@/config'
import {getters as appGetters} from "@/layout/store/app"
import {getters as tagsViewGetters, mutations as tagsViewMutations} from "@/layout/store/tagsView"
import ContextMenu from "@/component/menu/ContextMenu"
import ScrollPanel from './ScrollPanel'
import {refreshPage} from "@/util/route"
export default {
mixins: [shortcutMixin, decideRouterTransitionMixin, persistenceMixin],
@ -70,7 +70,7 @@ export default {
//
for (const tag of getAffixTags(this.menus)) {
tagsViewMutations.addVisitedView(tag)
tagsViewMutations.addTagOnly(tag)
}
//
@ -78,7 +78,7 @@ export default {
},
//meta.titletab
addTag(to) {
to.meta.title && tagsViewMutations.addView(to)
to.meta.title && tagsViewMutations.addTagAndCache(to)
},
//tab
@ -95,23 +95,23 @@ export default {
* */
refreshSelectedTag() {
if (!this.selectedTag) return
tagsViewMutations.delCachedView(this.selectedTag)
this.$nextTick(() => this.$router.replace({path: `/redirect${this.selectedTag.fullPath}`}))
tagsViewMutations.delCacheOnly(this.selectedTag)
this.$nextTick(() => refreshPage(this.selectedTag))
},
closeSelectedTag(view) {
if (this.isAffix(view)) return
tagsViewMutations.delView(view)
tagsViewMutations.delTagAndCache(view)
this.isActive(view) && this.gotoLastView()
},
closeOthersTags() {
tagsViewMutations.delOthersViews(this.selectedTag)
tagsViewMutations.delOtherTagAndCache(this.selectedTag)
//
if (this.selectedTag.path !== this.$route.path) {
return this.$router.push(this.selectedTag)
}
},
closeAllTags() {
tagsViewMutations.delAllViews()
tagsViewMutations.delAllTagAndCache()
this.gotoLastView()
},
@ -124,7 +124,7 @@ export default {
if (this.$route.path !== latest.path) {
this.$router.push(latest.path)
}
else this.$router.replace(`/redirect${this.$route.fullPath}`)
else refreshPage()
},
openMenu(tag, e) {
@ -143,7 +143,7 @@ export default {
this.selectedTag = tag
},
renderTags(h) {
renderTags() {
return this.visitedViews.map(tag => {
const {path, query, fullPath, meta: {title}} = tag
const active = this.isActive(tag), affix = this.isAffix(tag)
@ -166,7 +166,7 @@ export default {
)
})
},
renderContextMenu(h) {
renderContextMenu() {
const menu = this.contextmenu
const items = [
{content: '刷新', click: this.refreshSelectedTag},
@ -184,22 +184,17 @@ export default {
}
},
beforeDestroy() {
//fade
tagsViewMutations.transitionName(routeConfig.animate.default)
},
mounted() {
this.initTags()
},
render(h) {
render() {
return (
<nav class="tags-view-container">
<scroll-panel ref="scrollPanel" class="tags-view-wrapper">
{this.renderTags(h)}
{this.renderTags()}
</scroll-panel>
{this.renderContextMenu(h)}
{this.renderContextMenu()}
</nav>
)
}

@ -2,6 +2,7 @@
* 顶部菜单和侧边栏菜单的公共混入
*/
import {mutations as tagsViewMutations} from "@/layout/store/tagsView"
import {refreshPage} from "@/util/route"
import {isExternal} from "@/util/validate"
export default {
@ -32,8 +33,8 @@ export default {
//触发的菜单路径是当前路由时,根据参数判断是否进行刷新
if (this.$route.path === fullPath) {
if (!refreshWhenSame) return
tagsViewMutations.delCachedView(this.$route)
this.$nextTick(() => this.$router.replace({path: '/redirect' + this.$route.fullPath}))
tagsViewMutations.delCacheOnly(this.$route)
this.$nextTick(() => refreshPage())
}
else this.$router.push(fullPath)
},

@ -42,6 +42,6 @@ export default {
if (!this.shouldPersistent) return
const tags = getTagsView()
Array.isArray(tags) && tags.forEach(tagsViewMutations.addVisitedView)
Array.isArray(tags) && tags.forEach(tagsViewMutations.addTagOnly)
}
}

@ -33,21 +33,40 @@ export const getters = createGetters(store)
export const mutations = bindThis({
...createMutations(store),
/**
* 多页签的启用/停用
* 停用时会移除所有缓存并且重置路由过渡动画
* @param v 启用为true停用为false
*/
enabled(v) {
store.enabled = v
//多页签停用时清除所有页面缓存
!v && this.delAllViews()
if (!v) {
this.transitionName(routeConfig.animate.default)
this.delAllTagAndCache()
}
},
addVisitedView({name, path, fullPath, meta = {}}) {
/**
* 在页签栏上添加一个页签path已存在的不会重复添加调用时需要保证meta.title有值
* @param view {routeConfig}
*/
addTagOnly(view) {
const {name, path, fullPath, meta} = view
if (store.visitedViews.some(v => v.path === path)) {
return
}
store.visitedViews.push({name, path, fullPath, meta: {...meta}})
},
addCachedView(view) {
/**
* 将传入的routeConfig加入<keep-router-view-alive>的缓存中
* 以下调用无效设置了不缓存是iframe页未设置唯一标识已缓存
* @param view {routeConfig}
*/
addCacheOnly(view) {
const {noCache, iframe, usePathKey, useFullPathKey} = view.meta || {}
if (noCache || iframe || !view.name && !usePathKey && !useFullPathKey) {
@ -62,33 +81,64 @@ export const mutations = bindThis({
store.cachedViews.push(key)
},
addView(view) {
this.addVisitedView(view)
this.addCachedView(view)
/**
* 同时调用{@link #addTagOnly}{@link #addCacheOnly}
* @param view {routeConfig}
*/
addTagAndCache(view) {
this.addTagOnly(view)
this.addCacheOnly(view)
},
delVisitedView(view) {
/**
* 根据path从页签栏中移除一个页签
* @param view {path}routeConfig
*/
delTagOnly(view) {
const index = store.visitedViews.findIndex(i => i.path === view.path)
index > -1 && store.visitedViews.splice(index, 1)
},
delCachedView(view) {
/**
* 删除<keep-router-view-alive>对应的缓存
* @param view {routeConfig}
*/
delCacheOnly(view) {
const key = getRouterViewCacheKey(view)
const index = store.cachedViews.indexOf(key)
index > -1 && store.cachedViews.splice(index, 1)
},
delView(view) {
this.delVisitedView(view)
this.delCachedView(view)
pageMutations.delIframe(view.meta ? view.meta.iframe : null)
/**
* 同时调用{@link #delTagOnly}{@link #delCacheOnly}移除iframe页
* @param view {routeConfig}
*/
delTagAndCache(view) {
this.delTagOnly(view)
this.delCacheOnly(view)
const iframe = view.meta && view.meta.iframe
iframe && pageMutations.delIframe(iframe)
},
delOthersViews(view) {
/**
* 从页签栏上移除除了routeConfig以外的所有非固定页签
* 并且从<keep-router-view-alive>中移除除了routeConfig以外的所有缓存
* @param view {routeConfig}
*/
delOtherTagAndCache(view) {
const visitedViews = store.visitedViews.filter(v => v.meta.affix || v.path === view.path)
const key = store.cachedViews.find(key => key === getRouterViewCacheKey(view))
store.visitedViews = visitedViews
store.cachedViews = key ? [key] : []
},
delAllViews() {
/**
* 从页签栏上移除所有非固定页签并且移除<keep-router-view-alive>的所有缓存
*/
delAllTagAndCache() {
store.visitedViews = store.visitedViews.filter(tag => tag.meta && tag.meta.affix)
store.cachedViews = []
}

@ -0,0 +1,30 @@
# 布局设计
## 导航模式
### 桌面端
1.侧边栏导航:侧边栏菜单为完整菜单,顶部菜单不渲染
2.顶部导航:侧边栏菜单不渲染,顶部菜单为完整菜单
3.混合导航:顶部菜单为完整菜单的所有根节点,侧边栏菜单为当前激活的顶部菜单的子级
### 移动端
1.侧边栏导航:侧边栏菜单为完整菜单,顶部菜单不渲染,侧边栏为抽屉模式
2.顶部导航:设置无效,效果等同于侧边栏导航
3.混合导航:设置无效,效果等同于侧边栏导航
## 侧边栏
### 桌面端
未设置自动隐藏时,折叠状态依据用户设置
设置了自动隐藏时,折叠状态无效,侧边栏为抽屉模式
### 移动端
侧边栏只能为抽屉模式

@ -234,7 +234,7 @@ export default {
const editUrl = this.$route.path.replace('/add', `/edit/${id}`)
return closeCurrentPage(editUrl)
}
else return this.init(this.form.id)
return this.init(this.form.id)
},
//关闭页面

@ -50,7 +50,7 @@ const actions = {
return Promise.all([
dispatch('socket/close', null, {root: true}),
dispatch('removeUser'),
tagsViewMutations.delAllViews()
tagsViewMutations.delAllTagAndCache()
])
})
.then(() => {

@ -3,17 +3,25 @@ import {uppercaseFirst} from "@/filter"
import {isEmpty} from "@/util"
/**
* @desc 判断是否需要鉴权需要则返回true
* 判断是否需要鉴权需要则返回true
* 以下情况不需要鉴权①无meta②设置了noAuth=true③没有名称
*
* @param route vue-router的routeConfig或项目中说明的路由配置项
* @returns {boolean}
* @return {boolean}
*/
export function needAuth(route) {
if (route.path.startsWith('/redirect')) return false
const {meta} = route
return !meta || !meta.noAuth || !isEmpty(meta.title, meta.dynamicTitle)
return !meta || !meta.noAuth || isEmpty(meta.title) && isEmpty(meta.dynamicTitle)
}
//接口权限判断,有权限则返回true
/**
* 接口权限判断有权限则返回true
* 调用时需要保证用户已经登录成功
*
* @param path {string} 接口路径
* @return {boolean}
*/
export function auth(path) {
if (store.state.user.admin === true) return true
@ -27,7 +35,9 @@ export function auth(path) {
/**
* what i can
* 批量生成canXxx的计算属性
*
* @param apiMap <key:接口名称value:接口对象>
* @return {object}
*/
export function wic(apiMap) {
const attrs = {}

@ -1,25 +1,48 @@
//根据body宽度判断是否为移动端,是则返回true
/**
* 根据body宽度判断是否为移动端是则返回true
*
* @return {boolean}
*/
export function isMobile() {
const rect = document.body.getBoundingClientRect()
return rect.width < 501
}
/**
* 判断是否为dom元素
*
* @param obj
* @return {boolean}
*/
export function isDom(obj) {
return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string'
return !!obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string'
}
export function getElementInnerWidth(ele) {
if (!ele) return 0
/**
* 获取元素的内宽去除padding
*
* @param el {HTMLElement}
* @return {number}
*/
export function getElementInnerWidth(el) {
if (!el) return 0
const style = window.getComputedStyle(ele)
const style = window.getComputedStyle(el)
return parseFloat(style.width) - (parseFloat(style.paddingLeft) + parseFloat(style.paddingRight))
}
export function getElementHeight(ele, style) {
if (!ele) return 0
/**
* 获取元素的真实高度
*
* @param el {HTMLElement}
* @param style 元素的style键值对
* @return {number}
*/
export function getElementHeight(el, style) {
if (!el) return 0
const node = ele.cloneNode(true)
const node = el.cloneNode(true)
node.style.opacity = '0'
if (style) {
@ -38,19 +61,25 @@ export function getElementHeight(ele, style) {
return height
}
//获取元素距离其容器的顶部距离
export function getOffsetTop(element, container) {
if (!element) return 0
/**
* 获取元素距离其容器的顶部距离
*
* @param el {HTMLElement} 目标元素
* @param container {HTMLElement|Window} 容器元素
* @return {number}
*/
export function getTopDistance(el, container) {
if (!el) return 0
if (!element.getClientRects().length) {
if (!el.getClientRects().length) {
return 0
}
const rect = element.getBoundingClientRect()
const rect = el.getBoundingClientRect()
if (rect.width || rect.height) {
if (container === window) {
container = element.ownerDocument.documentElement
container = el.ownerDocument.documentElement
return rect.top - container.clientTop
}
return rect.top - container.getBoundingClientRect().top
@ -60,10 +89,11 @@ export function getOffsetTop(element, container) {
}
/**
* @desc 加载js或css
* @param url
* @param type js|css
* @returns {Promise<String>} 加载成功返回url若资源此前已加载返回undefined
* 加载js或css
*
* @param url {string} 资源地址
* @param type {string} 'js''css'
* @return {Promise<String>} 加载成功返回url若资源此前已加载返回undefined
*/
export function loadExternalResource(url, type = 'js') {
return new Promise((resolve, reject) => {
@ -95,9 +125,10 @@ export function loadExternalResource(url, type = 'js') {
}
/**
* @desc 平滑滚动至指定的位置
* @param el 滚动容器或可用于querySelector的字符串或一个返回DOM的函数
* @param position 滚动的目的地
* 平滑滚动至指定的位置
*
* @param el {Window|HTMLElement|string|function} 滚动容器或可用于querySelector的字符串或一个返回DOM的函数
* @param position {number} 滚动的目的地
* @param options
*/
export function scrollTo(el, position, options) {
@ -107,22 +138,27 @@ export function scrollTo(el, position, options) {
direction = 'top' //滚动方向,top滚动至距元素顶部distance的位置,left滚动至距元素左边distance的位置
} = options || {}
if (typeof el === 'string') el = document.querySelector(el)
else if (typeof el === 'function') el = el()
if (typeof el === 'string') {
el = document.querySelector(el)
}
else if (typeof el === 'function') {
el = el()
}
if (!isDom(el)) return
const elPosition = getScroll(el, direction === 'top')
const scrollFunc = ((el, direction) => {
const toTop = direction === 'top'
const elPosition = getScroll(el, toTop)
const scrollFunc = (el => {
if (el === window) {
return direction === 'top'
return toTop
? y => el.scrollTo(window.pageXOffset, window.pageYOffset + y)
: x => el.scrollTo(window.pageXOffset + x, window.pageYOffset)
}
return direction === 'top'
return toTop
? y => el.scrollTop += y
: x => el.scrollLeft += x
})(el, direction)
})(el)
let times = duration / 16, distance = (position - elPosition) / (times + 1)
@ -138,6 +174,13 @@ export function scrollTo(el, position, options) {
return window.requestAnimationFrame(frameFunc)
}
/**
* 获取元素的滚动距离
*
* @param el {Window|HTMLElement}
* @param top {boolean} 是否获取垂直的滚动距离
* @return {number}
*/
export function getScroll(el, top) {
return el === window
? el[top ? 'pageYOffset' : 'pageXOffset']

@ -1,39 +0,0 @@
function getColorChannels(color) {
color = color.replace('#', '')
if (/^[0-9a-fA-F]{3}$/.test(color)) {
color = color.split('')
for (let i = 2; i >= 0; i--) {
color.splice(i, 0, color[i])
}
color = color.join('')
}
if (/^[0-9a-fA-F]{6}$/.test(color)) {
return {
red: parseInt(color.slice(0, 2), 16),
green: parseInt(color.slice(2, 4), 16),
blue: parseInt(color.slice(4, 6), 16)
}
}
else {
return {
red: 255,
green: 255,
blue: 255
}
}
}
export function mixColor(color, percent) {
let {red, green, blue} = getColorChannels(color)
if (percent > 0) { // shade given color
red *= 1 - percent
green *= 1 - percent
blue *= 1 - percent
}
else { // tint given color
red += (255 - red) * percent
green += (255 - green) * percent
blue += (255 - blue) * percent
}
return `rgb(${Math.round(red)}, ${Math.round(green)}, ${Math.round(blue)})`
}

@ -1,9 +1,9 @@
/**
* @desc el-tree展开折叠控制
* el-tree展开折叠控制
* @param ref el-tree实例
* @param action 'expand' | 'collapse'
* @param level 展开的节点最大深度为0时匹配全部节点
* @param func 自定义展开的函数传入一个node优先使用
* @param func 自定义展开的函数传入一个node返回boolean优先使用
*/
export function expandControl(ref, action = 'expand', level = 1, func) {
const handler = function () {

@ -5,14 +5,14 @@ import {download} from "@/util/file"
import {flatTree} from "@/util/tree"
/**
* 导出excel
* 导出excel的抽象模板
* 响应头为json时进行前端导出否则直接下载文件
*
* @param url 搜索的请求地址
* @param searchForm 搜索参数
* @param options {columns, merge}只有前端导出时才需要
* @param json2workbook
* @param workbook2excel
* @param options {object}只有前端导出时才需要
* @param json2workbook 将json数据转换为workbook的函数参考./exceljs.js #json2workbook
* @param workbook2excel workbook对象转换为excel文件参考./exceljs.js #workbook2excel
*/
export function abstractExportExcel(url, searchForm, options, json2workbook, workbook2excel) {
request({url, method: 'post', responseType: 'blob', data: searchForm})
@ -45,7 +45,7 @@ export function abstractExportExcel(url, searchForm, options, json2workbook, wor
* @param primaryKey 该行json的唯一标识字段名
* @param orderKey 该行json的序号字段名
* @param ignoreRows 忽略的行数默认为1忽略表头
* @return 包含合并信息的数组形如['A1:A10',...]
* @return {array} 包含合并信息的数组形如['A1:A10',...]
*/
export function mergeExcel(props, data, primaryKey, orderKey, ignoreRows = 1) {
const result = [], merge = [], temp = []
@ -97,7 +97,7 @@ export function mergeExcel(props, data, primaryKey, orderKey, ignoreRows = 1) {
nextRow[orderKey] = currentRow[orderKey]
}
props.forEach((_, colIndex) => !isEmpty(_) && compare(currentRow, lastRow, nextRow, colIndex))
props.forEach((prop, colIndex) => !isEmpty(prop) && compare(currentRow, lastRow, nextRow, colIndex))
}
function mergeResultConstructor(arr, colIndex) {
@ -209,9 +209,9 @@ export function generateHeader(columns, separator = '-') {
/**
* 生成二维数组例如json为[{id:1,name:'x'}]结果为[[1,'x']]
*
* @param data json数组
* @param propMap 表头和字段名的映射表
* @return 基数组为excel中每一行的二维数组
* @param data json数组
* @param propMap 表头和字段名的映射表
* @return {array} 基数组为excel中每一行的二维数组
*/
export function jsonArray2rowArray(data, propMap) {
return data.map(i => Object.keys(i)
@ -221,7 +221,13 @@ export function jsonArray2rowArray(data, propMap) {
}, []))
}
//数字转excel列名
/**
* 数字转excel列名
* 1 -> A 27 -> AB
*
* @param n {number}
* @return {string}
*/
export function number2excelColumnHeader(n) {
let s = ""
while (n >= 0) {

@ -1,6 +1,12 @@
import {isEmpty} from "@/util"
import {download} from "@/util/file"
import {abstractExportExcel, jsonArray2rowArray, mergeExcel, generateHeader, number2excelColumnHeader} from "./common"
import {
abstractExportExcel,
jsonArray2rowArray,
mergeExcel,
generateHeader,
number2excelColumnHeader
} from "./common"
//默认的excel单元格样式
const defaultCellStyle = {
@ -21,6 +27,9 @@ const defaultHeaderStyle = {
border: {top: {style: 'thin'}, left: {style: 'thin'}, bottom: {style: 'thin'}, right: {style: 'thin'}}
}
/**
* excel导出的具体实现
*/
export function exportExcel(url, searchForm, options) {
abstractExportExcel(url, searchForm, options, json2workbook, workbook2excel)
}
@ -31,11 +40,12 @@ export function workbook2excel(workbook, filename) {
}
/**
* @desc 通过json数组生成workbook对象
* 通过json数组生成workbook对象
*
* @param data json数组
* @param columns 列配置
* @param mergeOption 合并配置项
* @returns workbook
* @return workbook exceljs的workbook对象
*/
export function json2workbook(data, columns, mergeOption) {
const columnStyle = [] //列设置
@ -60,7 +70,17 @@ export function json2workbook(data, columns, mergeOption) {
return generateWorkbook('Sheet1', columnStyle, header, body, mergeCells)
}
export function generateWorkbook(sheetName, columnStyle, header, body, mergeCells) {
/**
* 生成workbook的代码抽取
*
* @param sheetName {string} 第一个sheet的名称
* @param columnStyle {array} 列样式
* @param header {array} 头部二维数组
* @param body {array} 内容二维数组
* @param mergeCells {array} 需要合并的单元格形如['A1:B1'...]
* @return workbook
*/
function generateWorkbook(sheetName, columnStyle, header, body, mergeCells) {
const workbook = new window.ExcelJS.Workbook()
const sheet = workbook.addWorksheet(sheetName)

@ -1,8 +1,32 @@
/**
* 路由控制工具类
*/
import {isEmpty} from "@/util"
import {isString} from "@/util/validate"
import router from '@/router'
import {mutations as tagsViewMutations} from "@/layout/store/tagsView"
/**
* 路由刷新
*
* @param route {string|route} 需要刷新的路由不传时为当前路由如果是字符串时请确保以'/'开头
* @param replace {boolean} 是否使用replace进行跳转
* @return {Promise} 返回vue-router跳转的结果
*/
export function refreshPage(route = router.currentRoute, replace = true) {
const target = `/redirect${isString(route) ? route : route.fullPath}`
return router[replace ? 'replace' : 'push'](target)
}
/**
* 关闭当前页如果传入next则跳转到next页面
* @param next 跳转的目标页面作为第一个参数传入vue-router.replace
* @return 仅在next有值时返回vue-router.replace的结果
*/
export function closeCurrentPage(next) {
tagsViewMutations.delView(router.currentRoute)
!isEmpty(next) && router.replace(next)
tagsViewMutations.delTagAndCache(router.currentRoute)
if (!isEmpty(next)) {
return router.replace(next)
}
}

@ -3,8 +3,14 @@ import md5 from 'js-md5'
export {md5}
/**
* 解压字符串
*
* @param b64Data {string} base64编码的字符串
* @return {string}
*/
export function unzip(b64Data) {
let strData = atob(b64Data)
let strData = window.atob(b64Data)
// Convert binary string to character-number array
const charData = strData.split('').map(x => x.charCodeAt(0))
// Turn number array into byte-array
@ -16,7 +22,13 @@ export function unzip(b64Data) {
return decodeURIComponent(strData)
}
/**
* 压缩字符串
*
* @param str {string}
* @return {string}
*/
export function zip(str) {
const binaryString = pako.gzip(encodeURIComponent(str), {to: 'string'})
return btoa(binaryString)
return window.btoa(binaryString)
}

@ -5,11 +5,11 @@ import {unzip, zip} from "@/util/secret"
const {keyPrefix, zip: defaultUseZip} = storage
/**
* @desc 读取本地存储
* @param key 键名自动加上统一前缀
* @param storage window.sessionStorage或window.localStorage
* @param useZip 是否启用了压缩
* @returns {any|undefined}
* 读取本地存储
* @param key {string} 键名自动加上统一前缀
* @param storage window.sessionStorage或window.localStorage
* @param useZip {boolean} 是否启用了压缩
* @return {*|undefined}
*/
export function get(key, storage = window.sessionStorage, useZip = defaultUseZip) {
let obj = storage.getItem(`${keyPrefix}${key}`)
@ -26,10 +26,10 @@ export function get(key, storage = window.sessionStorage, useZip = defaultUseZip
/**
* 写入本地存储
* @param key 键名自动加上统一前缀
* @param obj 需要写入本地存储的js对象
* @param storage window.sessionStorage或window.localStorage
* @param useZip 是否启用了压缩
* @param key {string} 键名自动加上统一前缀
* @param obj 需要写入本地存储的js对象
* @param storage window.sessionStorage或window.localStorage
* @param useZip {boolean} 是否启用了压缩
*/
export function set(key, obj, storage = window.sessionStorage, useZip = defaultUseZip) {
const item = useZip ? zip(JSON.stringify(obj)) : JSON.stringify(obj)
@ -37,19 +37,19 @@ export function set(key, obj, storage = window.sessionStorage, useZip = defaultU
}
/**
* @desc 判断本地存储中是否存在指定键名
* @param key 键名自动加上统一前缀
* @param storage window.sessionStorage或window.localStorage
* @returns {boolean} 存在则返回true
* 判断本地存储中是否存在指定键名
* @param key {string} 键名自动加上统一前缀
* @param storage window.sessionStorage或window.localStorage
* @return {boolean} 存在则返回true
*/
export function exist(key, storage = window.sessionStorage) {
return !isEmpty(storage.getItem(`${keyPrefix}${key}`))
}
/**
* @desc 根据指定键名移除本地存储对应项
* @param key 键名自动加上统一前缀
* @param storage window.sessionStorage或window.localStorage
* 根据指定键名移除本地存储对应项
* @param key {string} 键名自动加上统一前缀
* @param storage window.sessionStorage或window.localStorage
*/
export function remove(key, storage = window.sessionStorage) {
storage.removeItem(`${keyPrefix}${key}`)
@ -59,6 +59,11 @@ const sessionUserKey = 'SESS-USER'
const localPersonalSettingsKey = 'LOCAL-PERSONAL-SETTINGS'
const sessionTagsViewKey = 'SESS-TAGS-VIEW'
/**
* 获取用户信息获取失败时会清除用户信息并返回一个空对象
*
* @return {object}
*/
export function getUser() {
let user = get(sessionUserKey)
if (isEmpty(user)) {
@ -68,10 +73,20 @@ export function getUser() {
return user
}
/**
* 将用户信息持久化到sessionStorage传入空值时将清除用户信息
*
* @param user
*/
export function setUser(user) {
isEmpty(user) ? removeUser() : set(sessionUserKey, user)
}
/**
* 获取用户个性设置获取失败时会清除设置信息并返回一个空对象
*
* @return {object}
*/
export function getLocalPersonalSettings() {
let settings = get(localPersonalSettingsKey, window.localStorage)
if (isEmpty(settings)) {
@ -81,12 +96,22 @@ export function getLocalPersonalSettings() {
return settings
}
/**
* 将用户个性设置持久化到localStorage传入空值时将清除设置信息
*
* @param settings
*/
export function setLocalPersonalSettings(settings) {
isEmpty(settings)
? removeLocalPersonalSettings()
: set(localPersonalSettingsKey, settings, window.localStorage)
}
/**
* 获取持久化后的页签获取失败时会清除持久化数据并返回一个空数组
*
* @return {array}
*/
export function getTagsView() {
let tagsView = get(sessionTagsViewKey)
if (isEmpty(tagsView)) {
@ -96,6 +121,11 @@ export function getTagsView() {
return tagsView
}
/**
* 将页签持久化到sessionStorage传入空值时将清除页签信息
*
* @param tagsView
*/
export function setTagsView(tagsView) {
isEmpty(tagsView) ? removeTagsView() : set(sessionTagsViewKey, tagsView)
}

@ -5,10 +5,11 @@ const DEFAULT_PROPS = {id: 'id', pid: 'pid', children: 'children', leafHasChildr
/**
* 列表转树形结构
* @param {Array} list
* @param {Number|String|Function} rootSign 根节点的判定方法
* @param {Object} props 树的配置项
* @returns {Array}
*
* @param list {array}
* @param rootSign {number|String|Function} 根节点的判定方法
* @param props 树的配置项{id, pid, children, leafHasChildren}
* @return {array}
*/
export function createTree(list, rootSign = 0, props = {}) {
if (!list || list.length <= 0) return []
@ -27,6 +28,12 @@ export function createTree(list, rootSign = 0, props = {}) {
if (leafHasChildren) i[children] = []
})
const predicate = (() => {
return typeof rootSign === 'function'
? rootSign
: key => key === rootSign
})()
return list.filter(node => {
const key = node[pid]
const parent = info[key]
@ -34,15 +41,16 @@ export function createTree(list, rootSign = 0, props = {}) {
if (!parent[children]) parent[children] = []
parent[children].push(node)
}
return typeof rootSign === 'function' ? rootSign(node) : key === rootSign
return predicate(key, node)
})
}
/**
* 完全树full拿到某些带value的节点数组limit获得裁剪后的树
* 只用于裁剪客户和供应商的行政区域树
* @param full
* @param limit
*
* @param full {array}
* @param limit {array}
*/
export function createLimitTree(full, limit) {
const map = limit.reduce((m, n) => {
@ -68,8 +76,9 @@ export function createLimitTree(full, limit) {
/**
* createLimitTree的另一版本
*
* @param fullMap 完全树的节点map
* @param limit
* @param limit {array}
*/
export function createLimitTreeByMap(fullMap, limit) {
const resultNodes = {}
@ -105,19 +114,28 @@ export function createLimitTreeByMap(fullMap, limit) {
/**
* 根据判断函数裁剪树当节点不满足predicate且无下级节点时将被裁剪
* @param tree
* @param predicate
* @param childrenKey
* @returns {*[]} 经过裁剪后的树
*
* @param tree {array} 原始树
* @param predicate {function} 判断节点是否满足条件的函数传入一个节点data
* @param childrenKey {string} 节点中代表子节点数组含义的字段名称比如[{ch:[...]}]就是'ch'
* @return {array} 经过裁剪后的树
*/
export function shakeTree(tree = [], predicate = () => true, childrenKey = 'children') {
function shakeTree(tree = [], predicate = () => true, childrenKey = 'children') {
return tree.filter(data => {
data[childrenKey] = shakeTree(data[childrenKey], predicate)
return predicate(data) || data[childrenKey].length
})
}
export function calc(node, valueKey = 'value', childrenKey = 'children') {
/**
* 自底向上计算每个节点下有多少子节点
*
* @param node
* @param valueKey {string}
* @param childrenKey {string}
* @return {*|number}
*/
function calc(node, valueKey = 'value', childrenKey = 'children') {
if (!node) return 0
const children = node[childrenKey]
@ -132,6 +150,7 @@ export function calc(node, valueKey = 'value', childrenKey = 'children') {
return value
}
//树转一维id数组
export function getNodeId(arr) {
if (!arr) return []

Loading…
Cancel
Save