layout改造

文件位置整理
右键菜单组件改写
样式修改
master
toesbieya 6 years ago
parent f4447fa23b
commit 87757f0b9e
  1. 24
      vue/src/asset/style/index.scss
  2. 8
      vue/src/component/Empty/index.vue
  3. 8
      vue/src/component/ExtraArea/index.vue
  4. 8
      vue/src/component/RegionSelector/Tree/TreeDialog.vue
  5. 0
      vue/src/component/abstract/Dialog/LoadingMask.vue
  6. 8
      vue/src/component/abstract/Dialog/index.vue
  7. 6
      vue/src/component/abstract/Dialog/style.scss
  8. 0
      vue/src/component/abstract/Form/index.vue
  9. 0
      vue/src/component/abstract/Form/item.vue
  10. 0
      vue/src/component/abstract/Pagination/index.vue
  11. 0
      vue/src/component/abstract/Table/index.vue
  12. 4
      vue/src/component/biz/doc/DocHistory.vue
  13. 7
      vue/src/component/checkbox/Group.vue
  14. 0
      vue/src/component/form/Anchor/index.vue
  15. 8
      vue/src/component/form/CollapseCard/index.vue
  16. 4
      vue/src/component/form/Search/index.vue
  17. 0
      vue/src/component/form/Search/item.vue
  18. 0
      vue/src/component/form/ValidateInfo/index.vue
  19. 63
      vue/src/component/menu/ContextMenu/index.vue
  20. 11
      vue/src/component/menu/ContextmenuItem.vue
  21. 4
      vue/src/component/menu/NavMenu/item.vue
  22. 29
      vue/src/directive/dragDialog.js
  23. 2
      vue/src/directive/index.js
  24. 0
      vue/src/directive/ripple/index.js
  25. 4
      vue/src/globalMethod/Guide/main.vue
  26. 18
      vue/src/globalMethod/Guide/utils.js
  27. 0
      vue/src/layout/component/Aside/Logo.vue
  28. 13
      vue/src/layout/component/Aside/component/Mask.vue
  29. 101
      vue/src/layout/component/Aside/index.vue
  30. 23
      vue/src/layout/component/Aside/style.scss
  31. 23
      vue/src/layout/component/Header/component/Navbar/component/HeadMenu.vue
  32. 199
      vue/src/layout/component/Header/component/Navbar/component/SettingDrawer.vue
  33. 20
      vue/src/layout/component/Header/component/Navbar/index.vue
  34. 5
      vue/src/layout/component/Header/component/Navbar/style.scss
  35. 37
      vue/src/layout/component/Header/component/TagsView/index.vue
  36. 16
      vue/src/layout/component/Header/index.vue
  37. 67
      vue/src/layout/component/Main/index.vue
  38. 2
      vue/src/layout/component/Page/component/BackToTop.vue
  39. 26
      vue/src/layout/component/Page/component/KeepRouterViewAlive.vue
  40. 0
      vue/src/layout/component/Page/component/PageFooter.vue
  41. 0
      vue/src/layout/component/Page/component/PageHeader.vue
  42. 5
      vue/src/layout/component/Page/component/PageView.vue
  43. 56
      vue/src/layout/component/Page/index.vue
  44. 0
      vue/src/layout/component/Page/style.scss
  45. 20
      vue/src/layout/index.vue
  46. 33
      vue/src/layout/store/app.js
  47. 75
      vue/src/layout/store/aside.js
  48. 35
      vue/src/layout/store/iframe.js
  49. 44
      vue/src/layout/store/page.js
  50. 69
      vue/src/layout/store/setting.js
  51. 91
      vue/src/layout/store/tagsView.js
  52. 17
      vue/src/layout/util.js
  53. 10
      vue/src/mixin/docDetailMixin.js
  54. 2
      vue/src/mixin/guide/data/navbar.js
  55. 4
      vue/src/mixin/tablePageMixin.js
  56. 12
      vue/src/router/guard/iframe.js
  57. 2
      vue/src/store/module/app.js
  58. 2
      vue/src/store/module/message.js
  59. 4
      vue/src/store/module/resource.js
  60. 2
      vue/src/store/module/socket.js
  61. 3
      vue/src/store/module/user.js
  62. 18
      vue/src/store/util.js
  63. 31
      vue/src/util/index.js
  64. 8
      vue/src/util/secret.js
  65. 2
      vue/src/util/storage.js
  66. 4
      vue/src/view/app/common/DetailPage/index.vue
  67. 10
      vue/src/view/index/component/PanelGroup/index.vue
  68. 12
      vue/src/view/message/manage/EditDialog.vue
  69. 4
      vue/src/view/message/manage/indexPage.vue
  70. 4
      vue/src/view/message/user/indexPage.vue
  71. 24
      vue/src/view/purchase/inbound/detailPage.vue
  72. 4
      vue/src/view/purchase/inbound/indexPage.vue
  73. 24
      vue/src/view/purchase/order/detailPage.vue
  74. 4
      vue/src/view/purchase/order/indexPage.vue
  75. 24
      vue/src/view/sell/order/detailPage.vue
  76. 4
      vue/src/view/sell/order/indexPage.vue
  77. 24
      vue/src/view/sell/outbound/detailPage.vue
  78. 4
      vue/src/view/sell/outbound/indexPage.vue
  79. 10
      vue/src/view/stock/current/DetailDialog.vue
  80. 4
      vue/src/view/stock/current/indexPage.vue
  81. 2
      vue/src/view/system/category/component/Form.vue
  82. 26
      vue/src/view/system/category/component/List.vue
  83. 10
      vue/src/view/system/customer/EditDialog.vue
  84. 4
      vue/src/view/system/customer/indexPage.vue
  85. 10
      vue/src/view/system/department/component/EditDialog.vue
  86. 40
      vue/src/view/system/department/indexPage.vue
  87. 2
      vue/src/view/system/menu/indexPage.vue
  88. 10
      vue/src/view/system/role/EditDialog.vue
  89. 4
      vue/src/view/system/role/indexPage.vue
  90. 10
      vue/src/view/system/supplier/EditDialog.vue
  91. 4
      vue/src/view/system/supplier/indexPage.vue
  92. 10
      vue/src/view/system/user/component/EditDialog.vue
  93. 4
      vue/src/view/system/user/indexPage.vue
  94. 10
      vue/src/view/userCenter/component/Account.vue
  95. 8
      vue/src/view/userCenter/component/Avatar.vue

