From fa77be0885bdff8c0742733145d4cc32dec96bf7 Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Tue, 9 Sep 2025 15:08:41 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=20=E6=96=B0=E5=A2=9E=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E7=BB=84=E4=BB=B6=E6=96=87=E4=BB=B6=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E9=89=B4=E6=9D=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 ++++ src/editor/upload/use/use-van-upload.ts | 24 ++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 649fd1dcc0..d84baacf99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ ## [Unreleased] +### Added + +- 新增上传组件文件下载鉴权 + ## [0.7.41-alpha.13] - 2025-09-04 ### Added diff --git a/src/editor/upload/use/use-van-upload.ts b/src/editor/upload/use/use-van-upload.ts index 88320b738e..97a1918692 100644 --- a/src/editor/upload/use/use-van-upload.ts +++ b/src/editor/upload/use/use-van-upload.ts @@ -98,6 +98,24 @@ export function useVanUpload( if (newVal?.length && downloadUrl.value) { newVal.forEach((file: IData) => { file.url = file.url || downloadUrl.value.replace('%fileId%', file.id); + if (ibiz.config.common.enableDownloadTicket) + ibiz.util.file + .getDownloadTicket( + c.context, + c.params, + props.data, + { + fileId: file.id, + }, + c.downloadTicketParams, + ) + .then(downloadTicket => { + if (downloadTicket && downloadTicket.ticket) + file.url = downloadUrl.value.replace( + '%fileId%', + downloadTicket.ticket, + ); + }); }); } }, @@ -143,8 +161,10 @@ export function useVanUpload( // 上传成功回调 const onSuccess = (response: IData) => { - if (!response) { - return; + if (!response) return; + // 启用传入下载凭证 + if (ibiz.config.common.enableDownloadTicket && response.ticket) { + ibiz.util.file.setDownloadTicket(response.id, response.ticket); } files.value.push({ name: response.filename, -- Gitee From 7e7be78ae27ac03cb0b97b48ca584c26bb66d7cc Mon Sep 17 00:00:00 2001 From: ShineKOT <1917095344@qq.com> Date: Tue, 9 Sep 2025 15:10:08 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E5=B7=A6=E5=8F=B3=E6=BB=91=E5=8A=A8=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E8=A1=8C=E4=B8=BA=E7=BB=84=EF=BC=8C=E5=8D=A1=E7=89=87=E9=A1=B9?= =?UTF-8?q?=E8=A1=8C=E4=B8=BA=E7=BB=84=EF=BC=8C=E9=97=A8=E6=88=B7=E9=83=A8?= =?UTF-8?q?=E4=BB=B6=E7=95=8C=E9=9D=A2=E8=A1=8C=E4=B8=BA=E7=BB=84=EF=BC=8C?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=E6=8C=89=E9=92=AE=E7=BB=84=EF=BC=8C=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E6=8C=89=E9=92=AE=E7=BB=84=E8=AF=86=E5=88=AB=E5=BC=95?= =?UTF-8?q?=E7=94=A8=E7=95=8C=E9=9D=A2=E8=A1=8C=E4=B8=BA=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + src/common/action-group/action-group.scss | 38 +++ src/common/action-group/action-group.tsx | 226 ++++++++++++++++++ src/common/action-toolbar/action-toolbar.tsx | 34 ++- src/common/button-list/button-list.scss | 38 ++- src/common/button-list/button-list.tsx | 214 ++++++++++------- src/common/index.ts | 2 + src/control/list/md-ctrl/md-ctrl.tsx | 34 ++- .../panel-button-list.controller.ts | 40 ++-- 9 files changed, 479 insertions(+), 148 deletions(-) create mode 100644 src/common/action-group/action-group.scss create mode 100644 src/common/action-group/action-group.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index d84baacf99..db2f789e86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### Added - 新增上传组件文件下载鉴权 +- 新增列表左右滑动界面行为组,卡片项行为组,门户部件界面行为组,面板按钮组,表单按钮组识别引用界面行为组 ## [0.7.41-alpha.13] - 2025-09-04 diff --git a/src/common/action-group/action-group.scss b/src/common/action-group/action-group.scss new file mode 100644 index 0000000000..e6e5150d0f --- /dev/null +++ b/src/common/action-group/action-group.scss @@ -0,0 +1,38 @@ +@include b(action-group) { + + @include e('separator') { + width: 100%; + height: rem(1px); + display: inline-block; + vertical-align: middle; + background-color: getCssVar('color', 'border'); + } + + @include e(button) { + .van-button__text { + display: flex; + align-items: center; + gap: getCssVar(spacing, extra, tight); + } + } + .van-popover__content { + display: flex; + flex-direction: column; + .van-button { + padding: 0; + font-size: var(--van-popover-action-font-size); + line-height: var(--van-line-height-md); + width: var(--van-popover-action-width); + height: var(--van-popover-action-height); + border-color: getCssVar(color, white); + &:before { + border-color: getCssVar(color, white); + } + .van-button__text { + display: flex; + align-items: center; + gap: getCssVar(spacing, extra, tight); + } + } + } +} \ No newline at end of file diff --git a/src/common/action-group/action-group.tsx b/src/common/action-group/action-group.tsx new file mode 100644 index 0000000000..ef66a24681 --- /dev/null +++ b/src/common/action-group/action-group.tsx @@ -0,0 +1,226 @@ +/* eslint-disable no-return-assign */ +import { computed, defineComponent, PropType, ref } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { IButtonContainerState } from '@ibiz-template/runtime'; +import { IAppDEUIActionGroupDetail } from '@ibiz/model-core'; +import { convertBtnType } from '../../util'; +import './action-group.scss'; + +export const IBizActionGroup = defineComponent({ + name: 'IBizActionGroup', + props: { + actionDetail: { + type: Object as PropType, + required: true, + }, + actionsState: { + type: Object as PropType, + required: true, + }, + popoverClassName: { + type: String, + }, + direction: { + type: String as PropType<'horizontal' | 'vertical'>, + default: 'horizontal', + }, + }, + emits: { + actionClick: (detail: IAppDEUIActionGroupDetail, event: MouseEvent) => true, + popoverVisibleChange: (visible: boolean) => true, + }, + setup(props, { emit }) { + const ns = useNamespace('action-group'); + + /** + * 是否显示popover + */ + const showPopover = ref(false); + + /** + * 按钮ref + */ + const buttonRef = ref(); + + /** + * 子popover是否显示 + */ + const childPopover = ref(false); + + /** + * 成员集合 + */ + const details = computed(() => { + if ( + props.actionDetail.detailType === 'DEUIACTIONGROUP' && + props.actionDetail.refUIActionGroup + ) + return props.actionDetail.refUIActionGroup.uiactionGroupDetails || []; + return []; + }); + + /** + * 是否显示分组 + */ + const visible = computed(() => { + const visible = details.value.some(item => { + return props.actionsState?.[item.id!]?.visible; + }); + return visible; + }); + + /** + * 弹出位置 + */ + const placement = computed(() => { + if (!buttonRef.value) + return props.direction === 'horizontal' ? 'right' : 'bottom'; + const { innerWidth, innerHeight } = window; + const { + offsetLeft: x, + offsetTop: y, + offsetWidth: width, + } = buttonRef.value.$el; + const centerX = innerWidth - width - 128; + const centerY = details.value.length + ? innerHeight - details.value.length * 44 + : innerHeight / 2; + if (y > centerY) { + if (props.direction === 'horizontal') + return x > centerX ? 'left-end' : 'right-end'; + return x > centerX ? 'top-end' : 'top-start'; + } + if (props.direction === 'horizontal') + return x > centerX ? 'left-start' : 'right-start'; + return x > centerX ? 'bottom-end' : 'bottom-start'; + }); + + /** + * @description 处理点击 + * @param {IAppDEUIActionGroupDetail} detail + * @param {MouseEvent} event + */ + const handleClick = async ( + detail: IAppDEUIActionGroupDetail, + event: MouseEvent, + closePopover: boolean = false, + ) => { + if (closePopover) showPopover.value = false; + emit('actionClick', detail, event); + }; + + /** + * @description Popover 显示状态改变 + */ + const onPopoverVisibleChange = (visible: boolean) => { + emit('popoverVisibleChange', visible); + }; + + /** + * @description 绘制分隔符 + * @returns {*} + */ + const renderSeparator = (visible?: boolean) => { + if (visible) return
; + }; + + /** + * @description 绘制行为 + * @returns {*} + */ + const renderActions = () => { + return details.value?.map((detail: IAppDEUIActionGroupDetail) => { + if (detail.detailType === 'DEUIACTIONGROUP') + return [ + renderSeparator(detail.addSeparator), + + (childPopover.value = visible) + } + />, + ]; + if (props.actionsState?.[detail.id!]?.visible) + return [ + renderSeparator(detail.addSeparator), + handleClick(detail, e, true)} + disabled={props.actionsState[detail.id!].disabled} + class={[ns.e('item'), detail.sysCss?.codeName]} + > + {{ + icon: () => { + return ( + detail.showIcon && + detail.sysImage && ( + + ) + ); + }, + }} + , + ]; + return null; + }); + }; + + return { + ns, + visible, + buttonRef, + placement, + showPopover, + childPopover, + renderActions, + onPopoverVisibleChange, + }; + }, + render() { + if (this.visible) + return ( + this.onPopoverVisibleChange(true)} + onClose={() => this.onPopoverVisibleChange(false)} + > + {{ + reference: () => { + return ( + +
+ {this.actionDetail.refUIActionGroup?.id} +
+ +
+ ); + }, + default: () => { + return this.renderActions(); + }, + }} +
+ ); + return undefined; + }, +}); diff --git a/src/common/action-toolbar/action-toolbar.tsx b/src/common/action-toolbar/action-toolbar.tsx index d6c16e63ce..c6ea347c6e 100644 --- a/src/common/action-toolbar/action-toolbar.tsx +++ b/src/common/action-toolbar/action-toolbar.tsx @@ -34,28 +34,45 @@ export const IBizActionToolbar = defineComponent({ emit('action-click', detail, event); }; - return { ns, handleClick }; + const renderSeparator = () => { + return
; + }; + + return { ns, handleClick, renderSeparator }; }, render() { const details = this.actionDetails || []; - if (this.mode === 'buttons') { - // 按钮模式 + // 按钮模式 + if (this.mode === 'buttons') return (
{details.length > 0 && details.map(detail => { - if (this.actionsState[detail.id!].visible) { + if (detail.detailType === 'DEUIACTIONGROUP') + return [ + detail.addSeparator && this.renderSeparator(), + , + ]; + if (this.actionsState?.[detail.id!]?.visible) { return [ - detail.addSeparator && ( -
- ), + detail.addSeparator && this.renderSeparator(), this.handleClick(detail, e)} disabled={this.actionsState[detail.id!].disabled} - class={[this.ns.e('item'), this.ns.is('disabled', false)]} + class={[ + this.ns.e('item'), + this.ns.is('disabled', false), + detail.sysCss?.codeName, + ]} > {{ icon: () => { @@ -74,7 +91,6 @@ export const IBizActionToolbar = defineComponent({ })}
); - } // 下拉模式 return (
{ibiz.i18n.t('component.actionToolbar.noSupportDropDown')}
diff --git a/src/common/button-list/button-list.scss b/src/common/button-list/button-list.scss index ba09b9acba..7a9683c765 100644 --- a/src/common/button-list/button-list.scss +++ b/src/common/button-list/button-list.scss @@ -28,6 +28,15 @@ gap: getCssVar(spacing, extra, tight); } + @include e(button) { + @include m(caption) { + display: flex; + line-height: 100%; + align-items: center; + gap: getCssVar(spacing, extra, tight); + } + } + @include e(popover) { --ibiz-color-bg-0: #{var(--van-popover-light-text-color)}; @@ -37,18 +46,25 @@ background-color: getCssVar(color, disabled-bg); } - @include m(item) { + .van-popover__content { display: flex; - align-items: center; - gap: getCssVar(spacing, extra, tight); - @include button-list-icon-style; - } - - @include m(caption) { - display: flex; - line-height: 100%; - align-items: center; - gap: getCssVar(spacing, extra, tight); + flex-direction: column; + .van-button { + padding: 0; + font-size: var(--van-popover-action-font-size); + line-height: var(--van-line-height-md); + width: var(--van-popover-action-width); + height: var(--van-popover-action-height); + border-color: getCssVar(color, white); + &:before { + border-color: getCssVar(color, white); + } + .van-button__text { + display: flex; + align-items: center; + gap: getCssVar(spacing, extra, tight); + } + } } } } diff --git a/src/common/button-list/button-list.tsx b/src/common/button-list/button-list.tsx index da2a6bb18e..cedb0a2ac7 100644 --- a/src/common/button-list/button-list.tsx +++ b/src/common/button-list/button-list.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-return-assign */ import { computed, defineComponent, PropType, ref, Ref } from 'vue'; import { JSX } from 'vue/jsx-runtime'; import { useNamespace } from '@ibiz-template/vue3-util'; @@ -37,6 +38,54 @@ export const IBizButtonList = defineComponent({ const showPopover: Ref = ref(false); + /** + * 按钮ref + */ + const buttonRef = ref(); + + /** + * 子popover是否显示 + */ + const childPopover = ref(false); + + /** + * 按钮组成员 + */ + const details = computed(() => { + const { buttonListType, uiactionGroup } = props.model; + if (buttonListType === 'UIACTIONGROUP') + return ( + (uiactionGroup?.uiactionGroupDetails as IAppDEUIActionGroupDetail[]) || + [] + ); + return ( + (props.model as IPanelButtonList).panelButtons || + (props.model as IDEFormButtonList).deformButtons || + [] + ); + }); + + /** + * 弹出位置 + */ + const placement = computed(() => { + if (!buttonRef.value) return 'bottom'; + const { innerWidth, innerHeight } = window; + const { + offsetLeft: x, + offsetTop: y, + offsetWidth: width, + } = buttonRef.value.$el; + const centerX = innerWidth - width - 128; + const centerY = details.value.length + ? innerHeight - details.value.length * 44 + : innerHeight / 2; + if (y > centerY) { + return x > centerX ? 'top-end' : 'top-start'; + } + return x > centerX ? 'bottom-end' : 'bottom-start'; + }); + /** * 按钮组样式 */ @@ -52,6 +101,7 @@ export const IBizButtonList = defineComponent({ * @param {IAppDEUIActionGroupDetail} item */ const handleClick = (item: IData, e?: MouseEvent): void => { + e?.stopPropagation(); emit('click', item.id, e); }; @@ -65,77 +115,62 @@ export const IBizButtonList = defineComponent({ }; /** - * 按钮组成员 - * + * @description 绘制分隔符 + * @param {boolean} [visible] + * @returns {*} */ - const details = computed(() => { - const { buttonListType, uiactionGroup } = props.model; - if (buttonListType === 'UIACTIONGROUP') - return ( - (uiactionGroup?.uiactionGroupDetails as IAppDEUIActionGroupDetail[]) || - [] - ); - return ( - (props.model as IPanelButtonList).panelButtons || - (props.model as IDEFormButtonList).deformButtons || - [] - ); - }); - - /** - * Popover中的行为项 - * - */ - const actions = computed(() => { - return details.value - .filter(detail => props.buttonsState[detail.id!]?.visible !== false) - .map(detail => { - return { - ...detail, - disabled: props.buttonsState[detail.id!]?.disabled, - className: detail.sysCss?.cssName, - }; - }); - }); + const renderSeparator = (visible?: boolean) => { + if (visible) return
; + }; /** - * 绘制默认行为项 - * - * @return {*} {JSX.Element} + * @description 绘制行为项 + * @param {('horizontal' | 'vertical')} direction + * @returns {*} */ - const renderActions = (): JSX.Element => { - return ( -
- {details.value.map(item => { - if (props.buttonsState[item.id!]?.visible === false) return; - return ( - handleClick(item, event)} - > - {(item as IData).showIcon !== false && ( - - )} - {item.showCaption && ( - {item.caption} - )} - - ); - })} -
- ); + const renderActions = (direction: 'horizontal' | 'vertical') => { + const showSeparator = direction === 'vertical'; + return details.value.map((item: IAppDEUIActionGroupDetail) => { + if (item.detailType === 'DEUIACTIONGROUP') + return [ + renderSeparator(item.addSeparator && showSeparator), + + (childPopover.value = visible) + } + direction={direction === 'horizontal' ? 'vertical' : 'horizontal'} + />, + ]; + if (props.buttonsState[item.id!]?.visible) + return [ + renderSeparator(item.addSeparator && showSeparator), + handleClick(item, event)} + > + {(item as IData).showIcon !== false && ( + + )} + {item.showCaption && ( + {item.caption} + )} + , + ]; + return null; + }); }; /** @@ -147,54 +182,45 @@ export const IBizButtonList = defineComponent({ return ( handleClick(data)} + close-on-click-outside={!childPopover.value} > {{ - action: ({ action }: { action: IData }) => { - return ( - - {action.showIcon !== false && action.sysImage && ( - - )} - {action.showCaption && action.caption} - - ); - }, reference: () => { - const { caption, sysImage, showCaption } = props.model; + const { caption, sysImage } = props.model; return ( {sysImage && ( - + )} - {showCaption && ( - + {caption && ( + {caption} )} ); }, + default: () => { + return renderActions('vertical'); + }, }} ); }; - return { ns, renderDropdown, renderActions }; + return { ns, buttonRef, renderDropdown, renderActions }; }, render() { return ( @@ -207,9 +233,13 @@ export const IBizButtonList = defineComponent({ style={this.model.cssStyle} > {this.model.actionGroupExtractMode === 'ITEM' || - this.model.buttonListType === 'BUTTONS' - ? this.renderActions() - : this.renderDropdown()} + this.model.buttonListType === 'BUTTONS' ? ( +
+ {this.renderActions('horizontal')} +
+ ) : ( + this.renderDropdown() + )} ); }, diff --git a/src/common/index.ts b/src/common/index.ts index e86f6c1285..743379633f 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -25,6 +25,7 @@ import { IBizMdCtrlSetting } from './md-ctrl-setting/md-ctrl-setting'; import { IBizPreviewImage } from './preview-image/preview-image'; import { IBizDateRangeCalendar } from './date-range-picker/date-range-picker'; import { IBizCropping } from './cropping/cropping'; +import { IBizActionGroup } from './action-group/action-group'; export * from './col/col'; export * from './row/row'; @@ -32,6 +33,7 @@ export * from './keep-alive/keep-alive'; export const IBizCommonComponents = { install: (v: App): void => { + v.component(IBizActionGroup.name!, IBizActionGroup); v.component(IBizDateRangeCalendar.name!, IBizDateRangeCalendar); v.component(IBizViewShell.name!, IBizViewShell); v.component(IBizRow.name!, IBizRow); diff --git a/src/control/list/md-ctrl/md-ctrl.tsx b/src/control/list/md-ctrl/md-ctrl.tsx index d56f6f5a0e..4189497fa0 100644 --- a/src/control/list/md-ctrl/md-ctrl.tsx +++ b/src/control/list/md-ctrl/md-ctrl.tsx @@ -4,8 +4,9 @@ import { debounce } from 'lodash-es'; import { IDEMobMDCtrl, IUIActionGroup } from '@ibiz/model-core'; import { IControlProvider, - IMobMDCtrlRowState, MDCtrlController, + IMobMDCtrlRowState, + getAllUIActionItems, } from '@ibiz-template/runtime'; import { useListRender } from '../list-render-util'; import { usePagination } from '../../../util'; @@ -145,28 +146,25 @@ export const MDCtrlControl = defineComponent({ // 绘制滑动行为组 const renderSlidingActionGroup = (group: IUIActionGroup, data: IData) => { - const groupDetails = group.uiactionGroupDetails || []; - if (!groupDetails || groupDetails.length === 0) { - return null; - } + const groupDetails = getAllUIActionItems(group.uiactionGroupDetails); + if (!groupDetails || groupDetails.length === 0) return null; const row = c.state.rows.find( (rowData: IMobMDCtrlRowState) => data.srfkey === rowData.data.srfkey, )!; const btnContainer = row.uaColStates[group.id!]; return groupDetails.map(detail => { - const btn = btnContainer[detail.id!]; - if (btn.visible === false) { - return null; - } - return ( - c.onActionClick(detail, row, e)} - /> - ); + const btn = btnContainer?.[detail.id!]; + if (btn?.visible) + return ( + c.onActionClick(detail, row, e)} + /> + ); + return null; }); }; diff --git a/src/panel-component/panel-button-list/panel-button-list.controller.ts b/src/panel-component/panel-button-list/panel-button-list.controller.ts index 5b150550ac..27092d7c57 100644 --- a/src/panel-component/panel-button-list/panel-button-list.controller.ts +++ b/src/panel-component/panel-button-list/panel-button-list.controller.ts @@ -1,11 +1,12 @@ /* eslint-disable object-shorthand */ import { - ButtonContainerState, + UIActionUtil, PanelController, - PanelItemController, PanelNotifyState, UIActionButtonState, - UIActionUtil, + PanelItemController, + getAllUIActionItems, + ButtonContainerState, } from '@ibiz-template/runtime'; import { IPanelButton, @@ -75,17 +76,20 @@ export class PanelButtonListController extends PanelItemController { const { buttonListType, uiactionGroup, panelButtons } = this.model; if (buttonListType === 'UIACTIONGROUP') { - uiactionGroup?.uiactionGroupDetails?.forEach(detail => { - if (detail.uiactionId) { - const buttonState = new UIActionButtonState( - detail.id!, - this.model.appId, - detail.uiactionId, - detail, - ); - this.state.buttonsState.addState(detail.id!, buttonState); - } - }); + if (uiactionGroup?.uiactionGroupDetails) { + const actions = getAllUIActionItems(uiactionGroup.uiactionGroupDetails); + actions.forEach(detail => { + if (detail.uiactionId) { + const buttonState = new UIActionButtonState( + detail.id!, + detail.appId, + detail.uiactionId, + detail, + ); + this.state.buttonsState.addState(detail.id!, buttonState); + } + }); + } } else { panelButtons?.forEach(button => { if (button.uiactionId) { @@ -143,10 +147,10 @@ export class PanelButtonListController extends PanelItemController detail.id === id, - ); + if (buttonListType === 'UIACTIONGROUP') { + const actions = getAllUIActionItems(uiactionGroup?.uiactionGroupDetails); + return actions.find(detail => detail.id === id); + } return panelButtons?.find(button => button.id === id); } -- Gitee