13 changed files with 643 additions and 529 deletions
@ -0,0 +1,148 @@ |
|||
<template> |
|||
<div |
|||
ref="divRef" |
|||
:class="'flex ' + (toolbar.props.align === 'left' ? 'justify-start' : toolbar.props.align === 'center' ? 'justify-center' : 'justify-end')" |
|||
:style="`column-gap:${toolbar.props.xGap}px`" |
|||
> |
|||
<template v-for="(btn, index) in baseButtons" :key="'button_' + index"> |
|||
<!-- 分割符按钮 --> |
|||
<q-separator v-if="typeof btn.data === 'string' && btn.data === 'separator'" vertical class="class-action-item" /> |
|||
|
|||
<!-- 按钮组 --> |
|||
<q-btn-dropdown |
|||
v-else-if="Array.isArray(btn.data) && btn.data.length > 0" |
|||
unelevated |
|||
:dense="toolbar.props.dense" |
|||
outline |
|||
no-wrap |
|||
no-caps |
|||
v-bind="btn.data[0]" |
|||
:label="toolbar.props.dense ? '' : toolbar.getLabel(btn.data[0].label)" |
|||
:icon="toolbar.props.dense ? undefined : toolbar.getIcon(btn.data[0].icon)" |
|||
:split="btn.data[0].click ? true : false" |
|||
:disable="btn.data[0]?.enableIf ? !btn.data[0].enableIf(toolbar.cm.args.value) : false" |
|||
class="class-action-item" |
|||
content-class="w-toolbar-btn-dropdown" |
|||
@click="toolbar.buttonClick(btn.data[0])" |
|||
> |
|||
<template v-if="toolbar.props.dense" #label> |
|||
<div :style="btn.data[0].click ? 'padding: 0px 8px' : 'padding: 0px 5px 0px 8px'"> |
|||
<q-icon v-if="btn.data[0].icon" :name="toolbar.getIcon(btn.data[0].icon)" size="xs"></q-icon> |
|||
<span style="padding-left: 3px">{{ toolbar.getLabel(btn.data[0].label) }}</span> |
|||
</div> |
|||
</template> |
|||
<q-list> |
|||
<template v-for="(childrenBtn, childrenIndex) in btn.data" :key="'button_c_' + childrenIndex"> |
|||
<template v-if="childrenIndex === 0"></template> |
|||
<template v-else> |
|||
<q-separator v-if="typeof childrenBtn === 'string' && childrenBtn === 'separator'" /> |
|||
<ChildrenBtn v-else-if="Array.isArray(childrenBtn) && childrenBtn.length > 0" :button="childrenBtn"></ChildrenBtn> |
|||
<q-item |
|||
v-else |
|||
v-close-popup |
|||
clickable |
|||
:disable="childrenBtn?.enableIf ? !childrenBtn.enableIf(toolbar.cm.args.value) : false" |
|||
@click="toolbar.buttonClick(childrenBtn)" |
|||
> |
|||
<q-item-section> |
|||
<q-item-label |
|||
><q-icon v-if="childrenBtn?.icon" :name="toolbar.getIcon(childrenBtn?.icon)" left size="20px"></q-icon> |
|||
{{ toolbar.getLabel(childrenBtn?.label) }}</q-item-label |
|||
> |
|||
</q-item-section> |
|||
</q-item> |
|||
</template> |
|||
</template> |
|||
</q-list> |
|||
</q-btn-dropdown> |
|||
|
|||
<!-- 单个按钮 --> |
|||
<q-btn |
|||
v-else |
|||
:padding="toolbar.props.dense ? toolbar.padding : undefined" |
|||
:disable="btn?.data?.enableIf ? !btn.data.enableIf(toolbar.cm.args.value) : false" |
|||
no-wrap |
|||
no-caps |
|||
outline |
|||
v-bind="btn.data" |
|||
align="center" |
|||
:icon="toolbar.props.dense ? undefined : toolbar.getIcon(btn.data.icon)" |
|||
:label="toolbar.props.dense ? '' : toolbar.getLabel(btn.data.label)" |
|||
class="class-action-item" |
|||
@click="toolbar.buttonClick(btn.data)" |
|||
> |
|||
<div v-if="toolbar.props.dense"> |
|||
<q-icon v-if="btn.data.icon" :name="toolbar.getIcon(btn.data.icon)" size="xs"></q-icon> |
|||
<span style="padding-left: 3px">{{ toolbar.getLabel(btn.data.label) }}</span> |
|||
</div> |
|||
</q-btn> |
|||
</template> |
|||
|
|||
<!-- moreActions --> |
|||
<q-btn-dropdown |
|||
v-if="moreButtons.length > 0" |
|||
unelevated |
|||
outline |
|||
label="" |
|||
no-wrap |
|||
no-caps |
|||
:icon="undefined" |
|||
:dense="toolbar.props.dense" |
|||
class="class-more-action" |
|||
content-class="w-toolbar-btn-dropdown" |
|||
> |
|||
<template #label> |
|||
<div style="padding: 0px 5px 0px 8px"> |
|||
<span>{{ $t('more') }}</span> |
|||
</div> |
|||
</template> |
|||
<q-list> |
|||
<template v-for="(childrenBtn, childrenIndex) in moreButtons" :key="'moreAction_' + childrenIndex"> |
|||
<q-separator v-if="typeof childrenBtn.data === 'string' && childrenBtn.data === 'separator'" /> |
|||
<ChildrenBtn v-else-if="Array.isArray(childrenBtn.data) && childrenBtn.data.length > 0" :button="childrenBtn.data"></ChildrenBtn> |
|||
<q-item |
|||
v-else |
|||
v-close-popup |
|||
clickable |
|||
:disable="childrenBtn?.data?.enableIf ? !childrenBtn.data.enableIf(toolbar.cm.args.value) : false" |
|||
@click="toolbar.buttonClick(childrenBtn.data)" |
|||
> |
|||
<q-item-section> |
|||
<q-item-label |
|||
><q-icon v-if="childrenBtn.data.icon" :name="toolbar.getIcon(childrenBtn.data.icon)" left size="20px"></q-icon> |
|||
{{ toolbar.getLabel(childrenBtn.data.label) }}</q-item-label |
|||
> |
|||
</q-item-section> |
|||
</q-item> |
|||
</template> |
|||
</q-list> |
|||
</q-btn-dropdown> |
|||
</div> |
|||
</template> |
|||
<script setup lang="ts"> |
|||
import { inject, ref } from 'vue'; |
|||
import { $t } from '@/platform'; |
|||
import { Toolbar } from './Toolbar'; |
|||
import ChildrenBtn from './ChildrenBtn.vue'; |
|||
|
|||
const divRef = ref(); |
|||
const toolbar = <Toolbar>inject('toolbar'); |
|||
const props = defineProps({ |
|||
baseButtons: { |
|||
type: <any>Array, |
|||
default: () => { |
|||
return []; |
|||
}, |
|||
}, |
|||
moreButtons: { |
|||
type: <any>Array, |
|||
default: () => { |
|||
return []; |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
defineExpose({ |
|||
divRef, |
|||
}); |
|||
</script> |
@ -0,0 +1,78 @@ |
|||
import { computed } from 'vue'; |
|||
|
|||
export class ComputedManager { |
|||
props: any; |
|||
|
|||
constructor(props: any) { |
|||
this.props = props; |
|||
} |
|||
|
|||
/** |
|||
* 按钮click与enableIf等函数通用入参 |
|||
*/ |
|||
args = computed(() => { |
|||
return { |
|||
selected: this.firstSelected.value, |
|||
selecteds: this.selected.value, |
|||
ticked: this.firstTicked.value, |
|||
tickeds: this.ticked.value, |
|||
grid: this.props.grid, |
|||
selectedColName: this.selectedColName.value, |
|||
}; |
|||
}); |
|||
|
|||
/** |
|||
* w-grid 表格选中的所有记录 |
|||
*/ |
|||
selected = computed(() => { |
|||
if (this.props.grid) { |
|||
return this.props.grid.getSelectedRows(); |
|||
} |
|||
return []; |
|||
}); |
|||
|
|||
/** |
|||
* w-grid 表格选中的第一条数据 |
|||
*/ |
|||
firstSelected = computed(() => { |
|||
const rows = this.selected.value; |
|||
if (rows.length > 0) { |
|||
return rows[0]; |
|||
} |
|||
return undefined; |
|||
}); |
|||
|
|||
/** |
|||
* w-grid 表格checkbox勾选的所有记录 |
|||
*/ |
|||
ticked = computed(() => { |
|||
if (this.props.grid) { |
|||
return this.props.grid.getTickedRows(); |
|||
} |
|||
return []; |
|||
}); |
|||
|
|||
/** |
|||
* w-grid 表格checkbox勾选的第一条数据 |
|||
*/ |
|||
firstTicked = computed(() => { |
|||
const rows = this.ticked.value; |
|||
if (rows.length > 0) { |
|||
return rows[0]; |
|||
} |
|||
return undefined; |
|||
}); |
|||
|
|||
/** |
|||
* w-grid 表格选中的单元格对应的列名 |
|||
*/ |
|||
selectedColName = computed(() => { |
|||
if (this.props.grid) { |
|||
const cell = this.props.grid.getSelectedCell(); |
|||
if (cell) { |
|||
return cell['colName']; |
|||
} |
|||
} |
|||
return undefined; |
|||
}); |
|||
} |
@ -0,0 +1,223 @@ |
|||
import { Tools } from '@/platform'; |
|||
import { ModelRef, nextTick } from 'vue'; |
|||
import { ComputedManager } from './ComputedManager'; |
|||
|
|||
export class Toolbar { |
|||
/** |
|||
* 按钮内部内容padding |
|||
*/ |
|||
padding = '5px 12px 5px 8px'; |
|||
/** |
|||
* 更多按钮宽度 |
|||
*/ |
|||
moreButtonWidth = 100; |
|||
|
|||
/** |
|||
* 配置属性 |
|||
*/ |
|||
props: any; |
|||
/** |
|||
* 模型值 |
|||
*/ |
|||
modelValue: ModelRef<any>; |
|||
/** |
|||
* 计算属性管理器 |
|||
*/ |
|||
cm: ComputedManager; |
|||
/** |
|||
* 以按钮 name 构建的 json 对象,value 为按钮配置 |
|||
*/ |
|||
buttonsJson: any = {}; |
|||
/** |
|||
* 根据传入的按钮配置处理完成的按钮数组 |
|||
*/ |
|||
buttons: any[] = []; |
|||
|
|||
constructor(props: any, modelValue: ModelRef<any>) { |
|||
// 初始化
|
|||
this.props = props; |
|||
this.modelValue = modelValue; |
|||
this.cm = new ComputedManager(props); |
|||
this.extractButton(modelValue.value); |
|||
|
|||
// 对外方法绑定 this,避免模板中直接配置方法时(非显式调用) this 失效
|
|||
this.buttonClick = this.buttonClick.bind(this); |
|||
this.initButtons = this.initButtons.bind(this); |
|||
this.setButtonWidth = this.setButtonWidth.bind(this); |
|||
this.getIcon = this.getIcon.bind(this); |
|||
this.getLabel = this.getLabel.bind(this); |
|||
} |
|||
|
|||
/** |
|||
* 按钮执行点击函数 |
|||
* @param button |
|||
* @returns |
|||
*/ |
|||
async buttonClick(button) { |
|||
// 准备按钮入参
|
|||
const context = {}; |
|||
const args = { |
|||
...this.cm.args.value, |
|||
context: context, |
|||
}; |
|||
|
|||
// 按钮enableIf校验
|
|||
if (button.enableIf && !button.enableIf(args)) { |
|||
console.warn('[w-toolbar] The function `enableIf` returns false, causing the `click` not to trigger.' + ' button name is `' + button['name'] + '`'); |
|||
return; |
|||
} |
|||
|
|||
// beforeClick 执行
|
|||
let beforeResult = true; |
|||
if (button.beforeClick) { |
|||
beforeResult = await button.beforeClick(args); |
|||
} |
|||
|
|||
// click 执行
|
|||
if (beforeResult && button.click) { |
|||
let callAfterEditorOpen = true; |
|||
const clickProps = { ...args }; |
|||
if (button._click) { |
|||
if (button.overrideClick) { |
|||
callAfterEditorOpen = false; |
|||
} |
|||
clickProps['_click'] = (_args: any) => { |
|||
button._click(_args || args); |
|||
callAfterEditorOpen = true; |
|||
}; |
|||
} |
|||
await button.click(clickProps); |
|||
|
|||
// afterClick 执行
|
|||
nextTick(async () => { |
|||
if (button.afterClick) { |
|||
await button.afterClick(args); |
|||
} |
|||
|
|||
// afterEditorOpen 执行
|
|||
if (button.afterEditorOpen && callAfterEditorOpen) { |
|||
button.afterEditorOpen(args); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 初始化按钮 |
|||
*/ |
|||
initButtons() { |
|||
this.buttons = []; |
|||
for (let i = 0; i < this.modelValue.value.length; i++) { |
|||
const btn: any = this.modelValue.value[i]; |
|||
if (i === 0 && typeof btn === 'string' && btn === 'separator') { |
|||
// 按钮数组第一个元素为分割符时直接跳过
|
|||
continue; |
|||
} |
|||
if (typeof btn === 'string' && btn === 'separator' && this.buttons.length > 0 && this.buttons[this.buttons.length - 1].data === btn) { |
|||
// 连续的分割符直接跳过,避免出现重复的无意义分割符
|
|||
continue; |
|||
} |
|||
if (Array.isArray(btn) && btn.length > 0) { |
|||
this.buttons.push({ data: this.handleChildrenAndSeparator(btn) }); |
|||
} else if (typeof btn === 'string' && btn === 'separator') { |
|||
this.buttons.push({ data: btn }); |
|||
} else { |
|||
this.buttons.push({ data: this.buttonsJson[btn.name] }); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 设置按钮实际显示需要的宽度 |
|||
* @param buttonsRef |
|||
*/ |
|||
setButtonWidth(buttonsRef: any) { |
|||
const nodes = buttonsRef.getElementsByClassName('class-action-item'); |
|||
this.moreButtonWidth = buttonsRef.getElementsByClassName('class-more-action')[0].clientWidth; |
|||
if (nodes) { |
|||
for (let i = 0; i < nodes.length; i++) { |
|||
if (this.buttons[i]) { |
|||
this.buttons[i].width = nodes[i].clientWidth; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取按钮 icon |
|||
* @param icon |
|||
* @returns |
|||
*/ |
|||
getIcon(icon) { |
|||
if (Tools.isUndefinedOrNull(icon)) { |
|||
return undefined; |
|||
} else if (typeof icon === 'function') { |
|||
return icon(this.cm.args); |
|||
} else { |
|||
return icon; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取按钮 label |
|||
* @param label |
|||
* @returns |
|||
*/ |
|||
getLabel(label) { |
|||
if (Tools.isUndefinedOrNull(label)) { |
|||
return ''; |
|||
} else if (typeof label === 'function') { |
|||
return label(this.cm.args); |
|||
} else { |
|||
return label; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 提取按钮到json中 |
|||
* @param buttonArray |
|||
*/ |
|||
private extractButton(buttonArray: any) { |
|||
for (const btn of buttonArray) { |
|||
if (Array.isArray(btn)) { |
|||
this.extractButton(btn); |
|||
} else if (typeof btn === 'object') { |
|||
this.buttonsJson[btn.name] = { ...btn }; |
|||
if (this.props.noIcon) { |
|||
this.buttonsJson[btn.name].icon = undefined; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 处理子按钮和分割符 |
|||
* @param arr |
|||
* @returns |
|||
*/ |
|||
private handleChildrenAndSeparator(arr) { |
|||
const tempArr = <any>[]; |
|||
for (let i = 0; i < arr.length; i++) { |
|||
const btn = arr[i]; |
|||
if (i === 0 && typeof btn === 'string' && btn === 'separator') { |
|||
// 按钮数组第一个元素为分割符时直接跳过
|
|||
continue; |
|||
} |
|||
if (typeof btn === 'string' && btn === 'separator' && tempArr.length > 0 && tempArr[tempArr.length - 1] === btn) { |
|||
// 连续的分割符直接跳过,避免出现重复的无意义分割符
|
|||
continue; |
|||
} |
|||
if (Array.isArray(btn) && btn.length > 0) { |
|||
const handleResult = this.handleChildrenAndSeparator(btn); |
|||
if (handleResult && handleResult.length > 0) { |
|||
tempArr.push(handleResult); |
|||
} |
|||
} else if (typeof btn === 'string' && btn === 'separator') { |
|||
tempArr.push(btn); |
|||
} else { |
|||
tempArr.push(this.buttonsJson[btn.name]); |
|||
} |
|||
} |
|||
return tempArr; |
|||
} |
|||
} |
Loading…
Reference in new issue