为什么都没了

This commit is contained in:
路 范
2021-09-24 12:59:34 +08:00
parent a975b3bdee
commit a66921f0f4
962 changed files with 252792 additions and 0 deletions

View 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
},
}

View 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>

View 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>

View 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>
)
}
}

View 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>
)
}
}

View 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']
}
]

View 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>

View 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 }} />
},
}

View 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>
)
},
}

View 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)
}
}

View 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>

View 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>
)
},
}

View 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>
)
}
}

View 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>
)
}
}