diff --git a/CHANGELOG.md b/CHANGELOG.md index d541a7d72cd5a9211dafb393524548ad942cc525..f33b78111cb9fa230505e41ef5b5bb7096ed89dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ - 修复密码框点击切换显示隐藏密码图标时未切换的问题 +### Added + +- 新增级联下拉编辑器 + ## [0.7.41-alpha.10] - 2025-08-27 ### Added diff --git a/src/editor/dropdown-list/dropdown-list-editor.provider.ts b/src/editor/dropdown-list/dropdown-list-editor.provider.ts index ba487344de32049b4812849bc835d3dac6f14e10..e784e0cde865a550caee6100c91b58cb239e73c3 100644 --- a/src/editor/dropdown-list/dropdown-list-editor.provider.ts +++ b/src/editor/dropdown-list/dropdown-list-editor.provider.ts @@ -32,6 +32,9 @@ export class DropDownListEditorProvider implements IEditorProvider { case 'EMOJI_PICKER': componentName = 'IBizEmojiPicker'; break; + case 'MOBDROPDOWNLIST_CASCADER': + componentName = 'IBizCascaderDropdown'; + break; default: break; } diff --git a/src/editor/dropdown-list/ibiz-cascader-dropdown/ibiz-cascader-dropdown.scss b/src/editor/dropdown-list/ibiz-cascader-dropdown/ibiz-cascader-dropdown.scss new file mode 100644 index 0000000000000000000000000000000000000000..1fe64898fa65a40d8bb481b6d9277ca76618a824 --- /dev/null +++ b/src/editor/dropdown-list/ibiz-cascader-dropdown/ibiz-cascader-dropdown.scss @@ -0,0 +1,34 @@ +@include b(cascader-dropdown) { + height: 100%; + + .van-field { + font-size: getCssVar('form-item', 'font-size'); + + &::after { + display: none; + } + } + + .van-cell--clickable:active { + background-color: transparent; + } + + @include m(disabled) { + color: getCssVar('form-item', 'disabled-color'); + + --van-field-input-text-color: #{getCssVar('form-item', 'disabled-color')}; + } + + @include m(readonly) { + --van-field-input-text-color: #{getCssVar('form-item', 'readonly-color')}; + + color: getCssVar('form-item', 'readonly-color'); + } + + input { + text-align: getCssVar(form-item-container, editor-align); + } + &.#{bem('cascader','','readonly')} { + text-align: getCssVar(form-item-container, editor-align); + } +} diff --git a/src/editor/dropdown-list/ibiz-cascader-dropdown/ibiz-cascader-dropdown.tsx b/src/editor/dropdown-list/ibiz-cascader-dropdown/ibiz-cascader-dropdown.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2b00ff32f75a29f9f628b1840fe4e7bd0c8161ea --- /dev/null +++ b/src/editor/dropdown-list/ibiz-cascader-dropdown/ibiz-cascader-dropdown.tsx @@ -0,0 +1,184 @@ +import { ref, Ref, defineComponent, computed } from 'vue'; +import { + getDropdownProps, + getEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { isNil } from 'ramda'; +import { DropDownListEditorController } from '../dropdown-list-editor.controller'; +import { IBizCommonRightIcon } from '../../common/right-icon/right-icon'; +import { usePopstateListener } from '../../../util'; +import './ibiz-cascader-dropdown.scss'; + +/** + * 移动端级联下拉列表(单选) + * @primary + * @description 使用van-cascader组件,用于选择树形代码表数据。基于`移动端下拉列表(单选)`编辑器扩展,编辑器样式代码名称为:CASCADER + * @ignoreprops autoFocus | overflowMode + * @ignoreemits infoTextChange | enter + */ +export const IBizCascaderDropdown = defineComponent({ + name: 'IBizCascaderDropdown', + props: getDropdownProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('cascader-dropdown'); + const c: DropDownListEditorController = props.controller!; + + // 树数据(用于维护级联选择器默认选中数据) + const treeData: Ref = ref([]); + // 代码表map数据 + const codeListMap = new Map(); + // 选中值 + const selectValue: Ref = ref(null); + const show = ref(false); + const onClose = () => { + show.value = false; + }; + + // 转化为树形结构数据 + const transformTreeData = (list: readonly IData[], pValueField: string) => { + // 清空map,保证 O(n) 复杂度 + codeListMap.clear(); + + // 第一遍:把每一项先包装成 TreeNode + list.forEach(item => { + codeListMap.set(item.value, { ...item }); + }); + + const rootNodes: IData[] = []; + if (pValueField) { + // 第二遍:根据父属性把节点挂到父节点 children 里 + list.forEach(item => { + const node = codeListMap.get(item.value)!; + if (isNil(item.data[pValueField])) { + // 父属性为空 => 根节点 + rootNodes.push(node); + } else { + const parent = codeListMap.get(item.data[pValueField]); + // 如果父节点不存在,直接丢弃 + if (parent) { + if (!parent.children) { + parent.children = []; + } + parent.children.push(node); + } + } + }); + } else { + // 没有父值属性时,直接平铺 + rootNodes.push(...list); + } + + return rootNodes; + }; + + // 加载代码表数据 + const loadCodeList = async () => { + const codeList = await c.loadCodeList(props.data!); + // 存在父值属性时转换为树型数据,否则平铺 + const { pvaluefield } = c.editorParams; + treeData.value = transformTreeData(codeList, pvaluefield); + }; + + loadCodeList(); + + // 处理级联选择器值改变 + const onFinish = ($event: IData) => { + const { value } = $event; + onClose(); + + emit('change', value); + }; + + const onChange = ($event: IData) => { + const { value } = $event; + emit('change', value); + }; + + // 当前值 + const curValue = computed(() => { + const item = codeListMap.get(props.value || ''); + return item?.text || ''; + }); + + const onBlur = () => { + emit('blur'); + }; + + const onFocus = () => { + emit('focus'); + }; + + const openPopup = () => { + if (props.disabled || props.readonly) { + return; + } + show.value = !show.value; + }; + + // 监听popstate事件 + usePopstateListener(onClose); + + return { + ns, + c, + show, + treeData, + selectValue, + curValue, + onBlur, + onFocus, + openPopup, + onClose, + onChange, + onFinish, + }; + }, + render() { + return ( +
+ {this.readonly && this.curValue} + {!this.readonly && ( + + {{ + 'right-icon': !this.readonly && ( + + ), + }} + + )} + + + +
+ ); + }, +}); diff --git a/src/editor/dropdown-list/index.ts b/src/editor/dropdown-list/index.ts index 78c0001b3f694604e3ef225815336885cb472db0..80125ad4ab2066371721ac3445754864fce62ac1 100644 --- a/src/editor/dropdown-list/index.ts +++ b/src/editor/dropdown-list/index.ts @@ -1,4 +1,5 @@ export { IBizDropdown } from './ibiz-dropdown/ibiz-dropdown'; export { IBizEmojiPicker } from './ibiz-emoji-picker/ibiz-emoji-picker'; +export { IBizCascaderDropdown } from './ibiz-cascader-dropdown/ibiz-cascader-dropdown'; export * from './dropdown-list-editor.controller'; export * from './dropdown-list-editor.provider'; diff --git a/src/editor/index.ts b/src/editor/index.ts index 89104b38b35005653c03252118ec7c5e8ea492cc..d1438e2c2aea3b509b6233381fe12fff8ea32429 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -11,6 +11,7 @@ import { import { IBizDropdown, IBizEmojiPicker, + IBizCascaderDropdown, DropDownListEditorProvider, } from './dropdown-list'; import { CheckBoxListEditorProvider, IBizCheckboxList } from './check-box-list'; @@ -60,6 +61,7 @@ export const IBizEditor = { v.component(IBizDropdown.name, IBizDropdown); v.component(IBizDropdownList.name, IBizDropdownList); v.component(IBizEmojiPicker.name, IBizEmojiPicker); + v.component(IBizCascaderDropdown.name, IBizCascaderDropdown); v.component(IBizCheckboxList.name, IBizCheckboxList); v.component(IBizSlider.name, IBizSlider); v.component(IBizRaw.name, IBizRaw); @@ -142,6 +144,12 @@ export const IBizEditor = { () => new DropDownListEditorProvider('EMOJI_PICKER'), ); + // 级联下拉 + registerEditorProvider( + 'MOBDROPDOWNLIST_CASCADER', + () => new DropDownListEditorProvider('MOBDROPDOWNLIST_CASCADER'), + ); + // 下拉列表框多选(复选框) registerEditorProvider( 'MOBCHECKLIST',