This commit is contained in:
192
Web/src/components/authorized/index.js
Normal file
192
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()
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
}
|
||||
5
Web/src/components/container/index.vue
Normal file
5
Web/src/components/container/index.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<section :class="$root.global.settings.container || 'container-fluid'">
|
||||
<slot />
|
||||
</section>
|
||||
</template>
|
||||
202
Web/src/components/yoAuthorityView/index.js
Normal file
202
Web/src/components/yoAuthorityView/index.js
Normal file
@@ -0,0 +1,202 @@
|
||||
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">
|
||||
<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)
|
||||
|
||||
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))
|
||||
},
|
||||
|
||||
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)
|
||||
})
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
107
Web/src/components/yoList/index.js
Normal file
107
Web/src/components/yoList/index.js
Normal file
@@ -0,0 +1,107 @@
|
||||
export default {
|
||||
props: {
|
||||
pageNo: {
|
||||
default: 1,
|
||||
type: Number,
|
||||
},
|
||||
pageSize: {
|
||||
default: 10,
|
||||
type: Number,
|
||||
},
|
||||
loadData: {
|
||||
type: Function,
|
||||
require: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
|
||||
data: [],
|
||||
|
||||
pagination: {
|
||||
current: this.pageNo,
|
||||
pageSize: this.pageSize,
|
||||
total: 0,
|
||||
size: 'small',
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `总共${total}条数据`
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.onLoadData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
onLoading() {
|
||||
this.loading = {
|
||||
indicator: <a-icon type="loading" spin />
|
||||
}
|
||||
},
|
||||
|
||||
onLoaded() {
|
||||
this.loading = false
|
||||
},
|
||||
|
||||
onLoadData() {
|
||||
this.onLoading()
|
||||
|
||||
this.loadData({
|
||||
pageNo: this.pagination.current,
|
||||
pageSize: this.pagination.pageSize,
|
||||
...this.sorter
|
||||
}).then((res) => {
|
||||
this.data = res.rows
|
||||
this.pagination.total = res.totalRows
|
||||
this.onLoaded()
|
||||
})
|
||||
},
|
||||
|
||||
onReloadData(refresh = false) {
|
||||
if (refresh && refresh.constructor === Boolean && this.pagination.constructor === Object) {
|
||||
this.pagination.current = this.pageNo
|
||||
this.pagination.pageSize = this.pageSize
|
||||
}
|
||||
this.onLoadData()
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
render() {
|
||||
const props = {
|
||||
loading: this.loading,
|
||||
pagination: this.pagination,
|
||||
dataSource: this.data,
|
||||
rowKey: record => record.id,
|
||||
...this.$attrs
|
||||
}
|
||||
|
||||
const on = {
|
||||
//change: this.onTableChange
|
||||
}
|
||||
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>
|
||||
<a-list {...{ props, on, scopedSlots: { ...this.$scopedSlots } }}>
|
||||
{Object.keys(this.$slots).map((name) => (
|
||||
<template slot={name}>{this.$slots[name]}</template>
|
||||
))}
|
||||
</a-list>
|
||||
</section>
|
||||
)
|
||||
},
|
||||
}
|
||||
64
Web/src/components/yoTable/column.vue
Normal file
64
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>
|
||||
198
Web/src/components/yoTable/index.js
Normal file
198
Web/src/components/yoTable/index.js
Normal file
@@ -0,0 +1,198 @@
|
||||
// 列设置用jsx实现起来较为困难
|
||||
import ColumnSetting from './column'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
pageNo: {
|
||||
default: 1,
|
||||
type: Number,
|
||||
},
|
||||
pageSize: {
|
||||
default: 10,
|
||||
type: Number,
|
||||
},
|
||||
loadData: {
|
||||
type: Function,
|
||||
require: true,
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
require: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
|
||||
type: '',
|
||||
|
||||
data: [],
|
||||
|
||||
pagination: {
|
||||
current: this.pageNo,
|
||||
pageSize: this.pageSize,
|
||||
total: 0,
|
||||
size: 'small',
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `总共${total}条数据`
|
||||
},
|
||||
|
||||
sorter: {
|
||||
sortField: '',
|
||||
sortOrder: '',
|
||||
},
|
||||
|
||||
columnSettingVisible: false
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
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({
|
||||
pageNo: this.pagination.current,
|
||||
pageSize: this.pagination.pageSize,
|
||||
...this.sorter
|
||||
}).then((res) => {
|
||||
if (res.rows) {
|
||||
// 普通表格
|
||||
this.type = 'table'
|
||||
this.data = res.rows
|
||||
this.pagination.total = res.totalRows
|
||||
} else if (res) {
|
||||
// 树形表格
|
||||
this.type = 'tree'
|
||||
this.data = this.onClearChildren(res)
|
||||
this.pagination = false
|
||||
}
|
||||
this.onLoaded()
|
||||
})
|
||||
},
|
||||
|
||||
onReloadData(refresh = false) {
|
||||
if (refresh && refresh.constructor === Boolean && this.pagination.constructor === Object) {
|
||||
this.pagination.current = this.pageNo
|
||||
this.pagination.pageSize = this.pageSize
|
||||
}
|
||||
this.onLoadData()
|
||||
},
|
||||
|
||||
onTableChange(pagination, filters, sorter) {
|
||||
this.pagination = pagination
|
||||
this.sorter = sorter
|
||||
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
|
||||
},
|
||||
},
|
||||
|
||||
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,
|
||||
scroll: { x: 'max-content' }
|
||||
}
|
||||
|
||||
const on = {
|
||||
change: this.onTableChange
|
||||
}
|
||||
return (
|
||||
<section>
|
||||
<a-alert type="warning" closable>
|
||||
<template slot="message">
|
||||
后端没有排序参数
|
||||
<br />
|
||||
字段固定应该遵循左侧固定到最左,右侧固定到最右(此逻辑难以实现)
|
||||
</template>
|
||||
</a-alert>
|
||||
<br />
|
||||
<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
Web/src/components/yoTableActions/index.js
Normal file
23
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
188
Web/src/components/yoTreeLayout/index.js
Normal file
188
Web/src/components/yoTreeLayout/index.js
Normal file
@@ -0,0 +1,188 @@
|
||||
export default {
|
||||
props: {
|
||||
loadData: {
|
||||
type: Function,
|
||||
require: true,
|
||||
},
|
||||
|
||||
defaultExpandedKeys: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
|
||||
data: [],
|
||||
list: [],
|
||||
|
||||
searchValue: '',
|
||||
|
||||
expandedKeys: [],
|
||||
autoExpandParent: true
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.onLoadData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
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
|
||||
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
onReloadData() {
|
||||
this.onLoadData()
|
||||
},
|
||||
|
||||
onExpand(expandedKeys) {
|
||||
this.expandedKeys = expandedKeys;
|
||||
this.autoExpandParent = false;
|
||||
},
|
||||
|
||||
onSearch(value) {
|
||||
const expandedKeys = this.list
|
||||
.map(p => {
|
||||
if (p.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.id)
|
||||
})
|
||||
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.children) {
|
||||
this.generateKey(p.children, Object.assign([], n))
|
||||
}
|
||||
})
|
||||
return data
|
||||
},
|
||||
|
||||
generateList(data) {
|
||||
// 这里获取不到Key
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const { key, id, title, children } = data[i]
|
||||
this.list.push({ key, id, title });
|
||||
if (children) {
|
||||
this.generateList(children);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getParentKey(key, tree) {
|
||||
let parentKey;
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
const node = tree[i];
|
||||
if (node.children) {
|
||||
if (node.children.some(item => item.key === key)) {
|
||||
parentKey = node.key;
|
||||
} else if (this.getParentKey(key, node.children)) {
|
||||
parentKey = this.getParentKey(key, node.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
return parentKey;
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
|
||||
const swiperOptions = {
|
||||
direction: 'vertical',
|
||||
slidesPerView: 'auto',
|
||||
freeMode: true,
|
||||
scrollbar: true,
|
||||
mousewheel: true,
|
||||
}
|
||||
|
||||
const props = {
|
||||
treeData: this.data,
|
||||
expandedKeys: this.expandedKeys,
|
||||
autoExpandParent: this.autoExpandParent,
|
||||
}
|
||||
|
||||
const on = {
|
||||
expand: this.onExpand,
|
||||
select: this.onSelect
|
||||
}
|
||||
|
||||
const scopedSlots = {
|
||||
title: ({ 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>
|
||||
<swiper options={swiperOptions}>
|
||||
<a-spin style={{ height: '100%' }} spinning={this.loading}>
|
||||
<a-icon slot="indicator" type="loading" spin />
|
||||
<swiper-slide>
|
||||
<a-tree {...{ props, on, scopedSlots }} />
|
||||
</swiper-slide>
|
||||
</a-spin>
|
||||
</swiper>
|
||||
</a-layout-sider>
|
||||
<a-layout-content>
|
||||
{this.$scopedSlots.default ? this.$scopedSlots.default() : null}
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user