diff --git a/package.json b/package.json index 29fa8918b852e1ebdf86a9e29c5e020b89c297a6..14204fd4f81f49ba80013a0a1a3715ac6fd5493c 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,8 @@ "lodash-es": "^4.17.21", "qx-util": "^0.4.8", "ramda": "^0.29.0", + "qs": "^6.11.1", + "@types/qs": "^6.9.7", "vue": "^3.3.4", "vue-router": "^4.2.0" }, @@ -113,4 +115,4 @@ "*.ts": "eslint --fix", "*.scss": "stylelint --custom-syntax=postcss-scss" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 636b0af635e65923d67b9bb7043f7a5f76da8709..ce4962031f88117dbcf6f149a9729e1cfbc373f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,9 @@ dependencies: '@ibiz/model-core': specifier: ^0.0.9 version: 0.0.9 + '@types/qs': + specifier: ^6.9.7 + version: 6.9.7 async-validator: specifier: ^4.2.5 version: 4.2.5 @@ -31,6 +34,9 @@ dependencies: lodash-es: specifier: ^4.17.21 version: 4.17.21 + qs: + specifier: ^6.11.1 + version: 6.11.1 qx-util: specifier: ^0.4.8 version: 0.4.8 @@ -625,6 +631,10 @@ packages: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} dev: true + /@types/qs@6.9.7: + resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} + dev: false + /@types/ramda@0.29.1: resolution: {integrity: sha512-Ff5RRG9YRqMgWOqZVVavSjGEvYHUnXnGF0YPGbzIWhB3o8qiccSJZlFX2z8qm3G1H/IC5w0ozHmlezUeQCtGfQ==} dependencies: diff --git a/src/common/action-toolbar/action-toolbar.tsx b/src/common/action-toolbar/action-toolbar.tsx index f3ce0dd0d214a640fda976ec462e24c947ed3672..34310def1708b4e87198e969e3f6580553f1f351 100644 --- a/src/common/action-toolbar/action-toolbar.tsx +++ b/src/common/action-toolbar/action-toolbar.tsx @@ -54,10 +54,10 @@ export const IBizActionToolbar = defineComponent({ disabled={this.actionsState[detail.id!].disabled} class={[this.ns.e('item'), this.ns.is('disabled', false)]} > - {/* {detail.showIcon && action.getPSSysImage() && ( - + {detail.showIcon && detail.sysImage && ( + )} - {detail.showCaption ? action!.caption : ''} */} + {detail.showCaption ? detail.caption : ''} , ]; } @@ -88,7 +88,6 @@ export const IBizActionToolbar = defineComponent({ {details.length > 0 && details.map(detail => { - // const action = detail.getPSUIAction(); if (this.actionsState[detail.id!].visible) { return ( - {/* {detail.showIcon && action.getPSSysImage() && ( - + {detail.showIcon && detail.sysImage && ( + )} - {detail.showCaption ? action!.caption : ''} */} + {detail.showCaption ? detail.caption : ''} ); } diff --git a/src/common/app-rawitem/app-rawitem.tsx b/src/common/app-rawitem/app-rawitem.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f93d2e60b15e06b250cda7480fcda9f28260f24a --- /dev/null +++ b/src/common/app-rawitem/app-rawitem.tsx @@ -0,0 +1,192 @@ +import { useNamespace } from '@ibiz-template/vue3-util'; +import { defineComponent, ref } from 'vue'; +import { createUUID } from 'qx-util'; + +export const AppRawItem = defineComponent({ + name: 'AppRawItem', + props: { + type: { + type: String, + required: true, + }, + content: { + type: [String, Object], + }, + }, + setup(props) { + const ns = useNamespace('rawitem'); + + // 视频类型内容参数 + const playerParams = ref({ + id: createUUID(), + path: '', + mute: true, + autoplay: true, + replay: false, + showcontrols: true, + }); + + // 分割线类型参数 + const dividerParams = ref({ + contentPosition: 'center', + html: '', + }); + + // 类型参数 + const alertParams = ref({ + type: 'info', + title: '', + closeabled: true, + showIcon: false, + }); + + // 文本类型显示值 + const rawItemText = ref(''); + + if ( + [ + 'TEXT', + 'HEADING1', + 'HEADING2', + 'HEADING3', + 'HEADING4', + 'HEADING5', + 'HEADING6', + 'PARAGRAPH', + 'HTML', + ].includes(props.type) + ) { + if (props.content && typeof props.content === 'string') { + const items = props.content.match(/\{{(.+?)\}}/g); + if (items) { + items.forEach((item: string) => { + rawItemText.value = (props.content as string).replace( + /\{{(.+?)\}}/, + // eslint-disable-next-line no-eval + eval(item.substring(2, item.length - 2)), + ); + }); + } else { + rawItemText.value = props.content; + } + rawItemText.value = rawItemText.value.replaceAll('<', '<'); + rawItemText.value = rawItemText.value.replaceAll('>', '>'); + rawItemText.value = rawItemText.value.replaceAll('&nbsp;', ' '); + rawItemText.value = rawItemText.value.replaceAll(' ', ' '); + } + } + + if (['VIDEO', 'DIVIDER', 'INFO', 'WARNING', 'ERROR'].includes(props.type)) { + if (props.content) { + let rawConfig = {}; + try { + if (typeof props.content === 'string') { + // eslint-disable-next-line no-new-func + const func = new Function(`return (${props.content});`); + rawConfig = func(); + switch (props.type) { + case 'VIDEO': + Object.assign(playerParams.value, rawConfig); + break; + case 'DIVIDER': + Object.assign(dividerParams.value, rawConfig); + break; + case 'INFO': + case 'WARNING': + case 'ERROR': + alertParams.value.type = props.type.toLocaleLowerCase(); + Object.assign(alertParams.value, rawConfig); + break; + default: + break; + } + } + } catch { + ibiz.log.error(`${props.type}类型自定义参数配置错误`); + } + } + } + + return { ns, rawItemText, playerParams, dividerParams, alertParams }; + }, + render() { + if (this.type === 'IMAGE') { + return ( + + ); + } + if (this.type === 'TEXT') { + return {this.rawItemText}; + } + if (this.type === 'HEADING1') { + return

{this.rawItemText}

; + } + if (this.type === 'HEADING2') { + return

{this.rawItemText}

; + } + if (this.type === 'HEADING3') { + return

{this.rawItemText}

; + } + if (this.type === 'HEADING4') { + return

{this.rawItemText}

; + } + if (this.type === 'HEADING5') { + return
{this.rawItemText}
; + } + if (this.type === 'HEADING6') { + return
{this.rawItemText}
; + } + if (this.type === 'PARAGRAPH') { + return

{this.rawItemText}

; + } + if (this.type === 'HTML') { + return ( +
+ ); + } + if (this.type === 'VIDEO') { + return ( +
+ +
+ ); + } + if (this.type === 'DIVIDER') { + return ( + + + + ); + } + if ( + this.type === 'INFO' || + this.type === 'WARNING' || + this.type === 'ERROR' + ) { + return ( + + ); + } + if (['MARKDOWN', 'PLACEHOLDER'].includes(this.type)) { + return
{this.type}类型暂未支持
; + } + return null; + }, +}); diff --git a/src/common/index.ts b/src/common/index.ts index 90a8334c1142e51c5721ca955e32fb7d5fc00b20..27256c728792fdfd429863e5c9d7e105c753368a 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -8,6 +8,7 @@ import { IBizRow } from './row/row'; import { IBizControlBase } from './control-base/control-base'; import { IBizControlShell } from './control-shell/control-shell'; import { IBizViewShell } from './view-shell/view-shell'; +import { AppRawItem } from './app-rawitem/app-rawitem'; export * from './icon/icon'; export * from './keep-alive/keep-alive'; @@ -18,6 +19,7 @@ export * from './action-toolbar/action-toolbar'; export * from './control-base/control-base'; export * from './control-shell/control-shell'; export * from './view-shell/view-shell'; +export * from './app-rawitem/app-rawitem'; export const IBizCommonComponents = { install: (v: App) => { @@ -30,6 +32,7 @@ export const IBizCommonComponents = { v.component(IBizActionToolbar.name, IBizActionToolbar); v.component(IBizViewShell.name, IBizViewShell); v.component(IBizControlShell.name, IBizControlShell); + v.component(AppRawItem.name, AppRawItem); }, }; diff --git a/src/editor/autocomplete/autocomplete-editor.controller.ts b/src/editor/autocomplete/autocomplete-editor.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..1df93ef0a009125dac1f5e027a961ce87c5749e9 --- /dev/null +++ b/src/editor/autocomplete/autocomplete-editor.controller.ts @@ -0,0 +1,150 @@ +import { IHttpResponse, RuntimeModelError } from '@ibiz-template/core'; +import { EditorController } from '@ibiz-template/runtime'; +import { IAppDataEntity, IAutoComplete } from '@ibiz/model-core'; + +/** + * 自动完成编辑器控制器 + * @return {*} + * @author: zhujiamin + * @Date: 2022-08-25 10:57:58 + */ +export class AutoCompleteEditorController extends EditorController { + /** + * 占位 + * @return {*} + * @author: zhujiamin + * @Date: 2022-08-25 14:33:14 + */ + public placeHolder = '请选择数据'; + + /** + *值项 + */ + public valueItem = ''; + + /** + * 主键属性名称 + */ + public keyName: string = ''; + + /** + * 主文本属性名称 + */ + public textName: string = ''; + + /** + * 实体codeName + */ + public serviceName: string = ''; + + /** + * 数据集codeName + */ + public interfaceName: string = ''; + + /** + * 自填模式sort排序 + */ + public sort: string | undefined = ''; + + /** + * 编辑器实体 + */ + public appDataEntity: IAppDataEntity | null = null; + + protected async onInit(): Promise { + super.onInit(); + this.valueItem = this.parent.valueItemName?.toLowerCase() || ''; + if (this.model.appDataEntityId) { + this.appDataEntity = await ibiz.hub.getAppDataEntity( + this.model.appDataEntityId, + this.context.srfappid, + )!; + if (this.appDataEntity) { + this.keyName = this.appDataEntity?.keyAppDEFieldId || ''; + this.textName = this.appDataEntity?.majorAppDEFieldId || ''; + this.getAcParams(); + this.sort = this.getAcSort(); + } + } + } + + /** + * 获取Ac参数 + */ + public getAcParams() { + if (this.appDataEntity?.codeName) { + this.serviceName = this.appDataEntity.codeName; + } + if (this.model.appDEDataSetId) { + this.interfaceName = this.model.appDEDataSetId; + } + // zjm todo 实体自填模式模型 + // if (this.model.deACMode) { + // this.textName = this.model.deACMode.textFieldName || this.textName; + // this.keyName = this.model.deACMode.valueFieldName || this.keyName; + // } + } + + /** + * 获取自填模式sort排序 + */ + public getAcSort() { + // if (this.model.deACMode) { + // const { sortField, sortDir } = this.model.deACMode; + // if (sortField && sortDir) { + // return `${sortField},${sortDir}`; + // } + // } + return undefined; + } + + /** + * 加载实体数据集数据 + * + * @param {string} query 模糊匹配字符串 + * @param {IData} data 表单数据 + * @returns {*} {Promise>} + * @memberof PickerEditorController + */ + public async getServiceData( + query: string, + data: IData, + ): Promise> { + // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars + const { context, params } = this.handlePublicParams( + data, + this.context, + this.params, + ); + if (this.sort && !Object.is(this.sort, '')) { + Object.assign(params, { sort: this.sort }); + } + Object.assign(params, { query, size: 1000 }); + if (this.serviceName && this.interfaceName) { + const app = ibiz.hub.getApp(this.appDataEntity!.appId); + const deService = await app.deService.getService(this.serviceName); + const res = await deService.exec(this.interfaceName, context, params); + return res as IHttpResponse; + } + throw new RuntimeModelError(this.model, '请配置实体和实体数据集'); + } + + /** + * 计算回填数据 + * + * @author lxm + * @date 2022-10-24 16:10:24 + * @param {IData} data 选中数据 + * @returns {*} {Promise>} + */ + async calcFillDataItems( + _data: IData, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): Promise> { + // if (this.model.deACMode) { + // return DEACModeUtil.calcFillDataItems(data, this.model.deACMode); + // } + return []; + } +} diff --git a/src/editor/autocomplete/autocomplete-editor.provider.ts b/src/editor/autocomplete/autocomplete-editor.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..f07c9c3a527f5046c76ea2b17e27c752e4260268 --- /dev/null +++ b/src/editor/autocomplete/autocomplete-editor.provider.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { IAutoComplete } from '@ibiz/model-core'; +import { AutoCompleteEditorController } from './autocomplete-editor.controller'; + +/** + * 多选框列表编辑器适配器 + * + * @author lxm + * @date 2022-09-19 22:09:03 + * @export + * @class AutoCompleteEditorProvider + * @implements {EditorProvider} + */ +export class AutoCompleteEditorProvider implements IEditorProvider { + formEditor: string = 'IBizAutoComplete'; + + gridEditor: string = 'IBizAutoComplete'; + + async createController( + editorModel: IAutoComplete, + parentController: IEditorContainerController, + ): Promise { + const c = new AutoCompleteEditorController(editorModel, parentController); + await c.init(); + return c; + } +} diff --git a/src/editor/autocomplete/ibiz-autocomplete/ibiz-autocomplete.scss b/src/editor/autocomplete/ibiz-autocomplete/ibiz-autocomplete.scss new file mode 100644 index 0000000000000000000000000000000000000000..94bb482abcfe7e8ab44de6fff1262527a2110398 --- /dev/null +++ b/src/editor/autocomplete/ibiz-autocomplete/ibiz-autocomplete.scss @@ -0,0 +1,3 @@ +@include b(autocomplete) { + width: 100%; +} diff --git a/src/editor/autocomplete/ibiz-autocomplete/ibiz-autocomplete.tsx b/src/editor/autocomplete/ibiz-autocomplete/ibiz-autocomplete.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d3ff1d4a85dcc4c492455f3f44552ae6dee4946a --- /dev/null +++ b/src/editor/autocomplete/ibiz-autocomplete/ibiz-autocomplete.tsx @@ -0,0 +1,189 @@ +import { computed, defineComponent, onMounted, Ref, ref, watch } from 'vue'; +import { + getAutoCompleteProps, + getEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import './ibiz-autocomplete.scss'; +import { isEmpty } from 'ramda'; +import { AutoCompleteEditorController } from '../autocomplete-editor.controller'; + +export const IBizAutoComplete = defineComponent({ + name: 'IBizAutoComplete', + props: getAutoCompleteProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('autocomplete'); + + const c = props.controller!; + + const editorType = c.model.editorType; + + // 当前值 + const curValue: Ref | string | null> = ref(''); + + // 实体数据集 + const items: Ref = ref([]); + + // 是否显示所有数据 + const isShowAll = ref(true); + + // 自动选择Ref + const autoCompleteRef = ref(); + + watch( + () => props.value, + newVal => { + if (newVal || newVal === null) { + curValue.value = newVal; + if (newVal === null) { + curValue.value = ''; + } + const value = props.data[c.valueItem]; + const index = items.value.findIndex((item: IData) => + Object.is(item[c.keyName], value), + ); + if (index !== -1) { + return; + } + // items里匹配不到当前值项值时,生成自身的选项 + items.value = []; + if (!isEmpty(newVal) && !isEmpty(value)) { + items.value.push({ [c.textName]: newVal, [c.keyName]: value }); + } + } + }, + { immediate: true }, + ); + + // 处理选中数据后的处理逻辑 + const handleDataSelect = async (data: IData) => { + // 处理回填数据 + const dataItems = await c.calcFillDataItems(data); + if (dataItems.length) { + dataItems.forEach(dataItem => { + emit('change', dataItem.value, dataItem.name); + }); + } + + // 处理值项和本身的值 + if (c.valueItem) { + emit('change', data[c.keyName], c.valueItem); + } + emit('change', data[c.textName]); + }; + + // 往外抛值 + const onACSelect = async (item: IData) => { + await handleDataSelect(item); + isShowAll.value = true; + }; + + // 搜索 + const onSearch = async (query: string, cb?: Function) => { + if (c.appDataEntity) { + const trimQuery = query.trim(); + const res = await c.getServiceData(trimQuery, props.data); + if (res) { + items.value = res.data as IData[]; + if (cb && cb instanceof Function) { + cb(items.value); + } + } + } + }; + + // 清除 + const onClear = () => { + // zjm todo 实体自填模式相关 + // 清空回填数据 + // const dataItems = c.model.deACMode?.dataItems; + // if (dataItems?.length) { + // dataItems.forEach(dataItem => { + // emit('change', null, dataItem.name); + // }); + // } + // if (c.valueItem) { + // emit('change', null, c.valueItem); + // } + // emit('change', null); + }; + + // 聚焦 + const onFocus = (e: IData) => { + const query = isShowAll.value ? '' : e.target.value; + onSearch(query); + }; + + onMounted(() => { + // 监听autocomplete的activated + watch( + () => autoCompleteRef.value?.activated, + newVal => { + if (typeof newVal === 'boolean') { + emit('operate', newVal); + } + }, + ); + }); + + // 输入框focus时是否显示建议 + const triggerOnFocus = computed(() => { + return !Object.is('AC_NOBUTTON', editorType); + }); + + // 是否可以清空 + const autoCompleteClearable = computed(() => { + return !( + Object.is('AC_NOBUTTON', editorType) || + Object.is('AC_FS_NOBUTTON', editorType) + ); + }); + + return { + ns, + c, + curValue, + triggerOnFocus, + autoCompleteClearable, + autoCompleteRef, + onSearch, + onClear, + onFocus, + onACSelect, + }; + }, + render() { + return ( + + {{ + default: ({ item }: { item: IData }) => { + return ( +
{ + this.onACSelect(item); + }} + > + {item[this.c.textName]} +
+ ); + }, + suffix: () => {}, + }} +
+ ); + }, +}); diff --git a/src/editor/autocomplete/index.ts b/src/editor/autocomplete/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..36af1b88ae95e91af2ff268394b5c4606e412ae0 --- /dev/null +++ b/src/editor/autocomplete/index.ts @@ -0,0 +1,3 @@ +export { IBizAutoComplete } from './ibiz-autocomplete/ibiz-autocomplete'; +export * from './autocomplete-editor.controller'; +export * from './autocomplete-editor.provider'; diff --git a/src/editor/check-box-list/checkbox-list-editor.controller.ts b/src/editor/check-box-list/checkbox-list-editor.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..73db271bfa1038a7950cff214f7f7441828f772d --- /dev/null +++ b/src/editor/check-box-list/checkbox-list-editor.controller.ts @@ -0,0 +1,26 @@ +import { CodeListEditorController } from '@ibiz-template/runtime'; +import { IAppCodeList, ICheckBoxList } from '@ibiz/model-core'; + +/** + * 选项框列表编辑器控制器 + * @return {*} + * @author: zhujiamin + * @Date: 2022-08-25 10:57:58 + */ +export class CheckBoxListEditorController extends CodeListEditorController { + /** + * 代码表模型 + * @return {*} + * @author: zhujiamin + * @Date: 2023-05-24 10:55:50 + */ + codeList: IAppCodeList | undefined = undefined; + + protected async onInit(): Promise { + super.onInit(); + if (this.model.appCodeListId) { + const app = await ibiz.hub.getApp(this.context.srfappid); + this.codeList = app.codeList.getCodeList(this.model.appCodeListId); + } + } +} diff --git a/src/editor/check-box-list/checkbox-list-editor.provider.ts b/src/editor/check-box-list/checkbox-list-editor.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..590054bc5bbb9309dc346b4cfd688e925d0369a2 --- /dev/null +++ b/src/editor/check-box-list/checkbox-list-editor.provider.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { ICheckBoxList } from '@ibiz/model-core'; +import { CheckBoxListEditorController } from './checkbox-list-editor.controller'; + +/** + * 多选框列表编辑器适配器 + * + * @author lxm + * @date 2022-09-19 22:09:03 + * @export + * @class CheckBoxListEditorProvider + * @implements {EditorProvider} + */ +export class CheckBoxListEditorProvider implements IEditorProvider { + formEditor: string = 'IBizCheckboxList'; + + gridEditor: string = 'IBizGridCheckboxList'; + + async createController( + editorModel: ICheckBoxList, + parentController: IEditorContainerController, + ): Promise { + const c = new CheckBoxListEditorController(editorModel, parentController); + await c.init(); + return c; + } +} diff --git a/src/editor/check-box-list/ibiz-checkbox-list/ibiz-checkbox-list.scss b/src/editor/check-box-list/ibiz-checkbox-list/ibiz-checkbox-list.scss new file mode 100644 index 0000000000000000000000000000000000000000..16c97705ce2faa7e7da015c36bed90b5b9f1f9eb --- /dev/null +++ b/src/editor/check-box-list/ibiz-checkbox-list/ibiz-checkbox-list.scss @@ -0,0 +1,12 @@ +@include b('checkbox-list') { + @include set-component-css-var('checkbox-list', $checkbox-list); + + @include e('text') { + font-size: getCssVar('checkbox-list', 'font-size'); + color: getCssVar('checkbox-list', 'text-color'); + } + + @include m(readonly) { + color: getCssVar('text-color', 'readonly'); + } +} diff --git a/src/editor/check-box-list/ibiz-checkbox-list/ibiz-checkbox-list.tsx b/src/editor/check-box-list/ibiz-checkbox-list/ibiz-checkbox-list.tsx new file mode 100644 index 0000000000000000000000000000000000000000..97ceda7910fde2c7810f6b1ae00445560e5c496d --- /dev/null +++ b/src/editor/check-box-list/ibiz-checkbox-list/ibiz-checkbox-list.tsx @@ -0,0 +1,171 @@ +import { computed, defineComponent, ref, watch } from 'vue'; +import { + getCheckboxListProps, + getEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { isNil } from 'ramda'; +import './ibiz-checkbox-list.scss'; +import { CheckBoxListEditorController } from '../checkbox-list-editor.controller'; + +export const IBizCheckboxList = defineComponent({ + name: 'IBizCheckboxList', + props: getCheckboxListProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('checkbox-list'); + + const c = props.controller; + + const codeList = c.codeList; + + // 代码表数据 + const items = ref([]); + watch( + () => props.data, + newVal => { + c.loadCodeList(newVal).then(_codeList => { + items.value = _codeList; + }); + }, + { + immediate: true, + deep: true, + }, + ); + + // 当前模式 + const currentMode = computed(() => { + if (codeList && codeList.orMode) { + return codeList.orMode.toLowerCase(); + } + return 'str'; + }); + + // 值分隔符 + let valueSeparator = ','; + if (codeList && codeList.valueSeparator) { + valueSeparator = codeList.valueSeparator; + } + + // 选中数组 + const selectArray = computed({ + get() { + if (!isNil(props.value)) { + if (Object.is(currentMode.value, 'num') && items) { + const selectsArray: Array = []; + const num: number = + typeof props.value === 'string' + ? parseInt(props.value, 10) + : props.value; + items.value.forEach((item: IData) => { + // eslint-disable-next-line no-bitwise + if ((num & item.value) === item.value) { + selectsArray.push(item.value); + } + }); + return selectsArray; + } + if (Object.is(currentMode.value, 'str')) { + const strVal = props.value! as String; + if (strVal !== '') { + if (codeList) { + const selects: Array = + strVal.split(valueSeparator); + if (codeList.codeItemValueNumber) { + for (let i = 0, len = selects.length; i < len; i++) { + selects[i] = Number(selects[i]); + } + } + return selects; + } + return strVal.split(','); + } + } + } + return []; + }, + set(val: Array) { + let value: null | string | number | string[] = null; + if (Object.is(currentMode.value, 'num')) { + let temp: number = 0; + val.forEach(item => { + const numVal: number = + typeof item === 'string' ? parseInt(item, 10) : item; + // eslint-disable-next-line no-bitwise + temp |= numVal; + }); + value = temp; + } else if (Object.is(currentMode.value, 'str')) { + const _datas: string[] = []; + if (items.value.length > 0) { + items.value.forEach((_item: IData) => { + const index = val.findIndex((_key: unknown) => + Object.is(_item.value, _key), + ); + if (index === -1) { + return; + } + _datas.push(_item.value); + }); + value = _datas.join(valueSeparator); + } + } + emit('change', value); + }, + }); + + const onSelectArrayChange = (value: Array) => { + selectArray.value = value; + }; + + const valueText = computed(() => { + const valueArr = Array.isArray(selectArray.value) + ? selectArray.value + : [selectArray.value]; + + return items.value + .filter(item => valueArr.includes(item.value)) + .map(item => item.text) + .join(','); + }); + + return { + ns, + items, + selectArray, + valueText, + onSelectArrayChange, + }; + }, + render() { + return ( +
+ {this.readonly ? ( + this.valueText + ) : ( + + {this.items.map((item, index: number) => ( + + {item.text} + + ))} + + )} +
+ ); + }, +}); diff --git a/src/editor/check-box-list/ibiz-grid-checkbox-list/ibiz-grid-checkbox-list.tsx b/src/editor/check-box-list/ibiz-grid-checkbox-list/ibiz-grid-checkbox-list.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ac2d3c9575c6ae57290b0158151cf2a26934cb03 --- /dev/null +++ b/src/editor/check-box-list/ibiz-grid-checkbox-list/ibiz-grid-checkbox-list.tsx @@ -0,0 +1,29 @@ +import { defineComponent, h, resolveComponent } from 'vue'; +import { + getGridEditorEmits, + getGridInputProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { CheckBoxListEditorController } from '../checkbox-list-editor.controller'; + +export const IBizGridCheckboxList = defineComponent({ + name: 'IBizGridCheckboxList', + props: getGridInputProps(), + emits: getGridEditorEmits(), + setup() { + const ns = useNamespace('grid-checkbox-list'); + + return { + ns, + }; + }, + render() { + return ( +
+ {h(resolveComponent('IBizCheckboxList'), { + ...this.$props, + })} +
+ ); + }, +}); diff --git a/src/editor/check-box-list/index.ts b/src/editor/check-box-list/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..418a6ccd846ceadaa86d67d1c2e57dd18e34c283 --- /dev/null +++ b/src/editor/check-box-list/index.ts @@ -0,0 +1,4 @@ +export { IBizCheckboxList } from './ibiz-checkbox-list/ibiz-checkbox-list'; +export { IBizGridCheckboxList } from './ibiz-grid-checkbox-list/ibiz-grid-checkbox-list'; +export * from './checkbox-list-editor.controller'; +export * from './checkbox-list-editor.provider'; diff --git a/src/editor/check-box/check-box-editor.controller.ts b/src/editor/check-box/check-box-editor.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..90e66002d15b15b34fbea7a8d3fc6e31fcc88d11 --- /dev/null +++ b/src/editor/check-box/check-box-editor.controller.ts @@ -0,0 +1,11 @@ +import { EditorController } from '@ibiz-template/runtime'; +import { ICheckBox } from '@ibiz/model-core'; + +/** + * 选项框编辑器控制器 + * + * @export + * @class CheckBoxEditorController + * @extends {EditorController} + */ +export class CheckBoxEditorController extends EditorController {} diff --git a/src/editor/check-box/check-box-editor.provider.ts b/src/editor/check-box/check-box-editor.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..b5bccbb53c31ea38ead1d053f7ac38dc4ae04541 --- /dev/null +++ b/src/editor/check-box/check-box-editor.provider.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { ICheckBox } from '@ibiz/model-core'; +import { CheckBoxEditorController } from './check-box-editor.controller'; + +/** + * 选项框编辑器适配器 + * + * @author lxm + * @date 2022-09-19 22:09:03 + * @export + * @class CheckBoxEditorProvider + * @implements {EditorProvider} + */ +export class CheckBoxEditorProvider implements IEditorProvider { + formEditor: string = 'IBizCheckbox'; + + gridEditor: string = 'IBizGridCheckbox'; + + async createController( + editorModel: ICheckBox, + parentController: IEditorContainerController, + ): Promise { + const c = new CheckBoxEditorController(editorModel, parentController); + await c.init(); + return c; + } +} diff --git a/src/editor/check-box/ibiz-checkbox/ibiz-checkbox.tsx b/src/editor/check-box/ibiz-checkbox/ibiz-checkbox.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d60d2e85368d2365ae0a8c65ce0d8d98af7e5922 --- /dev/null +++ b/src/editor/check-box/ibiz-checkbox/ibiz-checkbox.tsx @@ -0,0 +1,66 @@ +import { computed, defineComponent } from 'vue'; +import { + getCheckboxProps, + getEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { CheckBoxEditorController } from '../check-box-editor.controller'; + +export const IBizCheckbox = defineComponent({ + name: 'IBizCheckbox', + props: getCheckboxProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('checkbox'); + + const c = props.controller; + + const editorModel = c.model; + + let selectValue = 1; + + let nullValue = 0; + + if (editorModel.editorParams?.selectvalue) { + selectValue = editorModel.editorParams.selectvalue; + } + if (editorModel.editorParams?.nullvalue) { + nullValue = editorModel.editorParams.nullvalue; + } + + // 当前值 + const currentVal = computed({ + get() { + if (props.value === selectValue) { + return true; + } + return false; + }, + set(val: boolean) { + let value; + if (val) { + value = selectValue; + } else { + value = nullValue; + } + emit('change', value); + }, + }); + + return { + ns, + editorModel, + currentVal, + }; + }, + render() { + return ( +
+ +
+ ); + }, +}); diff --git a/src/editor/check-box/index.ts b/src/editor/check-box/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..a3e0b8d4a36cfa9064e73c0fddec4b7d06090ff1 --- /dev/null +++ b/src/editor/check-box/index.ts @@ -0,0 +1,3 @@ +export { IBizCheckbox } from './ibiz-checkbox/ibiz-checkbox'; +export * from './check-box-editor.controller'; +export * from './check-box-editor.provider'; diff --git a/src/editor/data-picker/ibiz-mpicker/ibiz-mpicker.scss b/src/editor/data-picker/ibiz-mpicker/ibiz-mpicker.scss new file mode 100644 index 0000000000000000000000000000000000000000..8413e606926f04b934ef023696e9890bb8172f26 --- /dev/null +++ b/src/editor/data-picker/ibiz-mpicker/ibiz-mpicker.scss @@ -0,0 +1,46 @@ +@include b('mpicker') { + @include set-component-css-var('mpicker', $mpicker); + @include flex; + + input::placeholder { + color: getCssVar('mpicker', 'placeholder-color'); + } + + .el-input.is-disabled .el-input__inner { + color: getCssVar('mpicker', 'disabled-text-color'); + background-color: getCssVar('mpicker', 'disabled-bg-color'); + border-color: getCssVar('mpicker', 'disabled-border-color'); + } + + .el-select { + width: 100%; + } + + ion-icon { + cursor: pointer; + } + + @include e('buns-position') { + position: relative; + right: 11px; + display: inline-block; + } + + @include e('btns') { + position: absolute; + right: 0; + height: getCssVar('mpicker', 'height'); + color: getCssVar('mpicker', 'placeholder-color'); + @include flex(row, center, center); + } + + @include m(disabled) { + ion-icon{ + pointer-events: none; + } + } + + @include m(readonly) { + color: getCssVar('text-color', 'readonly'); + } +} diff --git a/src/editor/data-picker/ibiz-mpicker/ibiz-mpicker.tsx b/src/editor/data-picker/ibiz-mpicker/ibiz-mpicker.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c6426808794cecb2bcbdc9ac31e5c3d387be9197 --- /dev/null +++ b/src/editor/data-picker/ibiz-mpicker/ibiz-mpicker.tsx @@ -0,0 +1,221 @@ +import { ref, watch, Ref, defineComponent, computed } from 'vue'; +import { + getDataPickerProps, + getEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import './ibiz-mpicker.scss'; +import { PickerEditorController } from '../picker-editor.controller'; + +export const IBizMPicker = defineComponent({ + name: 'IBizMPicker', + props: getDataPickerProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('mpicker'); + + const c = props.controller!; + + // 当前表单项绑定值key的集合 + const curValue: Ref> = ref([]); + + // 实体数据集 + const items: Ref = ref([]); + + // 选中项key-value键值对 + const selectItems: Ref = ref([]); + + // 下拉是否打开 + const open = ref(false); + + // 加载中 + const loading: Ref = ref(false); + + // 监听传入值 + watch( + () => props.value, + newVal => { + curValue.value = []; + selectItems.value = []; + if (newVal) { + selectItems.value = JSON.parse(newVal); + selectItems.value.forEach((item: IData) => { + curValue.value.push(item.srfkey); + + // 选项没有的时候补充选项 + const index = items.value.findIndex(i => + Object.is(i.srfkey, item.srfkey), + ); + if (index < 0) { + items.value.push({ + srfkey: item.srfkey, + srfmajortext: item.srfmajortext, + }); + } + }); + } + }, + { immediate: true, deep: true }, + ); + + // 处理视图关闭,往外抛值 + const handleOpenViewClose = (result: IData[]) => { + // 抛出值集合 + const valArr: IData[] = []; + if (result && Array.isArray(result)) { + result.forEach((select: IData) => { + // 回显并且回来的选中值只有srfkey和srfmajortext,所以|| + const formattedItem = { + srfkey: select[c.keyName] || select.srfkey, + srfmajortext: select[c.textName] || select.srfmajortext, + }; + valArr.push(formattedItem); + + // 选项不存在的补充到选项里 + const index = items.value.findIndex(item => + Object.is(item[c.keyName], select[c.keyName]), + ); + if (index < 0) { + items.value.push(formattedItem); + } + }); + } + const value = valArr.length > 0 ? JSON.stringify(valArr) : ''; + emit('change', value); + }; + + // 打开数据选择视图 + const openPickUpView = async () => { + let selectedData; + if (selectItems.value.length) { + selectedData = JSON.stringify(selectItems.value); + } + const res = await c.openPickUpView(props.data!, selectedData); + if (res) { + handleOpenViewClose(res); + } + }; + + // 下拉选中回调 + const onSelect = (selects: string[]) => { + const valArr: Array = []; + if (selects.length > 0) { + selects.forEach((select: string) => { + const findItem = items.value.find(item => + Object.is(item.srfkey, select), + ); + if (findItem) { + valArr.push({ + srfkey: findItem.srfkey, + srfmajortext: findItem.srfmajortext, + }); + } + }); + const value = valArr.length > 0 ? JSON.stringify(valArr) : ''; + emit('change', value); + } else { + emit('change', ''); + } + }; + + // 搜索 + const onSearch = async (query: string) => { + if (c.appDataEntity) { + loading.value = true; + try { + const trimQuery = query.trim(); + const res = await c.getServiceData(trimQuery, props.data!); + loading.value = false; + if (res) { + items.value = res.data.map(item => ({ + srfkey: item[c.keyName], + srfmajortext: item[c.textName], + })); + } + } catch (error) { + loading.value = false; + } + } + }; + + // 下拉打开 + const onOpenChange = (flag: boolean) => { + open.value = flag; + emit('operate', flag); + if (open.value) { + items.value = []; + onSearch(''); + } + }; + + // 信息展示,只显示名称。 + const valueText = computed(() => { + return selectItems.value + .map(item => { + return item.srfmajortext; + }) + .join(','); + }); + return { + ns, + c, + curValue, + loading, + items, + valueText, + onSearch, + onOpenChange, + onSelect, + openPickUpView, + }; + }, + render() { + return ( +
+ {this.readonly && this.valueText} + {!this.readonly && ( + + {this.items.map((item, index) => { + return ( + + ); + })} + + )} + {!this.readonly && ( +
+
+ {this.c.pickupView ? ( + + ) : null} +
+
+ )} +
+ ); + }, +}); diff --git a/src/editor/data-picker/ibiz-picker-dropdown/ibiz-picker-dropdown.scss b/src/editor/data-picker/ibiz-picker-dropdown/ibiz-picker-dropdown.scss new file mode 100644 index 0000000000000000000000000000000000000000..c9340bb06aaffbdcf1d1c63fe48842a9b33164f4 --- /dev/null +++ b/src/editor/data-picker/ibiz-picker-dropdown/ibiz-picker-dropdown.scss @@ -0,0 +1,27 @@ +@include b('picker-dropdown') { + @include set-component-css-var('picker-dropdown', $picker-dropdown); + + .el-select { + width: 100%; + } + + .el-input .el-input__inner { + font-size: getCssVar('picker-dropdown', 'font-size'); + color: getCssVar('picker-dropdown', 'text-color'); + border-color: getCssVar('picker-dropdown', 'border-color'); + } + + .el-input .el-input__inner::placeholder { + color: getCssVar('picker-dropdown', 'placeholder-color'); + } + + .el-input.is-disabled .el-input__inner { + color: getCssVar('picker-dropdown', 'disabled-text-color'); + background-color: getCssVar('picker-dropdown', 'disabled-bg-color'); + border-color: getCssVar('picker-dropdown', 'disabled-border-color'); + } + + @include m(readonly) { + color: getCssVar('text-color', 'readonly'); + } +} diff --git a/src/editor/data-picker/ibiz-picker-dropdown/ibiz-picker-dropdown.tsx b/src/editor/data-picker/ibiz-picker-dropdown/ibiz-picker-dropdown.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4de698ecabea2f3811b940e79c0779e88e12c3ca --- /dev/null +++ b/src/editor/data-picker/ibiz-picker-dropdown/ibiz-picker-dropdown.tsx @@ -0,0 +1,195 @@ +import { ref, Ref, watch, defineComponent } from 'vue'; +import { + getDataPickerProps, + getEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { isNil } from 'ramda'; +import './ibiz-picker-dropdown.scss'; +import { PickerEditorController } from '../picker-editor.controller'; + +export const IBizPickerDropDown = defineComponent({ + name: 'IBizPickerDropDown', + props: getDataPickerProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('picker-dropdown'); + + const c = props.controller; + + // 当前值 + const curValue: Ref | string | null> = ref(''); + + // 实体数据集 + const items: Ref = ref([]); + + watch( + () => props.value, + newVal => { + if (newVal || newVal === null) { + curValue.value = newVal; + const value = props.data[c.valueItem]; + const index = items.value.findIndex((item: IData) => + Object.is(item[c.keyName], value), + ); + if (index !== -1) { + return; + } + // items里匹配不到当前值项值时,生成自身的选项 + items.value = []; + if (!isNil(newVal) && !isNil(value)) { + items.value.push({ [c.textName]: newVal, [c.keyName]: value }); + } + } + }, + { immediate: true }, + ); + + // 获取关联数据项值 + const refValue = ref(''); + + watch( + () => props.data[c.valueItem], + (newVal, oldVal) => { + if (newVal !== oldVal) { + refValue.value = newVal; + // 如果值项被清空了,主文本也需清空 + if (newVal === null) { + emit('change', null); + } + } + }, + { immediate: true, deep: true }, + ); + + // 加载中 + const loading: Ref = ref(false); + + // 往外抛值 + const onACSelect = async (item: IData) => { + // 处理回填数据 + const dataItems = await c.calcFillDataItems(item); + if (dataItems.length) { + dataItems.forEach(dataItem => { + emit('change', dataItem.value, dataItem.name); + }); + } + + // 处理值项和本身的值 + if (c.valueItem) { + emit('change', item[c.keyName], c.valueItem); + } + emit('change', item[c.textName]); + }; + + // 值变更 + const onSelect = (select: string | Array) => { + const index = items.value.findIndex(item => + Object.is(item[c.keyName], select), + ); + if (index >= 0) { + onACSelect(items.value[index]); + } + }; + + // 在搜索中时,再次触发搜索记录搜索值,等待上次搜索触发完成后再次搜索 + let waitQuery: string | null = null; + + // 搜索 + const onSearch = async (query: string) => { + if (c.appDataEntity && loading.value === false) { + loading.value = true; + try { + const trimQuery = query.trim(); + const res = await c.getServiceData(trimQuery, props.data!); + if (res) { + items.value = res.data as IData[]; + } + } finally { + loading.value = false; + if (waitQuery != null) { + const selfQuery = waitQuery; + waitQuery = null; + await onSearch(selfQuery); + } + } + } else { + waitQuery = query; + } + }; + + // 下拉打开 + const onOpenChange = (isOpen: boolean) => { + emit('operate', isOpen); + if (isOpen) { + items.value = []; + onSearch(''); + } + }; + + // 清除 + const onClear = () => { + // zjm todo 实体自填模式相关 + // 清空回填数据 + // const dataItems = c.model.deACMode?.dataItems; + // if (dataItems?.length) { + // dataItems.forEach(dataItem => { + // emit('change', null, dataItem.name); + // }); + // } + // if (c.valueItem) { + // emit('change', null, c.valueItem); + // } + // emit('change', null); + }; + return { + ns, + c, + refValue, + loading, + items, + onOpenChange, + onClear, + onSelect, + onSearch, + }; + }, + render() { + if (this.readonly) { + return ( +
{this.value}
+ ); + } + return ( +
+ {this.readonly ? ( + this.value + ) : ( + + {this.items.map((item, index) => { + return ( + + ); + })} + + )} +
+ ); + }, +}); diff --git a/src/editor/data-picker/ibiz-picker-embed-view/ibiz-picker-embed-view.scss b/src/editor/data-picker/ibiz-picker-embed-view/ibiz-picker-embed-view.scss new file mode 100644 index 0000000000000000000000000000000000000000..1e11ef926c4b3ba9f1c97efcf1e03f7a632496af --- /dev/null +++ b/src/editor/data-picker/ibiz-picker-embed-view/ibiz-picker-embed-view.scss @@ -0,0 +1,6 @@ +@include b(picker-embed-view) { + @include b(picker-embed-view-value) { + height: 32px; + line-height: 32px; + } +} diff --git a/src/editor/data-picker/ibiz-picker-embed-view/ibiz-picker-embed-view.tsx b/src/editor/data-picker/ibiz-picker-embed-view/ibiz-picker-embed-view.tsx new file mode 100644 index 0000000000000000000000000000000000000000..af2c0bb15f75d1befe32848fc90274d98c4db207 --- /dev/null +++ b/src/editor/data-picker/ibiz-picker-embed-view/ibiz-picker-embed-view.tsx @@ -0,0 +1,105 @@ +import { defineComponent, ref, resolveComponent, watch, h } from 'vue'; +import { + getDataPickerProps, + getEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import './ibiz-picker-embed-view.scss'; +import { clone } from 'ramda'; +import { EventBase, ViewMode } from '@ibiz-template/runtime'; +import { PickerEditorController } from '../picker-editor.controller'; + +export const IBizPickerEmbedView = defineComponent({ + name: 'IBizPickerEmbedView', + props: getDataPickerProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('picker-embed-view'); + + const c = props.controller!; + + // 视图上下文 + const context = ref(clone(c.context)); + + // 视图参数 + const params = ref(clone(c.params)); + + // 设置视图参数 + const setViewParam = () => { + const { context: _context, params: _params } = c.handlePublicParams( + props.data, + c.context, + c.params, + ); + Object.assign(context.value, _context); + Object.assign(params.value, _params); + }; + setViewParam(); + + // 监听表单数据,发生变化时重写计算视图参数 + watch( + () => props.data, + (newVal, oldVal) => { + if (newVal !== oldVal) { + setViewParam(); + } + }, + ); + + // 视图数据改变 + const onViewDataChange = (event: IData[]) => { + let tempValue: string = ''; + let temText: string = ''; + if (event && Array.isArray(event)) { + event.forEach((select: IData) => { + tempValue += `${select.srfkey},`; + temText += `${select.srfmajortext},`; + }); + tempValue = tempValue.substring(0, tempValue.length - 1); + temText = temText.substring(0, temText.length - 1); + if (c.valueItem) { + emit('change', tempValue, c.valueItem); + } + emit('change', temText); + } + }; + + // 绑定事件 + const onSelectionChange = (event: EventBase) => { + onViewDataChange(event.data); + }; + + return { ns, c, context, params, onSelectionChange }; + }, + render() { + const viewShell = resolveComponent('ViewShell'); + return ( +
+ {this.c.pickupView ? ( + [ +
+ {h(viewShell, { + context: this.context, + params: this.params, + modal: { mode: ViewMode.EMBED }, + modelData: this.c.pickupView, + onSelectionChange: this.onSelectionChange, + })} +
, +
+ {this.$props.value ? ( + this.$props.value.split(',').map(item => { + return {item}; ; + }) + ) : ( + {this.c.placeHolder} + )} +
, + ] + ) : ( +
{this.c.placeHolder}
+ )} +
+ ); + }, +}); diff --git a/src/editor/data-picker/ibiz-picker-link/ibiz-picker-link.scss b/src/editor/data-picker/ibiz-picker-link/ibiz-picker-link.scss new file mode 100644 index 0000000000000000000000000000000000000000..b936a434454c005f325fc03590a2d85a3a392936 --- /dev/null +++ b/src/editor/data-picker/ibiz-picker-link/ibiz-picker-link.scss @@ -0,0 +1,11 @@ +@include b(picker-link) { + color: getCssVar('color', 'primary'); + cursor: pointer; + + @include m(disabled) { + cursor: not-allowed; + } + @include m(readonly) { + color: getCssVar('text-color', 'readonly'); + } +} diff --git a/src/editor/data-picker/ibiz-picker-link/ibiz-picker-link.tsx b/src/editor/data-picker/ibiz-picker-link/ibiz-picker-link.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b015134d4ee8e7c7ecc0fe21cffe67b82e190a88 --- /dev/null +++ b/src/editor/data-picker/ibiz-picker-link/ibiz-picker-link.tsx @@ -0,0 +1,59 @@ +import { defineComponent, ref, Ref, watch } from 'vue'; +import { + getDataPickerProps, + getEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import './ibiz-picker-link.scss'; +import { PickerEditorController } from '../picker-editor.controller'; + +export const IBizPickerLink = defineComponent({ + name: 'IBizPickerLink', + props: getDataPickerProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('picker-link'); + + const c = props.controller!; + + const curValue: Ref = ref(''); + + watch( + () => props.value, + (newVal, oldVal) => { + if (newVal && newVal !== oldVal) { + curValue.value = `${newVal}`; + } + }, + { immediate: true }, + ); + + // 处理视图关闭,往外抛值 + const handleOpenViewClose = (result: IData[]) => { + const item: IData = {}; + if (result && Array.isArray(result)) { + Object.assign(item, result[0]); + } + if (c.valueItem) { + emit('change', item[c.keyName], c.valueItem); + } + emit('change', item[c.textName]); + }; + + // 打开数据链接视图 + const openLinkView = async () => { + const res = await c.openLinkView(props.data!); + if (res) { + handleOpenViewClose(res); + } + }; + return { ns, openLinkView, curValue }; + }, + render() { + return ( + + ); + }, +}); diff --git a/src/editor/data-picker/ibiz-picker-select-view/ibiz-picker-select-view.scss b/src/editor/data-picker/ibiz-picker-select-view/ibiz-picker-select-view.scss new file mode 100644 index 0000000000000000000000000000000000000000..b503e4394c9861db8d574e5c0d4fba06e9743627 --- /dev/null +++ b/src/editor/data-picker/ibiz-picker-select-view/ibiz-picker-select-view.scss @@ -0,0 +1,11 @@ +@include b(picker-select-view) { + width: 100%; + + .el-dropdown { + width: 100%; + } + + .el-select { + width: 100%; + } +} diff --git a/src/editor/data-picker/ibiz-picker-select-view/ibiz-picker-select-view.tsx b/src/editor/data-picker/ibiz-picker-select-view/ibiz-picker-select-view.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ad121378d3aa845327e855073b647f131c9c51bb --- /dev/null +++ b/src/editor/data-picker/ibiz-picker-select-view/ibiz-picker-select-view.tsx @@ -0,0 +1,365 @@ +import { + defineComponent, + onMounted, + ref, + Ref, + resolveComponent, + watch, + h, +} from 'vue'; +import { + getDataPickerProps, + getEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import './ibiz-picker-select-view.scss'; +import { clone } from 'ramda'; +import { EventBase, ViewMode } from '@ibiz-template/runtime'; +import { PickerEditorController } from '../picker-editor.controller'; + +export const IBizPickerSelectView = defineComponent({ + name: 'IBizPickerSelectView', + props: getDataPickerProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('picker-select-view'); + + const c = props.controller!; + + const editorModel = c.model; + + // 是否单选 + const singleSelect = ref(true); + if (editorModel.editorParams) { + if (editorModel.editorParams.multiple) { + singleSelect.value = !c.handleStringToBoolean( + editorModel.editorParams.multiple, + ); + } + } + + // 当前多选框选中值的key集合 + const keySet: Ref = ref([]); + + // 所有操作过的下拉选项对象集合 + const items: Ref = ref([]); + + // 选中数据 + let selectedData = []; + + // 输入框值(搜索值) + const queryValue: Ref = ref(''); + + // 下拉显示控制变量 + const visible = ref(false); + + // 选择视图宽度 + const pickViewWidth = ref('auto'); + + // 视图上下文 + const context = ref(clone(c.context)); + + // 视图参数 + const params = ref(clone(c.params)); + + // 输入框change事件 + const onInputChange = () => { + context.value = { query: queryValue.value, ...context.value }; + }; + + // 当前输入框dom + const domRef = ref(); + + // 改变显示事件 + const triggerMenu = (show?: boolean) => { + if (props.disabled) { + return; + } + if (!show) { + visible.value = !visible.value; + } else { + visible.value = show; + } + }; + + watch( + () => props.value, + newVal => { + if (singleSelect.value) { + queryValue.value = newVal || ''; + if (!props.data || !c.valueItem || !props.data[c.valueItem]) { + ibiz.log.error('值项异常'); + } else { + selectedData = [ + { srfkey: props.data[c.valueItem], srfmajortext: props.value }, + ]; + params.value.selectedData = selectedData; + } + } else { + keySet.value = []; + const selectItems: IData[] = []; + if (newVal) { + if (!props.data || !c.valueItem || !props.data[c.valueItem]) { + ibiz.log.error('值项异常'); + } else { + const tempValue = props.data[c.valueItem].split(','); + const tempText = newVal.split(','); + tempValue.forEach((srfkey: string, index: number) => { + selectItems.push({ + srfmajortext: tempText[index], + srfkey, + }); + }); + items.value = []; + selectItems.forEach((item: IData) => { + keySet.value.push(item.srfkey); + const index = items.value.findIndex(i => + Object.is(i.srfkey, item.srfkey), + ); + if (index < 0) { + items.value.push({ + srfmajortext: item.srfmajortext, + srfkey: item.srfkey, + }); + } + }); + } + } + selectedData = selectItems; + params.value.selectedData = selectedData; + } + }, + { + immediate: false, + deep: true, + }, + ); + + onMounted(() => { + if (domRef.value) { + pickViewWidth.value = `${domRef.value.offsetWidth}px`; + } + }); + + // 清除 + const onClear = () => { + // zjm todo 实体自填模式相关 + // 清空回填数据 + // const dataItems = c.model.deACMode?.dataItems; + // if (dataItems?.length) { + // dataItems.forEach(dataItem => { + // emit('change', null, dataItem.name); + // }); + // } + // if (c.valueItem) { + // emit('change', null, c.valueItem); + // } + // emit('change', null); + }; + + // 视图数据改变 + const onViewDataChange = (event: IData[]) => { + if (event.length === 0) { + onClear(); + return; + } + if (singleSelect.value) { + visible.value = false; + if (c.valueItem) { + const tempValue = event[0][c.keyName] + ? event[0][c.keyName] + : event[0].srfkey; + emit('change', tempValue, c.valueItem); + } + const tempText = event[0][c.textName] + ? event[0][c.textName] + : event[0].srfmajortext; + emit('change', tempText); + } else { + let tempValue: string = ''; + let tempText: string = ''; + if (event && Array.isArray(event)) { + items.value = []; + event.forEach((select: IData) => { + const srfkey = select[c.keyName] + ? select[c.keyName] + : select.srfkey; + tempValue += `${srfkey},`; + const srfmajortext = select[c.textName] + ? select[c.textName] + : select.srfmajortext; + tempText += `${srfmajortext},`; + const index = items.value.findIndex(item => + Object.is(item.srfkey, srfkey), + ); + if (index < 0) { + items.value.push({ srfmajortext, srfkey }); + } + }); + } + tempValue = tempValue.substring(0, tempValue.length - 1); + tempText = tempText.substring(0, tempText.length - 1); + if (c.valueItem) { + emit('change', tempValue, c.valueItem); + } + emit('change', tempText); + } + }; + + // 打开数据链接视图 + const openLinkView = async (e: MouseEvent) => { + e.stopPropagation(); + const res = await c.openLinkView(props.data); + if (res) { + onViewDataChange(res); + } + }; + + // 下拉选中回调 + const onSelectChange = (selects: string[]) => { + const val: IData[] = []; + if (selects.length > 0) { + selects.forEach((select: string) => { + const index = items.value.findIndex(item => + Object.is(item.srfkey, select), + ); + if (index >= 0) { + val.push(items.value[index]); + } + }); + } + let tempValue: string = ''; + let tempText: string = ''; + val.forEach((select: IData) => { + const srfkey = select[c.keyName] ? select[c.keyName] : select.srfkey; + tempValue += `${srfkey},`; + const srfmajortext = select[c.textName] + ? select[c.textName] + : select.srfmajortext; + tempText += `${srfmajortext},`; + }); + tempValue = tempValue.substring(0, tempValue.length - 1); + tempText = tempText.substring(0, tempText.length - 1); + // 处理值项和本身的值 + if (c.valueItem) { + emit('change', tempValue, c.valueItem); + } + emit('change', tempText); + }; + + // 远程搜索 + const remoteMethod = (e: string) => { + queryValue.value = e; + }; + + // 绑定事件 + const onSelectionChange = (event: EventBase) => { + onViewDataChange(event.data); + }; + + return { + ns, + c, + singleSelect, + keySet, + items, + queryValue, + visible, + pickViewWidth, + context, + params, + domRef, + onInputChange, + triggerMenu, + onViewDataChange, + onClear, + openLinkView, + onSelectChange, + remoteMethod, + onSelectionChange, + }; + }, + render() { + return ( +
+ + {{ + default: () => { + return this.singleSelect ? ( + { + this.triggerMenu(true); + }} + > + {{ + suffix: () => { + return [ + this.queryValue && !this.disabled && !this.readonly && ( + + ), + this.c.linkView && ( + + ), + ]; + }, + }} + + ) : ( + { + this.triggerMenu(true); + }} + disabled={this.disabled || this.readonly} + > + {this.items.map((item: IData, index: number) => { + return ( + + ); + })} + + ); + }, + dropdown: () => { + const viewShell = resolveComponent('ViewShell'); + return ( + + {h(viewShell, { + context: this.context, + params: this.params, + modal: { mode: ViewMode.EMBED }, + modelData: this.c.pickupView, + style: { height: '100%', width: this.pickViewWidth }, + onSelectionChange: this.onSelectionChange, + })} + + ); + }, + }} + +
+ ); + }, +}); diff --git a/src/editor/data-picker/ibiz-picker/ibiz-picker.scss b/src/editor/data-picker/ibiz-picker/ibiz-picker.scss new file mode 100644 index 0000000000000000000000000000000000000000..738da3c9e22cb806094a102e07d86cfb61654165 --- /dev/null +++ b/src/editor/data-picker/ibiz-picker/ibiz-picker.scss @@ -0,0 +1,74 @@ +@include b('picker') { + @include set-component-css-var('picker', $picker); + + .el-input .el-input__inner { + font-size: getCssVar('picker', 'font-size'); + color: getCssVar('picker', 'text-color'); + border-color: getCssVar('picker', 'border-color'); + } + + .el-input .el-input__inner::placeholder { + color: getCssVar('picker', 'placeholder-color'); + } + + .el-input.is-disabled .el-input__inner { + color: getCssVar('picker', 'disabled-text-color'); + background-color: getCssVar('picker', 'disabled-bg-color'); + border-color: getCssVar('picker', 'disabled-border-color'); + } + + ion-icon { + cursor: pointer; + } + + @include e('buns-position') { + position: relative; + display: inline-block; + } + + @include e('btns') { + position: absolute; + right: 0; + @include flex; + + height: getCssVar('picker', 'height'); + + span { + @include flex($direction: column, $horizontal: center); + } + } + + @include e('autocomplete') { + @include flex; + + .el-autocomplete { + width: 100%; + } + } + + @include e('transfer-item') { + padding: 5px; + cursor: pointer; + + &:hover { + background-color: getCssVar('picker', 'hover-bg-color'); + } + } + + @include e('link') { + width: 32px; + height: 32px; + padding: 0 6px; + font-size: 16px; + } + + @include m(disabled) { + ion-icon{ + pointer-events: none; + } + } + + @include m(readonly) { + color: getCssVar('text-color', 'readonly'); + } +} diff --git a/src/editor/data-picker/ibiz-picker/ibiz-picker.tsx b/src/editor/data-picker/ibiz-picker/ibiz-picker.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a5e5e0e6a35de3fa2da7f14ebb44a20dd4244a6f --- /dev/null +++ b/src/editor/data-picker/ibiz-picker/ibiz-picker.tsx @@ -0,0 +1,267 @@ +import { ref, watch, Ref, defineComponent, onMounted } from 'vue'; +import { + getDataPickerProps, + getEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { isEmpty } from 'ramda'; +import './ibiz-picker.scss'; +import { PickerEditorController } from '../picker-editor.controller'; + +export const IBizPicker = defineComponent({ + name: 'IBizPicker', + props: getDataPickerProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('picker'); + + const c = props.controller!; + + // 当前值 + const curValue: Ref | string | null> = ref(''); + + // 实体数据集 + const items: Ref = ref([]); + + // 是否显示所有数据 + const isShowAll = ref(true); + + // 自动选择Ref + const pickerAutoCompleteRef = ref(); + + watch( + () => props.value, + newVal => { + if (newVal || newVal === null) { + curValue.value = newVal; + if (newVal === null) { + curValue.value = ''; + } + const value = props.data[c.valueItem]; + const index = items.value.findIndex((item: IData) => + Object.is(item[c.keyName], value), + ); + if (index !== -1) { + return; + } + // items里匹配不到当前值项值时,生成自身的选项 + items.value = []; + if (!isEmpty(newVal) && !isEmpty(value)) { + items.value.push({ [c.textName]: newVal, [c.keyName]: value }); + } + } + }, + { immediate: true }, + ); + + // 处理选中数据后的处理逻辑 + const handleDataSelect = async (data: IData) => { + // 处理回填数据 + const dataItems = await c.calcFillDataItems(data); + if (dataItems.length) { + dataItems.forEach(dataItem => { + emit('change', dataItem.value, dataItem.name); + }); + } + + // 处理值项和本身的值 + if (c.valueItem) { + emit('change', data[c.keyName], c.valueItem); + } + emit('change', data[c.textName]); + }; + + // 打开数据选择视图 + const openPickUpView = async (e: MouseEvent) => { + e.stopPropagation(); + const res = await c.openPickUpView(props.data); + if (res && res[0]) { + await handleDataSelect(res[0]); + } + }; + + // 打开数据链接视图 + const openLinkView = async (e: MouseEvent) => { + e.stopPropagation(); + const res = await c.openLinkView(props.data); + if (res && res[0]) { + await handleDataSelect(res[0]); + } + }; + + // 往外抛值 + const onACSelect = async (item: IData) => { + await handleDataSelect(item); + isShowAll.value = true; + }; + + // 搜索 + const onSearch = async (query: string, cb?: Function) => { + if (c.appDataEntity) { + const trimQuery = query.trim(); + const res = await c.getServiceData(trimQuery, props.data); + if (res) { + items.value = res.data as IData[]; + if (cb && cb instanceof Function) { + cb(items.value); + } + } + } + }; + + // 清除 + const onClear = () => { + // zjm todo 实体自填模式相关 + // 清空回填数据 + // const dataItems = c.model.deACMode?.dataItems; + // if (dataItems?.length) { + // dataItems.forEach(dataItem => { + // emit('change', null, dataItem.name); + // }); + // } + // if (c.valueItem) { + // emit('change', null, c.valueItem); + // } + // emit('change', null); + }; + + // 聚焦 + const onFocus = (e: IData) => { + const query = isShowAll.value ? '' : e.target.value; + onSearch(query); + }; + + const closeCircle = (c.linkView ? 1 : 0) + (c.pickupView ? 1 : 0); + + if (!c.noAC) { + onMounted(() => { + // 监听autocomplete的activated + watch( + () => pickerAutoCompleteRef.value?.activated, + newVal => { + if (typeof newVal === 'boolean') { + emit('operate', newVal); + } + }, + ); + }); + } + + return { + ns, + c, + curValue, + items, + pickerAutoCompleteRef, + openPickUpView, + openLinkView, + onACSelect, + onSearch, + onClear, + onFocus, + closeCircle, + }; + }, + render() { + if (this.readonly) { + return ( +
{this.value}
+ ); + } + return ( +
+ {this.c.noAC ? ( + + {{ + suffix: () => { + if (this.$slots.append) { + return this.$slots.append({}); + } + if (this.c.noButton) { + return; + } + return [ + this.c.pickupView ? ( + + ) : null, + this.c.linkView ? ( + + ) : null, + ]; + }, + }} + + ) : ( +
+ + {{ + default: ({ item }: { item: IData }) => { + if (this.$slots.append) { + return this.$slots.append({}); + } + return ( +
{ + this.onACSelect(item); + }} + > + {item[this.c.textName]} +
+ ); + }, + suffix: () => { + if (this.c.noButton) { + return; + } + return [ + this.c.pickupView ? ( + + ) : null, + this.c.linkView ? ( + + ) : null, + ]; + }, + }} +
+
+ )} +
+ ); + }, +}); diff --git a/src/editor/data-picker/index.ts b/src/editor/data-picker/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..aa905262922064368bf8e899b981119b5c962b57 --- /dev/null +++ b/src/editor/data-picker/index.ts @@ -0,0 +1,8 @@ +export { IBizPicker } from './ibiz-picker/ibiz-picker'; +export { IBizMPicker } from './ibiz-mpicker/ibiz-mpicker'; +export { IBizPickerDropDown } from './ibiz-picker-dropdown/ibiz-picker-dropdown'; +export { IBizPickerLink } from './ibiz-picker-link/ibiz-picker-link'; +export { IBizPickerEmbedView } from './ibiz-picker-embed-view/ibiz-picker-embed-view'; +export { IBizPickerSelectView } from './ibiz-picker-select-view/ibiz-picker-select-view'; +export * from './picker-editor.controller'; +export * from './picker-editor.provider'; diff --git a/src/editor/data-picker/picker-editor.controller.ts b/src/editor/data-picker/picker-editor.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..cdaa2b5fc0bc7e9ae6c62f65e1b758daa515d910 --- /dev/null +++ b/src/editor/data-picker/picker-editor.controller.ts @@ -0,0 +1,265 @@ +import { IHttpResponse, RuntimeModelError } from '@ibiz-template/core'; +import { EditorController, OpenAppViewCommand } from '@ibiz-template/runtime'; +import { IAppDataEntity, IAppView, IPicker } from '@ibiz/model-core'; + +/** + * 数据选择编辑器控制器 + * @return {*} + * @author: zhujiamin + * @Date: 2022-08-25 10:57:58 + */ +export class PickerEditorController extends EditorController { + /** + * 是否多选 + */ + public multiple = false; + + /** + * 占位 + */ + public placeHolder = '请选择数据'; + + /** + *选择视图相关参数 + */ + public pickupView: IAppView | null = null; + + /** + *链接视图相关参数 + */ + public linkView: IAppView | null = null; + + /** + *值项 + */ + public valueItem = ''; + + /** + * 主键属性名称 + */ + public keyName: string = ''; + + /** + * 主文本属性名称 + */ + public textName: string = ''; + + /** + * 实体codeName + */ + public serviceName: string = ''; + + /** + * 数据集codeName + */ + public interfaceName: string = ''; + + /** + * 自填模式sort排序 + */ + public sort: string | undefined = ''; + + /** + * 不支持AC(根据编辑器类型得) + */ + public noAC: boolean = false; + + /** + * 不支持按钮(根据编辑器类型得) + */ + public noButton: boolean = false; + + /** + * 编辑器实体 + */ + public appDataEntity: IAppDataEntity | null = null; + + protected async onInit(): Promise { + super.onInit(); + this.initParams(); + // zjm todo 值项名称父模型没有valueItemName + this.valueItem = this.parent.valueItemName?.toLowerCase() || ''; + if (this.model.appDataEntityId) { + this.appDataEntity = await ibiz.hub.getAppDataEntity( + this.model.appDataEntityId, + this.context.srfappid, + )!; + if (this.appDataEntity) { + this.keyName = this.appDataEntity?.keyAppDEFieldId || ''; + this.textName = this.appDataEntity?.majorAppDEFieldId || ''; + this.getAcParams(); + this.sort = this.getAcSort(); + } + } + await this.initPickupViewParams(); + await this.initLinkViewParams(); + } + + /** + * 初始化noAc和noButton + */ + public initParams() { + switch (this.model.editorType) { + case 'PICKEREX_NOAC': + case 'PICKEREX_NOAC_LINK': + this.noAC = true; + break; + case 'PICKEREX_NOBUTTON': + this.noButton = true; + break; + default: + this.noButton = false; + this.noAC = false; + } + } + + /** + * 初始化选择视图相关参数 + */ + public async initPickupViewParams() { + if (this.model.pickupAppViewId) { + this.pickupView = await ibiz.hub.getAppView(this.model.pickupAppViewId); + } + } + + /** + * 初始化链接视图相关参数 + */ + public async initLinkViewParams() { + if (this.model.linkAppViewId) { + this.linkView = await ibiz.hub.getAppView(this.model.linkAppViewId); + } + } + + /** + * 获取Ac参数 + */ + public getAcParams() { + if (this.appDataEntity?.codeName) { + this.serviceName = this.appDataEntity.codeName; + } + if (this.model.appDEDataSetId) { + this.interfaceName = this.model.appDEDataSetId; + } + // zjm todo 实体自填模式模型 + // if (this.model.deACMode) { + // this.textName = this.model.deACMode.textFieldName || this.textName; + // this.keyName = this.model.deACMode.valueFieldName || this.keyName; + // } + } + + /** + * 获取自填模式sort排序 + */ + public getAcSort() { + // if (this.model.deACMode) { + // const { sortField, sortDir } = this.model.deACMode; + // if (sortField && sortDir) { + // return `${sortField},${sortDir}`; + // } + // } + return undefined; + } + + /** + * 加载实体数据集数据 + * + * @param {string} query 模糊匹配字符串 + * @param {IData} data 表单数据 + * @returns {*} {Promise>} + * @memberof PickerEditorController + */ + public async getServiceData( + query: string, + data: IData, + ): Promise> { + // 附加编辑器视图参数 + const tempParams = { ...this.params, query, size: 1000 }; + if (this.sort && !Object.is(this.sort, '')) { + Object.assign(tempParams, { sort: this.sort }); + } + + const { context, params } = this.handlePublicParams( + data, + this.context, + tempParams, + ); + if (this.serviceName && this.interfaceName) { + const app = ibiz.hub.getApp(this.appDataEntity!.appId); + const deService = await app.deService.getService(this.serviceName); + const res = await deService.exec(this.interfaceName, context, params); + return res as IHttpResponse; + } + throw new RuntimeModelError(this.model, '请配置实体和实体数据集'); + } + + /** + * 打开数据选择视图 + * + * @param {IData} data 数据对象 + * @param {IData[]} selectedData 选中项集合 + * @returns {*} {(Promise)} + * @memberof PickerEditorController + */ + public async openPickUpView( + data: IData, + selectedData?: string, + ): Promise { + const { context, params } = this.handlePublicParams( + data, + this.context, + this.params, + ); + if (selectedData) { + params.selectedData = selectedData; + } + if (!this.pickupView) { + throw new RuntimeModelError(this.model, '请配置数据选择视图'); + } + const res = await ibiz.openView.modal(this.pickupView, context, params); + if (res && res.ok && res.data) { + return res.data; + } + ibiz.log.debug('模态取消或关闭异常', res); + } + + /** + * 打开数据链接视图 + */ + public async openLinkView(data: IData): Promise { + const { context, params } = this.handlePublicParams( + data, + this.context, + this.params, + ); + context.srfkey = data[this.valueItem]; + const { linkAppViewId } = this.model; + if (!linkAppViewId) { + throw new RuntimeModelError(this.model, '请配置数据链接视图'); + } + return ibiz.commands.execute( + OpenAppViewCommand.TAG, + linkAppViewId, + context, + params, + ); + } + + /** + * 计算回填数据 + * + * @author lxm + * @date 2022-10-24 16:10:24 + * @param {IData} data 选中数据 + * @returns {*} {Promise>} + */ + async calcFillDataItems( + _data: IData, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): Promise> { + // if (this.model.deACMode) { + // return DEACModeUtil.calcFillDataItems(data, this.model.deACMode); + // } + return []; + } +} diff --git a/src/editor/data-picker/picker-editor.provider.ts b/src/editor/data-picker/picker-editor.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..13f26d1f656d691c660d3271e321f7e10a90848d --- /dev/null +++ b/src/editor/data-picker/picker-editor.provider.ts @@ -0,0 +1,57 @@ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { IPicker } from '@ibiz/model-core'; +import { PickerEditorController } from './picker-editor.controller'; + +/** + * 数据选择器编辑器适配器 + * + * @author lxm + * @date 2022-09-19 22:09:03 + * @export + * @class DataPickerEditorProvider + * @implements {EditorProvider} + */ +export class DataPickerEditorProvider implements IEditorProvider { + formEditor: string; + + gridEditor: string; + + constructor(editorType: string) { + let componentName = 'IBizPicker'; + switch (editorType) { + case 'PICKEREX_TRIGGER': + componentName = 'IBizPickerDropDown'; + break; + case 'PICKEREX_LINK': + case 'PICKEREX_LINKONLY': + componentName = 'IBizPickerLink'; + break; + case 'ADDRESSPICKUP': + case 'ADDRESSPICKUP_AC': + componentName = 'IBizMPicker'; + break; + case 'PICKEREX_DROPDOWNVIEW': + case 'PICKEREX_DROPDOWNVIEW_LINK': + componentName = 'IBizPickerSelectView'; + break; + case 'PICKUPVIEW': + componentName = 'IBizPickerEmbedView'; + break; + default: + } + this.formEditor = componentName; + this.gridEditor = 'IBizGridPicker'; + } + + async createController( + editorModel: IPicker, + parentController: IEditorContainerController, + ): Promise { + const c = new PickerEditorController(editorModel, parentController); + await c.init(); + return c; + } +} diff --git a/src/editor/date-picker/date-picker-editor.controller.ts b/src/editor/date-picker/date-picker-editor.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..ef34e35ac7272c00752a151d3564ec105a07e089 --- /dev/null +++ b/src/editor/date-picker/date-picker-editor.controller.ts @@ -0,0 +1,73 @@ +import { EditorController } from '@ibiz-template/runtime'; +import { IDatePicker } from '@ibiz/model-core'; + +/** + * 选项框列表编辑器控制器 + * @return {*} + * @author: zhujiamin + * @Date: 2022-08-25 10:57:58 + */ +export class DatePickerEditorController extends EditorController { + /** + * 占位 + * @return {*} + * @author: zhujiamin + * @Date: 2022-08-25 14:33:14 + */ + public placeHolder = '请选择日期'; + + /** + * 根据编辑器类型获取格式化 + * + * @author lxm + * @date 2022-11-03 16:11:21 + * @protected + * @returns {*} {string} + */ + getFormatByType(editorType: string | undefined): string { + switch (editorType) { + // 时间选择器 + case 'DATEPICKER': + return 'YYYY-MM-DD HH:mm:ss'; + // 时间选择控件 + case 'DATEPICKEREX': + return 'YYYY-MM-DD HH:mm:ss'; + // 时间选择控件_无小时 + case 'DATEPICKEREX_NOTIME': + return 'YYYY-MM-DD'; + // 时间选择控件_小时 + case 'DATEPICKEREX_HOUR': + return 'YYYY-MM-DD HH'; + // 时间选择控件_分钟 + case 'DATEPICKEREX_MINUTE': + return 'YYYY-MM-DD HH:mm'; + // 时间选择控件_秒钟 + case 'DATEPICKEREX_SECOND': + return 'YYYY-MM-DD HH:mm:ss'; + // 时间选择控件_无日期 + case 'DATEPICKEREX_NODAY': + return 'HH:mm:ss'; + // 时间选择控件_无日期无秒钟 + case 'DATEPICKEREX_NODAY_NOSECOND': + return 'HH:mm'; + // 时间选择控件_无秒钟 + case 'DATEPICKEREX_NOSECOND': + return 'YYYY-MM-DD HH:mm'; + default: + return 'YYYY-MM-DD HH:mm:ss'; + } + } + + /** + * 值格式化 + * @return {*} + * @author: zhujiamin + * @Date: 2022-08-25 14:33:14 + */ + public valueFormat: string | undefined; + + protected async onInit(): Promise { + super.onInit(); + this.valueFormat = this.getFormatByType(this.model.editorType); + } +} diff --git a/src/editor/date-picker/date-picker-editor.provider.ts b/src/editor/date-picker/date-picker-editor.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..c03c9eca49dbff7a24ee27c5bda04b3d33387f71 --- /dev/null +++ b/src/editor/date-picker/date-picker-editor.provider.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { IDatePicker } from '@ibiz/model-core'; +import { DatePickerEditorController } from './date-picker-editor.controller'; + +/** + * 日期时间选择器编辑器适配器 + * + * @author lxm + * @date 2022-09-19 22:09:03 + * @export + * @class DatePickerEditorProvider + * @implements {EditorProvider} + */ +export class DatePickerEditorProvider implements IEditorProvider { + formEditor: string = 'IBizDatePicker'; + + gridEditor: string = 'IBizGridDatePicker'; + + async createController( + editorModel: IDatePicker, + parentController: IEditorContainerController, + ): Promise { + const c = new DatePickerEditorController(editorModel, parentController); + await c.init(); + return c; + } +} diff --git a/src/editor/date-picker/ibiz-date-picker/ibiz-date-picker.scss b/src/editor/date-picker/ibiz-date-picker/ibiz-date-picker.scss new file mode 100644 index 0000000000000000000000000000000000000000..97b728a78131d7b344d56f4eb4e4f23a8e587b06 --- /dev/null +++ b/src/editor/date-picker/ibiz-date-picker/ibiz-date-picker.scss @@ -0,0 +1,27 @@ +@include b('date-picker') { + @include set-component-css-var('date-picker', $date-picker); + + .el-date-editor { + width: 100%; + } + + input { + font-size: getCssVar('date-picker', 'font-size'); + color: getCssVar('date-picker', 'text-color'); + border-color: getCssVar('date-picker', 'border-color'); + } + + input::placeholder { + color: getCssVar('date-picker', 'placeholder-color'); + } + + input[disabled] { + color: getCssVar('date-picker', 'disabled-text-color'); + background-color: getCssVar('date-picker', 'disabled-bg-color'); + border-color: getCssVar('date-picker', 'disabled-border-color'); + } + + @include m(readonly) { + color: getCssVar('text-color', 'readonly'); + } +} diff --git a/src/editor/date-picker/ibiz-date-picker/ibiz-date-picker.tsx b/src/editor/date-picker/ibiz-date-picker/ibiz-date-picker.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ff0bc69c805bbd1e2177c1357f4bfd0c430c76cc --- /dev/null +++ b/src/editor/date-picker/ibiz-date-picker/ibiz-date-picker.tsx @@ -0,0 +1,140 @@ +/* eslint-disable no-nested-ternary */ +import { ref, watch, defineComponent } from 'vue'; +import { + getDatePickerProps, + getEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import './ibiz-date-picker.scss'; +import dayjs from 'dayjs'; +import { DatePickerEditorController } from '../date-picker-editor.controller'; + +export const IBizDatePicker = defineComponent({ + name: 'IBizDatePicker', + props: getDatePickerProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('date-picker'); + const c = props.controller; + + const editorModel = c!.model; + + const type = ref('date'); + const format = ref('YYYY-MM-DD'); + + const isTimePicker = ref(false); + + switch (editorModel.editorType) { + case 'DATEPICKEREX': + case 'DATEPICKEREX_NOTIME': + type.value = 'date'; + break; + case 'DATEPICKEREX_NODAY': + case 'DATEPICKEREX_NODAY_NOSECOND': + isTimePicker.value = true; + type.value = 'time'; + break; + case 'DATEPICKEREX_HOUR': + case 'DATEPICKEREX_MINUTE': + case 'DATEPICKEREX_SECOND': + case 'DATEPICKEREX_NOSECOND': + case 'DATEPICKER': + default: + type.value = 'datetime'; + } + // 值格式化 + const valueFormat = c!.valueFormat; + if (valueFormat) { + format.value = valueFormat; + } + + // 格式化后的值 + const formatValue = ref(); + watch( + () => props.value, + (newVal, oldVal) => { + // 空值不转换 + if (newVal && newVal !== oldVal) { + const formatVal = dayjs(newVal).format(valueFormat); + if (formatVal !== 'Invalid Date') { + formatValue.value = formatVal; + } else { + formatValue.value = newVal; + } + } + }, + { immediate: true }, + ); + // 处理值变更 + const handleChange = (date: string, _dateType: IData) => { + emit('change', date); + }; + + const onOpenChange = (isOpen: boolean) => { + emit('operate', isOpen); + }; + + const inputRef = ref(); + + if (props.autoFocus) { + watch(inputRef, newVal => { + if (newVal) { + const input = newVal.$el.getElementsByTagName('input')[0]; + input.click(); + } + }); + } + + return { + ns, + c, + editorModel, + type, + format, + formatValue, + handleChange, + onOpenChange, + inputRef, + isTimePicker, + }; + }, + render() { + return ( +
+ {this.readonly ? ( + this.formatValue + ) : this.isTimePicker ? ( + + ) : ( + + )} +
+ ); + }, +}); diff --git a/src/editor/date-picker/ibiz-grid-date-picker/ibiz-grid-date-picker.tsx b/src/editor/date-picker/ibiz-grid-date-picker/ibiz-grid-date-picker.tsx new file mode 100644 index 0000000000000000000000000000000000000000..707c356a26e918b14bf46bbcef0a260bbd539e78 --- /dev/null +++ b/src/editor/date-picker/ibiz-grid-date-picker/ibiz-grid-date-picker.tsx @@ -0,0 +1,29 @@ +import { defineComponent, h, resolveComponent } from 'vue'; +import { + getGridDatePickerProps, + getGridEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { DatePickerEditorController } from '../date-picker-editor.controller'; + +export const IBizGridDatePicker = defineComponent({ + name: 'IBizGridDatePicker', + props: getGridDatePickerProps(), + emits: getGridEditorEmits(), + setup() { + const ns = useNamespace('grid-date-picker'); + + return { + ns, + }; + }, + render() { + return ( +
+ {h(resolveComponent('IBizDatePicker'), { + ...this.$props, + })} +
+ ); + }, +}); diff --git a/src/editor/date-picker/index.ts b/src/editor/date-picker/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..c668158358396030539357ad50d9f0d6843e56d2 --- /dev/null +++ b/src/editor/date-picker/index.ts @@ -0,0 +1,4 @@ +export { IBizDatePicker } from './ibiz-date-picker/ibiz-date-picker'; +export { IBizGridDatePicker } from './ibiz-grid-date-picker/ibiz-grid-date-picker'; +export * from './date-picker-editor.controller'; +export * from './date-picker-editor.provider'; diff --git a/src/editor/dropdown-list/dropdown-list-editor.controller.ts b/src/editor/dropdown-list/dropdown-list-editor.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..67bf6d7aad62f283e4e7dd08d998570f641035c7 --- /dev/null +++ b/src/editor/dropdown-list/dropdown-list-editor.controller.ts @@ -0,0 +1,33 @@ +import { CodeListEditorController } from '@ibiz-template/runtime'; +import { IDropDownList } from '@ibiz/model-core'; + +/** + * 下拉列表框编辑器控制器 + * @return {*} + * @author: zhujiamin + * @Date: 2022-08-25 10:57:58 + */ +export class DropDownListEditorController extends CodeListEditorController { + /** + * 占位 + * @return {*} + * @author: zhujiamin + * @Date: 2022-08-25 14:33:14 + */ + public placeHolder = '请选择'; + + /** + * 是否多选 + * @return {*} + * @author: zhujiamin + * @Date: 2022-08-25 14:33:14 + */ + public multiple = false; + + protected async onInit(): Promise { + super.onInit(); + if (this.model.editorType === 'MDROPDOWNLIST') { + this.multiple = true; + } + } +} diff --git a/src/editor/dropdown-list/dropdown-list-editor.provider.ts b/src/editor/dropdown-list/dropdown-list-editor.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..c335e2c0f365541325d89613b85f8f28609f8a27 --- /dev/null +++ b/src/editor/dropdown-list/dropdown-list-editor.provider.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { IDropDownList } from '@ibiz/model-core'; +import { DropDownListEditorController } from './dropdown-list-editor.controller'; + +/** + * 多选框列表编辑器适配器 + * + * @author lxm + * @date 2022-09-19 22:09:03 + * @export + * @class DropDownListEditorProvider + * @implements {EditorProvider} + */ +export class DropDownListEditorProvider implements IEditorProvider { + formEditor: string = 'IBizDropdown'; + + gridEditor: string = 'IBizGridDropdown'; + + async createController( + editorModel: IDropDownList, + parentController: IEditorContainerController, + ): Promise { + const c = new DropDownListEditorController(editorModel, parentController); + await c.init(); + return c; + } +} diff --git a/src/editor/dropdown-list/ibiz-dropdown/ibiz-dropdown.scss b/src/editor/dropdown-list/ibiz-dropdown/ibiz-dropdown.scss new file mode 100644 index 0000000000000000000000000000000000000000..1c8870eb903771935a7c6cac8ee59f0a898f03de --- /dev/null +++ b/src/editor/dropdown-list/ibiz-dropdown/ibiz-dropdown.scss @@ -0,0 +1,5 @@ +@include b(dropdown) { + .el-select { + width: 100%; + } +} diff --git a/src/editor/dropdown-list/ibiz-dropdown/ibiz-dropdown.tsx b/src/editor/dropdown-list/ibiz-dropdown/ibiz-dropdown.tsx new file mode 100644 index 0000000000000000000000000000000000000000..74d54576d3d8600c7081118070c0bb49c44a3386 --- /dev/null +++ b/src/editor/dropdown-list/ibiz-dropdown/ibiz-dropdown.tsx @@ -0,0 +1,154 @@ +import { ref, Ref, defineComponent, computed, watch } from 'vue'; +import { + getDropdownProps, + getEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import './ibiz-dropdown.scss'; +import { DropDownListEditorController } from '../dropdown-list-editor.controller'; + +export const IBizDropdown = defineComponent({ + name: 'IBizDropdown', + props: getDropdownProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('dropdown'); + const c = props.controller; + + const items: Ref = ref([]); + + // 是否是树形 + const hasChildren = ref(false); + + // 树数据 + const treeNodes: Ref = ref([]); + + // 处理树数据 + const handleTreeNodes = (nodes: readonly IData[]) => { + if (nodes.length === 0) { + return []; + } + const list: IData[] = []; + nodes.forEach((codeItem: IData) => { + const tempObj: IData = { + label: codeItem.text, + value: codeItem.value, + children: [], + }; + if (codeItem.children && codeItem.children.length > 0) { + tempObj.children = handleTreeNodes(codeItem.children); + } + list.push(tempObj); + }); + return list; + }; + + c.loadCodeList(props.data!).then((codeList: readonly IData[]) => { + items.value = codeList; + for (let i = 0; i < items.value.length; i++) { + const _item = items.value[i]; + if (_item.children) { + hasChildren.value = true; + treeNodes.value = handleTreeNodes(codeList); + break; + } + } + }); + + // 当前值 + const curValue: Ref | string | undefined> = computed({ + get() { + if (props.value) { + return c!.multiple ? props.value?.split(',') : props.value; + } + return props.value; + }, + set(select: string | Array | undefined) { + if (Array.isArray(select)) { + emit('change', select.length === 0 ? null : select.join(',')); + } else { + emit('change', select); + } + }, + }); + + const valueText = computed(() => { + const valueArr = Array.isArray(curValue.value) + ? curValue.value + : [curValue.value]; + + return items.value + .filter(item => valueArr.includes(item.value)) + .map(item => item.text) + .join(','); + }); + + const inputRef = ref(); + + if (props.autoFocus) { + watch(inputRef, newVal => { + if (newVal) { + newVal.focus(); + } + }); + } + + const onOpenChange = (isOpen: boolean) => { + emit('operate', isOpen); + }; + + return { + ns, + c, + curValue, + items, + valueText, + hasChildren, + onOpenChange, + inputRef, + treeNodes, + }; + }, + + render() { + // 编辑态内容 + const editContent = this.hasChildren ? ( + + ) : ( + + {this.items.map(item => { + return ; + })} + + ); + return ( +
+ {this.readonly ? this.valueText : editContent} +
+ ); + }, +}); diff --git a/src/editor/dropdown-list/ibiz-grid-dropdown/ibiz-grid-dropdown.tsx b/src/editor/dropdown-list/ibiz-grid-dropdown/ibiz-grid-dropdown.tsx new file mode 100644 index 0000000000000000000000000000000000000000..eb6b61e2abb7e445a39f887aa29a44ba8540b86b --- /dev/null +++ b/src/editor/dropdown-list/ibiz-grid-dropdown/ibiz-grid-dropdown.tsx @@ -0,0 +1,29 @@ +import { defineComponent, h, resolveComponent } from 'vue'; +import { + getGridDropdownProps, + getGridEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { DropDownListEditorController } from '../dropdown-list-editor.controller'; + +export const IBizGridDropdown = defineComponent({ + name: 'IBizGridDropdown', + props: getGridDropdownProps(), + emits: getGridEditorEmits(), + setup() { + const ns = useNamespace('grid-dropdown'); + + return { + ns, + }; + }, + render() { + return ( +
+ {h(resolveComponent('IBizDropdown'), { + ...this.$props, + })} +
+ ); + }, +}); diff --git a/src/editor/dropdown-list/index.ts b/src/editor/dropdown-list/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..9229b9c62cabaf23eac846eff353803ac83de402 --- /dev/null +++ b/src/editor/dropdown-list/index.ts @@ -0,0 +1,4 @@ +export { IBizDropdown } from './ibiz-dropdown/ibiz-dropdown'; +export { IBizGridDropdown } from './ibiz-grid-dropdown/ibiz-grid-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 fe25d5f90d0b4273aa9642404219a2f407a86a25..6503166dc8b038f8e7f68c482618a435ee2f73bd 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -1,25 +1,242 @@ import { registerEditorProvider } from '@ibiz-template/runtime'; import { App } from 'vue'; -import { IBizSpan } from './span'; -import { SpanEditorProvider } from './span/span-editor.provider'; -import { IBizInput } from './text-box'; -import { TextBoxEditorProvider } from './text-box/text-box-editor.provider'; - -export * from './span'; -export * from './text-box'; +import { NotSupportedEditor } from './not-supported-editor/not-supported-editor'; +import { IBizSpan, IBizSpanLink, SpanEditorProvider } from './span'; +import { + IBizInput, + IBizInputNumber, + IBizInputIP, + IBizGridInput, + IBizGridInputNumber, + TextBoxEditorProvider, +} from './text-box'; +import { + IBizDropdown, + IBizGridDropdown, + DropDownListEditorProvider, +} from './dropdown-list'; +import { IBizCheckbox, CheckBoxEditorProvider } from './check-box'; +import { + IBizCheckboxList, + IBizGridCheckboxList, + CheckBoxListEditorProvider, +} from './check-box-list'; +import { + IBizRadio, + IBizGridRadio, + RadioButtonListEditorProvider, +} from './radio-button-list'; +import { + IBizDatePicker, + IBizGridDatePicker, + DatePickerEditorProvider, +} from './date-picker'; +import { IBizRaw, RawEditorProvider } from './raw'; +import { IBizStepper, StepperEditorProvider } from './stepper'; +import { IBizRate, RateEditorProvider } from './rate'; +import { IBizSwitch, SwitchEditorProvider } from './switch'; +import { IBizSlider, SliderEditorProvider } from './slider'; +import { IBizListBox, ListBoxEditorProvider } from './list-box'; +import { IBizAutoComplete, AutoCompleteEditorProvider } from './autocomplete'; +import { + IBizFileUpload, + IBizImageSelect, + IBizImageUpload, + FileUploaderEditorProvider, +} from './upload'; +import { + IBizPicker, + IBizMPicker, + IBizPickerDropDown, + IBizPickerLink, + IBizPickerEmbedView, + IBizPickerSelectView, + DataPickerEditorProvider, +} from './data-picker'; export const IBizEditor = { install: (v: App) => { - v.component(IBizInput.name, IBizInput); + // 组件注册 + v.component(NotSupportedEditor.name, NotSupportedEditor); v.component(IBizSpan.name, IBizSpan); - const textBoxEditorProvider = new TextBoxEditorProvider(); - registerEditorProvider('TEXTBOX', () => textBoxEditorProvider); + v.component(IBizSpanLink.name, IBizSpanLink); + v.component(IBizInput.name, IBizInput); + v.component(IBizInputNumber.name, IBizInputNumber); + v.component(IBizInputIP.name, IBizInputIP); + v.component(IBizGridInput.name, IBizGridInput); + v.component(IBizGridInputNumber.name, IBizGridInputNumber); + v.component(IBizDropdown.name, IBizDropdown); + v.component(IBizGridDropdown.name, IBizGridDropdown); + v.component(IBizCheckbox.name, IBizCheckbox); + v.component(IBizCheckboxList.name, IBizCheckboxList); + v.component(IBizGridCheckboxList.name, IBizGridCheckboxList); + v.component(IBizRadio.name, IBizRadio); + v.component(IBizGridRadio.name, IBizGridRadio); + v.component(IBizDatePicker.name, IBizDatePicker); + v.component(IBizGridDatePicker.name, IBizGridDatePicker); + v.component(IBizRaw.name, IBizRaw); + v.component(IBizStepper.name, IBizStepper); + v.component(IBizRate.name, IBizRate); + v.component(IBizSwitch.name, IBizSwitch); + v.component(IBizSlider.name, IBizSlider); + v.component(IBizListBox.name, IBizListBox); + v.component(IBizAutoComplete.name, IBizAutoComplete); + v.component(IBizFileUpload.name, IBizFileUpload); + v.component(IBizImageSelect.name, IBizImageSelect); + v.component(IBizImageUpload.name, IBizImageUpload); + v.component(IBizPicker.name, IBizPicker); + v.component(IBizMPicker.name, IBizMPicker); + v.component(IBizPickerDropDown.name, IBizPickerDropDown); + v.component(IBizPickerLink.name, IBizPickerLink); + v.component(IBizPickerEmbedView.name, IBizPickerEmbedView); + v.component(IBizPickerSelectView.name, IBizPickerSelectView); + // 标签 registerEditorProvider('SPAN', () => new SpanEditorProvider()); registerEditorProvider( 'SPAN_LINK', () => new SpanEditorProvider('SPAN_LINK'), ); + // 文本框 + const textBoxEditorProvider = new TextBoxEditorProvider(); + registerEditorProvider('TEXTBOX', () => textBoxEditorProvider); + registerEditorProvider('TEXTAREA', () => textBoxEditorProvider); + registerEditorProvider('TEXTAREA_10', () => textBoxEditorProvider); + registerEditorProvider('PASSWORD', () => textBoxEditorProvider); + registerEditorProvider('NUMBER', () => new TextBoxEditorProvider('NUMBER')); + registerEditorProvider( + 'IPADDRESSTEXTBOX', + () => new TextBoxEditorProvider('IPADDRESSTEXTBOX'), + ); + // 下拉列表框 + registerEditorProvider( + 'DROPDOWNLIST', + () => new DropDownListEditorProvider(), + ); + registerEditorProvider( + 'MDROPDOWNLIST', + () => new DropDownListEditorProvider(), + ); + // 复选框 + registerEditorProvider('CHECKBOX', () => new CheckBoxEditorProvider()); + // 复选框列表 + registerEditorProvider( + 'CHECKBOXLIST', + () => new CheckBoxListEditorProvider(), + ); + // 单选框列表 + registerEditorProvider( + 'RADIOBUTTONLIST', + () => new RadioButtonListEditorProvider(), + ); + + // 日期选择器 + const datePickerProvider = new DatePickerEditorProvider(); + registerEditorProvider('DATEPICKER', () => datePickerProvider); + registerEditorProvider('DATEPICKEREX', () => datePickerProvider); + registerEditorProvider('DATEPICKEREX_NOTIME', () => datePickerProvider); + registerEditorProvider('DATEPICKEREX_HOUR', () => datePickerProvider); + registerEditorProvider('DATEPICKEREX_MINUTE', () => datePickerProvider); + registerEditorProvider('DATEPICKEREX_SECOND', () => datePickerProvider); + registerEditorProvider('DATEPICKEREX_NODAY', () => datePickerProvider); + registerEditorProvider( + 'DATEPICKEREX_NODAY_NOSECOND', + () => datePickerProvider, + ); + // 文件上传 + registerEditorProvider( + 'FILEUPLOADER', + () => new FileUploaderEditorProvider('FILEUPLOADER'), + ); + registerEditorProvider( + 'FILEUPLOADER_ONE', + () => new FileUploaderEditorProvider('FILEUPLOADER_ONE'), + ); + // 图片上传 + registerEditorProvider( + 'PICTURE', + () => new FileUploaderEditorProvider('PICTURE'), + ); + registerEditorProvider( + 'PICTURE_ONE', + () => new FileUploaderEditorProvider('PICTURE_ONE'), + ); + registerEditorProvider( + 'PICTURE_ONE_RAW', + () => new FileUploaderEditorProvider('PICTURE_ONE_RAW'), + ); + // 直接内容 + registerEditorProvider('RAW', () => new RawEditorProvider()); + // 步进器 + registerEditorProvider('STEPPER', () => new StepperEditorProvider()); + // 评分器 + registerEditorProvider('RATING', () => new RateEditorProvider()); + // 滑动输入条 + registerEditorProvider('SLIDER', () => new SliderEditorProvider()); + // 开关 + registerEditorProvider('SWITCH', () => new SwitchEditorProvider()); + // 列表框 + registerEditorProvider('LISTBOX', () => new ListBoxEditorProvider()); + registerEditorProvider('LISTBOXPICKUP', () => new ListBoxEditorProvider()); + // 自动完成 + const AutoCompleteProvider = new AutoCompleteEditorProvider(); + registerEditorProvider('AC', () => AutoCompleteProvider); + registerEditorProvider('AC_FS', () => AutoCompleteProvider); + registerEditorProvider('AC_NOBUTTON', () => AutoCompleteProvider); + registerEditorProvider('AC_FS_NOBUTTON', () => AutoCompleteProvider); + // 数据选择类 + registerEditorProvider( + 'PICKER', + () => new DataPickerEditorProvider('PICKER'), + ); + registerEditorProvider( + 'PICKEREX_NOAC', + () => new DataPickerEditorProvider('PICKEREX_NOAC'), + ); + registerEditorProvider( + 'PICKEREX_NOAC_LINK', + () => new DataPickerEditorProvider('PICKEREX_NOAC_LINK'), + ); + registerEditorProvider( + 'PICKEREX_TRIGGER_LINK', + () => new DataPickerEditorProvider('PICKEREX_TRIGGER_LINK'), + ); + registerEditorProvider( + 'PICKEREX_TRIGGER', + () => new DataPickerEditorProvider('PICKEREX_TRIGGER'), + ); + registerEditorProvider( + 'PICKEREX_LINK', + () => new DataPickerEditorProvider('PICKEREX_LINK'), + ); + registerEditorProvider( + 'ADDRESSPICKUP', + () => new DataPickerEditorProvider('ADDRESSPICKUP'), + ); + registerEditorProvider( + 'ADDRESSPICKUP_AC', + () => new DataPickerEditorProvider('ADDRESSPICKUP_AC'), + ); + registerEditorProvider( + 'PICKEREX_LINKONLY', + () => new DataPickerEditorProvider('PICKEREX_LINKONLY'), + ); + registerEditorProvider( + 'PICKEREX_NOBUTTON', + () => new DataPickerEditorProvider('PICKEREX_NOBUTTON'), + ); + registerEditorProvider( + 'PICKEREX_DROPDOWNVIEW', + () => new DataPickerEditorProvider('PICKEREX_DROPDOWNVIEW'), + ); + registerEditorProvider( + 'PICKEREX_DROPDOWNVIEW_LINK', + () => new DataPickerEditorProvider('PICKEREX_DROPDOWNVIEW_LINK'), + ); + registerEditorProvider( + 'PICKUPVIEW', + () => new DataPickerEditorProvider('PICKUPVIEW'), + ); }, }; diff --git a/src/editor/list-box/ibiz-list-box/ibiz-list-box.scss b/src/editor/list-box/ibiz-list-box/ibiz-list-box.scss new file mode 100644 index 0000000000000000000000000000000000000000..050f0af531abb65f1444cc242500652cae560f39 --- /dev/null +++ b/src/editor/list-box/ibiz-list-box/ibiz-list-box.scss @@ -0,0 +1,9 @@ +@include b(list-box) { + @include b(list-box-radio) { + @include flex(column, flex-start, flex-start); + } + + @include b(list-box-checkbox) { + @include flex(column, flex-start, flex-start); + } +} diff --git a/src/editor/list-box/ibiz-list-box/ibiz-list-box.tsx b/src/editor/list-box/ibiz-list-box/ibiz-list-box.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c8de0ab352df138557df278e3e277a2479e5aa27 --- /dev/null +++ b/src/editor/list-box/ibiz-list-box/ibiz-list-box.tsx @@ -0,0 +1,200 @@ +import { computed, defineComponent, Ref, ref, watch } from 'vue'; +import { + getListBoxProps, + getEditorEmits, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { isNil } from 'ramda'; +import './ibiz-list-box.scss'; +import { ListBoxEditorController } from '../list-box-editor.controller'; +import { ListBoxPickerEditorController } from '../list-box-picker-editor.controller'; + +export const IBizListBox = defineComponent({ + name: 'IBizListBox', + props: getListBoxProps< + ListBoxEditorController | ListBoxPickerEditorController + >(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('list-box'); + + const c = props.controller; + + const editorModel = c.model; + + let multiple: boolean = false; + if (editorModel.editorParams) { + if (editorModel.editorParams.multiple) { + multiple = c.handleStringToBoolean(editorModel.editorParams.multiple); + } + } + + const codeList = (c as ListBoxEditorController).codeList; + + const editorType = editorModel.editorType; + + // 代码表数据(LISTBOX) or AC请求数据(LISTBOXPICKUP) + const items = ref([]); + + // 更新列表框选项 + const loadListBoxItems = async () => { + if (Object.is('LISTBOX', editorType)) { + const controller = c as ListBoxEditorController; + controller.loadCodeList(props.data).then(_codeList => { + items.value = _codeList; + }); + } else if (Object.is('LISTBOXPICKUP', editorType)) { + const controller = c as ListBoxPickerEditorController; + if (controller.appDataEntity) { + const res = await controller.getServiceData(props.data); + if (res) { + items.value = res.data.map(item => ({ + value: item[controller.keyName], + text: item[controller.textName], + })); + } + } + } + }; + + loadListBoxItems(); + + // 当前模式 + const currentMode = computed(() => { + if (codeList && codeList.orMode) { + return codeList.orMode.toLowerCase(); + } + return 'str'; + }); + + // 值分隔符 + let valueSeparator = ','; + if (codeList && codeList.valueSeparator) { + valueSeparator = codeList.valueSeparator; + } + + // 选中数组 + const selectArray: Ref<(string | number)[]> = ref([]); + + // 监听当前值生成selectArray + watch( + () => props.value, + newVal => { + if (!isNil(newVal)) { + if (multiple) { + let selectsArray: Array = []; + if (editorType === 'LISTBOX') { + if (Object.is(currentMode.value, 'num') && items) { + const num: number = + typeof newVal === 'string' ? parseInt(newVal, 10) : newVal; + items.value.forEach((item: IData) => { + // eslint-disable-next-line no-bitwise + if ((num & item.value) === item.value) { + selectsArray.push(item.value); + } + }); + } else if (Object.is(currentMode.value, 'str')) { + if (newVal !== '') { + if (codeList && typeof newVal === 'string') { + selectsArray = newVal.split(valueSeparator); + if (codeList.codeItemValueNumber) { + for (let i = 0, len = selectsArray.length; i < len; i++) { + selectsArray[i] = Number(selectsArray[i]); + } + } + } + } + } + } else if (editorType === 'LISTBOXPICKUP') { + if (newVal !== '') { + selectsArray = (newVal as string).split(valueSeparator); + } + } + selectArray.value = selectsArray; + } else { + selectArray.value = [newVal]; + } + } else { + selectArray.value = []; + } + }, + { immediate: true, deep: true }, + ); + + // 选中数据改变 + const onSelectArrayChange = (value: Array) => { + let _value; + // 单多选 + if (multiple) { + const values = [...value]; + if (Object.is('LISTBOX', editorType)) { + // 根据代码表模式对值进行计算 + if (Object.is(currentMode, 'num')) { + let temp: number = 0; + values.forEach(item => { + // eslint-disable-next-line no-bitwise + temp |= parseInt(item as string, 10); + }); + _value = temp; + } else if (Object.is(currentMode, 'str')) { + _value = values.join(valueSeparator); + } + } else if (Object.is('LISTBOXPICKUP', editorType)) { + _value = values.join(valueSeparator); + } + } else { + _value = value; + } + emit('change', _value); + }; + + return { + ns, + items, + selectArray, + onSelectArrayChange, + multiple, + }; + }, + render() { + return ( +
+ {this.multiple ? ( + + {this.items.map((item, index: number) => ( + + {item.text} + + ))} + + ) : ( + + {this.items.map((item, index: number) => ( + { + this.onSelectArrayChange(item.value); + }} + > + {item.text} + + ))} + + )} +
+ ); + }, +}); diff --git a/src/editor/list-box/index.ts b/src/editor/list-box/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..edb6044e7fbb64e5f05353302186597f035eccb8 --- /dev/null +++ b/src/editor/list-box/index.ts @@ -0,0 +1,4 @@ +export { IBizListBox } from './ibiz-list-box/ibiz-list-box'; +export * from './list-box-editor.controller'; +export * from './list-box-picker-editor.controller'; +export * from './list-box-editor.provider'; diff --git a/src/editor/list-box/list-box-editor.controller.ts b/src/editor/list-box/list-box-editor.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..879e587c8199812ac102c4dce5331593641eb039 --- /dev/null +++ b/src/editor/list-box/list-box-editor.controller.ts @@ -0,0 +1,26 @@ +import { CodeListEditorController } from '@ibiz-template/runtime'; +import { IAppCodeList, IListBox } from '@ibiz/model-core'; + +/** + * 列表框编辑器控制器 + * @return {*} + * @author: zhujiamin + * @Date: 2022-08-25 10:57:58 + */ +export class ListBoxEditorController extends CodeListEditorController { + /** + * 代码表模型 + * @return {*} + * @author: zhujiamin + * @Date: 2023-05-24 10:55:50 + */ + codeList: IAppCodeList | undefined = undefined; + + protected async onInit(): Promise { + super.onInit(); + if (this.model.appCodeListId) { + const app = await ibiz.hub.getApp(this.context.srfappid); + this.codeList = app.codeList.getCodeList(this.model.appCodeListId); + } + } +} diff --git a/src/editor/list-box/list-box-editor.provider.ts b/src/editor/list-box/list-box-editor.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..03af813b44788a437dab4919176be1b94d325174 --- /dev/null +++ b/src/editor/list-box/list-box-editor.provider.ts @@ -0,0 +1,43 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { IListBox, IListBoxPicker } from '@ibiz/model-core'; +import { ListBoxEditorController } from './list-box-editor.controller'; +import { ListBoxPickerEditorController } from './list-box-picker-editor.controller'; + +/** + * 列表框编辑器适配器 + * + * @author lxm + * @date 2022-09-19 22:09:03 + * @export + * @class ListBoxEditorProvider + * @implements {EditorProvider} + */ +export class ListBoxEditorProvider implements IEditorProvider { + formEditor: string = 'IBizListBox'; + + gridEditor: string = 'IBizListBox'; + + async createController( + editorModel: IListBox | IListBoxPicker, + parentController: IEditorContainerController, + ): Promise { + let c; + if (editorModel.editorType === 'LISTBOXPICKUP') { + c = new ListBoxPickerEditorController( + editorModel as IListBoxPicker, + parentController, + ); + } else { + c = new ListBoxEditorController( + editorModel as IListBox, + parentController, + ); + } + await c.init(); + return c; + } +} diff --git a/src/editor/list-box/list-box-picker-editor.controller.ts b/src/editor/list-box/list-box-picker-editor.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..31eb524e8606fe3a101c910e8d24a198e92f3eaa --- /dev/null +++ b/src/editor/list-box/list-box-picker-editor.controller.ts @@ -0,0 +1,89 @@ +import { IHttpResponse } from '@ibiz-template/core'; +import { EditorController } from '@ibiz-template/runtime'; +import { IAppDataEntity, IListBoxPicker } from '@ibiz/model-core'; + +/** + * 列表框picker编辑器控制器 + * @return {*} + * @author: zhujiamin + * @Date: 2022-08-25 10:57:58 + */ +export class ListBoxPickerEditorController extends EditorController { + /** + * 主键属性名称 + */ + public keyName: string = ''; + + /** + * 主文本属性名称 + */ + public textName: string = ''; + + /** + * 实体codeName + */ + public serviceName: string = ''; + + /** + * 数据集codeName + */ + public interfaceName: string = ''; + + /** + * 编辑器实体 + */ + public appDataEntity: IAppDataEntity | null = null; + + protected async onInit(): Promise { + super.onInit(); + if (this.model.editorType === 'LISTBOXPICKUP') { + // zjm todo 列表框选择模型缺少实体请求相关的,如appDataEntityId + // this.appDataEntity = await ibiz.hub.getAppDataEntity( + // this.model.appDataEntityId!, + // this.context.srfappid, + // )!; + + if (this.appDataEntity) { + this.keyName = this.appDataEntity?.keyAppDEFieldId || ''; + this.textName = this.appDataEntity?.majorAppDEFieldId || ''; + this.getAcParams(); + } + } + } + + /** + * 获取Ac参数 + */ + public getAcParams() { + if (this.appDataEntity?.codeName) { + this.serviceName = this.appDataEntity.codeName; + } + // if (this.model.appDEDataSetId) { + // this.interfaceName = this.model.appDEDataSetId; + // } + } + + /** + * 加载实体数据集数据 + * + * @param {string} query 模糊匹配字符串 + * @param {IData} data 表单数据 + * @returns {*} {Promise>} + * @memberof PickerEditorController + */ + public async getServiceData(_data: IData): Promise> { + // const { context, params } = this.handlePublicParams( + // data, + // this.context, + // this.params, + // ); + // Object.assign(params, { size: 1000 }); + // if (this.serviceName && this.interfaceName) { + // const deService = await this.app.es.getService(this.serviceName); + // const res = await deService.exec(this.interfaceName, context, params); + // return res as IHttpResponse; + // } + // throw new DefectModelError(this.model.source, '请配置实体和实体数据集'); + return {} as IHttpResponse; + } +} diff --git a/src/editor/not-supported-editor/not-supported-editor.scss b/src/editor/not-supported-editor/not-supported-editor.scss new file mode 100644 index 0000000000000000000000000000000000000000..73a131e96b20e9f6f6ad690cce94ce15b080becf --- /dev/null +++ b/src/editor/not-supported-editor/not-supported-editor.scss @@ -0,0 +1,3 @@ +@include b(not-supported-editor) { + color: getCssVar('color', 'danger'); +} \ No newline at end of file diff --git a/src/editor/not-supported-editor/not-supported-editor.tsx b/src/editor/not-supported-editor/not-supported-editor.tsx new file mode 100644 index 0000000000000000000000000000000000000000..76976a10f81c5e42cdb4a63f12a6072f10a3f0df --- /dev/null +++ b/src/editor/not-supported-editor/not-supported-editor.tsx @@ -0,0 +1,25 @@ +import { useNamespace } from '@ibiz-template/vue3-util'; +import { defineComponent, PropType } from 'vue'; +import './not-supported-editor.scss'; +import { IEditor } from '@ibiz/model-core'; + +export const NotSupportedEditor = defineComponent({ + name: 'NotSupportedEditor', + props: { + modelData: { + type: Object as PropType, + required: true, + }, + }, + setup() { + const ns = useNamespace('not-supported-editor'); + return { ns }; + }, + render() { + return ( +
+ 未支持的编辑器类型 - {this.modelData.editorType} +
+ ); + }, +}); diff --git a/src/editor/radio-button-list/ibiz-grid-radio/ibiz-grid-radio.tsx b/src/editor/radio-button-list/ibiz-grid-radio/ibiz-grid-radio.tsx new file mode 100644 index 0000000000000000000000000000000000000000..75ef24a2b7dea908fa27a24a1f1666de7802ed5d --- /dev/null +++ b/src/editor/radio-button-list/ibiz-grid-radio/ibiz-grid-radio.tsx @@ -0,0 +1,29 @@ +import { defineComponent, h, resolveComponent } from 'vue'; +import { + getGridEditorEmits, + getGridRadioProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { RadioButtonListEditorController } from '../radio-button-list.controller'; + +export const IBizGridRadio = defineComponent({ + name: 'IBizGridRadio', + props: getGridRadioProps(), + emits: getGridEditorEmits(), + setup() { + const ns = useNamespace('grid-radio'); + + return { + ns, + }; + }, + render() { + return ( +
+ {h(resolveComponent('IBizRadio'), { + ...this.$props, + })} +
+ ); + }, +}); diff --git a/src/editor/radio-button-list/ibiz-radio/ibiz-radio.scss b/src/editor/radio-button-list/ibiz-radio/ibiz-radio.scss new file mode 100644 index 0000000000000000000000000000000000000000..2035f164f761649309f6d64544179bdad0cfdce3 --- /dev/null +++ b/src/editor/radio-button-list/ibiz-radio/ibiz-radio.scss @@ -0,0 +1,12 @@ +@include b('radio') { + @include set-component-css-var('radio', $radio); + + @include e('text') { + font-size: getCssVar('radio', 'font-size'); + color: getCssVar('radio', 'text-color'); + } + + @include m(readonly){ + color: getCssVar('text-color', 'readonly') + } +} diff --git a/src/editor/radio-button-list/ibiz-radio/ibiz-radio.tsx b/src/editor/radio-button-list/ibiz-radio/ibiz-radio.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e05e059d454add2b67937104e4c2aa1dbe9e42cf --- /dev/null +++ b/src/editor/radio-button-list/ibiz-radio/ibiz-radio.tsx @@ -0,0 +1,83 @@ +import { computed, defineComponent, ref, watch } from 'vue'; +import { + getEditorEmits, + getRadioProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import './ibiz-radio.scss'; +import { RadioButtonListEditorController } from '../radio-button-list.controller'; + +export const IBizRadio = defineComponent({ + name: 'IBizRadio', + props: getRadioProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('radio'); + + const c = props.controller; + + const editorModel = c.model; + + const onSelectValueChange = (value: string | number) => { + emit('change', value); + }; + + // 代码表 + const items = ref([]); + watch( + () => props.data, + newVal => { + c.loadCodeList(newVal).then(_codeList => { + items.value = _codeList; + }); + }, + { + immediate: true, + deep: true, + }, + ); + + const valueText = computed(() => { + return items.value.find(item => item.value === props.value)?.text || ''; + }); + + return { + ns, + editorModel, + items, + valueText, + onSelectValueChange, + }; + }, + render() { + return ( +
+ {this.readonly ? ( + this.valueText + ) : ( + + {this.items.map((_item, index: number) => ( + + {_item.text} + + ))} + + )} +
+ ); + }, +}); diff --git a/src/editor/radio-button-list/index.ts b/src/editor/radio-button-list/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..03f19f89669e12bc00ec42a8a6762410275f073b --- /dev/null +++ b/src/editor/radio-button-list/index.ts @@ -0,0 +1,4 @@ +export { IBizRadio } from './ibiz-radio/ibiz-radio'; +export { IBizGridRadio } from './ibiz-grid-radio/ibiz-grid-radio'; +export * from './radio-button-list.controller'; +export * from './radio-button-list.provider'; diff --git a/src/editor/radio-button-list/radio-button-list.controller.ts b/src/editor/radio-button-list/radio-button-list.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..40c82f559fad957978c470c8a068b0e7ee190820 --- /dev/null +++ b/src/editor/radio-button-list/radio-button-list.controller.ts @@ -0,0 +1,10 @@ +import { CodeListEditorController } from '@ibiz-template/runtime'; +import { IRadioButtonList } from '@ibiz/model-core'; + +/** + * 单选项列表编辑器控制器 + * @return {*} + * @author: zhujiamin + * @Date: 2022-08-25 10:57:58 + */ +export class RadioButtonListEditorController extends CodeListEditorController {} diff --git a/src/editor/radio-button-list/radio-button-list.provider.ts b/src/editor/radio-button-list/radio-button-list.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..9c702906602c50ee2a415f57b142c713cca4be7e --- /dev/null +++ b/src/editor/radio-button-list/radio-button-list.provider.ts @@ -0,0 +1,34 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { IRadioButtonList } from '@ibiz/model-core'; +import { RadioButtonListEditorController } from './radio-button-list.controller'; + +/** + * 单选框列表编辑器适配器 + * + * @author lxm + * @date 2022-09-19 22:09:03 + * @export + * @class RadioButtonListEditorProvider + * @implements {EditorProvider} + */ +export class RadioButtonListEditorProvider implements IEditorProvider { + formEditor: string = 'IBizRadio'; + + gridEditor: string = 'IBizGridRadio'; + + async createController( + editorModel: IRadioButtonList, + parentController: IEditorContainerController, + ): Promise { + const c = new RadioButtonListEditorController( + editorModel, + parentController, + ); + await c.init(); + return c; + } +} diff --git a/src/editor/rate/ibiz-rate/ibiz-rate.tsx b/src/editor/rate/ibiz-rate/ibiz-rate.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9304eb83ceb3f20d991db3f979dac484ad48d0af --- /dev/null +++ b/src/editor/rate/ibiz-rate/ibiz-rate.tsx @@ -0,0 +1,88 @@ +import { defineComponent, ref, watch } from 'vue'; +import { + getEditorEmits, + getRateProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { RateEditorController } from '../rate-editor.controller'; + +export const IBizRate = defineComponent({ + name: 'IBizRate', + props: getRateProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('rate'); + // 当前值 + const currentVal = ref(); + + const c = props.controller; + + const editorModel = c.model; + + // icon 的颜色 + let colors = ['#F7BA2A', '#F7BA2A', '#F7BA2A']; + // 是否显示辅助文字 + let showText = false; + // 设置允许的最大值 + let max = 5; + // 辅助文字数组 + let texts = []; + if (editorModel.editorParams) { + if (editorModel.editorParams.colors) { + colors = c.handleStringToObj(editorModel.editorParams.colors); + } + if (editorModel.editorParams.showtext) { + showText = c.handleStringToBoolean(editorModel.editorParams.showtext); + } + if (editorModel.editorParams.maxvalue) { + max = c.handleStringToNumber(editorModel.editorParams.maxvalue); + } + if (editorModel.editorParams.texts) { + texts = c.handleStringToObj(editorModel.editorParams.texts); + } + } + + watch( + () => props.value, + (newVal, oldVal) => { + if (newVal !== oldVal) { + if (!newVal) { + currentVal.value = 0; + } else { + currentVal.value = newVal as number; + } + } + }, + { immediate: true }, + ); + + const handleChange = (currentValue: number | undefined) => { + emit('change', currentValue); + }; + + return { + ns, + currentVal, + handleChange, + colors, + showText, + max, + texts, + }; + }, + render() { + return ( +
+ +
+ ); + }, +}); diff --git a/src/editor/rate/index.ts b/src/editor/rate/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..ba69243833c469aa2ac887b3afb9be767645cb38 --- /dev/null +++ b/src/editor/rate/index.ts @@ -0,0 +1,3 @@ +export { IBizRate } from './ibiz-rate/ibiz-rate'; +export * from './rate-editor.controller'; +export * from './rate-editor.provider'; diff --git a/src/editor/rate/rate-editor.controller.ts b/src/editor/rate/rate-editor.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..6bb6b5d30dc649a6e9eed98d484140f91f34a19f --- /dev/null +++ b/src/editor/rate/rate-editor.controller.ts @@ -0,0 +1,11 @@ +import { EditorController } from '@ibiz-template/runtime'; +import { IRating } from '@ibiz/model-core'; + +/** + * 评分器编辑器控制器 + * + * @export + * @class RateEditorController + * @extends {EditorController} + */ +export class RateEditorController extends EditorController {} diff --git a/src/editor/rate/rate-editor.provider.ts b/src/editor/rate/rate-editor.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..b60af9974c69ffecb3d3837304bab8a57f1f40cf --- /dev/null +++ b/src/editor/rate/rate-editor.provider.ts @@ -0,0 +1,29 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { IRating } from '@ibiz/model-core'; +import { RateEditorController } from './rate-editor.controller'; + +/** + * 评分器编辑器适配器 + * + * @export + * @class RateEditorProvider + * @implements {EditorProvider} + */ +export class RateEditorProvider implements IEditorProvider { + formEditor: string = 'IBizRate'; + + gridEditor: string = 'IBizRate'; + + async createController( + editorModel: IRating, + parentController: IEditorContainerController, + ): Promise { + const c = new RateEditorController(editorModel, parentController); + await c.init(); + return c; + } +} diff --git a/src/editor/raw/ibiz-raw/ibiz-raw.tsx b/src/editor/raw/ibiz-raw/ibiz-raw.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3f2c232b88ea82e739b352da4592b26a4f9f2b2f --- /dev/null +++ b/src/editor/raw/ibiz-raw/ibiz-raw.tsx @@ -0,0 +1,50 @@ +import { defineComponent } from 'vue'; +import { + getEditorEmits, + getRawProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { RawEditorController } from '../raw-editor.controller'; + +export const IBizRaw = defineComponent({ + name: 'IBizRaw', + props: getRawProps(), + emits: getEditorEmits(), + setup(props) { + const ns = useNamespace('raw'); + const c = props.controller; + const editorModel = c.model; + + // 传入内容 + const content = props.value; + + // 类型 + let type = 'TEXT'; + if (editorModel.editorParams?.contenttype) { + type = editorModel.editorParams.contenttype; + } + + return { + ns, + content, + type, + }; + }, + render() { + return ( +
+ +
+ ); + }, +}); diff --git a/src/editor/raw/index.ts b/src/editor/raw/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..ea51841051eec5ac82333e33561cf3738428d5e5 --- /dev/null +++ b/src/editor/raw/index.ts @@ -0,0 +1,3 @@ +export { IBizRaw } from './ibiz-raw/ibiz-raw'; +export * from './raw-editor.controller'; +export * from './raw-editor.provider'; diff --git a/src/editor/raw/raw-editor.controller.ts b/src/editor/raw/raw-editor.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..5950671a3442ac129eef1c9a1521af16c0cb3ac9 --- /dev/null +++ b/src/editor/raw/raw-editor.controller.ts @@ -0,0 +1,11 @@ +import { EditorController } from '@ibiz-template/runtime'; +import { IRaw } from '@ibiz/model-core'; + +/** + * 直接内容编辑器控制器 + * + * @export + * @class RawEditorController + * @extends {EditorController} + */ +export class RawEditorController extends EditorController {} diff --git a/src/editor/raw/raw-editor.provider.ts b/src/editor/raw/raw-editor.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..d4302f7e3bb486be12d8c7862e48cc0a5f08826b --- /dev/null +++ b/src/editor/raw/raw-editor.provider.ts @@ -0,0 +1,29 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { IRaw } from '@ibiz/model-core'; +import { RawEditorController } from './raw-editor.controller'; + +/** + * 直接内容编辑器适配器 + * + * @export + * @class RawEditorProvider + * @implements {EditorProvider} + */ +export class RawEditorProvider implements IEditorProvider { + formEditor: string = 'IBizRaw'; + + gridEditor: string = 'IBizRaw'; + + async createController( + editorModel: IRaw, + parentController: IEditorContainerController, + ): Promise { + const c = new RawEditorController(editorModel, parentController); + await c.init(); + return c; + } +} diff --git a/src/editor/slider/ibiz-slider/ibiz-slider.tsx b/src/editor/slider/ibiz-slider/ibiz-slider.tsx new file mode 100644 index 0000000000000000000000000000000000000000..834a411d410b8bde549d75dbd51336023a42beb0 --- /dev/null +++ b/src/editor/slider/ibiz-slider/ibiz-slider.tsx @@ -0,0 +1,115 @@ +import { defineComponent, ref, watch } from 'vue'; +import { + getEditorEmits, + getSliderProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { SliderEditorController } from '../slider-editor.controller'; + +export const IBizSlider = defineComponent({ + name: 'IBizSlider', + props: getSliderProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('slider'); + const c = props.controller; + const editorModel = c.model; + + // 步长 + let step = 1; + // 设置滑动输入条允许的最大值 + let max = Infinity; + // 设置滑动输入条允许的最小值 + let min = -Infinity; + // 是否显示间断点 + let showStops = false; + // 是否开启选择范围 + let range = false; + // 是否显示输入框,仅在非范围选择时有效 + let showInput = false; + if (editorModel.editorParams) { + if (editorModel.editorParams.stepvalue) { + step = c.handleStringToNumber(editorModel.editorParams.stepvalue); + } + if (editorModel.editorParams.maxvalue) { + max = c.handleStringToNumber(editorModel.editorParams.maxvalue); + } + if (editorModel.editorParams.minvalue) { + min = c.handleStringToNumber(editorModel.editorParams.minvalue); + } + if (editorModel.editorParams.showstops) { + showStops = c.handleStringToBoolean(editorModel.editorParams.showstops); + } + if (editorModel.editorParams.range) { + range = c.handleStringToBoolean(editorModel.editorParams.range); + } + if (editorModel.editorParams.showinput) { + showInput = c.handleStringToBoolean(editorModel.editorParams.showinput); + } + } + + // 当前值 + const currentVal = ref>(); + watch( + () => props.value, + (newVal, oldVal) => { + if (newVal !== oldVal) { + if (!newVal) { + // 如果是范围给数组 + if (range) { + currentVal.value = [0, 1]; + } else { + currentVal.value = 0; + } + } else { + // 如果是范围解析JSON成数组 + // eslint-disable-next-line no-lonely-if + if (range) { + currentVal.value = JSON.parse(newVal as string); + } else { + currentVal.value = newVal as number; + } + } + } + }, + { immediate: true }, + ); + + const handleChange = (currentValue: number | undefined | Array) => { + if (Array.isArray(currentValue)) { + emit('change', JSON.stringify(currentValue)); + } else { + emit('change', currentValue); + } + }; + + return { + ns, + currentVal, + handleChange, + step, + max, + min, + showStops, + range, + showInput, + }; + }, + render() { + return ( +
+ +
+ ); + }, +}); diff --git a/src/editor/slider/index.ts b/src/editor/slider/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..3b51bef234a9ef7d793969c98b6c858af3b16f6e --- /dev/null +++ b/src/editor/slider/index.ts @@ -0,0 +1,3 @@ +export { IBizSlider } from './ibiz-slider/ibiz-slider'; +export * from './slider-editor.controller'; +export * from './slider-editor.provider'; diff --git a/src/editor/slider/slider-editor.controller.ts b/src/editor/slider/slider-editor.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..ec968297ef445c9afec569f682b980f2cbb7d3fd --- /dev/null +++ b/src/editor/slider/slider-editor.controller.ts @@ -0,0 +1,11 @@ +import { EditorController } from '@ibiz-template/runtime'; +import { ISlider } from '@ibiz/model-core'; + +/** + * 滑动输入条编辑器控制器 + * + * @export + * @class SliderEditorController + * @extends {EditorController} + */ +export class SliderEditorController extends EditorController {} diff --git a/src/editor/slider/slider-editor.provider.ts b/src/editor/slider/slider-editor.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..6896c2788c79c9db8a40927e7a9996bc1049b79a --- /dev/null +++ b/src/editor/slider/slider-editor.provider.ts @@ -0,0 +1,29 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { ISlider } from '@ibiz/model-core'; +import { SliderEditorController } from './slider-editor.controller'; + +/** + * 滑动输入条编辑器适配器 + * + * @export + * @class SliderEditorProvider + * @implements {EditorProvider} + */ +export class SliderEditorProvider implements IEditorProvider { + formEditor: string = 'IBizSlider'; + + gridEditor: string = 'IBizSlider'; + + async createController( + editorModel: ISlider, + parentController: IEditorContainerController, + ): Promise { + const c = new SliderEditorController(editorModel, parentController); + await c.init(); + return c; + } +} diff --git a/src/editor/span/span-editor.controller.ts b/src/editor/span/span-editor.controller.ts index 8acec86bb968c862d67eb1a83289fd9388796888..a7af2e7cb48eb7419ab8bfe041fd5353f0a4a20e 100644 --- a/src/editor/span/span-editor.controller.ts +++ b/src/editor/span/span-editor.controller.ts @@ -1,5 +1,9 @@ -import { ISpan, IAppView } from '@ibiz/model-core'; -import { CodeListEditorController } from '@ibiz-template/runtime'; +import { ISpan, IAppView, IAppCodeList } from '@ibiz/model-core'; +import { + CodeListEditorController, + OpenAppViewCommand, +} from '@ibiz-template/runtime'; +import { RuntimeModelError } from '@ibiz-template/core'; /** * 标签编辑器控制器 @@ -8,22 +12,6 @@ import { CodeListEditorController } from '@ibiz-template/runtime'; * @Date: 2022-08-25 10:57:58 */ export class SpanEditorController extends CodeListEditorController { - /** - * 单位名称 - * @return {*} - * @author: zhujiamin - * @Date: 2022-09-29 13:58:39 - */ - public unitName: string | undefined; - - /** - * 值格式化 - * @return {*} - * @author: zhujiamin - * @Date: 2022-08-25 14:33:14 - */ - public valueFormat: string | undefined; - /** *值项 */ @@ -34,40 +22,44 @@ export class SpanEditorController extends CodeListEditorController { */ public linkView: IAppView | null = null; + /** + * 代码表模型 + * @return {*} + * @author: zhujiamin + * @Date: 2023-05-24 10:55:50 + */ + public codeList: IAppCodeList | undefined = undefined; + protected async onInit(): Promise { super.onInit(); - // todo 从父那拿的模型 - // this.unitName = this.model.unitName; - // this.valueFormat = this.model.valueFormat; - // this.valueItem = this.model.valueItemName?.toLowerCase() || ''; + // zjm todo 值项名称父没有valueItemName + this.valueItem = this.parent.valueItemName?.toLowerCase() || ''; + // 初始化代码表 + if (this.model.appCodeListId) { + const app = await ibiz.hub.getApp(this.context.srfappid); + this.codeList = app.codeList.getCodeList(this.model.appCodeListId); + } } /** * 打开数据链接视图 */ - public async openLinkView(_data: IData): Promise { - // const { context, params } = this.handlePublicParams( - // data, - // this.context, - // this.params, - // ); - // context.srfkey = data[this.valueItem]; - // const {linkAppViewId,} = this.model; - // if (!this.linkView) { - // throw new RuntimeModelError(this.model, '请配置数据链接视图'); - // } - // if (this.model.getPSAppDataEntity()) { - // const deName = this.linkView.getPSAppDataEntity()!.codeName; - // if (deName) { - // context[deName.toLowerCase()] = data[this.valueItem]; - // } - // } - // return ibiz.commands.execute( - // OpenAppViewCommand.TAG, - // this.linkView, - // context, - // params, - // ); - return []; + public async openLinkView(data: IData): Promise { + const { context, params } = this.handlePublicParams( + data, + this.context, + this.params, + ); + context.srfkey = data[this.valueItem]; + const { linkAppViewId } = this.model; + if (!linkAppViewId) { + throw new RuntimeModelError(this.model, '请配置数据链接视图'); + } + return ibiz.commands.execute( + OpenAppViewCommand.TAG, + linkAppViewId, + context, + params, + ); } } diff --git a/src/editor/span/span/span.tsx b/src/editor/span/span/span.tsx index 16c5c955e4161212b38a2278da87cac0d2625a69..e8e785270396020c0dfa44952a0c5d8f8018872f 100644 --- a/src/editor/span/span/span.tsx +++ b/src/editor/span/span/span.tsx @@ -1,8 +1,8 @@ -import { ref, defineComponent, Ref, watch } from 'vue'; +import { ref, defineComponent, Ref, watch, computed } from 'vue'; import { getSpanProps, useNamespace } from '@ibiz-template/vue3-util'; import dayjs from 'dayjs'; import './span.scss'; -// import { CodeListItem } from '@ibiz-template/runtime'; +import { CodeListItem } from '@ibiz-template/runtime'; import { SpanEditorController } from '../span-editor.controller'; export const IBizSpan = defineComponent({ @@ -10,15 +10,17 @@ export const IBizSpan = defineComponent({ props: getSpanProps(), setup(props) { const ns = useNamespace('span'); + const c = props.controller; + const text: Ref = ref(''); - // const codeList = c.model.appCodeListId; - // // 值分隔符 - // let valueSeparator = ','; - // if (codeList && codeList.valueSeparator) { - // valueSeparator = codeList.valueSeparator; - // } + const codeList = c.codeList; + // 值分隔符 + let valueSeparator = ','; + if (codeList && codeList.valueSeparator) { + valueSeparator = codeList.valueSeparator; + } watch( () => props.value, @@ -28,13 +30,13 @@ export const IBizSpan = defineComponent({ text.value = ''; return; } - if (c.valueFormat) { - text.value = dayjs(newVal).format(c.valueFormat); + if (c.parent.valueFormat) { + text.value = dayjs(newVal).format(c.parent.valueFormat); } else { text.value = newVal ? `${newVal}` : ''; } - if (c.unitName) { - text.value += c.unitName; + if (c.parent.unitName) { + text.value += c.parent.unitName; } } }, @@ -43,42 +45,42 @@ export const IBizSpan = defineComponent({ }, ); - // // 代码表数据 - // const items = ref([]); - // if (codeList) { - // watch( - // () => props.data, - // newVal => { - // c.loadCodeList(newVal).then(_codeList => { - // items.value = _codeList; - // }); - // }, - // { - // immediate: true, - // deep: true, - // }, - // ); - // } + // 代码表数据 + const items = ref([]); + if (codeList) { + watch( + () => props.data, + newVal => { + c.loadCodeList(newVal).then(_codeList => { + items.value = _codeList; + }); + }, + { + immediate: true, + deep: true, + }, + ); + } - // /** 代码表转换后文本 */ - // const codeListText = computed(() => { - // if (!codeList || !props.value || items.value.length === 0) { - // return undefined; - // } - // if (codeList.codeItemValueNumber) { - // return items.value.find(item => item.value === props.value)?.text; - // } - // const values = (props.value as string).split(valueSeparator); - // const selects = items.value.filter(item => - // values.includes(item.value as string & Number), - // ); - // return selects.map(item => item.text).join(valueSeparator); - // }); + // 代码表转换后文本 + const codeListText = computed(() => { + if (!codeList || !props.value || items.value.length === 0) { + return undefined; + } + if (codeList.codeItemValueNumber) { + return items.value.find(item => item.value === props.value)?.text; + } + const values = (props.value as string).split(valueSeparator); + const selects = items.value.filter(item => + values.includes(item.value as string & Number), + ); + return selects.map(item => item.text).join(valueSeparator); + }); return { ns, text, - // codeListText, + codeListText, }; }, render() { @@ -90,8 +92,7 @@ export const IBizSpan = defineComponent({ this.readonly ? this.ns.m('readonly') : '', ]} > - {/* {this.codeListText || this.text} */} - {this.text} + {this.codeListText || this.text} ); }, diff --git a/src/editor/stepper/ibiz-stepper/ibiz-stepper.tsx b/src/editor/stepper/ibiz-stepper/ibiz-stepper.tsx new file mode 100644 index 0000000000000000000000000000000000000000..538306c40058f8601c68ad6a0a81860e95944006 --- /dev/null +++ b/src/editor/stepper/ibiz-stepper/ibiz-stepper.tsx @@ -0,0 +1,108 @@ +import { defineComponent, ref, watch } from 'vue'; +import { + getEditorEmits, + getStepperProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { StepperEditorController } from '../stepper-editor.controller'; + +export const IBizStepper = defineComponent({ + name: 'IBizStepper', + props: getStepperProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('stepper'); + + const c = props.controller; + + const editorModel = c.model; + + const currentVal = ref(null); + + // 步进器步长 + let step = 1; + // 数值精度 + let precision = 0; + // 设置步进器允许的最大值 + let max = Infinity; + // 设置步进器允许的最小值 + let min = -Infinity; + if (editorModel.editorParams) { + if (editorModel.editorParams.stepvalue) { + step = c.handleStringToNumber(editorModel.editorParams.stepvalue); + } + if (editorModel.editorParams.precision) { + precision = c.handleStringToNumber(editorModel.editorParams.precision); + } + if (editorModel.editorParams.maxvalue) { + max = c.handleStringToNumber(editorModel.editorParams.maxvalue); + } + if (editorModel.editorParams.minvalue) { + min = c.handleStringToNumber(editorModel.editorParams.minvalue); + } + } + + watch( + () => props.value, + (newVal, oldVal) => { + if (newVal !== oldVal) { + const number = Number(newVal); + currentVal.value = Number.isNaN(number) ? 0 : number; + } + }, + { immediate: true }, + ); + + const handleChange = (e: number | null) => { + emit('change', e); + }; + + const inputRef = ref(); + + return { + ns, + c, + currentVal, + handleChange, + inputRef, + step, + precision, + max, + min, + }; + }, + render() { + let content = null; + if (this.readonly) { + // 只读显示 + content = `${this.currentVal}`; + } else { + // 编辑态显示 + content = [ + , + ]; + } + + return ( +
+ {content} +
+ ); + }, +}); diff --git a/src/editor/stepper/index.ts b/src/editor/stepper/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..a6574da627310c34ec896730c4444a8734033b23 --- /dev/null +++ b/src/editor/stepper/index.ts @@ -0,0 +1,3 @@ +export { IBizStepper } from './ibiz-stepper/ibiz-stepper'; +export * from './stepper-editor.controller'; +export * from './stepper-editor.provider'; diff --git a/src/editor/stepper/stepper-editor.controller.ts b/src/editor/stepper/stepper-editor.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..4ca70e33128c3c9d8e31febd7dcbcfffff7a4e9a --- /dev/null +++ b/src/editor/stepper/stepper-editor.controller.ts @@ -0,0 +1,11 @@ +import { EditorController } from '@ibiz-template/runtime'; +import { IStepper } from '@ibiz/model-core'; + +/** + * 步进器编辑器控制器 + * + * @export + * @class StepperEditorController + * @extends {EditorController} + */ +export class StepperEditorController extends EditorController {} diff --git a/src/editor/stepper/stepper-editor.provider.ts b/src/editor/stepper/stepper-editor.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..6dffdf7d60cf60f7abcfa7808df4a3937eb11552 --- /dev/null +++ b/src/editor/stepper/stepper-editor.provider.ts @@ -0,0 +1,29 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { IStepper } from '@ibiz/model-core'; +import { StepperEditorController } from './stepper-editor.controller'; + +/** + * 步进器编辑器适配器 + * + * @export + * @class StepperEditorProvider + * @implements {EditorProvider} + */ +export class StepperEditorProvider implements IEditorProvider { + formEditor: string = 'IBizStepper'; + + gridEditor: string = 'IBizStepper'; + + async createController( + editorModel: IStepper, + parentController: IEditorContainerController, + ): Promise { + const c = new StepperEditorController(editorModel, parentController); + await c.init(); + return c; + } +} diff --git a/src/editor/switch/ibiz-switch/ibiz-switch.tsx b/src/editor/switch/ibiz-switch/ibiz-switch.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f49bbede758f7bad2e9d152b668b0ca754598391 --- /dev/null +++ b/src/editor/switch/ibiz-switch/ibiz-switch.tsx @@ -0,0 +1,92 @@ +import { defineComponent, ref, watch } from 'vue'; +import { + getEditorEmits, + getSwitchProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { SwitchEditorController } from '../switch-editor.controller'; + +export const IBizSwitch = defineComponent({ + name: 'IBizSwitch', + props: getSwitchProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('switch'); + const c = props.controller; + const editorModel = c.model; + + // 当前值 + const currentVal = ref(false); + + // 文字描述 + let activeText = ''; + let inactiveText = ''; + + // 开关文字描述配置 + let dicData = [ + { value: 0, label: '' }, + { value: 1, label: '' }, + ]; + if (editorModel.editorParams) { + if (editorModel.editorParams.dicdata) { + dicData = c.handleStringToObj(editorModel.editorParams.dicdata); + } + } + + if (dicData && Array.isArray(dicData)) { + const inactiveResult = dicData.find((item: IData) => { + return item.value === 0; + }); + if (inactiveResult) { + inactiveText = inactiveResult.label; + } + const activeResult = dicData.find((item: IData) => { + return item.value === 1; + }); + if (activeResult) { + activeText = activeResult.label; + } + } + + watch( + () => props.value, + (newVal, oldVal) => { + if (newVal !== oldVal) { + if (!newVal) { + currentVal.value = false; + } else { + currentVal.value = props.value === 1; + } + } + }, + { immediate: true }, + ); + + const handleChange = (currentValue: boolean) => { + const emitValue = currentValue === true ? 1 : 0; + emit('change', emitValue); + }; + + return { + ns, + currentVal, + activeText, + inactiveText, + handleChange, + dicData, + }; + }, + render() { + return ( +
+ +
+ ); + }, +}); diff --git a/src/editor/switch/index.ts b/src/editor/switch/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..bd4b1960261ce875d39a3513b11b15e9a6987a60 --- /dev/null +++ b/src/editor/switch/index.ts @@ -0,0 +1,3 @@ +export { IBizSwitch } from './ibiz-switch/ibiz-switch'; +export * from './switch-editor.controller'; +export * from './switch-editor.provider'; diff --git a/src/editor/switch/switch-editor.controller.ts b/src/editor/switch/switch-editor.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..bf792746b45dc1dd5134ff855e7b778419bb2638 --- /dev/null +++ b/src/editor/switch/switch-editor.controller.ts @@ -0,0 +1,11 @@ +import { EditorController } from '@ibiz-template/runtime'; +import { ICheckBox } from '@ibiz/model-core'; + +/** + * 开关编辑器控制器 + * + * @export + * @class SwitchEditorController + * @extends {EditorController} + */ +export class SwitchEditorController extends EditorController {} diff --git a/src/editor/switch/switch-editor.provider.ts b/src/editor/switch/switch-editor.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..2acfd8388bad53d0aa2fbe3330a911e16ea9c4fd --- /dev/null +++ b/src/editor/switch/switch-editor.provider.ts @@ -0,0 +1,29 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { ICheckBox } from '@ibiz/model-core'; +import { SwitchEditorController } from './switch-editor.controller'; + +/** + * 开关编辑器适配器 + * + * @export + * @class SwitchEditorProvider + * @implements {EditorProvider} + */ +export class SwitchEditorProvider implements IEditorProvider { + formEditor: string = 'IBizSwitch'; + + gridEditor: string = 'IBizSwitch'; + + async createController( + editorModel: ICheckBox, + parentController: IEditorContainerController, + ): Promise { + const c = new SwitchEditorController(editorModel, parentController); + await c.init(); + return c; + } +} diff --git a/src/editor/text-box/ibiz-grid-input-number/ibiz-grid-input-number.tsx b/src/editor/text-box/ibiz-grid-input-number/ibiz-grid-input-number.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2c297131e79bb69736e66411408f602a924fe66e --- /dev/null +++ b/src/editor/text-box/ibiz-grid-input-number/ibiz-grid-input-number.tsx @@ -0,0 +1,29 @@ +import { defineComponent, h, resolveComponent } from 'vue'; +import { + getGridEditorEmits, + getGridInputNumberProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { TextBoxEditorController } from '../text-box-editor.controller'; + +export const IBizGridInputNumber = defineComponent({ + name: 'IBizGridInputNumber', + props: getGridInputNumberProps(), + emits: getGridEditorEmits(), + setup() { + const ns = useNamespace('grid-input-number'); + + return { + ns, + }; + }, + render() { + return ( +
+ {h(resolveComponent('IBizInputNumber'), { + ...this.$props, + })} +
+ ); + }, +}); diff --git a/src/editor/text-box/ibiz-grid-input/ibiz-grid-input.tsx b/src/editor/text-box/ibiz-grid-input/ibiz-grid-input.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ba3b834d5d1f705298e89914436feeef0fd1320f --- /dev/null +++ b/src/editor/text-box/ibiz-grid-input/ibiz-grid-input.tsx @@ -0,0 +1,29 @@ +import { defineComponent, h, resolveComponent } from 'vue'; +import { + getGridEditorEmits, + getGridInputProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import { TextBoxEditorController } from '../text-box-editor.controller'; + +export const IBizGridInput = defineComponent({ + name: 'IBizGridInput', + props: getGridInputProps(), + emits: getGridEditorEmits(), + setup() { + const ns = useNamespace('grid-input'); + + return { + ns, + }; + }, + render() { + return ( +
+ {h(resolveComponent('IBizInput'), { + ...this.$props, + })} +
+ ); + }, +}); diff --git a/src/editor/text-box/ibiz-input-ip/ibiz-input-ip.scss b/src/editor/text-box/ibiz-input-ip/ibiz-input-ip.scss new file mode 100644 index 0000000000000000000000000000000000000000..952d99cf623218898a0b6c46e35c207f3704a05a --- /dev/null +++ b/src/editor/text-box/ibiz-input-ip/ibiz-input-ip.scss @@ -0,0 +1,9 @@ +@include b(input-ip) { + @include flex; + + flex-wrap: nowrap; + + .el-input { + margin: 0 3px; + } +} diff --git a/src/editor/text-box/ibiz-input-ip/ibiz-input-ip.tsx b/src/editor/text-box/ibiz-input-ip/ibiz-input-ip.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c2f3d799f6633d5a41839f9095e7ebc5c4f2de97 --- /dev/null +++ b/src/editor/text-box/ibiz-input-ip/ibiz-input-ip.tsx @@ -0,0 +1,168 @@ +/* eslint-disable no-param-reassign */ +import { defineComponent, Ref, ref, watch } from 'vue'; +import { + getEditorEmits, + getInputIpProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import './ibiz-input-ip.scss'; +import { TextBoxEditorController } from '../text-box-editor.controller'; + +export const IBizInputIP = defineComponent({ + name: 'IBizInputIP', + props: getInputIpProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('input-ip'); + + const c = props.controller; + + // 获取当前值 + const currentVal = ref([]); + + // 当前组件是否已获取到焦点 + const activeElement = ref(false); + + // 当前组件输入框是否全部失焦 + const isAllBlur = ref(false); + + // 四段ip + const firstIp = ref(''); + const secIp = ref(''); + const thirdIp = ref(''); + const forIp = ref(''); + + // 初始赋值 + if (props.value) { + const ipArr = props.value.split('.'); + currentVal.value = ipArr; + firstIp.value = currentVal.value[0]; + secIp.value = currentVal.value[1]; + thirdIp.value = currentVal.value[2]; + forIp.value = currentVal.value[3]; + } + + // 验证格式 + const checkIpVal = ( + newVal: string, + oldVal: string, + ip: Ref, + index: number, + ) => { + if (newVal === '') return; + const reg = /^(([0-9]|([1-9]\d)|(1\d\d)|(2([0-4]\d|5[0-5]))))$/g; + if (reg.test(newVal)) { + currentVal.value[index] = newVal; + } else if (ip) { + ibiz.message.warning(`ip格式验证未通过,第${index + 1}段ip重置回旧值`); + ip.value = oldVal; + currentVal.value[index] = oldVal; + } + if (firstIp.value && secIp.value && thirdIp.value && forIp.value) { + emit( + 'change', + `${firstIp.value}.${secIp.value}.${thirdIp.value}.${forIp.value}`, + ); + } + }; + + // 监听每段IP变化 + watch(firstIp, (newVal: string, oldVal: string) => { + checkIpVal(newVal, oldVal, firstIp, 0); + }); + watch(secIp, (newVal: string, oldVal: string) => { + checkIpVal(newVal, oldVal, secIp, 1); + }); + watch(thirdIp, (newVal: string, oldVal: string) => { + checkIpVal(newVal, oldVal, thirdIp, 2); + }); + watch(forIp, (newVal: string, oldVal: string) => { + checkIpVal(newVal, oldVal, forIp, 3); + }); + + // 输入框获取到焦点时设置该组件已获取到焦点 + const getFocus = () => { + activeElement.value = true; + if (isAllBlur.value) { + isAllBlur.value = false; + } + }; + + // 失焦事件 + const blur = () => { + activeElement.value = false; + setTimeout(() => { + if (!activeElement.value) { + isAllBlur.value = true; + } + }, 0); + }; + + return { + ns, + c, + currentVal, + getFocus, + blur, + firstIp, + secIp, + thirdIp, + forIp, + }; + }, + render() { + return ( +
+ + . + + . + + . + +
+ ); + }, +}); diff --git a/src/editor/text-box/ibiz-input-number/ibiz-input-number.scss b/src/editor/text-box/ibiz-input-number/ibiz-input-number.scss new file mode 100644 index 0000000000000000000000000000000000000000..1400644af83a58029873ed7729058e726d1d75db --- /dev/null +++ b/src/editor/text-box/ibiz-input-number/ibiz-input-number.scss @@ -0,0 +1,34 @@ +@include b('input-number') { + @include set-component-css-var('input-number', $input-number); + + // 常规样式 + .el-input .el-input__inner { + font-size: getCssVar('input-number', 'font-size'); + color: getCssVar('input-number', 'text-color'); + border-color: getCssVar('input-number', 'border-color'); + } + + .el-input .el-input__inner::placeholder { + color: getCssVar('input-number', 'placeholder-color'); + } + + // 禁用样式 + .el-input.is-disabled .el-input__inner { + color: getCssVar('input-number', 'disabled-text-color'); + background-color: getCssVar('input-number', 'disabled-bg-color'); + border-color: getCssVar('input-number', 'disabled-border-color'); + } + + // 只读模式显示 + @include m(readonly) { + color: getCssVar('text-color', 'readonly'); + @include overflow-wrap; + } + + // 单位后缀样式 + .#{bem('input-number','unit')} { + margin-left: 10px; + font-size: getCssVar('input-number', 'font-size'); + font-style: normal; + } +} diff --git a/src/editor/text-box/ibiz-input-number/ibiz-input-number.tsx b/src/editor/text-box/ibiz-input-number/ibiz-input-number.tsx new file mode 100644 index 0000000000000000000000000000000000000000..415977370e47a46ae6300a1dc8d9f1860780aeec --- /dev/null +++ b/src/editor/text-box/ibiz-input-number/ibiz-input-number.tsx @@ -0,0 +1,93 @@ +import { defineComponent, ref, watch } from 'vue'; +import { + getEditorEmits, + getInputNumberProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import './ibiz-input-number.scss'; +import { TextBoxEditorController } from '../text-box-editor.controller'; + +export const IBizInputNumber = defineComponent({ + name: 'IBizInputNumber', + props: getInputNumberProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('input-number'); + + const c = props.controller; + + const currentVal = ref(null); + + watch( + () => props.value, + (newVal, oldVal) => { + if (newVal !== oldVal) { + const number = Number(newVal); + currentVal.value = Number.isNaN(number) ? 0 : number; + } + }, + { immediate: true }, + ); + + const handleChange = (e: number | null) => { + emit('change', e); + }; + + const inputRef = ref(); + + if (props.autoFocus) { + watch(inputRef, newVal => { + if (newVal) { + const input = newVal.$el.getElementsByTagName('input')[0]; + input.focus(); + } + }); + } + + return { + ns, + c, + currentVal, + handleChange, + inputRef, + }; + }, + render() { + const { unitName } = this.c.parent; + + let content = null; + if (this.readonly) { + // 只读显示 + content = `${this.currentVal}`; + // 当有值且单位存在时才显示单位 + if (content && unitName) { + content += unitName; + } + } else { + // 编辑态显示 + content = [ + , + unitName && {unitName}, + ]; + } + + return ( +
+ {content} +
+ ); + }, +}); diff --git a/src/editor/text-box/index.ts b/src/editor/text-box/index.ts index 540f5503d39ec99cd3b2996a1935a8bba2e1c4c4..ab9b972ce370a9f0ad6c9f82aa6f191dea3ff5af 100644 --- a/src/editor/text-box/index.ts +++ b/src/editor/text-box/index.ts @@ -1,3 +1,7 @@ export { IBizInput } from './input/input'; +export { IBizInputNumber } from './ibiz-input-number/ibiz-input-number'; +export { IBizInputIP } from './ibiz-input-ip/ibiz-input-ip'; +export { IBizGridInput } from './ibiz-grid-input/ibiz-grid-input'; +export { IBizGridInputNumber } from './ibiz-grid-input-number/ibiz-grid-input-number'; export * from './text-box-editor.controller'; export * from './text-box-editor.provider'; diff --git a/src/editor/text-box/input/input.tsx b/src/editor/text-box/input/input.tsx index 3f43263158f19c058a99a71576b808728cf6e77a..172d4eb3c0f38aa43c649c998d93c47a8ac3dce1 100644 --- a/src/editor/text-box/input/input.tsx +++ b/src/editor/text-box/input/input.tsx @@ -6,10 +6,11 @@ import { useNamespace, } from '@ibiz-template/vue3-util'; import './input.scss'; +import { TextBoxEditorController } from '../text-box-editor.controller'; export const IBizInput = defineComponent({ name: 'IBizInput', - props: getInputProps(), + props: getInputProps(), emits: getEditorEmits(), setup(props, { emit }) { const ns = useNamespace('input'); @@ -110,6 +111,7 @@ export const IBizInput = defineComponent({ } return { + c, ns, rows, type, @@ -121,7 +123,7 @@ export const IBizInput = defineComponent({ }; }, render() { - const { unitName } = this.controller.model; + const { unitName } = this.c.parent; let content = null; if (this.readonly) { diff --git a/src/editor/upload/ibiz-file-upload/ibiz-file-upload.scss b/src/editor/upload/ibiz-file-upload/ibiz-file-upload.scss new file mode 100644 index 0000000000000000000000000000000000000000..8c3532c8509771ef0984c1a5ee36eee600c369e3 --- /dev/null +++ b/src/editor/upload/ibiz-file-upload/ibiz-file-upload.scss @@ -0,0 +1,51 @@ +@include b('file-upload') { + @include set-component-css-var('file-upload', $file-upload); + + @include b('file-upload-button') { + span { + font-size: getCssVar('file-upload', 'font-size'); + color: getCssVar('file-upload', 'text-color'); + } + } + + .el-button:hover { + span { + color: getCssVar('file-upload', 'hover-color'); + } + } + + .el-upload-list { + @include flex(row, flex-start, flex-start, wrap); + } + + .el-upload-list__item { + @include flex(row, flex-start, center, nowrap); + + flex: 0 0 auto; + color: getCssVar('file-upload', 'text-color'); + + &:hover { + @include flex(row, flex-start, center, nowrap); + + background: getCssVar('file-upload', 'disabled-bg-color'); + + span { + color: getCssVar('file-upload', 'hover-color'); + } + } + } + + // 禁用态样式 + @include m('disabled') { + .el-upload-list__item>span{ + color: getCssVar('text-color', 'disabled'); + } + } + + // 只读态样式 + @include m('readonly') { + .el-upload-list__item>span{ + color: getCssVar('text-color', 'readonly'); + } + } +} diff --git a/src/editor/upload/ibiz-file-upload/ibiz-file-upload.tsx b/src/editor/upload/ibiz-file-upload/ibiz-file-upload.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7e60f885fa09643e8784cf0af93080cc3374d93a --- /dev/null +++ b/src/editor/upload/ibiz-file-upload/ibiz-file-upload.tsx @@ -0,0 +1,77 @@ +import { defineComponent } from 'vue'; +import { + getEditorEmits, + getUploadProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import './ibiz-file-upload.scss'; +import { useIViewUpload } from '../use/use-iview-upload'; +import { UploadEditorController } from '../upload-editor.controller'; + +export const IBizFileUpload = defineComponent({ + name: 'IBizFileUpload', + props: getUploadProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('file-upload'); + const c = props.controller; + + const { + uploadUrl, + headers, + files, + onDownload, + onError, + onRemove, + onSuccess, + beforeUpload, + } = useIViewUpload( + props, + value => { + emit('change', value); + }, + c, + ); + + return { + ns, + c, + uploadUrl, + headers, + files, + onDownload, + onError, + onRemove, + onSuccess, + beforeUpload, + }; + }, + render() { + return ( +
+ + 上传文件 + +
+ ); + }, +}); diff --git a/src/editor/upload/ibiz-image-select/ibiz-image-select.scss b/src/editor/upload/ibiz-image-select/ibiz-image-select.scss new file mode 100644 index 0000000000000000000000000000000000000000..1c9c0b58ac93db8fc364f08598960e86cac017a1 --- /dev/null +++ b/src/editor/upload/ibiz-image-select/ibiz-image-select.scss @@ -0,0 +1,62 @@ +@include b('image-select') { + @include set-component-css-var('image-select', $image-select); + + .el-upload--picture-card { + width: getCssVar('image-select', 'box-width'); + height: getCssVar('image-select', 'box-height'); + } + + .el-upload-list__item { + &:hover { + .#{bem(image-select-content-action)} { + display: block; + } + } + } + + .el-upload-list__item, + .#{bem(image-select-content-svg)} { + position: relative; + width: getCssVar('image-select', 'box-width'); + height: getCssVar('image-select', 'box-height'); + line-height: getCssVar('image-select', 'box-height'); + text-align: center; + background: getCssVar('image-select', 'list-item-bg-color'); + box-shadow: getCssVar('image-select', 'list-item-box-shadow'); + + img, + svg { + width: 100%; + height: 100%; + vertical-align: top; + } + } + + .#{bem(image-select-content-action)} { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: none; + background: getCssVar('image-select', 'list-item-cover-bg-color'); + + ion-icon { + margin: getCssVar('image-select', 'list-item-cover-i-margin'); + font-size: getCssVar('image-select', 'list-item-cover-i-size'); + color: getCssVar('image-select', 'list-item-cover-i-color'); + cursor: pointer; + } + } +} + +@include b(image-select-modal) { + .#{bem(image-select-modal-img)}, + .#{bem(image-select-modal-svg)} { + width: 100%; + + img { + width: 100%; + } + } +} diff --git a/src/editor/upload/ibiz-image-select/ibiz-image-select.tsx b/src/editor/upload/ibiz-image-select/ibiz-image-select.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2a19781ce0a15dba6d878065b442c26b075806d8 --- /dev/null +++ b/src/editor/upload/ibiz-image-select/ibiz-image-select.tsx @@ -0,0 +1,155 @@ +/* eslint-disable no-param-reassign */ +import { defineComponent, Ref, ref, watch } from 'vue'; +import { + getEditorEmits, + getUploadProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import './ibiz-image-select.scss'; +import { UploadEditorController } from '../upload-editor.controller'; + +export const IBizImageSelect = defineComponent({ + name: 'IBizImageSelect', + props: getUploadProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('image-select'); + + const c = props.controller; + + // 图片文件列表 + const fileList: Ref = ref([]); + + // 对话框绑定值 + const dialogVisible = ref(false); + + // img图片地址 + const dialogImageUrl = ref(''); + + // svg图片内容 + const svg = ref(''); + + // 值响应式变更 + watch( + () => props.value, + newVal => { + if (newVal) { + const reg = + /^(data:image)|(.(bmp|jpg|jpeg|png|tif|gif|pcx|tga|exif|fpx|svg|psd|cdr|pcd|dxf|ufo|eps|ai|raw|WMF|webp))$/; + if (reg.test(newVal)) { + dialogImageUrl.value = newVal; + svg.value = ''; + } else { + svg.value = newVal; + } + fileList.value.push({ url: newVal }); + } + }, + { immediate: true }, + ); + + // 打开图片预览 + const handlePictureCardPreview = () => { + dialogVisible.value = true; + }; + + // 图片上传 + const uploading = (file: IData) => { + const reg = /(.png|.jpg|.jpeg|.svg)$/; + if (reg.test(file.name)) { + const svgReg = /.svg$/; + const reader = new FileReader(); + if (svgReg.test(file.name)) { + reader.readAsText(file.raw); // 读取文件 + reader.onload = (evt: IData) => { + // 读取完文件之后会回来这里 + const fileString = evt.target.result; // 读取文件内容 + emit('change', fileString); + }; + } else { + reader.readAsDataURL(file.raw); + reader.onload = (evt: IData) => { + const fileString: string = evt.target.result; + emit('change', fileString); + }; + } + } else { + ibiz.message.error('文件类型错误'); + } + }; + + // 文件删除 + const removeFile = () => { + fileList.value = []; + emit('change', null); + }; + + return { + ns, + c, + fileList, + svg, + dialogImageUrl, + dialogVisible, + handlePictureCardPreview, + uploading, + removeFile, + }; + }, + render() { + return ( +
+ + {{ + default: () => { + return ; + }, + file: () => { + return [ + this.svg ? ( +
+ ) : ( +
+ +
+ ), +
+ this.handlePictureCardPreview()} + name='search' + > + this.removeFile()} + name='remove' + > +
, + ]; + }, + }} +
+ + {this.svg ? ( +
+ ) : ( +
+ +
+ )} +
+
+ ); + }, +}); diff --git a/src/editor/upload/ibiz-image-upload/ibiz-image-upload.scss b/src/editor/upload/ibiz-image-upload/ibiz-image-upload.scss new file mode 100644 index 0000000000000000000000000000000000000000..2278ad36566ccfc090cd736b4d4b96ee0d56559c --- /dev/null +++ b/src/editor/upload/ibiz-image-upload/ibiz-image-upload.scss @@ -0,0 +1,91 @@ +@include b('image-upload') { + @include set-component-css-var('image-upload', $image-upload); + + display: flex; + align-items: center; + + .el-upload--picture-card { + width: getCssVar('image-upload', 'box-width'); + height: getCssVar('image-upload', 'box-height'); + } + + @include e('btn') { + width: getCssVar('image-upload', 'box-width'); + height: getCssVar('image-upload', 'box-height'); + line-height: getCssVar('image-upload', 'box-height'); + } + + @include e('list-item') { + position: relative; + display: inline-block; + width: getCssVar('image-upload', 'box-width'); + height: getCssVar('image-upload', 'box-height'); + margin-right: getCssVar('image-upload', 'list-item-margin'); + line-height: getCssVar('image-upload', 'box-height'); + text-align: center; + background: getCssVar('image-upload', 'list-item-bg-color'); + border: getCssVar('image-upload', 'list-item-border'); + border-radius: getCssVar('image-upload', 'list-item-border-radius'); + box-shadow: getCssVar('image-upload', 'list-item-box-shadow'); + + img { + width: 100%; + height: 100%; + vertical-align: top; + } + + &:hover { + @include e('list-item-cover') { + display: block; + } + } + } + + @include e('list-item-cover') { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: none; + background: getCssVar('image-upload', 'list-item-cover-bg-color'); + + ion-icon { + margin: getCssVar('image-upload', 'list-item-cover-i-margin'); + font-size: getCssVar('image-upload', 'list-item-cover-i-size'); + color: getCssVar('image-upload', 'list-item-cover-i-color'); + cursor: pointer; + } + + img { + width: 100%; + height: 100%; + vertical-align: top; + } + } + + // 禁用态样式 + @include m('disabled') { + + @include e('download-icon') { + display: none; + } + @include e('remove-icon') { + display: none; + } + } + + // 只读态样式 + @include m('readonly') { + + @include e('remove-icon') { + display: none; + } + } +} + +@include b('image-upload-modal') { + @include e('img') { + width: 100%; + } +} diff --git a/src/editor/upload/ibiz-image-upload/ibiz-image-upload.tsx b/src/editor/upload/ibiz-image-upload/ibiz-image-upload.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6a80cee56113f021f1b678e16add60cda8a0ca5f --- /dev/null +++ b/src/editor/upload/ibiz-image-upload/ibiz-image-upload.tsx @@ -0,0 +1,134 @@ +/* eslint-disable no-param-reassign */ +import { defineComponent, ref } from 'vue'; +import { + getEditorEmits, + getUploadProps, + useNamespace, +} from '@ibiz-template/vue3-util'; +import './ibiz-image-upload.scss'; +import { useIViewUpload } from '../use/use-iview-upload'; +import { UploadEditorController } from '../upload-editor.controller'; + +export const IBizImageUpload = defineComponent({ + name: 'IBizImageUpload', + props: getUploadProps(), + emits: getEditorEmits(), + setup(props, { emit }) { + const ns = useNamespace('image-upload'); + + const c = props.controller; + + const { + uploadUrl, + headers, + files, + limit, + onDownload, + onError, + onRemove, + onSuccess, + beforeUpload, + } = useIViewUpload( + props, + value => { + emit('change', value); + }, + c, + ); + + const dialogImageUrl = ref(''); + const dialogVisible = ref(false); + + const onDialogVisibleChange = (value: boolean) => { + dialogVisible.value = value; + }; + + // 预览 + const onPreview = (file: IData) => { + dialogImageUrl.value = file.url; + dialogVisible.value = true; + }; + + return { + ns, + c, + files, + limit, + headers, + uploadUrl, + dialogImageUrl, + dialogVisible, + beforeUpload, + onSuccess, + onError, + onRemove, + onDownload, + onDialogVisibleChange, + onPreview, + }; + }, + render() { + // 编辑态展示 + return ( +
+
+ {this.files.map(item => ( +
+ +
+ this.onPreview(item)} + name='search' + > + this.onDownload(item)} + name='download' + > + this.onRemove(item)} + name='remove' + > +
+
+ ))} +
+ + + + this.onDialogVisibleChange(true)} + onClose={() => this.onDialogVisibleChange(false)} + > + + +
+ ); + }, +}); diff --git a/src/editor/upload/index.ts b/src/editor/upload/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..5b222f902dfd0d1d3b1623397d1b5f39593453f2 --- /dev/null +++ b/src/editor/upload/index.ts @@ -0,0 +1,5 @@ +export { IBizFileUpload } from './ibiz-file-upload/ibiz-file-upload'; +export { IBizImageUpload } from './ibiz-image-upload/ibiz-image-upload'; +export { IBizImageSelect } from './ibiz-image-select/ibiz-image-select'; +export * from './upload-editor.controller'; +export * from './upload-editor.provider'; diff --git a/src/editor/upload/upload-editor.controller.ts b/src/editor/upload/upload-editor.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..8098ef29288b1c94595e6796e5c777e7507713ac --- /dev/null +++ b/src/editor/upload/upload-editor.controller.ts @@ -0,0 +1,143 @@ +import { downloadFileFromBlob, RuntimeError } from '@ibiz-template/core'; +import { convertNavData, EditorController } from '@ibiz-template/runtime'; +import { IFileUploader } from '@ibiz/model-core'; +import qs from 'qs'; + +/** + * 文件上传编辑器控制器 + * @return {*} + * @author: zhujiamin + * @Date: 2022-08-25 10:57:58 + */ +export class UploadEditorController extends EditorController { + /** + * 是否支持拖拽 + */ + public isDrag: boolean = false; + + /** + * 是否多选 + */ + public multiple: boolean = true; + + /** + * 接受上传的文件类型 + */ + public accept: string = ''; + + /** + * 上传参数 + */ + public uploadParams?: IParams; + + /** + * 下载参数 + */ + public exportParams?: IParams; + + protected async onInit(): Promise { + await super.onInit(); + // 图片类型增加图片类型限制 + if (this.model.editorType?.includes('PICTURE')) { + this.accept = 'image/*'; + } + + // 单项的编辑器类型的设置单选 + if ( + ['FILEUPLOADER_ONE', 'PICTURE_ONE', 'PICTURE_ONE_RAW'].includes( + this.model.editorType!, + ) + ) { + this.multiple = false; + } + if (this.editorParams) { + const { isdrag, multiple, accept, uploadparams, exportparams } = + this.editorParams; + if (isdrag) { + this.isDrag = Boolean(isdrag); + } + if (multiple) { + this.multiple = Boolean(multiple); + } + if (accept) { + this.accept = accept; + } + if (uploadparams) { + // eslint-disable-next-line no-eval + this.uploadParams = eval(`(${uploadparams})`); + } + if (exportparams) { + // eslint-disable-next-line no-eval + this.exportParams = eval(`(${exportparams})`); + } + } + } + + /** + * 计算文件的上传路径和下载路径 + * 下载路径文件id用%fileId%占位,替换即可 + * 配置编辑器参数uploadParams和exportParams时,会像导航参数一样动态添加对应的参数到url上 + * + * @author lxm + * @date 2022-11-17 13:11:43 + * @param {IData} data + * @returns {*} {{ uploadUrl: string; downloadUrl: string }} + */ + calcBaseUrl(data: IData): { uploadUrl: string; downloadUrl: string } { + let uploadUrl = `${ibiz.env.baseUrl}/${ibiz.env.appId}${ibiz.env.uploadFileUrl}`; + let downloadUrl = `${ibiz.env.baseUrl}/${ibiz.env.appId}${ibiz.env.downloadFileUrl}/%fileId%`; + let uploadParams: IParams = {}; + let exportParams: IParams = {}; + if (this.uploadParams) { + uploadParams = convertNavData( + this.uploadParams, + this.context, + this.params, + data, + ); + } + if (this.exportParams) { + exportParams = convertNavData( + this.exportParams, + this.context, + this.params, + data, + ); + } + uploadUrl += qs.stringify(uploadParams, { addQueryPrefix: true }); + downloadUrl += qs.stringify(exportParams, { addQueryPrefix: true }); + + return { uploadUrl, downloadUrl }; + } + + /** + * 请求url获取文件流,并用JS触发文件下载 + * + * @author lxm + * @date 2022-11-17 14:11:09 + * @param {string} url + * @param {IData} file + */ + fileDownload(file: { url: string; name: string }): void { + // 发送get请求 + ibiz.net + .request(file.url, { + method: 'get', + responseType: 'blob', + baseURL: '', // 已经有baseURL了,这里无需再写 + }) + .then((response: IData) => { + if (response.status !== 200) { + throw new RuntimeError('下载文件失败'); + } + // 请求成功,后台返回的是一个文件流 + if (!response.data) { + throw new RuntimeError('文件流数据不存在'); + } else { + // 获取文件名 + const fileName = file.name; + downloadFileFromBlob(response.data, fileName); + } + }); + } +} diff --git a/src/editor/upload/upload-editor.provider.ts b/src/editor/upload/upload-editor.provider.ts new file mode 100644 index 0000000000000000000000000000000000000000..cf079a01eb4c8fcd54e6e42596d39d3f3f093e19 --- /dev/null +++ b/src/editor/upload/upload-editor.provider.ts @@ -0,0 +1,46 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + IEditorContainerController, + IEditorProvider, +} from '@ibiz-template/runtime'; +import { IFileUploader } from '@ibiz/model-core'; +import { UploadEditorController } from './upload-editor.controller'; + +/** + * 文件上传编辑器适配器 + * + * @author lxm + * @date 2022-09-19 22:09:03 + * @export + * @class FileUploaderEditorProvider + * @implements {EditorProvider} + */ +export class FileUploaderEditorProvider implements IEditorProvider { + formEditor: string = 'IBizFileUpload'; + + gridEditor: string = 'IBizGridFileUpload'; + + constructor(editorType: string) { + let componentName = 'IBizFileUpload'; + switch (editorType) { + case 'PICTURE': + case 'PICTURE_ONE': + componentName = 'IBizImageUpload'; + break; + case 'PICTURE_ONE_RAW': + componentName = 'IBizImageSelect'; + break; + default: + } + this.formEditor = componentName; + } + + async createController( + editorModel: IFileUploader, + parentController: IEditorContainerController, + ): Promise { + const c = new UploadEditorController(editorModel, parentController); + await c.init(); + return c; + } +} diff --git a/src/editor/upload/use/use-ibiz-upload.ts b/src/editor/upload/use/use-ibiz-upload.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab24f9e6f29919a02c647f4fe91af4b7e9327e61 --- /dev/null +++ b/src/editor/upload/use/use-ibiz-upload.ts @@ -0,0 +1,283 @@ +/* eslint-disable no-param-reassign */ +import { + HttpResponse, + isImage, + IUploadFile, + uploadFile, +} from '@ibiz-template/core'; +import { Ref, ref, watch } from 'vue'; +import { UploadEditorController } from '../upload-editor.controller'; + +export type FileInfo = { + name: string; + id: string; + status?: 'uploading' | 'finished' | 'fail' | 'cancel'; + percentage?: number; + url?: string; + /** + * 文件名(不带后缀) + */ + fileName?: string; + /** + * 文件类型(拓展名) + */ + fileExt?: string; + /** + * 是否是图片 + */ + isImage?: boolean; +}; + +/** + * 格式化文件信息 + * + * @author lxm + * @date 2022-11-18 15:11:38 + * @param {FileInfo} file + */ +export function formatFileInfo(file: FileInfo, downloadUrl: string): FileInfo { + file.url = downloadUrl.replace('%fileId%', file.id!); + if (!file.status) { + // 不存在时为回填回来的数据默认给他finished + file.status = 'finished'; + } + if (!file.fileName) { + const index = file.name.lastIndexOf('.'); + file.fileName = file.name.substring(0, index); + file.fileExt = file.name.substring(index); + file.isImage = isImage(file.name); + } + return file as FileInfo; +} + +/** + * 文件上传组件初始化,解析props并得到downloadUrl、uploadUrl、fileList + * + * @author lxm + * @date 2022-11-21 10:11:01 + * @export + * @param {{ + * data: Ref; + * value: Ref; + * controller: Ref; + * }} props + * @returns {*} + */ +export function useIBizUploadInit(props: { + data: Ref; + value: Ref; + controller: Ref; +}) { + // 上传文件路径 + const uploadUrl: Ref = ref(''); + + // 下载文件路径 + const downloadUrl: Ref = ref(''); + + // 文件列表 + const valueList: Ref = ref([]); + + // data响应式变更基础路径 + watch( + props.data, + newVal => { + if (newVal) { + const urls = props.controller.value.calcBaseUrl(newVal); + uploadUrl.value = urls.uploadUrl; + downloadUrl.value = urls.downloadUrl; + } + }, + { immediate: true, deep: true }, + ); + + // 值响应式变更 + watch( + props.value, + newVal => { + valueList.value = !newVal ? [] : JSON.parse(newVal); + if (valueList.value.length && downloadUrl.value) { + valueList.value.forEach((file: FileInfo) => { + formatFileInfo(file, downloadUrl.value); + }); + } + }, + { immediate: true }, + ); + + watch( + downloadUrl, + newVal => { + // 下载基础路径变更时全部url重算 + if (newVal && valueList.value.length) { + valueList.value.forEach((file: FileInfo) => { + formatFileInfo(file, newVal); + }); + } + }, + { immediate: true }, + ); + + return { + downloadUrl, + uploadUrl, + valueList, + }; +} + +/** + * 使用文件上传功能,传递外部已存在的文件集合,上传下载基础路径 + * + * @author lxm + * @date 2022-11-21 10:11:01 + * @export + * @param {{ + * downloadUrl: Ref; + * uploadUrl: Ref; + * value: Ref< + * { + * name: string; + * id: string; + * url?: string; + * }[] + * >; + * }} opts + * @returns {*} + */ +export function useIBizUpload(opts: { + downloadUrl: Ref; + uploadUrl: Ref; + value: Ref< + { + name: string; + id: string; + url?: string; + }[] + >; + multiple?: boolean; + accept?: string; +}) { + const uploadState = ref<'undo' | 'loading' | 'done'>('undo'); + const fileList = ref([]); + const { downloadUrl, value, uploadUrl } = opts; + + // 初始化fileList + watch( + value, + newVal => { + if (newVal.length > 0) { + fileList.value = []; + newVal.forEach(item => { + fileList.value.push(formatFileInfo(item, downloadUrl.value)); + }); + } + }, + { + immediate: true, + deep: true, + }, + ); + + // 开始上传后记录文件 + const beforeUpload = (fileData: File[], files: IUploadFile[]) => { + files.forEach(file => { + fileList.value.push({ + name: file.name, + status: file.status, + percentage: file.percentage, + id: file.uid, + url: '', + }); + }); + return true; + }; + + /** + * 更新文件里的上传进度 + * + * @author lxm + * @date 2022-11-18 15:11:09 + * @param {IUploadFile[]} files + */ + const onProgress = (files: IUploadFile[]) => { + files.forEach(file => { + fileList.value.find(item => { + if (item.id === file.uid) { + item.percentage = file.percentage; + return true; + } + return false; + }); + }); + }; + + const onSuccess = (resultFiles: IUploadFile[], res: HttpResponse) => { + // 一次上传成功这一批的文件都成功 + resultFiles.forEach(file => { + fileList.value.find(item => { + if (item.id === file.uid) { + // 把用后台数据替换当前信息,并格式化信息 + item.status = file.status; + item.id = res.data.fileid; + item.name = res.data.filename; + formatFileInfo(item, downloadUrl.value); + return true; + } + return false; + }); + }); + }; + + const onError = (resultFiles: IUploadFile[], error: unknown) => { + // 一次报错这一批的文件都上传失败 + resultFiles.forEach(file => { + fileList.value.find(item => { + if (item.id === file.uid) { + // 更新错误状态 + item.status = file.status; + return true; + } + return false; + }); + }); + // 处理报错信息 + console.error(error); + }; + + const onFinish = (_resultFiles: IUploadFile[]) => { + fileList.value = fileList.value.filter(file => file.status === 'finished'); + uploadState.value = 'done'; + }; + + // 手动控制文件上传,绑定组件的upload + const selectFile = () => { + uploadFile({ + multiple: opts.multiple, + accept: opts.accept, + uploadUrl: uploadUrl.value, + beforeUpload, + progress: onProgress, + success: onSuccess, + error: onError, + finish: onFinish, + }); + }; + + return { + selectFile, + fileList, + uploadState, + }; +} + +export function openImagePreview(file: FileInfo) { + return ibiz.overlay.modal( + 'ImagePreview', + { file }, + { + width: 'auto', + height: 'auto', + placement: 'center', + modalClass: 'ibiz-image-preview-modal', + }, + ); +} diff --git a/src/editor/upload/use/use-iview-upload.ts b/src/editor/upload/use/use-iview-upload.ts new file mode 100644 index 0000000000000000000000000000000000000000..a94eab623bf872b66824ce48d40cf61e3dcdb87f --- /dev/null +++ b/src/editor/upload/use/use-iview-upload.ts @@ -0,0 +1,180 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable no-param-reassign */ +import { HttpError } from '@ibiz-template/core'; +import { getCookie } from 'qx-util'; +import { computed, Ref, ref, watch } from 'vue'; +import { UploadEditorController } from '../upload-editor.controller'; + +/** + * iview的Upload适配逻辑 + * + * @author lxm + * @date 2022-11-17 16:11:12 + * @export + * @param {IParams} props + * @param {(_value: string | null) => {}} valueChange + * @param {UploadEditorController} c + * @returns {*} + */ +export function useIViewUpload( + props: IParams, + valueChange: (_value: string | null) => void, + c: UploadEditorController, +) { + // 文件列表 + const files: Ref< + { + id: string; + name: string; + url?: string; + }[] + > = ref([]); + + // 请求头 + const headers: Ref = ref({ + Authorization: `Bearer ${getCookie('access_token')}`, + }); + + // 上传文件路径 + const uploadUrl: Ref = ref(''); + + // 下载文件路径 + const downloadUrl: Ref = ref(''); + + // 文件上传缓存对象 + const uploadCache: IData = { + count: 0, + cacheFiles: [], // iview上传过程中不能改default-file-list,所以需要缓存 + }; + + // 值响应式变更 + watch( + () => props.value, + newVal => { + files.value = !newVal ? [] : JSON.parse(newVal); + }, + { immediate: true }, + ); + + // data响应式变更基础路径 + watch( + () => props.data, + newVal => { + if (newVal) { + const urls = c.calcBaseUrl(newVal); + uploadUrl.value = urls.uploadUrl; + downloadUrl.value = urls.downloadUrl; + } + }, + { immediate: true, deep: true }, + ); + + watch( + files, + newVal => { + // 变更后且下载基础路径存在时解析 + if (newVal?.length && downloadUrl.value) { + newVal.forEach((file: IData) => { + file.url = file.url || downloadUrl.value.replace('%fileId%', file.id); + }); + } + }, + { immediate: true }, + ); + + watch( + downloadUrl, + newVal => { + // 变更后且下载基础路径存在时解析 + if (newVal && files.value.length) { + files.value.forEach((file: IData) => { + file.url = downloadUrl.value.replace('%fileId%', file.id); + }); + } + }, + { immediate: true }, + ); + + /** + * 抛出值变更事件,根据files计算value + * + * @author lxm + * @date 2022-11-17 14:11:54 + */ + const emitValue = () => { + const _files = [...files.value, ...uploadCache.cacheFiles]; + const value: string | null = + _files.length > 0 + ? JSON.stringify(_files.map(file => ({ name: file.name, id: file.id }))) + : null; + uploadCache.cacheFiles = []; + valueChange(value); + }; + + // 上传前回调 + const beforeUpload = () => { + uploadCache.count += 1; + }; + + // 上传成功回调 + const onSuccess = (response: IData) => { + if (!response) { + return; + } + uploadCache.cacheFiles.push({ + name: response.filename, + id: response.fileid, + }); + uploadCache.count -= 1; + + // 回调都结束后抛出值变更 + if (uploadCache.count === 0) { + emitValue(); + } + }; + + // 上传失败回调 + const onError = (...args: IData[]) => { + const error = args[0]; + uploadCache.count -= 1; + throw new HttpError({ + response: { data: JSON.parse(error.message), status: error.status }, + } as any); + }; + + // 删除回调 + const onRemove = (file: IData) => { + if (props.disabled) { + return; + } + const index = files.value.findIndex(item => item.id === file.id); + if (index !== -1) { + files.value.splice(index, 1); + } + emitValue(); + }; + + // 下载文件 + const onDownload = (file: IData) => { + const url = file.url || downloadUrl.value.replace('%fileId%', file.id); + c.fileDownload({ url, name: file.name }); + }; + + // 允许上传文件的最大数量 + const limit = computed(() => { + return c.multiple ? 9999 : 1; + }); + + return { + uploadUrl, + downloadUrl, + headers, + files, + limit, + onDownload, + onError, + onRemove, + onSuccess, + beforeUpload, + }; +}