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