在报表上方增加搜索视图
先看效果
1.场景描述
如果不在报表的上方增加搜索视图,通常的做法是将搜索条件用一个TransientModel来完成,如下图,这种方式也是也是可行的,不过想要改变筛选条件时,要重新点开筛选框,比较麻烦。
2.实现过程
2.1.view视图
<record id="action_asset_liabilities_report" model="ir.actions.client">
<field name="name">资产负债表</field>
<field name="res_model">asset.liabilities.report</field>
<field name="tag">asset_liabilities_report</field>
</record>
<menuitem sequence="40" name='资产负债表'
id='menu_account_financial_report_tree'
parent="jcerp_account.menu_account_report_config"
action='action_account_financial_report_tree'/>
2.2.asset_liabilities_report的前端逻辑
这里的搜索逻辑与odoo原生的搜索逻辑类似,可以参考odoo源代码:@web/search/search_bar/search_bar。
2.3.report_search_bar设计
两个方面,
1、从report中传入搜索字段
2、是onClickSearch,最后点击搜索时,根据筛选条件获取报表数据。
export class ReportSearchBar extends Component {
}
ReportSearchBar.template = "jcerp_control_panel.ReportSearchBar";
ReportSearchBar.components = {
DateBetweenValue,
SearchAutoComplete,
Dropdown,
DropdownItem
}
ReportSearchBar.props = {
items: { type: Object },
onClickSearch: Function,
};
ReportSearchBar.defaultProps = {
autofocus: true,
};
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="jcerp_control_panel.ReportSearchBar.Input">
<div style="align-items: flex-start;display: grid;grid-template-columns: repeat(4, 1fr);grid-gap: 10px;">
<t t-foreach="items" t-as="item" t-key="item.id">
<div class="filter-item px-3 fs-5">
<label class="filter-item-label" t-att-for="item.description"><t t-esc="item.description"/></label>
<t t-if="item.fieldType === 'date' or item.fieldType === 'datetime'">
<div class="filter-no-border-input">
<DateBetweenValue onFromToChanged="(value) => this.onDateInput(item, value)"/>
</div>
</t>
<t t-elif="['many2one', 'selection', 'boolean', 'tags'].includes(item.fieldType)">
<SearchAutoComplete t-props="getAutoCompleteProps(item)"/>
</t>
<t t-else="">
<input type="text"
role="searchbox"
class="filter-border-input"
t-att-id="item.id"
t-on-keydown="onSearchKeydown"
t-on-input="onSearchInput"
/>
</t>
</div>
</t>
<div class="filter-item px-3 fs-5">
<button type="button" class="btn btn-secondary" t-on-click="onClickClear" style="margin-right: 10px;width: 60px;">重置</button>
<button type="button" class="btn btn-primary" t-on-click="onClickSearch" style="margin-right: 10px;width: 60px;">搜索</button>
<button type="button" class="btn btn-primary" t-on-click="(ev) => this.export_file_xlsx(ev)" style="width: 60px;">导出</button>
<Dropdown togglerClass="'btn btn-primary'" showCaret="true" class="'btn-group'">
<DropdownItem>
<button
class="btn btn-link text-nowrap"
t-on-click="(ev) => this.export_file_xlsx(ev)"
>
导出(xlsx)
</button>
</DropdownItem>
<DropdownItem>
<button class="btn btn-link text-nowrap" t-on-click="(ev) => this.export_file_pdf(ev)">
导出(pdf)
</button>
</DropdownItem>
</Dropdown>
</div>
</div>
</t>
<t t-name="jcerp_control_panel.ReportSearchBar">
<t t-call="jcerp_control_panel.ReportSearchBar.Input"/>
</t>
</templates>
2.4.SearchAutoComplete实现
SearchAutoComplete的实现参考odoo的源代码:@web/core/autocomplete/autocomplete
1、选择结果,多选时只显示第一项
2、删除单项选择结果
3、多选时显示,单选时不显示
4、输入框,模糊匹配
5、删除全部选择结果
6、可选列表
2.4.1.template代码
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="web.SearchAutoComplete">
<div class="search-input-container filter-border-input" t-ref="search-input-container">
<t t-if="selectedOption">
<div class="left-side">
<span class="search-option-label"><t t-esc="selectedOption ? selectedOption.label : ''"/></span>
<i class="fa fa-times-circle search-option-close" style="margin: 5px 0;"
t-ref="close_one"
t-on-click="() => this.closeSearchOption()"/>
</div>
<t t-if="effectiveCount > 1">
<span class="search-option-count">
+<t t-esc="effectiveCount"/>
</span>
</t>
</t>
<input
type="text"
t-att-id="props.id"
class="filter-border-input"
autocomplete="off"
t-att-placeholder="props.placeholder"
t-model="state.value"
t-on-blur="onInputBlur"
t-on-click.stop="onInputClick"
t-on-change="onInputChange"
t-on-input="onInput"
t-on-keydown="onInputKeydown"
t-on-focus="onInputFocus"
t-ref="input"
/>
<t t-if="selectedOption">
<i class="fa fa-times-circle search-input-close" style="margin: 5px 0;"
t-ref="close_all"
t-on-click="() => this.closeAllSearchOption()"/>
</t>
</div>
<button
type="button"
t-att-id="props.id"
disabled="true"
class="btn btn-search o-no-caret rounded-start-0 h-100"
t-on-click="onInput"
>
<i class="fa fa-caret-down" aria-hidden="true" data-hotkey="shift+q" title="Toggle Search Panel"/>
</button>
<t t-if="displayOptions">
<div class="o-autocomplete--dropdown-menu ui-widget show"
t-att-class="ulDropdownClass"
t-on-mousedown.prevent="" t-ref="sourcesList">
<t t-foreach="sources" t-as="source" t-key="source.id">
<t t-if="source.isLoading">
<li class="ui-menu-item"
t-att-class="{
'o-autocomplete--dropdown-item': props.dropdown,
'd-block': !props.dropdown
}">
<a href="#" class="dropdown-item ui-menu-item-wrapper">
<i class="fa fa-spin fa-circle-o-notch" /> <t t-esc="source.placeholder" />
</a>
</li>
</t>
<t t-else="">
<t t-foreach="source.options" t-as="option" t-key="option.id">
<li
class="o-autocomplete--dropdown-item ui-menu-item d-block"
t-att-class="option.classList"
t-on-mouseenter="() => this.onOptionMouseEnter([source_index, option_index])"
t-on-mouseleave="() => this.onOptionMouseLeave([source_index, option_index])"
t-on-click="() => this.onOptionClick([source_index, option_index])"
>
<div class="d-flex">
<a
href="#"
class="dropdown-item ui-menu-item-wrapper text-truncate"
t-att-style="option.checked ? 'color: #3D7EFF;' : ''"
t-att-class="{ 'ui-state-active': isActiveSourceOption(option)}"
>
<t t-if="source.optionTemplate">
<t t-call="{{ source.optionTemplate }}" />
</t>
<t t-else="">
<t t-esc="option.label" />
</t>
</a>
<t t-if="option.checked">
<i class="fa fa-check" style="color: #3D7EFF;margin: 5px 5px;"/>
</t>
</div>
</li>
</t>
</t>
</t>
</div>
</t>
</t>
</templates>
2.4.2.js代码
前端代码和odoo源代码类似,这里贴一部分关键代码。
1、selectOption
selectOption(option, params = {}) {
this.inEdition = false;
if (option.unselectable) {
this.inputRef.el.value = "";
return;
}
option.checked = !option.checked
if (option.value === 0) {
this.state.selectedOptions = [];
this.sources.forEach(source => {
source.options.forEach(source_option => {
source_option.checked = option.checked
})
})
if (option.checked) {
this.sources.forEach(source => {
source.options.forEach(source_option => {
this.state.selectedOptions.push(source_option)
})
})
this.props.onSelect(this.state.selectedOption, {
...params,
input: this.inputRef.el,
});
} else {
this.inputRef.el.value = "";
}
return;
}
const allOption = this.sources[0].options[0]
if (option.checked) {
this.state.selectedOptions.push(option)
if (this.state.selectedOptions.length >= this.optionCount - 1) {
allOption.checked = true;
this.state.selectedOptions.push(allOption)
}
} else {
allOption.checked = false;
this.state.selectedOptions = this.state.selectedOptions.filter(
item => item.value !== option.value && item.value !== 0
)
}
if (this.props.resetOnSelect) {
this.inputRef.el.value = "";
}
this.forceValFromProp = true;
this.props.onSelect(this.state.selectedOption, {
...params,
input: this.inputRef.el,
});
}
async loadSources(useInput) {
this.sources = [];
this.state.activeSourceOption = null;
const proms = [];
for (const pSource of this.props.sources) {
const source = this.makeSource(pSource);
this.sources.push(source);
const options = this.loadOptions(
pSource.options,
useInput ? this.inputRef.el.value.trim() : ""
);
if (options instanceof Promise) {
source.isLoading = true;
const prom = options.then((options) => {
source.options = options.map((option) => this.makeOption(option));
source.isLoading = false;
this.state.optionsRev++;
});
proms.push(prom);
} else {
source.options = options.map((option) => this.makeOption(option));
}
}
await Promise.all(proms);
this.navigate(0);
this.sources.forEach(source => {
source.options.forEach(option => {
// 如果找到了匹配的id,则设置checked为true
if (this.state.selectedOptions.some(item => item.value === option.value)) {
option.checked = true;
}
});
})
}
2.5.后端代码实现
这里主要时ReportSearchBar的可选项items的实现,这里没有解析odoo的搜索视图,因为在报表中可能会有一些与模型无关的选择项。因此在搜索选项以及根据搜索条件查询搜索结果、打印结果都由每个报表自定义实现。
@api.model
def get_search_items(self):
"""
获取搜索条件
"""
return [
{
'name': 'move_id',
'fieldType': 'many2one',
'res_model': 'account.move',
'label': '',
'operator': 'ilike',
'description': '凭证编号',
'value': '',
},
{
'name': 'ref',
'fieldType': 'char',
'label': '',
'operator': 'ilike',
'description': '参考',
'value': '',
},
{
'name': 'invoice_date',
'fieldType': 'date',
'label': '',
'operator': '=',
'description': '发票/账单日期',
'value': '',
},
{
'name': 'date',
'fieldType': 'date',
'label': '',
'operator': '=',
'description': '入账日期',
'value': '2000-01-01',
},
{
'name': 'partner_id',
'fieldType': 'many2one',
'res_model': 'res.partner',
'label': '',
'operator': 'ilike',
'description': '合作伙伴',
'domain': [],
'value': '',
},
]
3.总结
此次开发过程都是利用odoo原生的代码及组件实现,会涉及到较多时间的样式、事件调整及编写,后续会用element组件库来改写