@ -45,22 +45,20 @@ a:hover {
//非移动端的滚动条样式修改
@include pc {
.scroll-container {
&::-webkit-scrollbar {
width: 6px;
::-webkit-scrollbar {
width: 6px;
&:horizontal {
height: 6px;
}
&:horizontal {
height: 6px;
}
&-thumb {
width: 6px;
border-radius: 5px;
background-color: rgba($--color-primary, 0.8);
&-thumb {
width: 6px;
border-radius: 5px;
background-color: rgba($--color-primary, 0.8);
&:hover {
background-color: rgba($--color-primary, 1);
}
&:hover {
background-color: rgba($--color-primary, 1);
}
}
}

@ -1,4 +1,7 @@
<script type="text/jsx">
/**
* 从ant-design-vue处拷贝
*/
import DefaultEmptyImage from "./DefaultEmptyImage"
export default {
@ -21,9 +24,8 @@ export default {
if (typeof image === 'string') {
imageNode = <img alt={description} src={image}/>
}
else {
imageNode = <DefaultEmptyImage/>
}
else imageNode = <DefaultEmptyImage/>
return (
<div class={cls} {...{on: context.listeners}}>
<div class={`${prefixCls}-image`} style={imageStyle}>{imageNode}</div>

@ -4,7 +4,7 @@
<slot name="extra"/>
</el-col>
<div style="display: flex;justify-content: center;align-items: center">
<el-link :icon="iconClass" :underline="false" style="font-size: 16px" @click="expanded=!expanded"/>
<el-link :icon="iconClass" :underline="false" style="font-size: 16px" @click="expanded = !expanded"/>
</div>
<el-col :span="defaultSpan">
<slot/>
@ -20,11 +20,7 @@ export default {
extra: {type: Number, default: 5}
},
data() {
return {
expanded: false
}
},
data: () => ({expanded: false}),
computed: {
extraSpan() {

@ -1,10 +1,10 @@
<script type="text/jsx">
import {store, init} from '../store'
import FormDialog from "@/component/FormDialog"
import AbstractDialog from "@/component/abstract/Dialog"
import {createLimitTree, getNodeId} from "@/util/tree"
export default {
components: {FormDialog},
components: {AbstractDialog},
data() {
return {
@ -56,7 +56,7 @@ export default {
render() {
return (
<form-dialog
<abstract-dialog
v-model={this.visible}
directives={[{name: 'drag-dialog'}]}
class="tree-dialog"
@ -77,7 +77,7 @@ export default {
</span>
)}
</el-tree>
</form-dialog>
</abstract-dialog>
)
}
}

@ -2,7 +2,7 @@
import LoadingMask from './LoadingMask'
export default {
name: "FormDialog",
name: "AbstractDialog",
functional: true,
@ -25,7 +25,7 @@ export default {
<el-dialog
v-drag-dialog
visible={value}
custom-class="form-dialog"
custom-class="abstract-dialog"
top={top}
width={width}
on-close={onClose}
@ -33,10 +33,10 @@ export default {
>
<slot slot="title" name="title">{title}</slot>
<div class="form-dialog-container">
<div class="abstract-dialog-container">
<LoadingMask show={loading}/>
<el-scrollbar>
<div class="form-dialog-body">
<div class="abstract-dialog-body">
{children}
</div>
<div style="margin-bottom: 17px"/>

@ -1,13 +1,13 @@
.form-dialog {
.abstract-dialog {
.el-dialog__body {
padding: 0;
}
.form-dialog-container {
.abstract-dialog-container {
position: relative;
width: 100%;
.form-dialog-body {
.abstract-dialog-body {
padding: 24px 24px 0
}

@ -13,8 +13,8 @@
</template>
<script>
import AbstractPagination from '@/component/AbstractPagination'
import AbstractTable from "@/component/AbstractTable"
import AbstractPagination from '@/component/abstract/Pagination'
import AbstractTable from "@/component/abstract/Table"
import {search} from "@/api/doc/history"
import {isEmpty, timeFormat} from "@/util"

@ -7,11 +7,7 @@ export default {
data: () => ({children: []}),
render() {
return (
<div class="checkbox-group">
{this.$slots.default}
</div>
)
return <div class="checkbox-group">{this.$slots.default}</div>
}
}
</script>
@ -20,5 +16,6 @@ export default {
.checkbox-group {
display: flex;
justify-content: space-around;
width: 100%;
}
</style>

@ -2,7 +2,7 @@
<el-card :class="{collapsed}">
<div slot="header" class="clearfix">
<slot name="header">{{ header }}</slot>
<i class="el-icon-arrow-up form-card-icon"/>
<i class="el-icon-arrow-up form-collapse-card-icon"/>
</div>
<slot/>
</el-card>
@ -10,7 +10,7 @@
<script>
export default {
name: "FormCard",
name: "FormCollapseCard",
props: {
header: String
@ -35,14 +35,14 @@ export default {
</script>
<style>
.form-card-icon {
.form-collapse-card-icon {
float: right;
font-weight: bold;
transition: transform .3s;
cursor: pointer;
}
.collapsed .form-card-icon {
.collapsed .form-collapse-card-icon {
transform: rotate(180deg);
}

@ -12,8 +12,8 @@ export default {
props: {
labelWidth: {type: String, default: '120px'},
xs: {type: Number, default: 1},// <768px
sm: {type: Number, default: 2},// >=768px
xs: {type: Number, default: 1}, // <768px
sm: {type: Number, default: 2}, // >=768px
md: {type: Number, default: 3}, // >=998px
lg: {type: Number, default: 4} // >=1200px
},

@ -1,15 +1,20 @@
<template>
<ul v-show="value" class="contextmenu" :style="{left:realLeft,top:realTop}">
<slot/>
</ul>
</template>
<script type="text/jsx">
const Item = {
functional: true,
render(h, context) {
return <li class="context-menu-item" {...context.data}>{context.children}</li>
}
}
<script>
export default {
name: "Contextmenu",
name: "ContextMenu",
components: {Item},
props: {
value: Boolean,
items: Array,
left: Number,
top: Number
},
@ -23,21 +28,19 @@ export default {
watch: {
value(v) {
document.body[v ? 'addEventListener' : 'removeEventListener']('click', this.closeContextMenu)
if (v) this.$nextTick(this.autoAdapt)
document.body[v ? 'addEventListener' : 'removeEventListener']('click', this.close)
v && this.$nextTick(this.autoAdapt)
},
left(v) {
if (!v) return
this.autoAdaptLeft(v)
v && this.autoAdaptLeft(v)
},
top(v) {
if (!v) return
this.autoAdaptTop(v)
v && this.autoAdaptTop(v)
}
},
methods: {
closeContextMenu() {
close() {
this.$emit('input', false)
},
autoAdapt() {
@ -46,26 +49,46 @@ export default {
},
autoAdaptTop(v) {
if (!this.value) return
if (this.$el.offsetHeight > document.body.clientHeight - v && v > this.$el.offsetHeight) {
this.realTop = v - this.$el.offsetHeight + 'px'
const elOffsetHeight = this.$el.offsetHeight
if (elOffsetHeight > document.body.clientHeight - v && v > elOffsetHeight) {
this.realTop = v - elOffsetHeight + 'px'
}
else this.realTop = v + 'px'
},
autoAdaptLeft(v) {
if (!this.value) return
if (this.$el.offsetWidth > document.body.clientWidth - v) {
this.realLeft = v - this.$el.offsetWidth + 'px'
const elOffsetWidth = this.$el.offsetWidth
if (elOffsetWidth > document.body.clientWidth - v) {
this.realLeft = v - elOffsetWidth + 'px'
}
else this.realLeft = v + 'px'
}
},
render() {
if (!this.value || !Array.isArray(this.items) || this.items.length === 0) return
const style = {left: this.realLeft, top: this.realTop}
return (
<ul class="context-menu" style={style}>
{this.items.map(i => (
i && <item on-click={i.click}>{i.content}</item>
))}
</ul>
)
}
}
</script>
<style lang="scss" scoped>
<style lang="scss">
@import "~@/asset/style/variables.scss";
.contextmenu {
.context-menu {
margin: 0;
background: $aside-menu-background;
z-index: 10;

@ -1,11 +0,0 @@
<script type="text/jsx">
export default {
name: "ContextmenuItem",
functional: true,
render(h, context) {
return <li class="contextmenu-item" {...context.data}>{context.children}</li>
}
}
</script>

@ -1,6 +1,6 @@
<script type="text/jsx">
const MenuItemContent = {
name: "MenuItemContent",
name: "NavMenuItemContent",
functional: true,
@ -96,6 +96,8 @@ function renderMenu(h, {menu, showParent, collapse, showIconMaxDepth, depth = 1}
}
export default {
name: "NavMenuItem",
functional: true,
props: {

@ -1,13 +1,11 @@
export default {
bind(el, binding, vnode) {
bind(el) {
const dialogHeaderEl = el.querySelector('.el-dialog__header')
const dragDom = el.querySelector('.el-dialog')
dialogHeaderEl.style.cssText += ';cursor:move;'
dragDom.style.cssText += ';top:0px;'
const getStyle = (dom, attr) => getComputedStyle(dom)[attr]
dialogHeaderEl.onmousedown = (e) => {
dialogHeaderEl.addEventListener('mousedown', e => {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - dialogHeaderEl.offsetLeft
const disY = e.clientY - dialogHeaderEl.offsetTop
@ -19,14 +17,13 @@ export default {
const screenHeight = document.body.clientHeight
const minDragDomLeft = dragDom.offsetLeft
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
const maxDragDomLeft = screenWidth - minDragDomLeft - dragDomWidth
const minDragDomTop = dragDom.offsetTop
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight
const maxDragDomTop = screenHeight - minDragDomTop - dragDomHeight
// 获取到的值带px 正则匹配替换
let styL = getStyle(dragDom, 'left')
let styT = getStyle(dragDom, 'top')
let {left: styL, top: styT} = window.getComputedStyle(dragDom)
if (styL.includes('%')) {
styL = +document.body.clientWidth * (+styL.replace(/%/g, '') / 100)
@ -37,7 +34,7 @@ export default {
styT = +styT.replace(/px/g, '')
}
document.onmousemove = function (e) {
function onMousemove(e) {
// 通过事件委托,计算移动的距离
let left = e.clientX - disX
let top = e.clientY - disY
@ -59,15 +56,15 @@ export default {
// 移动当前元素
dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`
// emit onDrag event
vnode.child.$emit('dragDialog')
}
document.onmouseup = function (e) {
document.onmousemove = null
document.onmouseup = null
function onMouseup() {
window.removeEventListener('mousemove', onMousemove)
window.removeEventListener('mouseup', onMouseup)
}
}
window.addEventListener('mousemove', onMousemove)
window.addEventListener('mouseup', onMouseup)
})
}
}

@ -1,6 +1,6 @@
import Vue from 'vue'
import dragDialog from './dragDialog'
import ripple from './ripple/main'
import ripple from './ripple'
Vue.directive('drag-dialog', dragDialog)
Vue.directive('ripple', ripple)

@ -38,7 +38,7 @@
* vue里交互型的导航不太行太多东西做不到换成引导手册吧
*/
import {debounce, isEmpty} from "@/util"
import {addHighlightClasses, getCalculatedPosition, inAppView, jump, removeHighlightClasses} from "./utils"
import {addHighlightClasses, getCalculatedPosition, inMainPage, jump, removeHighlightClasses} from "./utils"
export default {
name: "Guide",
@ -204,7 +204,7 @@ export default {
this.lastHighlightedElement = this.highlightedElement
this.highlightedElement = el
if (inAppView(el)) jump(el)
if (inMainPage(el)) jump(el)
this.$_setStageStyle(getCalculatedPosition(el))

@ -9,9 +9,7 @@ export function getStyleProperty(element, propertyName, prefixVendor = false) {
const prefixedProperty = prefixes[counter] + propertyName
const foundValue = getStyleProperty(element, prefixedProperty)
if (foundValue) {
return foundValue
}
if (foundValue) return foundValue
}
return ''
@ -23,17 +21,19 @@ export function getStyleProperty(element, propertyName, prefixVendor = false) {
propertyValue = element.currentStyle[propertyName]
}
else if (document.defaultView && document.defaultView.getComputedStyle) {
propertyValue = document.defaultView
.getComputedStyle(element, null)
.getPropertyValue(propertyName)
propertyValue =
document
.defaultView
.getComputedStyle(element, null)
.getPropertyValue(propertyName)
}
return propertyValue && propertyValue.toLowerCase ? propertyValue.toLowerCase() : propertyValue
}
export function inAppView(element) {
let appView = document.querySelector('.router-main')
if (!appView) throw new Error('router-main未加载')
export function inMainPage(element) {
const appView = document.querySelector('.router-main')
if (!appView) throw new Error('路由页面未加载')
return appView.contains(element)
}

@ -1,13 +0,0 @@
<script type="text/jsx">
export default {
data: () => ({show: false}),
render() {
return (
<transition name="el-fade-in-linear">
{this.show && <div class="aside-hidden-mask" on-click={this.onClick}/>}
</transition>
)
}
}
</script>

@ -1,11 +1,9 @@
<script type="text/jsx">
import Vue from 'vue'
import menuMixin from "@/layout/mixin/menu"
import {getters as mainGetters} from "@/layout/store/main"
import {getters as settingGetters, mutations as settingMutations} from "@/layout/store/setting"
import Logo from './component/Logo'
import Mask from "./component/Mask"
import MenuItem from '@/component/menu/MenuItem'
import {getters as appGetters} from "@/layout/store/app"
import {getters as asideGetters, mutations as asideMutations} from "@/layout/store/aside"
import Logo from './Logo'
import NavMenuItem from '@/component/menu/NavMenu/item'
import {getSidebarMenus, getActiveMenuByRoute} from "@/layout/util"
export default {
@ -13,7 +11,7 @@ export default {
mixins: [menuMixin],
components: {Logo, MenuItem},
components: {Logo, NavMenuItem},
data() {
return {
@ -23,26 +21,20 @@ export default {
},
computed: {
device: () => mainGetters.device,
activeRootMenu: () => mainGetters.activeRootMenu,
device: () => appGetters.device,
activeRootMenu: () => appGetters.activeRootMenu,
//truefalsepc
collapse() {
return settingGetters.sidebarCollapse && this.device === 'pc'
return asideGetters.collapse && this.device === 'pc'
},
//
//pc
//
hideSidebar() {
const one = settingGetters.sidebarAutoHidden && this.mouseOutside && this.device === 'pc',
two = settingGetters.sidebarCollapse && this.device === 'mobile'
return one || two
},
//
showHiddenMask() {
return !settingGetters.sidebarCollapse && this.device === 'mobile'
hide() {
return asideGetters.autoHide && this.mouseOutside && this.device === 'pc'
|| asideGetters.collapse && this.device === 'mobile'
}
},
@ -71,7 +63,7 @@ export default {
device: {
immediate: true,
handler(v) {
v === 'mobile' && this.collapseSidebar()
v === 'mobile' && asideMutations.close()
}
},
@ -81,14 +73,10 @@ export default {
},
//
hideSidebar(v) {
if (!settingGetters.sidebarAutoHidden) return
hide(v) {
if (!asideGetters.autoHide) return
const method = `${v ? 'add' : 'remove'}EventListener`
document[method]('mousemove', this.moveEvent)
},
showHiddenMask(v) {
this.maskInstance.show = v
}
},
@ -100,40 +88,23 @@ export default {
//
onSelect(index, indexPath, item, jump = true) {
//
if (settingGetters.sidebarUniqueOpen && indexPath.length === 1) {
if (asideGetters.uniqueOpen && indexPath.length === 1) {
const menu = this.$refs.menu
const opened = menu.openedMenus
opened.forEach(i => i !== index && menu.closeMenu(i))
}
jump && this.actionOnSelectMenu(index)
},
collapseSidebar(e) {
if (e) {
e.preventDefault()
e.stopPropagation()
}
settingMutations.sidebarCollapse(true)
}
},
mounted() {
if (settingGetters.sidebarAutoHidden) {
if (asideGetters.autoHide) {
document.addEventListener('mousemove', this.moveEvent)
}
//
const MaskConstructor = Vue.extend(Mask)
this.maskInstance = new MaskConstructor().$mount()
this.maskInstance.onClick = this.collapseSidebar
document.body.appendChild(this.maskInstance.$el)
this.$once('hook:beforeDestroy', () => {
document.removeEventListener('mousemove', this.moveEvent)
document.body.removeChild(this.maskInstance.$el)
this.maskInstance.$destroy()
})
},
@ -141,40 +112,52 @@ export default {
const menus = getSidebarMenus()
if (menus.length <= 0) return
const asideClass = {
'aside': true,
'mobile': this.device === 'mobile',
'collapse': this.collapse,
'hide': this.hideSidebar
}
return (
const aside = (
<aside
class={asideClass}
class={{'aside': true, 'collapse': this.collapse}}
on-mouseenter={() => this.mouseOutside = false}
on-mouseleave={() => this.mouseOutside = true}
>
{settingGetters.showLogo && <logo collapse={this.collapse}/>}
{asideGetters.showLogo && <logo collapse={this.collapse}/>}
<el-menu
ref="menu"
class="el-menu--vertical"
collapse={this.collapse}
collapse-transition={false}
default-active={this.activeMenu}
unique-opened={settingGetters.sidebarUniqueOpen}
unique-opened={asideGetters.uniqueOpen}
on-select={this.onSelect}
>
{menus.map(m => (
<menu-item
<nav-menu-item
menu={m}
show-parent={settingGetters.sidebarShowParent}
collapse={settingGetters.sidebarCollapse}
show-parent={asideGetters.showParentOnCollapse}
collapse={asideGetters.collapse}
show-icon-max-depth={1}
/>
))}
</el-menu>
</aside>
)
//
const renderInDrawer = this.device === 'mobile' || asideGetters.autoHide
if (renderInDrawer) {
return (
<el-drawer
visible={asideGetters.show}
with-header={false}
direction="ltr"
size="auto"
on-close={() => asideMutations.close()}
>
{aside}
</el-drawer>
)
}
return aside
}
}
</script>

@ -13,23 +13,11 @@
box-shadow: 2px 0 6px rgba(0, 21, 41, 0.35);
transition: all 0.2s ease-in-out;
//移动端时为fixed
&.mobile {
position: fixed;
}
//折叠状态
&.collapse {
width: $aside-icon-size + 20px * 2;
}
//抽屉模式时的隐藏状态
&.hide {
width: 0;
pointer-events: none;
transform: translateX(-$aside-width);
}
.sidebar-logo-container {
position: relative;
width: 100%;
@ -124,17 +112,6 @@
}
}
//移动端侧边栏展开时的遮罩
.aside-hidden-mask {
background: rgba(0, 0, 0, .5);
backdrop-filter: blur(10px);
height: 100vh;
width: 100vw;
top: 0;
position: fixed;
z-index: 9;
}

@ -4,9 +4,8 @@
* TODO 菜单隐藏后再显示出来存在BUG怀疑是组件复用的问题
*/
import menuMixin from "@/layout/mixin/menu"
import {getters as mainGetters, mutations as mainMutations} from "@/layout/store/main"
import {getters as settingGetters} from "@/layout/store/setting"
import MenuItem from "@/component/menu/MenuItem"
import {getters as appGetters, mutations as appMutations} from "@/layout/store/app"
import NavMenuItem from "@/component/menu/NavMenu/item"
import {getActiveMenuByRoute} from "@/layout/util"
export default {
@ -14,7 +13,7 @@ export default {
mixins: [menuMixin],
components: {MenuItem},
components: {NavMenuItem},
props: {
//
@ -30,17 +29,17 @@ export default {
},
computed: {
navMode: () => settingGetters.navMode,
navMode: () => appGetters.navMode,
menus() {
if (mainGetters.device === 'mobile') return []
if (appGetters.device === 'mobile') return []
switch (this.navMode) {
case 'aside':
return []
case 'head' :
return mainGetters.menus
return appGetters.menus
case 'mix':
return mainGetters.menus.map(root => ({...root, children: undefined}))
return appGetters.menus.map(root => ({...root, children: undefined}))
}
}
},
@ -72,7 +71,7 @@ export default {
setActiveMenu(navMode = this.navMode, {path, meta, matched} = this.$route) {
//
const rootPath = matched[0].path || '/'
mainMutations.activeRootMenu(rootPath)
appMutations.activeRootMenu(rootPath)
//
if (navMode === 'mix') {
@ -82,7 +81,7 @@ export default {
else this.activeMenu = getActiveMenuByRoute({path, meta})
},
onSelect(index) {
if (this.navMode === 'mix' && index === mainGetters.activeRootMenu) {
if (this.navMode === 'mix' && index === appGetters.activeRootMenu) {
return
}
this.actionOnSelectMenu(index)
@ -144,7 +143,7 @@ export default {
},
renderVisibleMenus(h, visibleMenus) {
return visibleMenus.map(m => <menu-item menu={m} show-icon-max-depth={1}/>)
return visibleMenus.map(m => <nav-menu-item menu={m} show-icon-max-depth={1}/>)
},
renderHiddenMenus(h, hiddenMenus, hideAll) {
if (hiddenMenus.length <= 0) return
@ -153,7 +152,7 @@ export default {
? <v-icon slot="title" icon={'el-icon-menu'}/>
: <span slot="title" class="menu-item-content">{'...'}</span>
const children = hiddenMenus.map(m => <menu-item menu={m} show-icon-max-depth={0}/>)
const children = hiddenMenus.map(m => <nav-menu-item menu={m} show-icon-max-depth={0}/>)
children.unshift(content)
return <el-submenu index={'_'}>{children}</el-submenu>

@ -3,14 +3,14 @@
<div class="drawer-container">
<el-divider>主题色</el-divider>
<div class="drawer-item">
<checkbox-group :value="color" @input="changeColor">
<checkbox-group :value="appGetters.color" @input="changeThemeColor">
<color-checkbox v-for="i in colors" :key="i" :value="i"/>
</checkbox-group>
</div>
<el-divider>导航模式</el-divider>
<div class="drawer-item">
<checkbox-group :value="navMode" @input="e => setValue(e, 'navMode')">
<checkbox-group :value="appGetters.navMode" @input="changeNavMode">
<img-checkbox
v-for="{title, value, img} in navModes"
:key="value"
@ -21,13 +21,41 @@
</checkbox-group>
</div>
<template v-for="{title, children} in settings">
<el-divider>{{ title }}</el-divider>
<div v-for="{title, key} in children" :key="key" class="drawer-item">
<span>{{ title }}</span>
<el-switch :value="getValue(key)" class="drawer-switch" @input="e => setValue(e, key)"/>
</div>
</template>
<el-divider>页面设置</el-divider>
<div class="drawer-item">
<span>显示页头</span>
<el-switch :value="pageGetters.showPageHeader" @input="changePageShowHeader"/>
</div>
<div class="drawer-item">
<span>使用多页签</span>
<el-switch :value="tagsViewGetters.enabled" @input="changeTagsViewEnabled"/>
</div>
<div class="drawer-item">
<span>显示返回顶部按钮</span>
<el-switch :value="pageGetters.showBackToTop" @input="changePageBackToTop"/>
</div>
<el-divider>侧边栏设置</el-divider>
<div class="drawer-item">
<span>显示logo</span>
<el-switch :value="asideGetters.showLogo" @input="changeAsideShowLogo"/>
</div>
<div class="drawer-item">
<span>手风琴效果</span>
<el-switch :value="asideGetters.uniqueOpen" @input="changeAsideUniqueOpen"/>
</div>
<div class="drawer-item">
<span>折叠</span>
<el-switch :value="asideGetters.collapse" @input="changeAsideCollapse"/>
</div>
<div class="drawer-item">
<span>折叠时显示上级</span>
<el-switch :value="asideGetters.showParentOnCollapse" @input="changeAsideCollapseParent"/>
</div>
<div class="drawer-item">
<span>自动隐藏</span>
<el-switch :value="asideGetters.autoHide" @input="changeAsideAutoHide"/>
</div>
</div>
</el-drawer>
</template>
@ -39,7 +67,12 @@ import {isDev} from '@/config'
import CheckboxGroup from "@/component/checkbox/Group"
import ColorCheckbox from "@/component/checkbox/ColorCheckbox"
import ImgCheckbox from "@/component/checkbox/ImgCheckbox"
import {getters, mutations} from "@/layout/store/setting"
import {getters as appGetters, mutations as appMutations} from "@/layout/store/app"
import {getters as asideGetters, mutations as asideMutations} from "@/layout/store/aside"
import {getters as pageGetters, mutations as pageMutations} from "@/layout/store/page"
import {getters as tagsViewGetters, mutations as tagsViewMutations} from "@/layout/store/tagsView"
import {mergeObj} from "@/util"
import {getLocalPersonalSettings, setLocalPersonalSettings} from "@/util/storage"
export default {
name: "SettingDrawer",
@ -50,6 +83,8 @@ export default {
data() {
return {
appGetters, asideGetters, pageGetters, tagsViewGetters,
colors: ['#f5222d', '#fa541c', '#fadb14', '#3eaf7c', '#13c2c2', '#1890ff', '#722ed1', '#eb2f96'],
navModes: [
{
@ -68,51 +103,121 @@ export default {
img: 'https://gw.alipayobjects.com/zos/antfincdn/x8Ob%26B8cy8/LCkqqYNmvBEbokSDscrm.svg'
},
],
settings: [
{
title: '页面设置',
children: [
{title: '显示logo', key: 'showLogo'},
{title: '显示页头', key: 'showPageHeader'},
{title: '使用多页签', key: 'useTagsView'}
]
setting: {
app: {
color: '#1890ff',
navMode: 'mix'
},
{
title: '侧边栏设置',
children: [
{title: '手风琴效果', key: 'sidebarUniqueOpen'},
{title: '折叠', key: 'sidebarCollapse'},
{title: '折叠状态下显示上级', key: 'sidebarShowParent'},
{title: '自动隐藏', key: 'sidebarAutoHidden'},
]
page: {
showPageHeader: true,
useTagsView: true,
showBackToTop: true
},
{
title: '其他设置',
children: [
{title: '显示返回顶部按钮', key: 'showBackToTop'}
]
aside: {
showLogo: true,
uniqueOpen: true,
collapse: false,
showParentOnCollapse: false,
autoHide: false
}
]
}
}
},
computed: {
color: () => getters.themeColor,
navMode: () => getters.navMode,
},
methods: {
changeColor(color) {
this.setValue(color,'themeColor')
isDev && client.changer.changeColor({newColors: forElementUI.getElementUISeries(color)})
//layoutstore
updateLayoutStore() {
const {app, page, aside} = this.setting
appMutations.color(app.color)
appMutations.navMode(app.navMode)
pageMutations.showPageHeader(page.showPageHeader)
tagsViewMutations.enabled(page.useTagsView)
pageMutations.showBackToTop(page.showBackToTop)
asideMutations.showLogo(aside.showLogo)
asideMutations.uniqueOpen(aside.uniqueOpen)
asideMutations.collapse(aside.collapse)
asideMutations.showParentOnCollapse(aside.showParentOnCollapse)
asideMutations.autoHide(aside.autoHide)
},
//layoutstore
syncLayoutStore() {
const {app, page, aside, other} = this.setting
app.color = appGetters.color
app.navMode = appGetters.navMode
page.showPageHeader = pageGetters.showPageHeader
page.useTagsView = tagsViewGetters.enabled
page.showBackToTop = pageGetters.showBackToTop
aside.showLogo = asideGetters.showLogo
aside.uniqueOpen = asideGetters.uniqueOpen
aside.collapse = asideGetters.collapse
aside.showParentOnCollapse = asideGetters.showParentOnCollapse
aside.autoHide = asideGetters.autoHide
},
//
saveSetting() {
this.syncLayoutStore()
setLocalPersonalSettings(this.setting)
},
changeThemeColor(v) {
isDev && client.changer.changeColor({newColors: forElementUI.getElementUISeries(v)})
appMutations.color(v)
this.saveSetting()
},
changeNavMode(v) {
appMutations.navMode(v)
this.saveSetting()
},
changePageShowHeader(v) {
pageMutations.showPageHeader(v)
this.saveSetting()
},
changeTagsViewEnabled(v) {
tagsViewMutations.enabled(v)
this.saveSetting()
},
changePageBackToTop(v) {
pageMutations.showBackToTop(v)
this.saveSetting()
},
getValue(key) {
return getters[key]
changeAsideShowLogo(v) {
asideMutations.showLogo(v)
this.saveSetting()
},
changeAsideUniqueOpen(v) {
asideMutations.uniqueOpen(v)
this.saveSetting()
},
changeAsideCollapse(v) {
asideMutations.collapse(v)
this.saveSetting()
},
changeAsideCollapseParent(v) {
asideMutations.showParentOnCollapse(v)
this.saveSetting()
},
setValue(value, key) {
mutations[key](value)
changeAsideAutoHide(v) {
asideMutations.autoHide(v)
this.saveSetting()
}
},
mounted() {
mergeObj(this.setting, getLocalPersonalSettings())
//
setLocalPersonalSettings(this.setting)
this.updateLayoutStore()
}
}
</script>
@ -125,13 +230,11 @@ export default {
word-wrap: break-word;
.drawer-item {
display: flex;
justify-content: space-between;
color: rgba(0, 0, 0, .65);
font-size: 14px;
padding: 12px 0;
}
.drawer-switch {
float: right
}
}
</style>

@ -5,8 +5,8 @@ import Bell from "./component/Bell"
import Hamburger from './component/Hamburger'
import HeadMenu from "./component/HeadMenu"
import SettingDrawer from './component/SettingDrawer'
import {getters as mainGetters} from "@/layout/store/main"
import {getters as settingGetters, mutations as settingMutations} from "@/layout/store/setting"
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"
@ -22,10 +22,6 @@ export default {
computed: mapState('user', ['avatar', 'name', 'prepareLogout']),
methods: {
sidebarCtrl() {
settingMutations.sidebarCollapse(!settingGetters.sidebarCollapse)
},
refresh() {
this.$router.replace({path: `/redirect${this.$route.fullPath}`})
},
@ -52,18 +48,18 @@ export default {
//
//
const hasSidebarMenus = getSidebarMenus().length > 0,
isMobile = mainGetters.device === 'mobile',
notHeadMode = settingGetters.navMode !== 'head',
notAutoHidden = !settingGetters.sidebarAutoHidden,
isMobile = appGetters.device === 'mobile',
notHeadMode = appGetters.navMode !== 'head',
notAutoHidden = !asideGetters.autoHide,
renderHamburger = hasSidebarMenus && (isMobile || notHeadMode && notAutoHidden)
if (renderHamburger) {
const active = !settingGetters.sidebarCollapse
return <hamburger class="navbar-item" active={active} on-click={this.sidebarCtrl}/>
const active = isMobile ? asideGetters.show : !asideGetters.collapse
return <hamburger class="navbar-item" active={active} on-click={() => asideMutations.switch()}/>
}
},
renderHeadMenuMenu() {
if (settingGetters.navMode !== 'aside' && mainGetters.device !== 'mobile') {
if (appGetters.navMode !== 'aside' && appGetters.device !== 'mobile') {
return <head-menu always-show/>
}
},

@ -74,6 +74,11 @@
max-width: 100px;
}
}
//submenu的右侧箭头会稍微偏上不懂为啥
.el-submenu__icon-arrow {
margin-top: -4px;
}
}
//顶部菜单的顶级节点

@ -2,16 +2,15 @@
import shortcutMixin from '@/layout/mixin/shortcut'
import decideRouterTransitionMixin from '@/layout/mixin/decideRouterTransition'
import {route as routeConfig} from '@/config'
import {getters as mainGetters} from "@/layout/store/main"
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 ContextmenuItem from "@/component/menu/ContextmenuItem"
import ContextMenu from "@/component/menu/ContextMenu"
import ScrollPane from './ScrollPane'
export default {
mixins: [shortcutMixin, decideRouterTransitionMixin],
components: {Contextmenu, ContextmenuItem, ScrollPane},
components: {ContextMenu, ScrollPane},
data() {
return {
@ -28,7 +27,7 @@ export default {
computed: {
visitedViews: () => tagsViewGetters.visitedViews,
menus: () => mainGetters.menus
menus: () => appGetters.menus
},
watch: {
@ -166,19 +165,21 @@ export default {
)
})
},
renderContextmenu(h) {
renderContextMenu(h) {
const menu = this.contextmenu
return (
<contextmenu v-model={menu.show} left={menu.left} top={menu.top}>
<contextmenu-item on-click={this.refreshSelectedTag}>刷新</contextmenu-item>
{!this.isAffix(this.selectedTag) &&
<contextmenu-item on-click={() => this.closeSelectedTag(this.selectedTag)}>
关闭
</contextmenu-item>}
<contextmenu-item on-click={this.closeOthersTags}>关闭其他</contextmenu-item>
<contextmenu-item on-click={this.closeAllTags}>关闭全部</contextmenu-item>
</contextmenu>
)
const items = [
{content: '刷新', click: this.refreshSelectedTag},
this.isAffix(this.selectedTag)
? undefined
: {
content: '关闭',
click: () => this.closeSelectedTag(this.selectedTag)
},
{content: '关闭其他', click: this.closeOthersTags},
{content: '关闭全部', click: this.closeAllTags}
]
return <context-menu v-model={menu.show} items={items} left={menu.left} top={menu.top}/>
}
},
@ -198,7 +199,7 @@ export default {
<scroll-pane ref="scrollPane" class="tags-view-wrapper">
{this.renderTags(h)}
</scroll-pane>
{this.renderContextmenu(h)}
{this.renderContextMenu(h)}
</nav>
)
}

@ -1,6 +1,5 @@
<script type="text/jsx">
import {getters as settingGetters} from "@/layout/store/setting"
import {mutations as tagsViewMutations} from "@/layout/store/tagsView"
import {getters as tagsViewGetters} from "@/layout/store/tagsView"
import VNavbar from './component/Navbar'
import TagsView from './component/TagsView'
@ -9,22 +8,11 @@ export default {
components: {VNavbar, TagsView},
computed: {
useTagsView: () => settingGetters.useTagsView
},
watch: {
//
useTagsView(v) {
!v && tagsViewMutations.delAllViews()
}
},
render() {
return (
<header class="header-container">
<v-navbar/>
{this.useTagsView && <tags-view/>}
{tagsViewGetters.enabled && <tags-view/>}
</header>
)
}

@ -1,67 +0,0 @@
<script type="text/jsx">
import {getters as iframeGetters} from "@/layout/store/iframe"
import {getters as settingGetters} from "@/layout/store/setting"
import {getters as tagsViewGetters} from "@/layout/store/tagsView"
import BackToTop from "./component/BackToTop"
import PageHeader from "./component/PageHeader"
import PageView from "./component/PageView"
import PageFooter from "./component/PageFooter"
export default {
name: 'AppMain',
components: {BackToTop, PageHeader, PageView, PageFooter},
computed: {
showPageHeader() {
if (!settingGetters.showPageHeader) return false
return this.$route.meta.pageHeader !== false
},
showBackToTop: () => settingGetters.showBackToTop,
cachedViews: () => tagsViewGetters.cachedViews,
transitionName: () => tagsViewGetters.transitionName,
showIframe: () => iframeGetters.show,
currentIframe: () => iframeGetters.current,
iframeList: () => iframeGetters.list,
},
methods: {
renderPage() {
return (
<div v-show={!this.showIframe} class="scroll-container">
{this.showPageHeader && <page-header/>}
<page-view include={this.cachedViews} transition-name={this.transitionName}/>
<page-footer/>
</div>
)
},
renderIframe() {
return this.iframeList.map(src => (
<iframe
v-show={this.showIframe && src === this.currentIframe}
id={src}
key={src}
src={src}
frameborder="0"
height="100%"
width="100%"
/>
))
}
},
render() {
return (
<main class="router-main">
{this.renderPage()}
{this.renderIframe()}
{this.showBackToTop && <back-to-top/>}
</main>
)
}
}
</script>
<style lang="scss" src="./style.scss"></style>

@ -1,7 +1,5 @@
<script type="text/jsx">
export default {
name: "BackToTop",
functional: true,
render() {

@ -1,22 +1,15 @@
<script>
import {isEmpty} from "@/util"
import {getRouterViewCacheKey} from "@/layout/util"
const KEY = '_routerViewKey'
function getFirstComponentChild(children) {
if (Array.isArray(children)) {
for (const c of children) {
if (!isEmpty(c) && (!isEmpty(c.componentOptions) || c.isComment && c.asyncFactory)) {
return c
}
}
return children.find(c => !isEmpty(c) && (!isEmpty(c.componentOptions) || c.isComment && c.asyncFactory))
}
}
function getComponentName(opts) {
return opts && (opts.Ctor.options.name || opts.tag)
}
function removeCache(cache, key) {
const cached = cache[key]
cached && cached.componentInstance && cached.componentInstance.$destroy()
@ -26,10 +19,9 @@ function removeCache(cache, key) {
function getCacheKey(route, componentOptions) {
if (KEY in componentOptions) return componentOptions[KEY]
const {path, fullPath, meta} = route
const key = meta.usePathKey ? path : meta.useFullPathKey ? fullPath : getComponentName(componentOptions)
const key = getRouterViewCacheKey(route)
componentOptions[KEY] = key
if (key) componentOptions[KEY] = key
return key
}
@ -46,11 +38,10 @@ export default {
deep: true,
handler(val) {
const {cache, $route} = this
for (const key of Object.keys(cache)) {
const cachedNode = cache[key]
const name = getCacheKey($route, cachedNode.componentOptions)
if (!val || !val.includes(name)) {
removeCache(cache, key)
for (const [k, v] of Object.entries(cache)) {
const cacheKey = getCacheKey($route, v.componentOptions)
if (!val || !val.includes(cacheKey)) {
removeCache(cache, k)
}
}
}
@ -70,6 +61,7 @@ export default {
render() {
const slot = this.$slots.default
const vnode = getFirstComponentChild(slot)
let componentOptions = vnode && vnode.componentOptions
if (componentOptions) {

@ -6,10 +6,7 @@ export default {
functional: true,
props: {
include: Array,
transitionName: String
},
props: {include: Array, transitionName: String},
render(h, context) {
const {include, transitionName} = context.props

@ -0,0 +1,56 @@
<script type="text/jsx">
import {getters as pageGetters} from "@/layout/store/page"
import {getters as tagsViewGetters} from "@/layout/store/tagsView"
import BackToTop from "./component/BackToTop"
import PageHeader from "./component/PageHeader"
import PageView from "./component/PageView"
import PageFooter from "./component/PageFooter"
export default {
name: 'Page',
components: {BackToTop, PageHeader, PageView, PageFooter},
methods: {
renderPage() {
const render = pageGetters.showPageHeader && this.$route.meta.pageHeader !== false
const {cachedViews, transitionName} = tagsViewGetters
return (
<div v-show={!pageGetters.showIframe} class="scroll-container">
{render && <page-header/>}
<page-view include={cachedViews} transition-name={transitionName}/>
<page-footer/>
</div>
)
},
renderIframe() {
const {showIframe, iframeList, currentIframe} = pageGetters
return iframeList.map(src => (
<iframe
v-show={showIframe && src === currentIframe}
id={src}
key={src}
src={src}
frameborder="0"
height="100%"
width="100%"
/>
))
}
},
render() {
return (
<main class="router-main">
{this.renderPage()}
{this.renderIframe()}
{pageGetters.showBackToTop && <back-to-top/>}
</main>
)
}
}
</script>
<style lang="scss" src="./style.scss"></style>

@ -2,23 +2,23 @@
import offlineMixin from './mixin/offline'
import VAside from './component/Aside'
import VHeader from './component/Header'
import VMain from './component/Main'
import {getters as mainGetters, mutations as mainMutations} from "@/layout/store/main"
import {getters as settingGetters} from "@/layout/store/setting"
import {isMobile} from "@/util/browser"
import VPage from './component/Page'
import {getters as appGetters, mutations as appMutations} from "@/layout/store/app"
import {getters as tagsViewGetters} from "@/layout/store/tagsView"
import {debounce} from "@/util"
import {isMobile} from "@/util/browser"
export default {
name: 'Layout',
mixins: [offlineMixin],
components: {VAside, VHeader, VMain},
components: {VAside, VHeader, VPage},
computed: {
containerClass() {
const arr = ['main-container', 'has-nav', `nav-mode-${settingGetters.navMode}`]
settingGetters.useTagsView && arr.push('has-tags-view')
const arr = ['main-container', 'has-nav', `nav-mode-${appGetters.navMode}`]
tagsViewGetters.enabled && arr.push('has-tags-view')
return arr
}
},
@ -27,7 +27,7 @@ export default {
$_resizeHandler() {
if (!document.hidden) {
const mobile = isMobile()
mainMutations.device(mobile ? 'mobile' : 'pc')
appMutations.device(mobile ? 'mobile' : 'pc')
}
}
},
@ -44,14 +44,14 @@ export default {
},
render() {
const renderAide = mainGetters.device === 'mobile' || settingGetters.navMode !== 'head'
const renderAide = appGetters.device === 'mobile' || appGetters.navMode !== 'head'
return (
<section class="app-wrapper">
{renderAide && <v-aside/>}
<section class={this.containerClass}>
<v-header/>
<v-main/>
<v-page/>
</section>
</section>
)

@ -11,13 +11,33 @@ const state = {
activeRootMenu: '',
//所有的树形菜单,每个元素为顶部菜单,顶部菜单的子级(如果有)为侧边栏菜单
menus: []
menus: [],
//主题色
color: '#1890ff',
//导航模式
navMode: 'mix'
}
const store = Vue.observable(state)
export const getters = createGetters(store)
export const mutations = {
...createMutations(store),
menus(v) {
sort(v)
store.menus = v
}
}
//菜单排序
function sort(routes) {
if (!Array.isArray(routes) || routes.length === 0) {
return
}
const getSortValue = item => {
const sort = deepTap(item)
return isEmpty(sort) ? 10000 : sort
@ -47,14 +67,3 @@ function sort(routes) {
}
})
}
export const getters = createGetters(store)
export const mutations = {
...createMutations(store),
menus(v) {
sort(v)
store.menus = v
}
}

@ -0,0 +1,75 @@
/**
* 侧边栏的响应式数据
*/
import Vue from 'vue'
import {bindThis} from "@/util"
import {createGetters, createMutations} from "@/util/observable"
import {getters as appGetters} from "./app"
const state = {
//抽屉模式时的显隐
show: false,
//显示顶部logo
showLogo: true,
//手风琴效果
uniqueOpen: true,
//是否折叠
collapse: false,
//折叠时显示上级
showParentOnCollapse: false,
//自动隐藏
autoHide: false
}
const store = Vue.observable(state)
export const getters = createGetters(store)
export const mutations = bindThis({
...createMutations(store),
/*折叠和自动隐藏不能同时启用*/
collapse(v) {
if (v) store.autoHide = false
store.collapse = v
},
autoHide(v) {
if (v) store.collapse = false
store.autoHide = v
},
/*移动端是打开关闭抽屉,桌面端是展开折叠*/
open() {
if (appGetters.device === 'mobile') {
store.show = true
}
else store.collapse = false
},
close() {
if (appGetters.device === 'mobile') {
store.show = false
}
else store.collapse = true
},
//切换侧边栏的状态
switch(action) {
switch (action) {
case 'open':
return this.open()
case 'close':
return this.close()
default :
let open = true
if (appGetters.device === 'mobile') {
open = !store.show
}
else open = store.collapse
return open ? this.open() : this.close()
}
}
})

@ -1,35 +0,0 @@
import Vue from 'vue'
import {createGetters} from "@/util/observable"
const state = {
show: false,
current: '',
list: []
}
const store = Vue.observable(state)
function add(src) {
!store.list.includes(src) && store.list.push(src)
}
function del(src) {
const index = store.list.findIndex(i => i === src)
index > -1 && store.list.splice(index, 1)
}
function open({src}) {
store.show = true
store.current = src
add(src)
}
function close({src, del: needDelete}) {
store.show = false
store.current = ''
needDelete && del(src)
}
export const getters = createGetters(store)
export const mutations = {del, open, close}

@ -0,0 +1,44 @@
/**
* 路由页面的响应式数据
*/
import Vue from 'vue'
import {bindThis} from "@/util"
import {createGetters, createMutations} from "@/util/observable"
const state = {
/*iframe的控制*/
showIframe: false,
currentIframe: '',
iframeList: [],
//是否显示页头
showPageHeader: true,
//是否显示返回顶部按钮
showBackToTop: true
}
const store = Vue.observable(state)
export const getters = createGetters(store)
export const mutations = bindThis({
...createMutations(store),
addIframe(src) {
!store.iframeList.includes(src) && store.iframeList.push(src)
},
delIframe(src) {
const index = store.iframeList.findIndex(i => i === src)
index > -1 && store.iframeList.splice(index, 1)
},
openIframe({src}) {
store.showIframe = true
store.currentIframe = src
this.addIframe(src)
},
closeIframe({src, del}) {
store.showIframe = false
store.currentIframe = ''
del && this.delIframe(src)
}
})

@ -1,69 +0,0 @@
import Vue from 'vue'
import {isEmpty} from "@/util"
import {createGetters} from "@/util/observable"
import {getLocalPersonalSettings, setLocalPersonalSettings} from "@/util/storage"
const state = {
//主题色
themeColor: '#1890ff',
//导航模式
navMode: 'mix',
//是否显示logo
showLogo: true,
//是否显示页头
showPageHeader: true,
//是否使用多页签
useTagsView: true,
//侧边栏菜单手风琴效果
sidebarUniqueOpen: true,
//侧边栏菜单是否折叠
sidebarCollapse: false,
//侧边栏菜单折叠时,弹出菜单是否显示上级
sidebarShowParent: false,
//是否自动隐藏侧边栏菜单
sidebarAutoHidden: false,
//是否显示返回顶部按钮
showBackToTop: true,
}
//state填入初始值
const localSettings = getLocalPersonalSettings()
Object.keys(state).forEach(key => {
if (!isEmpty(localSettings[key])) state[key] = localSettings[key]
})
const store = Vue.observable(state)
//每次修改个人设置时,同步到localStorage
function createMutations(store) {
return Object.keys(store).reduce((mutations, key) => {
mutations[key] = data => {
store[key] = data
setLocalPersonalSettings(store)
}
return mutations
}, {})
}
export const getters = createGetters(store)
export const mutations = {
...createMutations(store),
sidebarCollapse(v) {
//当启用侧边栏折叠时,停用自动隐藏侧边栏
if (v) store.sidebarAutoHidden = false
store.sidebarCollapse = v
setLocalPersonalSettings(store)
},
sidebarAutoHidden(v) {
//当启用自动隐藏侧边栏时,停用侧边栏折叠
if (v) store.sidebarCollapse = false
store.sidebarAutoHidden = v
setLocalPersonalSettings(store)
}
}

@ -1,9 +1,17 @@
/**
* 多页签的响应式数据
*/
import Vue from 'vue'
import {route as routeConfig} from "@/config"
import {mutations as iframeMutations} from "@/layout/store/iframe"
import {mutations as pageMutations} from "@/layout/store/page"
import {getRouterViewCacheKey} from "@/layout/util"
import {bindThis} from "@/util"
import {createGetters, createMutations} from "@/util/observable"
const state = {
//是否启用
enabled: true,
//显示的页签,{...route,title:route.meta.title}对象数组
visitedViews: [],
@ -16,70 +24,63 @@ const state = {
const store = Vue.observable(state)
function getCachedViewKey(view) {
const {usePathKey, useFullPathKey} = view.meta || {}
return usePathKey ? view.path : useFullPathKey ? view.fullPath : view.name
}
function addVisitedView(view) {
const {title = '暂无标题'} = view.meta || {}
if (store.visitedViews.some(v => v.path === view.path)) return
store.visitedViews.push({...view, title})
}
function addCachedView(view) {
const {noCache, iframe, usePathKey, useFullPathKey} = view.meta || {}
export const getters = createGetters(store)
if (noCache || iframe || !view.name && !usePathKey && !useFullPathKey) return
export const mutations = bindThis({
...createMutations(store),
const key = getCachedViewKey(view)
enabled(v) {
store.enabled = v
//多页签停用时清除所有页面缓存
!v && this.delAllViews()
},
if (store.cachedViews.includes(key)) return
addVisitedView(view) {
const {title = '暂无标题'} = view.meta || {}
store.cachedViews.push(key)
}
if (store.visitedViews.some(v => v.path === view.path)) return
function delVisitedView(view) {
const index = store.visitedViews.findIndex(i => i.path === view.path)
index > -1 && store.visitedViews.splice(index, 1)
}
store.visitedViews.push({...view, title})
},
addCachedView(view) {
const {noCache, iframe, usePathKey, useFullPathKey} = view.meta || {}
function delCachedView(view) {
const key = getCachedViewKey(view)
const index = store.cachedViews.indexOf(key)
index > -1 && store.cachedViews.splice(index, 1)
}
if (noCache || iframe || !view.name && !usePathKey && !useFullPathKey) return
export const getters = createGetters(store)
const key = getRouterViewCacheKey(view)
export const mutations = {
...createMutations(store),
if (store.cachedViews.includes(key)) return
addVisitedView,
addCachedView,
store.cachedViews.push(key)
},
addView(view) {
addVisitedView(view)
addCachedView(view)
this.addVisitedView(view)
this.addCachedView(view)
},
delVisitedView,
delCachedView,
delVisitedView(view) {
const index = store.visitedViews.findIndex(i => i.path === view.path)
index > -1 && store.visitedViews.splice(index, 1)
},
delCachedView(view) {
const key = getRouterViewCacheKey(view)
const index = store.cachedViews.indexOf(key)
index > -1 && store.cachedViews.splice(index, 1)
},
delView(view) {
delVisitedView(view)
delCachedView(view)
iframeMutations.del(view.meta ? view.meta.iframe : null)
this.delVisitedView(view)
this.delCachedView(view)
pageMutations.delIframe(view.meta ? view.meta.iframe : null)
},
delOthersViews(view) {
const visitedViews = store.visitedViews.filter(v => v.meta.affix || v.path === view.path)
const name = store.cachedViews.find(name => name === getCachedViewKey(view))
const key = store.cachedViews.find(key => key === getRouterViewCacheKey(view))
store.visitedViews = visitedViews
store.cachedViews = name ? [name] : []
store.cachedViews = key ? [key] : []
},
delAllViews() {
store.visitedViews = store.visitedViews.filter(tag => tag.meta && tag.meta.affix)
store.cachedViews = []
}
}
})

@ -1,22 +1,27 @@
import {getters as mainGetters} from "@/layout/store/main"
import {getters as settingGetters} from "@/layout/store/setting"
import {getters as appGetters} from "@/layout/store/app"
//获取路由页面缓存所需的key
export function getRouterViewCacheKey({name, path, fullPath, meta = {}}) {
const {usePathKey, useFullPathKey} = meta
return usePathKey ? path : useFullPathKey ? fullPath : name
}
//获取侧边栏的菜单
export function getSidebarMenus() {
const menus = mainGetters.menus
const menus = appGetters.menus
if (!Array.isArray(menus)) {
return []
}
if (mainGetters.device === 'mobile') {
if (appGetters.device === 'mobile') {
return menus
}
switch (settingGetters.navMode) {
switch (appGetters.navMode) {
case 'aside':
return menus
case 'head':
return []
case 'mix':
const root = menus.find(i => i.path === mainGetters.activeRootMenu)
const root = menus.find(i => i.path === appGetters.activeRootMenu)
return root ? root.children || [] : []
}
}

@ -1,11 +1,11 @@
import {commonMethods} from "@/mixin/docTableMixin"
import AbstractForm from '@/component/AbstractForm'
import AbstractFormItem from "@/component/AbstractForm/item"
import AbstractTable from "@/component/AbstractTable"
import AbstractForm from '@/component/abstract/Form'
import AbstractFormItem from "@/component/abstract/Form/item"
import AbstractTable from "@/component/abstract/Table"
import DetailPage from "@/view/app/common/DetailPage"
import DocHistory from '@/component/biz/doc/DocHistory'
import DocSteps from '@/component/biz/doc/DocSteps'
import FormCard from '@/component/AbstractForm/FormCard'
import FormCollapseCard from '@/component/form/CollapseCard'
import UploadFile from '@/component/UploadFile'
import {isEmpty, mergeObj} from '@/util'
import {auth} from "@/util/auth"
@ -21,7 +21,7 @@ export default {
DetailPage,
DocHistory,
DocSteps,
FormCard,
FormCollapseCard,
UploadFile
},

@ -8,7 +8,7 @@ const steps = [
content: '这是个性设置按钮,可以根据自己喜好进行一些设置',
},
{
element: '.avatar-container.navbar-item',
element: '.navbar .el-dropdown.navbar-item',
content: '这是用户中心',
},
{

@ -2,8 +2,8 @@
* 列表页通用混入
* 引用者必须要有search方法
* */
import AbstractPagination from '@/component/AbstractPagination'
import AbstractTable from '@/component/AbstractTable'
import AbstractPagination from '@/component/abstract/Pagination'
import AbstractTable from '@/component/abstract/Table'
const mixin = {
components: {AbstractPagination, AbstractTable},

@ -1,19 +1,17 @@
import {mutations as iframeMutations} from "@/layout/store/iframe"
import {mutations as pageMutations} from "@/layout/store/page"
const beforeEach = (to, from, next) => {
//从iframe页面离开时,判断是否需要删除iframe
if (from.meta.iframe) {
let del = false
//如果设置了无缓存或是进行了刷新,那么移除iframe
if (from.meta.noCache || to.path === `/redirect${from.path}`) {
del = true
}
iframeMutations.close({src: from.meta.iframe, del})
const del = from.meta.noCache || to.path === `/redirect${from.path}`
pageMutations.closeIframe({src: from.meta.iframe, del})
}
//跳转至iframe页面时,打开iframe
if (to.meta.iframe) {
iframeMutations.open({src: to.meta.iframe})
pageMutations.openIframe({src: to.meta.iframe})
}
next()

@ -1,4 +1,4 @@
import {createMutations} from "@/util"
import {createMutations} from "@/store/util"
const state = {
//登陆页背景动画

@ -1,4 +1,4 @@
import {createMutations} from "@/util"
import {createMutations} from "@/store/util"
import {search} from '@/api/message/user'
const state = {

@ -1,6 +1,6 @@
import path from 'path'
import {route as routeConfig} from '@/config'
import {mutations as mainMutations} from "@/layout/store/main"
import {mutations as appMutations} from "@/layout/store/app"
import {addDynamicRoutes} from '@/router'
import {getDynamicRoutes} from '@/router/define'
import {parseRoutes, metaExtend} from "@/router/util"
@ -61,7 +61,7 @@ const actions = {
//获取经过权限过滤后的菜单
const menus = getAuthorizedMenus({resources, admin}, routes)
mainMutations.menus(menus)
appMutations.menus(menus)
commit('resource', {data: data || [], admin})
//设置初始化完成的标志

@ -2,7 +2,7 @@ import {MessageBox} from "element-ui"
import {isEmpty} from "@/util"
import {useMock, socketUrl} from '@/config'
import SocketIO from 'socket.io-client'
import {createMutations} from "@/util"
import {createMutations} from "@/store/util"
let socket

@ -1,4 +1,5 @@
import {createMutations, emptyOrDefault} from "@/util"
import {createMutations} from "@/store/util"
import {emptyOrDefault} from "@/util"
import {autoCompleteUrl} from "@/util/file"
import {getUser, setUser} from "@/util/storage"
import {mutations as tagsViewMutations} from "@/layout/store/tagsView"

@ -0,0 +1,18 @@
import {getInitialValue} from "@/util"
//vuex中根据state批量生成对应的mutation
export function createMutations(state, all = false) {
const keys = Object.keys(state)
const obj = {}
keys.forEach(key => {
obj[key] = (s, v) => s[key] = v
})
if (all) {
obj['$all'] = (s, v) => {
keys.forEach(key => {
s[key] = v && v[key] || getInitialValue(s[key])
})
}
}
return obj
}

@ -177,23 +177,6 @@ export function waitUntilSuccess(success, callback, interval = 1000, maxTryTime
})
}
//vuex中根据state批量生成对应的mutation
export function createMutations(state, all = false) {
const keys = Object.keys(state)
const obj = {}
keys.forEach(key => {
obj[key] = (s, v) => s[key] = v
})
if (all) {
obj['$all'] = (s, v) => {
keys.forEach(key => {
s[key] = v && v[key] || getInitialValue(s[key])
})
}
}
return obj
}
//删除所有url参数
export function delAllUrlParam() {
let paramStartIndex = location.href.indexOf('?')
@ -202,3 +185,17 @@ export function delAllUrlParam() {
history.replaceState(null, null, [...href].join(''))
}
}
//将传入对象的所有函数的this绑定为其自身
export function bindThis(obj, root = obj) {
if (!obj || typeof obj !== 'object') return
Object.entries(obj).forEach(([k, v]) => {
if (typeof v === 'function') {
obj[k] = v.bind(root)
}
bindThis(v, root)
})
return obj
}

@ -3,17 +3,17 @@ import pako from 'pako'
export function unzip(b64Data) {
let strData = atob(b64Data)
// Convert binary string to character-number array
let charData = strData.split('').map(x => x.charCodeAt(0))
const charData = strData.split('').map(x => x.charCodeAt(0))
// Turn number array into byte-array
let binData = new Uint8Array(charData)
const binData = new Uint8Array(charData)
// unzip
let data = pako.inflate(binData)
const data = pako.inflate(binData)
// Convert gunzipped byteArray back to ascii string:
strData = String.fromCharCode.apply(null, new Uint16Array(data))
return decodeURIComponent(strData)
}
export function zip(str) {
let binaryString = pako.gzip(encodeURIComponent(str), {to: 'string'})
const binaryString = pako.gzip(encodeURIComponent(str), {to: 'string'})
return btoa(binaryString)
}

@ -34,7 +34,7 @@ export function getLocalPersonalSettings() {
obj = JSON.parse(obj)
}
catch (e) {
console.error('个性化设置数据异常!', e)
console.error('本地个性化设置数据解析失败!', e)
removeLocalPersonalSettings()
return {}
}

@ -1,6 +1,6 @@
<script type="jsx">
import FormAnchor from "@/component/AbstractForm/FormAnchor"
import FormValidateInfo from "@/component/AbstractForm/FormValidateInfo"
import FormAnchor from "@/component/form/Anchor"
import FormValidateInfo from "@/component/form/ValidateInfo"
const renderHeader = (h, title, description, extra, close) => (
<div class="detail-page-header">

@ -82,12 +82,8 @@ export default {
}
</script>
<style lang="scss">
.panel-group {
padding-top: 17px;
> .el-col {
margin-bottom: 32px;
}
<style>
.panel-group > .el-col {
margin-bottom: 32px;
}
</style>

@ -1,5 +1,5 @@
<template>
<form-dialog :loading="loading" :title="title" :value="value" width="50%" @close="cancel" @open="open">
<abstract-dialog :loading="loading" :title="title" :value="value" width="50%" @close="cancel" @open="open">
<abstract-form :model="form" :rules="rules">
<abstract-form-item label="标 题" prop="title" dense>
<el-input v-model="form.title" maxlength="100" :readonly="!canSave"/>
@ -48,14 +48,14 @@
<el-button v-if="canPublish" size="small" type="primary" @click="publish"> </el-button>
<el-button v-if="canWithdraw" size="small" type="danger" @click="withdraw"> </el-button>
</template>
</form-dialog>
</abstract-dialog>
</template>
<script>
import dialogMixin from "@/mixin/dialogMixin"
import AbstractForm from "@/component/AbstractForm"
import AbstractFormItem from "@/component/AbstractForm/item"
import FormDialog from '@/component/FormDialog'
import AbstractForm from "@/component/abstract/Form"
import AbstractFormItem from "@/component/abstract/Form/item"
import AbstractDialog from '@/component/abstract/Dialog'
import RichTextEditor from "@/component/editor/RichTextEditor"
import UserSelector from '@/component/biz/UserSelector/SimpleMultipleUserSelector'
import {add, update, publish, withdraw} from "@/api/message/manage"
@ -68,7 +68,7 @@ export default {
mixins: [dialogMixin],
components: {AbstractForm, AbstractFormItem, FormDialog, RichTextEditor, UserSelector},
components: {AbstractForm, AbstractFormItem, AbstractDialog, RichTextEditor, UserSelector},
props: {
value: {type: Boolean, default: false},

@ -56,8 +56,8 @@
<script>
import tableMixin from '@/mixin/tablePageMixin'
import EditDialog from './EditDialog'
import SearchForm from "@/component/SearchForm"
import SearchFormItem from "@/component/SearchForm/item"
import SearchForm from "@/component/form/Search"
import SearchFormItem from "@/component/form/Search/item"
import {search, add, update, del} from "@/api/message/manage"
import {isEmpty} from '@/util'
import {auth} from "@/util/auth"

@ -10,7 +10,7 @@
</template>
<script>
import {getters as mainGetters} from "@/layout/store/main"
import {getters as appGetters} from "@/layout/store/app"
import MessageStream from './MessageStream'
export default {
@ -27,7 +27,7 @@ export default {
computed: {
tabPosition() {
return mainGetters.device === 'pc' ? 'left' : 'top'
return appGetters.device === 'pc' ? 'left' : 'top'
},
tabContentClass() {
return this.tabPosition === 'left' ? 'tab-main-right' : 'tab-main-top'

@ -10,20 +10,20 @@
@close="close"
>
<abstract-form :model="form" :rules="rules">
<form-card ref="progress" header="流程进度">
<form-collapse-card ref="progress" header="流程进度">
<doc-steps :status="form.status" :type="type"/>
</form-card>
</form-collapse-card>
<form-card ref="base" header="基础信息">
<form-collapse-card ref="base" header="基础信息">
<abstract-form-item label="采购订单" prop="pid">
<el-input v-if="canSave" :value="form.pid" readonly>
<el-button slot="append" @click="parentDialog=true">选择</el-button>
</el-input>
<template v-else>{{ form.pid }}</template>
</abstract-form-item>
</form-card>
</form-collapse-card>
<form-card ref="goods" header="入库商品">
<form-collapse-card ref="goods" header="入库商品">
<abstract-table :data="form.data" :highlight-current-row="false">
<el-table-column align="center" label="#" type="index" width="80"/>
<el-table-column align="center" label="商 品" prop="cname"/>
@ -41,18 +41,18 @@
</template>
</el-table-column>
</abstract-table>
</form-card>
</form-collapse-card>
<form-card ref="attachment" header="附件">
<form-collapse-card ref="attachment" header="附件">
<upload-file
:file-list="form.imageList"
:disabled="!canSave"
@remove="removeUpload"
@success="uploadSuccess"
/>
</form-card>
</form-collapse-card>
<form-card ref="remark" header="备注">
<form-collapse-card ref="remark" header="备注">
<el-input
v-model="form.remark"
:rows="4"
@ -61,11 +61,11 @@
show-word-limit
type="textarea"
/>
</form-card>
</form-collapse-card>
<form-card ref="history" header="单据历史">
<form-collapse-card ref="history" header="单据历史">
<doc-history :id="form.id"/>
</form-card>
</form-collapse-card>
</abstract-form>
<order-selector v-model="parentDialog" @select="selectParent"/>

@ -95,8 +95,8 @@
<script>
import docTableMixin from '@/mixin/docTableMixin'
import SearchForm from '@/component/SearchForm'
import SearchFormItem from "@/component/SearchForm/item"
import SearchForm from '@/component/form/Search'
import SearchFormItem from "@/component/form/Search/item"
import {baseUrl, add, update, del, withdraw, pass, reject, getSubById, search} from "@/api/doc/purchase/inbound"
export default {

@ -10,20 +10,20 @@
@close="close"
>
<abstract-form :model="form" :rules="rules">
<form-card ref="progress" header="流程进度">
<form-collapse-card ref="progress" header="流程进度">
<doc-steps :status="form.status" :finish="form.finish" :type="type"/>
</form-card>
</form-collapse-card>
<form-card ref="base" header="基础信息">
<form-collapse-card ref="base" header="基础信息">
<abstract-form-item label="供应商" prop="sname">
<el-input v-if="canSave" :value="form.sname" readonly>
<el-button slot="append" @click="supplierDialog=true">选择</el-button>
</el-input>
<template v-else>{{ form.sname }}</template>
</abstract-form-item>
</form-card>
</form-collapse-card>
<form-card ref="goods" header="采购商品">
<form-collapse-card ref="goods" header="采购商品">
<abstract-table :data="form.data" :highlight-current-row="false">
<el-table-column align="center" label="#" type="index" width="80"/>
<el-table-column align="center" label="商 品">
@ -87,18 +87,18 @@
</el-button>
</div>
</abstract-table>
</form-card>
</form-collapse-card>
<form-card ref="attachment" header="附件">
<form-collapse-card ref="attachment" header="附件">
<upload-file
:file-list="form.imageList"
:disabled="!canSave"
@remove="removeUpload"
@success="uploadSuccess"
/>
</form-card>
</form-collapse-card>
<form-card ref="remark" header="备注">
<form-collapse-card ref="remark" header="备注">
<el-input
v-model="form.remark"
:rows="4"
@ -107,11 +107,11 @@
show-word-limit
type="textarea"
/>
</form-card>
</form-collapse-card>
<form-card ref="history" header="单据历史">
<form-collapse-card ref="history" header="单据历史">
<doc-history :id="form.id"/>
</form-card>
</form-collapse-card>
</abstract-form>
<supplier-selector v-model="supplierDialog" @select="selectSupplier"/>

@ -123,8 +123,8 @@
<script>
import docTableMixin from '@/mixin/docTableMixin'
import SearchForm from '@/component/SearchForm'
import SearchFormItem from "@/component/SearchForm/item"
import SearchForm from '@/component/form/Search'
import SearchFormItem from "@/component/form/Search/item"
import {baseUrl, add, update, del, withdraw, pass, reject, getSubById, search} from "@/api/doc/purchase/order"
export default {

@ -10,20 +10,20 @@
@close="close"
>
<abstract-form :model="form" :rules="rules">
<form-card ref="progress" header="流程进度">
<form-collapse-card ref="progress" header="流程进度">
<doc-steps :status="form.status" :finish="form.finish" :type="type"/>
</form-card>
</form-collapse-card>
<form-card ref="base" header="基础信息">
<form-collapse-card ref="base" header="基础信息">
<abstract-form-item label="客 户" prop="customerName">
<el-input v-if="canSave" :value="form.customerName" readonly>
<el-button slot="append" @click="customerDialog=true">选择</el-button>
</el-input>
<template v-else>{{ form.customerName }}</template>
</abstract-form-item>
</form-card>
</form-collapse-card>
<form-card ref="goods" header="销售商品">
<form-collapse-card ref="goods" header="销售商品">
<abstract-table :data="form.data" :highlight-current-row="false">
<el-table-column align="center" label="#" type="index" width="80"/>
<el-table-column align="center" label="商品" prop="cname"/>
@ -70,18 +70,18 @@
</el-button>
</div>
</abstract-table>
</form-card>
</form-collapse-card>
<form-card ref="attachment" header="附件">
<form-collapse-card ref="attachment" header="附件">
<upload-file
:file-list="form.imageList"
:disabled="!canSave"
@remove="removeUpload"
@success="uploadSuccess"
/>
</form-card>
</form-collapse-card>
<form-card ref="remark" header="备注">
<form-collapse-card ref="remark" header="备注">
<el-input
v-model="form.remark"
:rows="4"
@ -90,11 +90,11 @@
show-word-limit
type="textarea"
/>
</form-card>
</form-collapse-card>
<form-card ref="history" header="单据历史">
<form-collapse-card ref="history" header="单据历史">
<doc-history :id="form.id"/>
</form-card>
</form-collapse-card>
</abstract-form>
<stock-selector v-model="stockDialog" @select="selectStock"/>

@ -130,8 +130,8 @@
<script>
import docTableMixin from '@/mixin/docTableMixin'
import SearchForm from '@/component/SearchForm'
import SearchFormItem from "@/component/SearchForm/item"
import SearchForm from '@/component/form/Search'
import SearchFormItem from "@/component/form/Search/item"
import {baseUrl, add, update, del, withdraw, pass, reject, getSubById, search} from "@/api/doc/sell/order"
export default {

@ -10,20 +10,20 @@
@close="close"
>
<abstract-form :model="form" :rules="rules">
<form-card ref="progress" header="流程进度">
<form-collapse-card ref="progress" header="流程进度">
<doc-steps :status="form.status" :type="type"/>
</form-card>
</form-collapse-card>
<form-card ref="base" header="基础信息">
<form-collapse-card ref="base" header="基础信息">
<abstract-form-item label="销售订单" prop="pid">
<el-input v-if="canSave" :value="form.pid" readonly>
<el-button slot="append" @click="parentDialog=true">选择</el-button>
</el-input>
<template v-else>{{ form.pid }}</template>
</abstract-form-item>
</form-card>
</form-collapse-card>
<form-card ref="goods" header="出库商品">
<form-collapse-card ref="goods" header="出库商品">
<abstract-table :data="displayTableData" :highlight-current-row="false">
<el-table-column align="center" type="expand">
<el-table slot-scope="{row}" :data="row.data" border show-summary
@ -56,18 +56,18 @@
<el-table-column align="center" label="出库数量" prop="num" width="150"/>
<el-table-column align="center" label="订单剩余未出库数量" prop="remainNum" width="250"/>
</abstract-table>
</form-card>
</form-collapse-card>
<form-card ref="attachment" header="附件">
<form-collapse-card ref="attachment" header="附件">
<upload-file
:file-list="form.imageList"
:disabled="!canSave"
@remove="removeUpload"
@success="uploadSuccess"
/>
</form-card>
</form-collapse-card>
<form-card ref="remark" header="备注">
<form-collapse-card ref="remark" header="备注">
<el-input
v-model="form.remark"
:rows="4"
@ -76,11 +76,11 @@
show-word-limit
type="textarea"
/>
</form-card>
</form-collapse-card>
<form-card ref="history" header="单据历史">
<form-collapse-card ref="history" header="单据历史">
<doc-history :id="form.id"/>
</form-card>
</form-collapse-card>
</abstract-form>
<order-selector v-model="parentDialog" @select="selectParent"/>

@ -102,8 +102,8 @@
<script>
import docTableMixin from '@/mixin/docTableMixin'
import SearchForm from '@/component/SearchForm'
import SearchFormItem from "@/component/SearchForm/item"
import SearchForm from '@/component/form/Search'
import SearchFormItem from "@/component/form/Search/item"
import {baseUrl, add, update, del, withdraw, pass, reject, getSubById, search} from "@/api/doc/sell/outbound"
export default {

@ -1,5 +1,5 @@
<template>
<form-dialog :value="value" :loading="false" :title="title" width="70%" @close="cancel" @open="search">
<abstract-dialog :value="value" :loading="false" :title="title" width="70%" @close="cancel" @open="search">
<liner-progress :show="loading"/>
<abstract-table
@ -35,13 +35,13 @@
<el-table-column align="center" label="库存数量" prop="num" show-overflow-tooltip/>
<el-table-column align="center" label="总 值" prop="total" show-overflow-tooltip/>
</abstract-table>
</form-dialog>
</abstract-dialog>
</template>
<script>
import AbstractTable from '@/component/AbstractTable'
import AbstractTable from '@/component/abstract/Table'
import LinerProgress from '@/component/LinerProgress'
import FormDialog from "@/component/FormDialog"
import AbstractDialog from "@/component/abstract/Dialog"
import dialogMixin from "@/mixin/dialogMixin"
import {isEmpty} from "@/util"
import {plus, mul} from "@/util/math"
@ -52,7 +52,7 @@ export default {
mixins: [dialogMixin],
components: {AbstractTable, LinerProgress, FormDialog},
components: {AbstractTable, LinerProgress, AbstractDialog},
props: {value: Boolean, title: String, cid: Number},

@ -68,8 +68,8 @@ import tableMixin from '@/mixin/tablePageMixin'
import CategoryTree from '@/component/biz/CategoryTree'
import DetailDialog from "./DetailDialog"
import ExtraArea from '@/component/ExtraArea'
import SearchForm from "@/component/SearchForm"
import SearchFormItem from "@/component/SearchForm/item"
import SearchForm from "@/component/form/Search"
import SearchFormItem from "@/component/form/Search/item"
import {search} from "@/api/stock/current"
import {isEmpty, debounce} from "@/util"
import {exportExcel} from "@/util/excel"

@ -29,7 +29,7 @@
</template>
<script>
import AbstractForm from "@/component/AbstractForm"
import AbstractForm from "@/component/abstract/Form"
import {add, update} from "@/api/system/category"
import {isEmpty, mergeObj, waitUntilSuccess} from '@/util'
import {elConfirm} from "@/util/message"

@ -4,17 +4,17 @@
<category-tree ref="tree" @node-click="see" @node-contextmenu="openContextMenu"/>
</el-scrollbar>
<contextmenu v-model="contextmenu.show" :left="contextmenu.left" :top="contextmenu.top">
<contextmenu-item v-show="canAdd" @click="add">添加分类</contextmenu-item>
<contextmenu-item v-show="currentCategory" @click="edit">编辑分类</contextmenu-item>
<contextmenu-item v-show="currentCategory" @click="del">删除分类</contextmenu-item>
</contextmenu>
<context-menu
v-model="contextmenu.show"
:items="contextMenuItems"
:left="contextmenu.left"
:top="contextmenu.top"
/>
</el-card>
</template>
<script>
import Contextmenu from "@/component/menu/Contextmenu"
import ContextmenuItem from "@/component/menu/ContextmenuItem"
import ContextMenu from "@/component/menu/ContextMenu"
import CategoryTree from '@/component/biz/CategoryTree'
import {add, del, getAll} from "@/api/system/category"
import {isEmpty, waitUntilSuccess} from '@/util'
@ -22,7 +22,7 @@ import {auth} from "@/util/auth"
import {elConfirm, elError, elSuccess} from "@/util/message"
export default {
components: {Contextmenu, ContextmenuItem, CategoryTree},
components: {ContextMenu, CategoryTree},
props: ['form'],
@ -43,6 +43,16 @@ export default {
canAdd() {
return auth(add.url) && !this.currentCategory
|| !this.currentCategory.leaf && this.currentCategory.level < this.maxLevel
},
contextMenuItems() {
const items = []
this.canAdd && items.push({content: '添加分类', click: this.add})
this.currentCategory && items.push({content: '编辑分类', click: this.edit})
this.currentCategory && items.push({content: '删除分类', click: this.del})
return items
}
},

@ -1,5 +1,5 @@
<template>
<form-dialog :loading="loading" :title="title" :value="value" @close="cancel" @open="open">
<abstract-dialog :loading="loading" :title="title" :value="value" @close="cancel" @open="open">
<abstract-form :model="form" :rules="rules" size="">
<el-form-item label="名 称" prop="name">
<el-input v-model="form.name" :readonly="!canEdit" maxlength="20"/>
@ -43,13 +43,13 @@
<el-button plain size="small" @click="closeDialog"> </el-button>
<el-button v-if="canEdit" size="small" type="primary" @click="confirm"> </el-button>
</template>
</form-dialog>
</abstract-dialog>
</template>
<script>
import dialogMixin from "@/mixin/dialogMixin"
import AbstractForm from "@/component/AbstractForm"
import FormDialog from '@/component/FormDialog'
import AbstractForm from "@/component/abstract/Form"
import AbstractDialog from '@/component/abstract/Dialog'
import RegionSelector from '@/component/RegionSelector'
import {add, update} from "@/api/system/customer"
import {isEmpty, mergeObj, resetObj} from '@/util'
@ -60,7 +60,7 @@ export default {
mixins: [dialogMixin],
components: {AbstractForm, FormDialog, RegionSelector},
components: {AbstractForm, AbstractDialog, RegionSelector},
props: {
value: {type: Boolean, default: false},

@ -77,8 +77,8 @@
import tableMixin from '@/mixin/tablePageMixin'
import EditDialog from './EditDialog'
import RegionSelector from "@/component/RegionSelector"
import SearchForm from "@/component/SearchForm"
import SearchFormItem from "@/component/SearchForm/item"
import SearchForm from "@/component/form/Search"
import SearchFormItem from "@/component/form/Search/item"
import {add, update, del, search, getLimitRegion} from "@/api/system/customer"
import {isEmpty} from '@/util'
import {wic} from "@/util/auth"

@ -1,5 +1,5 @@
<template>
<form-dialog :loading="loading" :title="title" :value="value" @close="cancel" @open="open">
<abstract-dialog :loading="loading" :title="title" :value="value" @close="cancel" @open="open">
<abstract-form :model="form" :rules="rules" size="">
<el-form-item v-show="parentName" label="上级部门">
<el-input :value="parentName" readonly/>
@ -19,13 +19,13 @@
<el-button plain size="small" @click="closeDialog"> </el-button>
<el-button size="small" type="primary" @click="confirm"> </el-button>
</template>
</form-dialog>
</abstract-dialog>
</template>
<script>
import dialogMixin from "@/mixin/dialogMixin"
import AbstractForm from "@/component/AbstractForm"
import FormDialog from '@/component/FormDialog'
import AbstractForm from "@/component/abstract/Form"
import AbstractDialog from '@/component/abstract/Dialog'
import {add, update} from "@/api/system/department"
import {isEmpty} from '@/util'
import {elConfirm, elSuccess} from "@/util/message"
@ -35,7 +35,7 @@ export default {
mixins: [dialogMixin],
components: {AbstractForm, FormDialog},
components: {AbstractForm, AbstractDialog},
props: {
value: {type: Boolean, default: false},

@ -32,16 +32,12 @@
</div>
</div>
<contextmenu
v-if="canAdd || canUpdate || canDel"
<context-menu
v-model="contextmenu.show"
:items="contextMenuItems"
:left="contextmenu.left"
:top="contextmenu.top"
>
<contextmenu-item v-if="canAdd" @click="add">添加部门</contextmenu-item>
<contextmenu-item v-if="canUpdate" @click="update">编辑部门</contextmenu-item>
<contextmenu-item v-if="canDel" @click="del">删除部门</contextmenu-item>
</contextmenu>
/>
<edit-dialog v-model="editDialog" :data="currentNode" :type="type" @success="search"/>
</el-card>
@ -52,20 +48,19 @@
* 详情见iview-admin
* https://admin.iviewui.com/component/org_tree_page
*/
import Contextmenu from "@/component/menu/Contextmenu"
import ContextmenuItem from "@/component/menu/ContextmenuItem"
import ContextMenu from "@/component/menu/ContextMenu"
import EditDialog from "./component/EditDialog"
import OrgTreeView from "./component/OrgTreeView"
import ZoomControl from './component/ZoomControl'
import {add, update, del, get} from "@/api/system/department"
import {auth} from "@/util/auth"
import {wic} from "@/util/auth"
import {elConfirm, elError, elSuccess} from "@/util/message"
import {createTreeByWorker} from "@/util/tree"
export default {
name: "departmentManagement",
components: {Contextmenu, ContextmenuItem, EditDialog, OrgTreeView, ZoomControl},
components: {ContextMenu, EditDialog, OrgTreeView, ZoomControl},
data() {
return {
@ -85,18 +80,21 @@ export default {
},
computed: {
...wic({add, update, del}),
contextMenuItems() {
const items=[]
this.canAdd&&items.push({content:'添加部门',click:this.add})
this.canUpdate&&items.push({content:'编辑部门',click:this.update})
this.canDel&&items.push({content:'删除部门',click:this.del})
return items
},
realZoom() {
return this.zoom / 100
},
canAdd() {
return auth(add.url)
},
canUpdate() {
return auth(update.url)
},
canDel() {
return auth(del.url)
},
}
},
methods: {

@ -146,7 +146,7 @@
<script>
import treePageMixin from '@/mixin/treePageMixin'
import AbstractForm from "@/component/AbstractForm"
import AbstractForm from "@/component/abstract/Form"
import JsonEditor from "@/component/editor/JsonEditor"
import {add, update, del} from '@/api/system/resource'
import {isEmpty, mergeObj, resetObj} from "@/util"

@ -1,5 +1,5 @@
<template>
<form-dialog :loading="loading" :title="title" :value="value" width="40%" @close="cancel" @open="open">
<abstract-dialog :loading="loading" :title="title" :value="value" width="40%" @close="cancel" @open="open">
<abstract-form :model="form" :rules="rules" size="">
<el-form-item label="名 称" prop="name">
<el-input v-model="form.name" maxlength="20"/>
@ -58,13 +58,13 @@
<el-button plain size="small" @click="closeDialog"> </el-button>
<el-button size="small" type="primary" @click="confirm"> </el-button>
</template>
</form-dialog>
</abstract-dialog>
</template>
<script>
import dialogMixin from "@/mixin/dialogMixin"
import AbstractForm from "@/component/AbstractForm"
import FormDialog from '@/component/FormDialog'
import AbstractForm from "@/component/abstract/Form"
import AbstractDialog from '@/component/abstract/Dialog'
import {get as getDepartments} from "@/api/system/department"
import {add, update} from "@/api/system/role"
import {isEmpty, mergeObj} from '@/util'
@ -76,7 +76,7 @@ export default {
mixins: [dialogMixin],
components: {AbstractForm, FormDialog},
components: {AbstractForm, AbstractDialog},
props: {
value: {type: Boolean, default: false},

@ -57,8 +57,8 @@
<script>
import tableMixin from '@/mixin/tablePageMixin'
import EditDialog from './EditDialog'
import SearchForm from "@/component/SearchForm"
import SearchFormItem from "@/component/SearchForm/item"
import SearchForm from "@/component/form/Search"
import SearchFormItem from "@/component/form/Search/item"
import {add, update, del, search} from "@/api/system/role"
import {isEmpty} from '@/util'
import {wic} from "@/util/auth"

@ -1,5 +1,5 @@
<template>
<form-dialog :loading="loading" :title="title" :value="value" @close="cancel" @open="open">
<abstract-dialog :loading="loading" :title="title" :value="value" @close="cancel" @open="open">
<abstract-form :model="form" :rules="rules" size="">
<el-form-item label="名 称" prop="name">
<el-input v-model="form.name" :readonly="!canEdit" maxlength="20"/>
@ -43,13 +43,13 @@
<el-button plain size="small" @click="closeDialog"> </el-button>
<el-button v-if="canEdit" size="small" type="primary" @click="confirm"> </el-button>
</template>
</form-dialog>
</abstract-dialog>
</template>
<script>
import dialogMixin from "@/mixin/dialogMixin"
import AbstractForm from "@/component/AbstractForm"
import FormDialog from '@/component/FormDialog'
import AbstractForm from "@/component/abstract/Form"
import AbstractDialog from '@/component/abstract/Dialog'
import RegionSelector from '@/component/RegionSelector'
import {add, update} from "@/api/system/supplier"
import {isEmpty, mergeObj, resetObj} from '@/util'
@ -60,7 +60,7 @@ export default {
mixins: [dialogMixin],
components: {AbstractForm, FormDialog, RegionSelector},
components: {AbstractForm, AbstractDialog, RegionSelector},
props: {
value: {type: Boolean, default: false},

@ -77,8 +77,8 @@
import tableMixin from '@/mixin/tablePageMixin'
import EditDialog from './EditDialog'
import RegionSelector from "@/component/RegionSelector"
import SearchForm from "@/component/SearchForm"
import SearchFormItem from "@/component/SearchForm/item"
import SearchForm from "@/component/form/Search"
import SearchFormItem from "@/component/form/Search/item"
import {add, update, del, getLimitRegion, search} from "@/api/system/supplier"
import {isEmpty} from '@/util'
import {wic} from "@/util/auth"

@ -1,5 +1,5 @@
<template>
<form-dialog :loading="loading" :title="title" :value="value" @close="cancel" @open="open">
<abstract-dialog :loading="loading" :title="title" :value="value" @close="cancel" @open="open">
<abstract-form :model="form" :rules="rules" label-width="90px" size="">
<el-form-item label="登录名" prop="loginName">
<el-input v-model="form.loginName" :readonly="!!form.id || !canEdit" maxlength="20"/>
@ -35,14 +35,14 @@
<el-button plain size="small" @click="closeDialog"> </el-button>
<el-button v-if="canEdit" size="small" type="primary" @click="confirm"> </el-button>
</template>
</form-dialog>
</abstract-dialog>
</template>
<script>
import dialogMixin from "@/mixin/dialogMixin"
import AbstractForm from "@/component/AbstractForm"
import AbstractForm from "@/component/abstract/Form"
import DepartmentSelector from "./DepartmentSelector"
import FormDialog from '@/component/FormDialog'
import AbstractDialog from '@/component/abstract/Dialog'
import RoleSelector from './RoleSelector'
import {checkLoginName, checkNickName} from "@/api/account"
import {add, update} from "@/api/system/user"
@ -54,7 +54,7 @@ export default {
mixins: [dialogMixin],
components: {AbstractForm, DepartmentSelector, FormDialog, RoleSelector},
components: {AbstractForm, DepartmentSelector, AbstractDialog, RoleSelector},
props: {
value: {type: Boolean, default: false},

@ -86,8 +86,8 @@
import tableMixin from '@/mixin/tablePageMixin'
import EditDialog from './component/EditDialog'
import RoleSelector from './component/RoleSelector'
import SearchForm from "@/component/SearchForm"
import SearchFormItem from "@/component/SearchForm/item"
import SearchForm from "@/component/form/Search"
import SearchFormItem from "@/component/form/Search/item"
import {search, add, update, del, kick, resetPwd} from "@/api/system/user"
import {isEmpty} from '@/util'
import {wic} from "@/util/auth"

@ -1,5 +1,5 @@
<template>
<form-dialog :loading="loading" title="修改密码" :value="value" width="30%" @close="cancel">
<abstract-dialog :loading="loading" title="修改密码" :value="value" width="30%" @close="cancel">
<abstract-form :model="form" :rules="rules">
<el-form-item label="旧密码" prop="oldPwd">
<el-input v-model.trim="form.oldPwd" maxlength="100" type="password"/>
@ -13,21 +13,21 @@
<el-button plain size="small" @click="closeDialog"> </el-button>
<el-button size="small" type="primary" @click="confirm"> </el-button>
</template>
</form-dialog>
</abstract-dialog>
</template>
<script>
import md5 from "js-md5"
import dialogMixin from "@/mixin/dialogMixin"
import AbstractForm from "@/component/AbstractForm"
import FormDialog from '@/component/FormDialog'
import AbstractForm from "@/component/abstract/Form"
import AbstractDialog from '@/component/abstract/Dialog'
import {updateUserPwd} from "@/api/account"
import {elSuccess} from "@/util/message"
export default {
mixins: [dialogMixin],
components: {AbstractForm, FormDialog},
components: {AbstractForm, AbstractDialog},
data() {
return {

@ -1,5 +1,5 @@
<template>
<form-dialog :loading="loading" title="上传头像" :value="value" width="50%" @close="cancel">
<abstract-dialog :loading="loading" title="上传头像" :value="value" width="50%" @close="cancel">
<el-row class="avatar-cropper">
<div class="img-wrapper">
<vue-cropper
@ -30,12 +30,12 @@
<el-button plain size="small" @click="closeDialog"> </el-button>
<el-button size="small" type="primary" @click="confirm"> </el-button>
</template>
</form-dialog>
</abstract-dialog>
</template>
<script>
import {VueCropper} from 'vue-cropper'
import FormDialog from '@/component/FormDialog'
import AbstractDialog from '@/component/abstract/Dialog'
import dialogMixin from "@/mixin/dialogMixin"
import {elError, elSuccess} from "@/util/message"
import {autoCompleteUrl, upload} from "@/util/file"
@ -46,7 +46,7 @@ export default {
mixins: [dialogMixin],
components: {VueCropper, FormDialog},
components: {VueCropper, AbstractDialog},
props: {
value: Boolean

Loading…
Cancel
Save