为什么都没了
This commit is contained in:
192
framework/Web/src/components/authorized/index.js
Normal file
192
framework/Web/src/components/authorized/index.js
Normal file
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* auth: 允许的权限
|
||||
* authExclude: 排除的权限
|
||||
*
|
||||
* auth的几种传值方式
|
||||
* 1.String
|
||||
* 例: auth="sysApp:page"
|
||||
* 直接传入字符串,对单项权限进行验证
|
||||
*
|
||||
* 2.Array
|
||||
* 2.1.单项权限
|
||||
* 例: :auth="['sysApp:page']"
|
||||
* 2.2.并且关系多项权限
|
||||
* 例: :auth="['sysApp:page', 'sysApp:add']"
|
||||
* 数组中传入多个字符串
|
||||
* 此时验证的是同时拥有"sysApp:page"和"sysApp:add"两项权限才会渲染
|
||||
* 2.3.或者关系多项权限
|
||||
* 例: :auth="[['sysApp:page', 'sysApp:add'], ['sysApp:edit']]"
|
||||
* 二维数组结构,内部数组之间为并且关系
|
||||
* 此时验证的是"sysApp:page"&"sysApp:add"||"sysApp:edit"
|
||||
* 注意:或者的条件必须包括在数组中,暴露在外则判定为并且
|
||||
* 2.4.可直接传入布尔值
|
||||
* 例: :auth="['sysApp:page', 1 === 1]"
|
||||
* :auth="[['sysApp:page', 'sysApp:add'], [1 === 1]]"
|
||||
*
|
||||
* 3.Json
|
||||
* 如果觉得多项权限时每次都要写应用编号比较繁琐,可对Array形式进行简化
|
||||
* 3.1.单项权限
|
||||
* 例: :auth="{ sysApp: 'page' }"
|
||||
* 3.2.并且关系多项权限
|
||||
* 例: :auth="{ sysApp: ['page', 'add'] }"
|
||||
* 3.3.或者关系多项权限
|
||||
* 例: :auth="{ sysApp: [['page', 'add'], ['edit']]}"
|
||||
* 3.4.可直接传入布尔值
|
||||
* 例: :auth="{ sysApp: ['page', 1 === 1] }"
|
||||
* :auth="{ sysApp: [['page', 'add'], [1 === 1]] }"
|
||||
*
|
||||
*/
|
||||
|
||||
import app from '@/main'
|
||||
|
||||
const authByArray = (auth, permissions) => {
|
||||
|
||||
const flags = []
|
||||
|
||||
auth.forEach(p => {
|
||||
switch (p.constructor) {
|
||||
case String:
|
||||
flags.push([permissions.indexOf(p) > -1, '&&'])
|
||||
break
|
||||
case Array:
|
||||
flags.push([authByArray(p, permissions), '||'])
|
||||
break
|
||||
case Boolean:
|
||||
flags.push([p, '&&'])
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
let result
|
||||
|
||||
flags.forEach((p, i) => {
|
||||
if (p[1] === '&&') {
|
||||
if (i === 0) {
|
||||
result = true
|
||||
}
|
||||
if (result) {
|
||||
result = p[0]
|
||||
}
|
||||
} else {
|
||||
if (i === 0) {
|
||||
result = false
|
||||
}
|
||||
if (!result) {
|
||||
result = p[0]
|
||||
}
|
||||
}
|
||||
//result = p[1] === '&&' ? result && p[0] : result || p[0]
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const authByJson = (auth, permissions) => {
|
||||
|
||||
let result = true
|
||||
|
||||
const flags = []
|
||||
|
||||
const deepName = (arr, key) => {
|
||||
arr.forEach((p, i) => {
|
||||
switch (p.constructor) {
|
||||
case String:
|
||||
arr[i] = `${key}:${p}`
|
||||
break
|
||||
case Array:
|
||||
p = deepName(p, key)
|
||||
break
|
||||
default:
|
||||
p = p
|
||||
break
|
||||
}
|
||||
})
|
||||
return arr
|
||||
}
|
||||
|
||||
for (let key in auth) {
|
||||
const app = auth[key]
|
||||
switch (app.constructor) {
|
||||
case String:
|
||||
flags.push(permissions.indexOf(`${key}:${p}`) > -1)
|
||||
break
|
||||
case Array:
|
||||
flags.push(authByArray(deepName(app, key), permissions))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
flags.forEach(p => {
|
||||
result = result && p
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
export const auth = (auth) => {
|
||||
|
||||
const { info } = app.global
|
||||
|
||||
if (!info) {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 超级管理员
|
||||
*/
|
||||
if (info.adminType === 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
const permissions = info.permissions
|
||||
|
||||
let flag = false
|
||||
|
||||
if (auth) {
|
||||
switch (auth.constructor) {
|
||||
case String:
|
||||
flag = permissions.indexOf(auth) > -1
|
||||
break
|
||||
case Array:
|
||||
flag = authByArray(auth, permissions)
|
||||
break
|
||||
case Object:
|
||||
flag = authByJson(auth, permissions)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return flag
|
||||
}
|
||||
|
||||
export default {
|
||||
functional: true,
|
||||
props: {
|
||||
auth: {
|
||||
default() {
|
||||
return new Array()
|
||||
},
|
||||
type: [Array, Object, String],
|
||||
},
|
||||
authExclude: {
|
||||
default() {
|
||||
return new Array()
|
||||
},
|
||||
type: Array,
|
||||
},
|
||||
},
|
||||
|
||||
render(h, context) {
|
||||
const { props, scopedSlots } = context
|
||||
const authExclude = props.authExclude
|
||||
|
||||
let flag = auth(props.auth)
|
||||
|
||||
if (flag) {
|
||||
return scopedSlots.default && scopedSlots.default()
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
}
|
||||
23
framework/Web/src/components/container/index.vue
Normal file
23
framework/Web/src/components/container/index.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<section
|
||||
:class="{
|
||||
[mode || $root.global.settings.container || 'container-fluid']: true,
|
||||
'container-flex': flex
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
</section>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
},
|
||||
flex: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
56
framework/Web/src/components/photoSwipe/index.vue
Normal file
56
framework/Web/src/components/photoSwipe/index.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div aria-hidden="true" class="pswp" role="dialog" tabindex="-1">
|
||||
<div class="pswp__bg"></div>
|
||||
<div class="pswp__scroll-wrap">
|
||||
<div class="pswp__container">
|
||||
<div class="pswp__item"></div>
|
||||
<div class="pswp__item"></div>
|
||||
<div class="pswp__item"></div>
|
||||
</div>
|
||||
<div class="pswp__ui pswp__ui--hidden">
|
||||
<div class="pswp__top-bar">
|
||||
<div class="pswp__counter"></div>
|
||||
<button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
|
||||
<button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
|
||||
<button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
|
||||
<div class="pswp__preloader">
|
||||
<div class="pswp__preloader__icn">
|
||||
<div class="pswp__preloader__cut">
|
||||
<div class="pswp__preloader__donut"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
|
||||
<div class="pswp__share-tooltip"></div>
|
||||
</div>
|
||||
<button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)"></button>
|
||||
<button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"></button>
|
||||
<div class="pswp__caption">
|
||||
<div class="pswp__caption__center"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import 'photoswipe/dist/photoswipe.css';
|
||||
import 'photoswipe/dist/default-skin/default-skin.css';
|
||||
import PhotoSwipe from 'photoswipe';
|
||||
import PhotoSwipeUI_Default from 'photoswipe/dist/photoswipe-ui-default';
|
||||
|
||||
const defaultOptions = {
|
||||
index: 0,
|
||||
bgOpacity: 0.75,
|
||||
};
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
initPhotoSwipe(items = [], options) {
|
||||
const pswpElement = this.$el;
|
||||
const gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, Object.assign(defaultOptions, options));
|
||||
gallery.init();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
254
framework/Web/src/components/yoAuthorityView/index.js
Normal file
254
framework/Web/src/components/yoAuthorityView/index.js
Normal file
@@ -0,0 +1,254 @@
|
||||
export default {
|
||||
props: {
|
||||
loadData: {
|
||||
type: Function,
|
||||
require: true,
|
||||
},
|
||||
|
||||
autoLoad: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
||||
defaultSelectedKeys: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
|
||||
data: [],
|
||||
list: []
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.autoLoad) {
|
||||
this.onLoadData()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
renderDescriptions(data) {
|
||||
return data.map(p => {
|
||||
return p.children && p.children.length ? this.renderItem(p) : this.renderCheckbox(p)
|
||||
})
|
||||
},
|
||||
|
||||
renderItem(data) {
|
||||
return (
|
||||
<a-descriptions bordered column={1}>
|
||||
<a-descriptions-item>
|
||||
<a-checkbox
|
||||
slot="label"
|
||||
value={data.id}
|
||||
checked={data.checked}
|
||||
indeterminate={data.indeterminate}
|
||||
onChange={(e) => this.onChange(e, data)}
|
||||
>{data.title}</a-checkbox>
|
||||
{this.renderDescriptions(data.children)}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
)
|
||||
},
|
||||
|
||||
renderCheckbox(data) {
|
||||
return (
|
||||
<div class="yo-authority-view--checkbox">
|
||||
{data.visibleParent && data.type == 2 &&
|
||||
<a-tooltip placement="top" title="选中此项才会显示父节点">
|
||||
<a-icon type="eye" style={{ color: '#1890ff' }} class="mr-xxs" />
|
||||
</a-tooltip>
|
||||
}
|
||||
<a-checkbox
|
||||
value={data.id}
|
||||
checked={data.checked}
|
||||
onChange={(e) => this.onChange(e, data)}
|
||||
>
|
||||
{data.title}
|
||||
</a-checkbox>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
onLoadData() {
|
||||
this.loading = true
|
||||
|
||||
this.loadData().then((res) => {
|
||||
this.data = this.generateCheck(res)
|
||||
|
||||
this.list = []
|
||||
this.generateList(this.data)
|
||||
|
||||
if (this.defaultSelectedKeys.length) {
|
||||
this.list.map(p => {
|
||||
if (this.defaultSelectedKeys.indexOf(p.id) > -1 && (!p.children || !p.children.length)) {
|
||||
this.onSelect(true, p)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
onReloadData() {
|
||||
|
||||
this.data = []
|
||||
this.onLoadData()
|
||||
|
||||
},
|
||||
|
||||
onChange(e, item) {
|
||||
|
||||
this.onSelect(e.target.checked, item)
|
||||
|
||||
const visible = this.getVisible()
|
||||
|
||||
this.$emit('select',
|
||||
// 返回所有选中
|
||||
this.list.filter(p => p.checked).map(p => p.id),
|
||||
// 返回所有选中和半选
|
||||
this.list.filter(p => p.checked || p.indeterminate).map(p => p.id),
|
||||
// 返回所有选中和半选,但是不返回没有子级选中visibleParent的半选
|
||||
visible
|
||||
)
|
||||
},
|
||||
|
||||
onSelect(check, item) {
|
||||
item.checked = check
|
||||
item.indeterminate = false
|
||||
if (item.children && item.children.length) {
|
||||
this.onChangeChildren(item.checked, item.children)
|
||||
}
|
||||
if (item.parentId) {
|
||||
this.onChangeParent(item.checked, item.parentId)
|
||||
}
|
||||
},
|
||||
|
||||
onChangeParent(checked, parentId) {
|
||||
const parent = this.list.find(p => p.id === parentId)
|
||||
if (parent) {
|
||||
const checkedCount = parent.children.filter(p => p.checked).length
|
||||
const indeterminateCount = parent.children.filter(p => p.indeterminate).length
|
||||
if (checkedCount === parent.children.length) {
|
||||
// 全选
|
||||
parent.checked = true
|
||||
parent.indeterminate = false
|
||||
} else if (!checkedCount && !indeterminateCount) {
|
||||
// 全不选
|
||||
parent.checked = false
|
||||
parent.indeterminate = false
|
||||
} else {
|
||||
// 半选
|
||||
parent.checked = false
|
||||
parent.indeterminate = true
|
||||
}
|
||||
this.onChangeParent(checked, parent.parentId)
|
||||
}
|
||||
},
|
||||
|
||||
onChangeChildren(checked, children) {
|
||||
children.forEach(p => {
|
||||
p.checked = checked
|
||||
p.indeterminate = false
|
||||
if (p.children && p.children.length) {
|
||||
this.onChangeChildren(checked, p.children)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
generateCheck(data) {
|
||||
data.forEach(p => {
|
||||
if (p.children && p.children.length) {
|
||||
p.children = this.generateCheck(p.children)
|
||||
}
|
||||
p.checked = false
|
||||
p.indeterminate = false
|
||||
})
|
||||
|
||||
return data
|
||||
},
|
||||
|
||||
generateList(data) {
|
||||
data.forEach(p => {
|
||||
if (p.children && p.children.length) {
|
||||
this.generateList(p.children)
|
||||
}
|
||||
this.list.push(p)
|
||||
})
|
||||
},
|
||||
|
||||
getVisible() {
|
||||
const checked = this.list.filter(p => p.checked)
|
||||
const caseChildren = checked.filter(p => p.visibleParent || p.type != 2)
|
||||
const visibleParents = []
|
||||
// 递归寻找父级
|
||||
const findVisibleParents = (children) => {
|
||||
const parents = []
|
||||
children.forEach(item => {
|
||||
if (item.parentId) {
|
||||
const parent = this.list.find(p => p.id === item.parentId)
|
||||
if (parent) {
|
||||
parents.push(parent)
|
||||
visibleParents.push(parent)
|
||||
}
|
||||
}
|
||||
})
|
||||
if (parents.length) {
|
||||
findVisibleParents(parents)
|
||||
}
|
||||
}
|
||||
|
||||
findVisibleParents(caseChildren)
|
||||
|
||||
const checkedIds = checked.map(p => p.id)
|
||||
const visibleParentsIds = visibleParents.map(p => p.id)
|
||||
|
||||
const result = checkedIds
|
||||
visibleParentsIds.forEach(p => {
|
||||
if (result.indexOf(p) === -1) {
|
||||
result.push(p)
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="yo-authority-view">
|
||||
<a-spin style={{ width: '100%' }} spinning={this.loading}>
|
||||
<a-icon slot="indicator" type="loading" spin />
|
||||
{
|
||||
!this.loading &&
|
||||
<a-descriptions bordered column={1}>
|
||||
{
|
||||
this.data.map(p => {
|
||||
return (
|
||||
<a-descriptions-item>
|
||||
<a-checkbox
|
||||
slot="label"
|
||||
value={p.id}
|
||||
checked={p.checked}
|
||||
indeterminate={p.indeterminate}
|
||||
onChange={(e) => this.onChange(e, p)}
|
||||
>{p.title}</a-checkbox>
|
||||
{this.renderDescriptions(p.children)}
|
||||
</a-descriptions-item>
|
||||
)
|
||||
})
|
||||
}
|
||||
</a-descriptions>
|
||||
}
|
||||
</a-spin>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
26
framework/Web/src/components/yoFormLink/index.js
Normal file
26
framework/Web/src/components/yoFormLink/index.js
Normal file
@@ -0,0 +1,26 @@
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
content: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="yo-form-link">
|
||||
<div class="yo-form-link--title">
|
||||
{this.$scopedSlots.default ? this.$scopedSlots.default() : this.title}
|
||||
</div>
|
||||
<div class="yo-form-link--content">
|
||||
{this.$scopedSlots.content ? this.$scopedSlots.content() : this.content}
|
||||
</div>
|
||||
<a-icon type="right" class="yo-form-link--right-icon" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
32
framework/Web/src/components/yoIconSelector/icons.js
Normal file
32
framework/Web/src/components/yoIconSelector/icons.js
Normal file
@@ -0,0 +1,32 @@
|
||||
export default [
|
||||
{
|
||||
key: 'directional',
|
||||
title: '方向性图标',
|
||||
icons: ['step-backward', 'step-forward', 'fast-backward', 'fast-forward', 'shrink', 'arrows-alt', 'down', 'up', 'left', 'right', 'caret-up', 'caret-down', 'caret-left', 'caret-right', 'up-circle', 'down-circle', 'left-circle', 'right-circle', 'double-right', 'double-left', 'vertical-left', 'vertical-right', 'forward', 'backward', 'rollback', 'enter', 'retweet', 'swap', 'swap-left', 'swap-right', 'arrow-up', 'arrow-down', 'arrow-left', 'arrow-right', 'play-circle', 'up-square', 'down-square', 'left-square', 'right-square', 'login', 'logout', 'menu-fold', 'menu-unfold', 'border-bottom', 'border-horizontal', 'border-inner', 'border-left', 'border-right', 'border-top', 'border-verticle', 'pic-center', 'pic-left', 'pic-right', 'radius-bottomleft', 'radius-bottomright', 'radius-upleft', 'fullscreen', 'fullscreen-exit']
|
||||
},
|
||||
{
|
||||
key: 'suggested',
|
||||
title: '提示建议性图标',
|
||||
icons: ['question', 'question-circle', 'plus', 'plus-circle', 'pause', 'pause-circle', 'minus', 'minus-circle', 'plus-square', 'minus-square', 'info', 'info-circle', 'exclamation', 'exclamation-circle', 'close', 'close-circle', 'close-square', 'check', 'check-circle', 'check-square', 'clock-circle', 'warning', 'issues-close', 'stop']
|
||||
},
|
||||
{
|
||||
key: 'editor',
|
||||
title: '编辑类图标',
|
||||
icons: ['edit', 'form', 'copy', 'scissor', 'delete', 'snippets', 'diff', 'highlight', 'align-center', 'align-left', 'align-right', 'bg-colors', 'bold', 'italic', 'underline', 'strikethrough', 'redo', 'undo', 'zoom-in', 'zoom-out', 'font-colors', 'font-size', 'line-height', 'column-height', 'dash', 'small-dash', 'sort-ascending', 'sort-descending', 'drag', 'ordered-list', 'radius-setting']
|
||||
},
|
||||
{
|
||||
key: 'data',
|
||||
title: '数据类图标',
|
||||
icons: ['area-chart', 'pie-chart', 'bar-chart', 'dot-chart', 'line-chart', 'radar-chart', 'heat-map', 'fall', 'rise', 'stock', 'box-plot', 'fund', 'sliders']
|
||||
},
|
||||
{
|
||||
key: 'brand_logo',
|
||||
title: '网站通用图标',
|
||||
icons: ['lock', 'unlock', 'bars', 'book', 'calendar', 'cloud', 'cloud-download', 'code', 'copy', 'credit-card', 'delete', 'desktop', 'download', 'ellipsis', 'file', 'file-text', 'file-unknown', 'file-pdf', 'file-word', 'file-excel', 'file-jpg', 'file-ppt', 'file-markdown', 'file-add', 'folder', 'folder-open', 'folder-add', 'hdd', 'frown', 'meh', 'smile', 'inbox', 'laptop', 'appstore', 'link', 'mail', 'mobile', 'notification', 'paper-clip', 'picture', 'poweroff', 'reload', 'search', 'setting', 'share-alt', 'shopping-cart', 'tablet', 'tag', 'tags', 'to-top', 'upload', 'user', 'video-camera', 'home', 'loading', 'loading-3-quarters', 'cloud-upload', 'star', 'heart', 'environment', 'eye', 'camera', 'save', 'team', 'solution', 'phone', 'filter', 'exception', 'export', 'customer-service', 'qrcode', 'scan', 'like', 'dislike', 'message', 'pay-circle', 'calculator', 'pushpin', 'bulb', 'select', 'switcher', 'rocket', 'bell', 'disconnect', 'database', 'compass', 'barcode', 'hourglass', 'key', 'flag', 'layout', 'printer', 'sound', 'usb', 'skin', 'tool', 'sync', 'wifi', 'car', 'schedule', 'user-add', 'user-delete', 'usergroup-add', 'usergroup-delete', 'man', 'woman', 'shop', 'gift', 'idcard', 'medicine-box', 'red-envelope', 'coffee', 'copyright', 'trademark', 'safety', 'wallet', 'bank', 'trophy', 'contacts', 'global', 'shake', 'api', 'fork', 'dashboard', 'table', 'profile', 'alert', 'audit', 'branches', 'build', 'border', 'crown', 'experiment', 'fire', 'money-collect', 'property-safety', 'read', 'reconciliation', 'rest', 'security-scan', 'insurance', 'interaction', 'safety-certificate', 'project', 'thunderbolt', 'block', 'cluster', 'deployment-unit', 'dollar', 'euro', 'pound', 'file-done', 'file-exclamation', 'file-protect', 'file-search', 'file-sync', 'gateway', 'gold', 'robot', 'shopping']
|
||||
},
|
||||
{
|
||||
key: 'application',
|
||||
title: '品牌和标识',
|
||||
icons: ['android', 'apple', 'windows', 'ie', 'chrome', 'github', 'aliwangwang', 'dingding', 'weibo-square', 'weibo-circle', 'taobao-circle', 'html5', 'weibo', 'twitter', 'wechat', 'youtube', 'alipay-circle', 'taobao', 'skype', 'qq', 'medium-workmark', 'gitlab', 'medium', 'linkedin', 'google-plus', 'dropbox', 'facebook', 'codepen', 'code-sandbox', 'amazon', 'google', 'codepen-circle', 'alipay', 'ant-design', 'aliyun', 'zhihu', 'slack', 'slack-square', 'behance', 'behance-square', 'dribbble', 'dribbble-square', 'instagram', 'yuque', 'alibaba', 'yahoo']
|
||||
}
|
||||
]
|
||||
63
framework/Web/src/components/yoIconSelector/index.vue
Normal file
63
framework/Web/src/components/yoIconSelector/index.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<a-drawer :visible="visible" @close="onClose" class="yo-icon-selector" title="选择图标" width="600">
|
||||
<a-tabs tab-position="left" v-model="active">
|
||||
<a-tab-pane :key="iconGroup.key" :tab="iconGroup.title" v-for="iconGroup in icons">
|
||||
<a-card>
|
||||
<a-card-grid
|
||||
:class="{ 'yo-icon--selected': selected == icon }"
|
||||
:key="icon"
|
||||
@click="onSelectIcon(icon)"
|
||||
v-for="icon in iconGroup.icons"
|
||||
>
|
||||
<a-icon :style="{ fontSize: '36px' }" :type="icon" />
|
||||
<span>{{ icon }}</span>
|
||||
</a-card-grid>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script>
|
||||
import icons from './icons';
|
||||
|
||||
export default {
|
||||
model: {
|
||||
prop: 'selected',
|
||||
event: 'select',
|
||||
},
|
||||
props: {
|
||||
selected: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
|
||||
icons,
|
||||
|
||||
active: icons[0].key,
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onOpen() {
|
||||
this.visible = true;
|
||||
if (this.selected) {
|
||||
this.active = (icons.find((p) => p.icons.indexOf(this.selected) > -1) || icons[0]).key;
|
||||
} else {
|
||||
this.active = icons[0].key;
|
||||
}
|
||||
},
|
||||
|
||||
onClose() {
|
||||
this.visible = false;
|
||||
},
|
||||
|
||||
onSelectIcon(icon) {
|
||||
this.$emit('select', icon);
|
||||
this.onClose();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
47
framework/Web/src/components/yoImage/index.js
Normal file
47
framework/Web/src/components/yoImage/index.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { PreviewFileBase64 } from '@/util/file'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'image'
|
||||
},
|
||||
id: {
|
||||
type: [String, Number],
|
||||
require: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
src: ''
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
async id() {
|
||||
this.src = await this.getSrc()
|
||||
}
|
||||
},
|
||||
|
||||
async created() {
|
||||
if (this.id) {
|
||||
this.src = await this.getSrc()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
async getSrc() {
|
||||
const base64 = await PreviewFileBase64(this.id)
|
||||
return base64
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
render() {
|
||||
return this.type == 'avatar' ?
|
||||
<a-avatar src={this.src} {...{ props: this.$attrs }} />
|
||||
:
|
||||
<img src={this.src} {...{ props: this.$attrs }} />
|
||||
},
|
||||
}
|
||||
120
framework/Web/src/components/yoList/index.js
Normal file
120
framework/Web/src/components/yoList/index.js
Normal file
@@ -0,0 +1,120 @@
|
||||
export default {
|
||||
props: {
|
||||
pageIndex: {
|
||||
default: 1,
|
||||
type: Number,
|
||||
},
|
||||
pageSize: {
|
||||
default: 10,
|
||||
type: Number,
|
||||
},
|
||||
loadData: {
|
||||
type: Function,
|
||||
require: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
|
||||
data: [],
|
||||
|
||||
pagination: {
|
||||
current: this.pageIndex,
|
||||
pageSize: this.pageSize,
|
||||
total: 0,
|
||||
size: 'small',
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `总共${total}条数据`
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.onLoadData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
onLoading() {
|
||||
this.loading = true
|
||||
},
|
||||
|
||||
onLoaded() {
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
onLoadData() {
|
||||
this.onLoading()
|
||||
|
||||
this.loadData({
|
||||
pageIndex: this.pagination.current,
|
||||
pageSize: this.pagination.pageSize,
|
||||
...this.sorter
|
||||
}).then((res) => {
|
||||
this.data = res.rows || res.data || res.items
|
||||
this.pagination.total = res.totalCount
|
||||
this.onLoaded()
|
||||
})
|
||||
},
|
||||
|
||||
onReloadData(refresh = false) {
|
||||
if (refresh && refresh.constructor === Boolean && this.pagination.constructor === Object) {
|
||||
this.pagination.current = this.pageIndex
|
||||
this.pagination.pageSize = this.pageSize
|
||||
}
|
||||
this.onLoadData()
|
||||
},
|
||||
|
||||
onListChange(current, pageSize) {
|
||||
this.pagination.current = current
|
||||
this.pagination.pageSize = pageSize
|
||||
this.onLoadData()
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
render() {
|
||||
const props = {
|
||||
dataSource: this.data,
|
||||
rowKey: record => record.id,
|
||||
...this.$attrs
|
||||
}
|
||||
|
||||
const on = {}
|
||||
return (
|
||||
<section>
|
||||
<div class="yo-action-bar">
|
||||
<div class="yo-action-bar--actions">
|
||||
{this.$scopedSlots.operator && this.$scopedSlots.operator()}
|
||||
</div>
|
||||
<div class="yo-action-bar--actions">
|
||||
<a-button-group>
|
||||
<a-button onClick={this.onReloadData}>刷新</a-button>
|
||||
</a-button-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="yo-list">
|
||||
<a-spin spinning={this.loading}>
|
||||
<a-icon slot="indicator" type="loading" spin />
|
||||
<a-list {...{ props, on, scopedSlots: { ...this.$scopedSlots } }}>
|
||||
{Object.keys(this.$slots).map((name) => (
|
||||
<template slot={name}>{this.$slots[name]}</template>
|
||||
))}
|
||||
</a-list>
|
||||
{
|
||||
!!this.data && !!this.data.length && <a-pagination
|
||||
size="small"
|
||||
{... { props: this.pagination }}
|
||||
onChange={this.onListChange}
|
||||
onShowSizeChange={this.onListChange}
|
||||
/>
|
||||
}
|
||||
</a-spin>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
},
|
||||
}
|
||||
155
framework/Web/src/components/yoModalForm/index.js
Normal file
155
framework/Web/src/components/yoModalForm/index.js
Normal file
@@ -0,0 +1,155 @@
|
||||
export default {
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'modal',
|
||||
validator: function (value) {
|
||||
return ['modal', 'drawer'].indexOf(value) > -1
|
||||
}
|
||||
},
|
||||
compareOnClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
action: {
|
||||
type: Function
|
||||
},
|
||||
successMessage: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
confirmLoading: false,
|
||||
|
||||
form: {}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
body() {
|
||||
return this.$slots.default && this.$slots.default[0].componentInstance
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
},
|
||||
|
||||
methods: {
|
||||
renderModal(props) {
|
||||
|
||||
const _props = {
|
||||
...props,
|
||||
confirmLoading: this.confirmLoading
|
||||
}
|
||||
|
||||
const _on = {
|
||||
cancel: () => this.onClose(this.compareOnClose),
|
||||
ok: () => this.onOk()
|
||||
}
|
||||
|
||||
return (<a-modal {...{ props: _props, on: _on }} class="yo-modal-form">
|
||||
{this.renderBody()}
|
||||
</a-modal>)
|
||||
},
|
||||
|
||||
renderDrawer(props) {
|
||||
|
||||
const _props = {
|
||||
...props
|
||||
}
|
||||
|
||||
const _on = {
|
||||
close: () => this.onClose(this.compareOnClose),
|
||||
ok: () => this.onOk()
|
||||
}
|
||||
|
||||
return (<a-drawer {...{ props: _props, on: _on }} class="yo-drawer-form">
|
||||
<div class="yo-drawer-form--body">
|
||||
{this.renderBody()}
|
||||
</div>
|
||||
<div class="ant-drawer-footer">
|
||||
<a-button onClick={_on.close}>取消</a-button>
|
||||
<a-button loading={this.confirmLoading} onClick={_on.ok} type="primary">确定</a-button>
|
||||
</div >
|
||||
</a-drawer>)
|
||||
},
|
||||
|
||||
renderBody() {
|
||||
return this.$scopedSlots.default && this.$scopedSlots.default()
|
||||
},
|
||||
|
||||
onOpen(data) {
|
||||
this.visible = true
|
||||
this.$nextTick(async () => {
|
||||
if (!this.body) return
|
||||
|
||||
this.body.onInit && await this.body.onInit(data)
|
||||
this.body.onFillData && this.body.onFillData(data)
|
||||
this.form = this.$_.cloneDeep(this.body.form)
|
||||
})
|
||||
},
|
||||
|
||||
onClose(compare = false) {
|
||||
const close = () => {
|
||||
this.body
|
||||
&& this.body.onResetFields
|
||||
&& this.body.onResetFields()
|
||||
this.visible = false
|
||||
}
|
||||
|
||||
if (this.body) {
|
||||
if (!this.$_.isEqual(this.form, this.body.form) && compare) {
|
||||
this.$confirm({
|
||||
title: '是否确认关闭',
|
||||
content: '当前内容已更改,是否确认不保存并且关闭',
|
||||
onOk: () => {
|
||||
close()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
close()
|
||||
}
|
||||
} else {
|
||||
close()
|
||||
}
|
||||
},
|
||||
|
||||
onOk() {
|
||||
this.body
|
||||
&& this.body.onGetData
|
||||
&& this.body.onGetData()
|
||||
.then((data) => {
|
||||
if (this.action) {
|
||||
this.confirmLoading = true
|
||||
this.action(data)
|
||||
.then(({ success }) => {
|
||||
if (success) {
|
||||
this.$message.success(this.successMessage || '保存成功')
|
||||
this.onClose();
|
||||
this.$emit('ok', data)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.confirmLoading = false
|
||||
})
|
||||
} else {
|
||||
this.onClose()
|
||||
this.$emit('ok', data)
|
||||
}
|
||||
}).catch(() => { })
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
const props = {
|
||||
...this.$props,
|
||||
...this.$attrs,
|
||||
visible: this.visible
|
||||
}
|
||||
|
||||
return this.type === 'modal' ? this.renderModal(props) : this.renderDrawer(props)
|
||||
}
|
||||
}
|
||||
64
framework/Web/src/components/yoTable/column.vue
Normal file
64
framework/Web/src/components/yoTable/column.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<a-dropdown :trigger="['click']" placement="bottomRight" v-model="visible">
|
||||
<a-button>显示列</a-button>
|
||||
<a-menu @click="() => { return false; }" class="yo-table--column-setting" slot="overlay">
|
||||
<a-menu-item>
|
||||
<a-checkbox :checked="checkedAll" :indeterminate="halfChecked" @change="onCheckAll">全选</a-checkbox>
|
||||
</a-menu-item>
|
||||
<a-menu-divider />
|
||||
<a-menu-item :key="column.dataIndex || column.key" v-for="column in columns">
|
||||
<a-checkbox :checked="!column.hidden" @change="(e) => onCheck(column, e)">{{column.title}}</a-checkbox>
|
||||
<a-icon
|
||||
:class="{ 'yo-table--fixed': column.fixed }"
|
||||
@click="onFixed(column)"
|
||||
type="pushpin"
|
||||
/>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
columns: {
|
||||
type: Array,
|
||||
require: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
|
||||
checkedAll: true,
|
||||
halfChecked: false,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.onHalfCheck();
|
||||
},
|
||||
|
||||
methods: {
|
||||
onHalfCheck() {
|
||||
this.halfChecked = this.columns.filter((p) => p.hidden).length > 0;
|
||||
},
|
||||
|
||||
onCheck(column, e) {
|
||||
this.$set(column, 'hidden', !e.target.checked);
|
||||
this.onHalfCheck();
|
||||
},
|
||||
|
||||
onCheckAll(e) {
|
||||
this.columns.forEach((column) => {
|
||||
this.$set(column, 'hidden', !e.target.checked);
|
||||
});
|
||||
this.checkedAll = e.target.checked;
|
||||
},
|
||||
|
||||
onFixed(column) {
|
||||
this.$set(column, 'fixed', !column.fixed);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
250
framework/Web/src/components/yoTable/index.js
Normal file
250
framework/Web/src/components/yoTable/index.js
Normal file
@@ -0,0 +1,250 @@
|
||||
// 列设置用jsx实现起来较为困难
|
||||
import ColumnSetting from './column'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
pageIndex: {
|
||||
default: 1,
|
||||
type: Number,
|
||||
},
|
||||
pageSize: {
|
||||
default: 10,
|
||||
type: Number,
|
||||
},
|
||||
loadData: {
|
||||
type: Function,
|
||||
require: true,
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
require: true,
|
||||
},
|
||||
moreQuery: {
|
||||
type: Function
|
||||
},
|
||||
autoLoad: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
|
||||
type: '',
|
||||
|
||||
data: [],
|
||||
|
||||
pagination: {
|
||||
current: this.pageIndex,
|
||||
pageSize: this.pageSize,
|
||||
total: 0,
|
||||
size: 'small',
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `总共${total}条数据`
|
||||
},
|
||||
|
||||
sorter: {
|
||||
sortField: '',
|
||||
sortOrder: '',
|
||||
},
|
||||
|
||||
columnSettingVisible: false
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
if (this.autoLoad)
|
||||
this.onLoadData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
renderColumnSetting() {
|
||||
|
||||
const props = {
|
||||
visible: this.columnSettingVisible,
|
||||
placement: 'bottomRight'
|
||||
}
|
||||
|
||||
const on = {
|
||||
visibleChange: (visible) => {
|
||||
this.columnSettingVisible = visible
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<a-dropdown {...{ props, on }}>
|
||||
<a-button onClick={() => this.columnSettingVisible = true}>设置列</a-button>
|
||||
<a-menu slot="overlay" onClick={() => { return false }}>
|
||||
{
|
||||
this.columns.map(column => {
|
||||
return (
|
||||
<a-menu-item key={column.dataIndex || column.key}>
|
||||
<a-checkbox checked={column.hidden} onChange={() => { column.hidden = !column.hidden }}>{column.title}</a-checkbox>
|
||||
</a-menu-item>
|
||||
)
|
||||
})
|
||||
}
|
||||
</a-menu>
|
||||
</a-dropdown>
|
||||
)
|
||||
},
|
||||
|
||||
onLoading() {
|
||||
this.loading = {
|
||||
indicator: <a-icon type="loading" spin />
|
||||
}
|
||||
},
|
||||
|
||||
onLoaded() {
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
onLoadData() {
|
||||
this.onLoading()
|
||||
|
||||
this.loadData({
|
||||
pageIndex: this.pagination.current,
|
||||
pageSize: this.pagination.pageSize,
|
||||
...this.sorter
|
||||
}).then((res) => {
|
||||
if (res.rows || res.data || res.items) {
|
||||
// 普通表格
|
||||
this.type = 'table'
|
||||
this.data = res.rows || res.data || res.items
|
||||
this.pagination.total = res.totalCount
|
||||
} else if (res) {
|
||||
// 树形表格
|
||||
this.type = 'tree'
|
||||
this.data = this.onClearChildren(res)
|
||||
this.pagination = false
|
||||
}
|
||||
}).finally(() => {
|
||||
this.onLoaded()
|
||||
})
|
||||
},
|
||||
|
||||
onReloadData(refresh = false) {
|
||||
if (refresh && refresh.constructor === Boolean && this.pagination.constructor === Object) {
|
||||
this.pagination.current = this.pageIndex
|
||||
this.pagination.pageSize = this.pageSize
|
||||
}
|
||||
this.onLoadData()
|
||||
},
|
||||
|
||||
onTableChange(pagination, filters, sorter) {
|
||||
this.pagination = pagination
|
||||
this.sorter = {
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
}
|
||||
this.onLoadData()
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除没有子节点内容的子节点位置
|
||||
*/
|
||||
onClearChildren(data) {
|
||||
data.forEach(p => {
|
||||
if (p.children) {
|
||||
if (p.children.length) {
|
||||
p.children = this.onClearChildren(p.children)
|
||||
} else {
|
||||
delete p.children
|
||||
}
|
||||
}
|
||||
})
|
||||
return data
|
||||
},
|
||||
|
||||
onQuery() {
|
||||
this.$emit('query')
|
||||
},
|
||||
|
||||
onResetQuery() {
|
||||
this.$emit('resetQuery')
|
||||
this.$emit('reset-query')
|
||||
},
|
||||
|
||||
onAddRow(row = {}) {
|
||||
if (!this.data.find(p => !p.id))
|
||||
this.data.push(row)
|
||||
},
|
||||
|
||||
onDeleteRow(row) {
|
||||
if (row && this.data.indexOf(row) > -1) {
|
||||
this.data.splice(this.data.indexOf(row), 1)
|
||||
}
|
||||
},
|
||||
|
||||
getData() {
|
||||
return this.data
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
const props = {
|
||||
loading: this.loading,
|
||||
pagination: this.pagination,
|
||||
dataSource: this.data,
|
||||
columns: this.columns.filter(p => !p.hidden),
|
||||
bordered: true,
|
||||
size: 'middle',
|
||||
rowKey: record => record.id || Math.random().toString(16).slice(2),
|
||||
scroll: { x: 'max-content' },
|
||||
...this.$attrs
|
||||
}
|
||||
|
||||
const on = {
|
||||
change: this.onTableChange,
|
||||
...this.$listeners
|
||||
}
|
||||
|
||||
const queryOn = {
|
||||
'submit.native.prevent': () => { }
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
{
|
||||
this.$scopedSlots.query &&
|
||||
<div class="yo-query-bar">
|
||||
<a-form-model layout="inline" {...{ on: queryOn }}>
|
||||
{this.$scopedSlots.query()}
|
||||
<a-form-model-item>
|
||||
<a-button-group class="mr-xs">
|
||||
<a-button onClick={this.onQuery} html-type="submit" type="primary">查询</a-button>
|
||||
<a-button onClick={this.onResetQuery}>重置</a-button>
|
||||
</a-button-group>
|
||||
{
|
||||
this.moreQuery && <a-button onClick={this.moreQuery}>更多查询条件</a-button>
|
||||
}
|
||||
</a-form-model-item>
|
||||
</a-form-model>
|
||||
</div>
|
||||
}
|
||||
<div class="yo-action-bar">
|
||||
<div class="yo-action-bar--actions">
|
||||
{this.$scopedSlots.operator && this.$scopedSlots.operator()}
|
||||
</div>
|
||||
<div class="yo-action-bar--actions">
|
||||
<a-button-group>
|
||||
<a-button onClick={this.onReloadData}>刷新</a-button>
|
||||
{
|
||||
this.type === 'table' && <ColumnSetting {...{ props: { columns: this.columns } }} />
|
||||
}
|
||||
</a-button-group>
|
||||
</div>
|
||||
</div>
|
||||
<a-table class="yo-table" {...{ props, on, scopedSlots: { ...this.$scopedSlots } }}>
|
||||
{Object.keys(this.$slots).map((name) => (
|
||||
<template slot={name}>{this.$slots[name]}</template>
|
||||
))}
|
||||
</a-table>
|
||||
</section>
|
||||
)
|
||||
},
|
||||
}
|
||||
23
framework/Web/src/components/yoTableActions/index.js
Normal file
23
framework/Web/src/components/yoTableActions/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
export default {
|
||||
render() {
|
||||
|
||||
const components = []
|
||||
|
||||
const slots = this.$slots.default.filter(p => p.tag)
|
||||
|
||||
slots.forEach((p, i) => {
|
||||
components.push(p)
|
||||
if (i < slots.length - 1) {
|
||||
components.push(<a-divider type="vertical" />)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div class="yo-table-actions">
|
||||
<div class="yo-table-actions--inner">
|
||||
{components}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
256
framework/Web/src/components/yoTreeLayout/index.js
Normal file
256
framework/Web/src/components/yoTreeLayout/index.js
Normal file
@@ -0,0 +1,256 @@
|
||||
let timer
|
||||
|
||||
export default {
|
||||
props: {
|
||||
loadData: {
|
||||
type: Function,
|
||||
require: true,
|
||||
},
|
||||
|
||||
defaultExpandedKeys: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
|
||||
replaceFields: {
|
||||
default() {
|
||||
return {
|
||||
value: 'id',
|
||||
title: 'title',
|
||||
children: 'children'
|
||||
}
|
||||
},
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
|
||||
data: [],
|
||||
list: [],
|
||||
|
||||
searchValue: '',
|
||||
|
||||
selectedKeys: [],
|
||||
expandedKeys: [],
|
||||
autoExpandParent: true
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.onLoadData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
renderBreadcrumbItem() {
|
||||
|
||||
const path = ['顶级']
|
||||
|
||||
const findPath = (data, level) => {
|
||||
level = level || 1
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const item = data[i]
|
||||
|
||||
path[level] = item[this.replaceFields.title]
|
||||
|
||||
if (item[this.replaceFields.value] === this.selectedKeys[0]) {
|
||||
path.length = level + 1
|
||||
return true
|
||||
}
|
||||
|
||||
if (item[this.replaceFields.children] && item[this.replaceFields.children].length) {
|
||||
const found = findPath(item[this.replaceFields.children], level + 1)
|
||||
if (found) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.selectedKeys.length) {
|
||||
findPath(this.data)
|
||||
}
|
||||
|
||||
return path.map(p => (
|
||||
<a-breadcrumb-item>{p}</a-breadcrumb-item>
|
||||
))
|
||||
},
|
||||
|
||||
onLoadData() {
|
||||
this.loading = true
|
||||
|
||||
this.loadData().then((res) => {
|
||||
const data = this.generateKey(res)
|
||||
this.list = []
|
||||
this.generateList(data)
|
||||
if (this.defaultExpandedKeys) {
|
||||
this.expandedKeys = this.list.map(p => p.key)
|
||||
}
|
||||
this.data = data
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
onReloadData() {
|
||||
this.onLoadData()
|
||||
},
|
||||
|
||||
onExpand(expandedKeys) {
|
||||
this.expandedKeys = expandedKeys
|
||||
this.autoExpandParent = false
|
||||
},
|
||||
|
||||
onUnexpandAll() {
|
||||
this.expandedKeys = []
|
||||
},
|
||||
|
||||
onSearch(value) {
|
||||
const expandedKeys = this.list
|
||||
.map(p => {
|
||||
if (p[this.replaceFields.title].indexOf(value) > -1) {
|
||||
return this.getParentKey(p.key, this.data)
|
||||
}
|
||||
return null
|
||||
})
|
||||
.filter((p, i, self) => p && self.indexOf(p) === i)
|
||||
|
||||
this.searchValue = value
|
||||
this.expandedKeys = expandedKeys
|
||||
this.autoExpandParent = true
|
||||
},
|
||||
|
||||
onSelect(selectedKeys) {
|
||||
const selectedIds = []
|
||||
selectedKeys.forEach(p => {
|
||||
const data = this.list.find(m => m.key === p)
|
||||
selectedIds.push(data[this.replaceFields.value])
|
||||
})
|
||||
this.selectedKeys = selectedIds
|
||||
this.$emit('select', selectedIds)
|
||||
},
|
||||
|
||||
generateKey(data, level) {
|
||||
const n = level || [0]
|
||||
n.push(0)
|
||||
data.forEach((p, i) => {
|
||||
n[n.length - 1] = i
|
||||
p.key = n.join('-')
|
||||
p.scopedSlots = { title: 'title' }
|
||||
if (p[this.replaceFields.children]) {
|
||||
this.generateKey(p[this.replaceFields.children], Object.assign([], n))
|
||||
}
|
||||
})
|
||||
return data
|
||||
},
|
||||
|
||||
generateList(data) {
|
||||
// 这里获取不到Key
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const { key } = data[i]
|
||||
this.list.push({
|
||||
key,
|
||||
[this.replaceFields.value]: data[i][this.replaceFields.value],
|
||||
[this.replaceFields.title]: data[i][this.replaceFields.title]
|
||||
})
|
||||
if (data[i][this.replaceFields.children]) {
|
||||
this.generateList(data[i][this.replaceFields.children])
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getParentKey(key, tree) {
|
||||
let parentKey;
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
const node = tree[i]
|
||||
if (node[this.replaceFields.children]) {
|
||||
if (node[this.replaceFields.children].some(item => item.key === key)) {
|
||||
parentKey = node.key
|
||||
} else if (this.getParentKey(key, node[this.replaceFields.children])) {
|
||||
parentKey = this.getParentKey(key, node[this.replaceFields.children])
|
||||
}
|
||||
}
|
||||
}
|
||||
return parentKey;
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
|
||||
const props = {
|
||||
treeData: this.data,
|
||||
expandedKeys: this.expandedKeys,
|
||||
autoExpandParent: this.autoExpandParent,
|
||||
}
|
||||
|
||||
const on = {
|
||||
expand: this.onExpand,
|
||||
select: this.onSelect
|
||||
}
|
||||
|
||||
const scopedSlots = {
|
||||
title: (props) => {
|
||||
const title = props[this.replaceFields.title]
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
title.indexOf(this.searchValue) > -1 ?
|
||||
<span>
|
||||
{title.substr(0, title.indexOf(this.searchValue))}
|
||||
<span style="color: #f50">{this.searchValue}</span>
|
||||
{title.substr(title.indexOf(this.searchValue) + this.searchValue.length)}
|
||||
</span>
|
||||
:
|
||||
<span>{title}</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<a-layout class="yo-tree-layout">
|
||||
<a-layout-sider width="240px">
|
||||
<a-layout-header>
|
||||
<div class="header-actions">
|
||||
<a-input-search allowClear={true} placeholder="请输入检索关键字" onSearch={this.onSearch} />
|
||||
</div>
|
||||
</a-layout-header>
|
||||
<div class="yo-tree-layout--bar">
|
||||
<a-tooltip placement="bottom" title="折叠全部">
|
||||
<a-icon type="switcher" onClick={this.onUnexpandAll} />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="yo-tree-layout--content">
|
||||
<a-spin style={{ height: '100%' }} spinning={this.loading}>
|
||||
<a-icon slot="indicator" type="loading" spin />
|
||||
{
|
||||
!this.loading && !this.list.length ?
|
||||
<a-empty description={false} class="ant-list-empty-text">
|
||||
<template slot="image">
|
||||
<a-icon class="h3 mt-xl mb-md" type="smile" />
|
||||
<p>暂无数据</p>
|
||||
</template>
|
||||
</a-empty>
|
||||
:
|
||||
<a-tree {...{ props, on, scopedSlots }} />
|
||||
}
|
||||
</a-spin>
|
||||
</div>
|
||||
</a-layout-sider>
|
||||
<a-layout-content>
|
||||
<container>
|
||||
<a-breadcrumb class="mt-md mb-md">
|
||||
{this.renderBreadcrumbItem()}
|
||||
</a-breadcrumb>
|
||||
</container>
|
||||
{this.$scopedSlots.default ? this.$scopedSlots.default() : null}
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user