From db5f6082455c961ef1901493661a261b5d54213a Mon Sep 17 00:00:00 2001 From: hisoka0728 <1399952343@qq.com> Date: Sat, 12 Oct 2024 20:31:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=20=E6=8F=90=E4=BA=A4=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E4=BD=9C=E4=B8=9A=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/locale/en/index.ts | 1 + src/locale/zh-CN/index.ts | 1 + .../async-action-preview.scss | 90 +++++++ .../async-action-preview.tsx | 226 ++++++++++++++++++ .../async-action-result.tsx | 29 +++ .../async-action-tab/async-action-tab.scss | 33 +++ .../async-action-tab/async-action-tab.tsx | 89 +++++++ .../async-action/async-action.controller.ts | 18 ++ .../async-action/async-action.provider.ts | 22 ++ .../async-action/async-action.provider.ts | 54 +++++ .../async-action/async-action.scss | 116 +++++++++ .../async-action/async-action.tsx | 124 ++++++++++ src/panel-component/async-action/index.ts | 33 +++ .../async-action/mob-async-action.scss | 11 + .../async-action/mob-async-action.tsx | 28 +++ src/panel-component/index.ts | 2 + src/panel-component/user-message.7z | Bin 0 -> 10972 bytes 17 files changed, 877 insertions(+) create mode 100644 src/panel-component/async-action/async-action-preview/async-action-preview.scss create mode 100644 src/panel-component/async-action/async-action-preview/async-action-preview.tsx create mode 100644 src/panel-component/async-action/async-action-result/async-action-result.tsx create mode 100644 src/panel-component/async-action/async-action-tab/async-action-tab.scss create mode 100644 src/panel-component/async-action/async-action-tab/async-action-tab.tsx create mode 100644 src/panel-component/async-action/async-action.controller.ts create mode 100644 src/panel-component/async-action/async-action.provider.ts create mode 100644 src/panel-component/async-action/async-action/async-action.provider.ts create mode 100644 src/panel-component/async-action/async-action/async-action.scss create mode 100644 src/panel-component/async-action/async-action/async-action.tsx create mode 100644 src/panel-component/async-action/index.ts create mode 100644 src/panel-component/async-action/mob-async-action.scss create mode 100644 src/panel-component/async-action/mob-async-action.tsx create mode 100644 src/panel-component/user-message.7z diff --git a/src/locale/en/index.ts b/src/locale/en/index.ts index 25841c82..1c3dfa1a 100644 --- a/src/locale/en/index.ts +++ b/src/locale/en/index.ts @@ -10,6 +10,7 @@ export default { add: 'Add', delete: 'Delete', retract: 'Retract', + close: 'Close', }, // 组件 component: { diff --git a/src/locale/zh-CN/index.ts b/src/locale/zh-CN/index.ts index 76e491f3..33d59208 100644 --- a/src/locale/zh-CN/index.ts +++ b/src/locale/zh-CN/index.ts @@ -10,6 +10,7 @@ export default { add: '添加', delete: '删除', retract: '收起', + close: '关闭', }, // 组件 component: { diff --git a/src/panel-component/async-action/async-action-preview/async-action-preview.scss b/src/panel-component/async-action/async-action-preview/async-action-preview.scss new file mode 100644 index 00000000..031370b0 --- /dev/null +++ b/src/panel-component/async-action/async-action-preview/async-action-preview.scss @@ -0,0 +1,90 @@ +$async-action-preview: (); + +@include b(async-action-preview) { + @include set-component-css-var('async-action-preview', $async-action-preview); + + height: 100%; + @include flex(column); + + padding: getCssVar(spacing, tight); + + @include e(title) { + font-size: getCssVar('font-size', 'header-5'); + font-weight: getCssVar('font-weight', 'bold'); + } + + .van-field__label, + .van-field__value { + font-size: rem(12px); + font-weight: getCssVar('font-weight', 'bold'); + } + + .van-field { + background: var(--van-dialog-background); + } + + @include e(button) { + width: rem(60px); + height: rem(26px); + margin-left: rem(6px); + } +} + +@include b(async-action-preview-header) { + @include flex(row, space-between, center); + + height: rem(48px); + padding: getCssVar(spacing, tight) 0; + border-bottom: 1px solid getCssVar(color, border); +} + +@include b(async-action-preview-time) { + padding: getCssVar(spacing, tight) 0; +} + +@include b(async-action-preview-count) { + height: rem(48px); + padding: getCssVar(spacing, tight) 0; + @include flex(row, space-between, center); + + .van-field__label { + margin: 0; + } +} + +@include b(async-action-preview-detail) { + flex-grow: 1; + overflow: auto; + border-top: 1px solid getCssVar(color, border); +} + +@include b(async-action-preview-detail-item) { + @include flex(row, space-between, center); + + height: rem(72px); + padding: getCssVar('spacing', 'base-tight') getCssVar(spacing, base); + border-bottom: 1px solid getCssVar(color, border); + + @include e(index) { + width: getCssVar('height-control', 'large'); + height: getCssVar('height-control', 'large'); + margin-right: getCssVar('spacing', 'base'); + font-size: getCssVar('font-size', 'header-4'); + line-height: getCssVar('height-control', 'large'); + color: getCssVar(color, primary, text); + text-align: center; + background-color: getCssVar(color, primary); + border-radius: 0; + } + + @include e(error) { + flex-grow: 1; + height: 100%; + text-align: left; + @include flex(column, space-between, flex-start); + } + + @include e(error-reason) { + color: getCssVar(color, text, 1); + } +} diff --git a/src/panel-component/async-action/async-action-preview/async-action-preview.tsx b/src/panel-component/async-action/async-action-preview/async-action-preview.tsx new file mode 100644 index 00000000..0614ac6a --- /dev/null +++ b/src/panel-component/async-action/async-action-preview/async-action-preview.tsx @@ -0,0 +1,226 @@ +import { + downloadFileFromBlob, + IPortalAsyncAction, + RuntimeError, +} from '@ibiz-template/core'; +import { IModal } from '@ibiz-template/runtime'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import qs from 'qs'; +import { defineComponent, PropType, reactive } from 'vue'; +import './async-action-preview.scss'; + +/** + * 请求url获取文件流,并用JS触发文件下载 + * + * @author lxm + * @date 2022-11-17 14:11:09 + * @param {string} url + * @param {IData} file + */ +function fileDownload(file: { url: string; name: string }): void { + // 发送get请求 + ibiz.net + .request(file.url, { + method: 'get', + responseType: 'blob', + baseURL: '', // 已经有baseURL了,这里无需再写 + }) + .then((response: IData) => { + let filename = qs.parse(response.headers['content-disposition'], { + delimiter: ';', + }).filename as string; + if (filename) { + // 特殊处理返回的文件名带有双引号 + if (filename.startsWith('"') && filename.endsWith('"')) { + filename = filename.substring(1, filename.length - 1); + } + file.name += `.${filename.split('.')[1]}`; + } + + if (response.status !== 200) { + throw new RuntimeError( + ibiz.i18n.t( + 'panelComponent.userMessage.asyncActionPreview.downloadFailedErr', + ), + ); + } + // 请求成功,后台返回的是一个文件流 + if (!response.data) { + throw new RuntimeError( + ibiz.i18n.t( + 'panelComponent.userMessage.asyncActionPreview.noExistentErr', + ), + ); + } else { + // 获取文件名 + const fileName = file.name; + downloadFileFromBlob(response.data, fileName); + } + }); +} + +export const AsyncActionPreview = defineComponent({ + name: 'IBizAsyncActionPreview', + props: { + asyncAction: { + type: Object as PropType, + required: true, + }, + modal: { type: Object as PropType, required: true }, + }, + setup(props) { + const ns = useNamespace('async-action-preview'); + + const onClose = () => { + props.modal.dismiss(); + }; + + const info = reactive({ + title: '', + beginTime: '', + endTime: '', + total: 0, + success: 0, + error: 0, + errorDetails: [ + { + row: 0, + reason: '', + }, + ], + errorFileUrl: '', + }); + + info.title = ibiz.i18n.t( + 'panelComponent.userMessage.asyncActionPreview.importDetailPrompt', + { name: props.asyncAction.asyncacitonname }, + ); + info.beginTime = props.asyncAction.begintime; + info.endTime = props.asyncAction.endtime; + if (props.asyncAction.actionresult) { + let actionResult: IData = props.asyncAction.actionresult; + if (typeof actionResult === 'string') { + try { + actionResult = JSON.parse(actionResult as string); + } catch (error) { + throw new RuntimeError( + ibiz.i18n.t( + 'panelComponent.userMessage.asyncActionPreview.parseImportInfoErr', + ), + ); + } + } + info.total = actionResult.total || 0; + info.success = actionResult.success || 0; + info.error = info.total - info.success; + + // 错误信息 + if (actionResult.errorinfo) { + info.errorDetails = Object.keys(actionResult.errorinfo).map(key => { + return { + row: Number(key), + reason: actionResult.errorinfo[key].errorInfo as string, + }; + }); + } else { + info.errorDetails = []; + } + + // 错误文件 + if (actionResult.errorfile) { + info.errorFileUrl = + `${ibiz.env.baseUrl}/${ibiz.env.appId}${ibiz.env.downloadFileUrl}/${actionResult.errorfile.folder}/${actionResult.errorfile.fileid}`.replace( + '/{cat}', + '', + ); + } + } + + const onDownLoad = () => { + fileDownload({ url: info.errorFileUrl, name: info.title }); + }; + + return { ns, info, onClose, onDownLoad }; + }, + render() { + return ( +
+
+
{this.info.title}
+
+ {this.info.errorFileUrl && ( + + {ibiz.i18n.t( + 'panelComponent.userMessage.asyncActionPreview.downloadErrFile', + )} + + )} + + {ibiz.i18n.t('app.close')} + +
+
+
+ +
+ + + + + + + + + + + + {this.info.errorDetails.length > 0 && ( +
+ {this.info.errorDetails.map(detail => { + return ( +
+
+ {detail.row} +
+
+
+ {ibiz.i18n.t('app.error')} +
+
+ {detail.reason} +
+
+
+ ); + })} +
+ )} +
+ ); + }, +}); diff --git a/src/panel-component/async-action/async-action-result/async-action-result.tsx b/src/panel-component/async-action/async-action-result/async-action-result.tsx new file mode 100644 index 00000000..8a62af0c --- /dev/null +++ b/src/panel-component/async-action/async-action-result/async-action-result.tsx @@ -0,0 +1,29 @@ +import { IPortalAsyncAction } from '@ibiz-template/core'; +import { IModal } from '@ibiz-template/runtime'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { computed, defineComponent, PropType } from 'vue'; +import { isObject } from 'lodash-es'; + +export const AsyncActionResult = defineComponent({ + name: 'IBizAsyncActionResult', + props: { + asyncAction: { + type: Object as PropType, + required: true, + }, + modal: { type: Object as PropType, required: true }, + }, + setup(props) { + const ns = useNamespace('async-action-preview'); + const message = computed(() => { + if (isObject(props.asyncAction.actionresult)) { + return JSON.stringify(props.asyncAction.actionresult); + } + return `${props.asyncAction.actionresult}`; + }); + return { ns, message }; + }, + render() { + return
{this.message}
; + }, +}); diff --git a/src/panel-component/async-action/async-action-tab/async-action-tab.scss b/src/panel-component/async-action/async-action-tab/async-action-tab.scss new file mode 100644 index 00000000..e46f7faf --- /dev/null +++ b/src/panel-component/async-action/async-action-tab/async-action-tab.scss @@ -0,0 +1,33 @@ +$async-action-tab: ( + load-more-height: 30px, +); + +@include b(async-action-tab) { + @include set-component-css-var('async-action-tab', $async-action-tab); + + width: 100%; + height: 100%; + overflow: hidden auto; + + @include e(item){ + min-height: 60px; + padding: getCssVar('spacing', 'tight') 0; + } + + @include e(nodata){ + @include flex(row, center, center); + + height: calc(100% - getCssVar(async-action-tab, load-more-height)) + } + + @include e(load-more){ + height: getCssVar(async-action-tab, load-more-height); + line-height: getCssVar(async-action-tab, load-more-height); + text-align: center; + + &:hover{ + color: getCssVar(color, primary, hover); + cursor: pointer; + } + } +} \ No newline at end of file diff --git a/src/panel-component/async-action/async-action-tab/async-action-tab.tsx b/src/panel-component/async-action/async-action-tab/async-action-tab.tsx new file mode 100644 index 00000000..39ddff59 --- /dev/null +++ b/src/panel-component/async-action/async-action-tab/async-action-tab.tsx @@ -0,0 +1,89 @@ +import { PropType, defineComponent, ref } from 'vue'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { + getAsyncActionProvider, + IAsyncActionController, + IAsyncActionProvider, +} from '@ibiz-template/runtime'; +import { IPortalAsyncAction, clone } from '@ibiz-template/core'; +import './async-action-tab.scss'; + +export const AsyncActionTab = defineComponent({ + name: 'IBizAsyncActionTab', + props: { + controller: { + type: Object as PropType, + required: true, + }, + }, + setup(props) { + const ns = useNamespace('async-action-tab'); + + const c = props.controller; + const allItems = ref([]); + + /** + * 从控制器里更新数据 + * @author lxm + * @date 2024-01-26 10:51:17 + */ + const updateData = () => { + allItems.value = clone(c.actions); + }; + + // 第一次计算数据 + updateData(); + + c.evt.on('dataChange', updateData); + + // 气泡控制 + const hiddenPopover = () => { + // 默认信息点击关闭 + }; + + return { + ns, + allItems, + hiddenPopover, + }; + }, + render() { + return ( +
+ {this.allItems.length > 0 && + this.allItems.map(msg => { + let provider: IAsyncActionProvider | undefined; + try { + provider = getAsyncActionProvider(msg); + } catch (error) { + ibiz.log.error(error); + } + if (provider) { + return provider.render({ + class: this.ns.e('item'), + action: msg, + onClose: this.hiddenPopover, + }); + } + return ( +
+ {ibiz.i18n.t( + 'panelComponent.userMessage.asyncActionTab.noSupportType', + { + type: msg.actiontype, + }, + )} +
+ ); + })} + {this.allItems.length === 0 && ( +
+ {ibiz.i18n.t( + 'panelComponent.userMessage.asyncActionTab.noAsyncAction', + )} +
+ )} +
+ ); + }, +}); diff --git a/src/panel-component/async-action/async-action.controller.ts b/src/panel-component/async-action/async-action.controller.ts new file mode 100644 index 00000000..6ad5c656 --- /dev/null +++ b/src/panel-component/async-action/async-action.controller.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { PanelItemController } from '@ibiz-template/runtime'; +import { IPanelField } from '@ibiz/model-core'; + +export class MobAsyncActionController extends PanelItemController { + /** + * 初始化 + * + * @protected + * @return {*} {Promise} + * @memberof MobAsyncActionController + */ + protected async onInit(): Promise { + await super.onInit(); + } +} diff --git a/src/panel-component/async-action/async-action.provider.ts b/src/panel-component/async-action/async-action.provider.ts new file mode 100644 index 00000000..c66fef2a --- /dev/null +++ b/src/panel-component/async-action/async-action.provider.ts @@ -0,0 +1,22 @@ +import { + IPanelItemProvider, + PanelController, + PanelItemController, +} from '@ibiz-template/runtime'; + +import { IPanelItem } from '@ibiz/model-core'; +import { MobAsyncActionController } from './async-action.controller'; + +export class MobAsyncActionProvider implements IPanelItemProvider { + component: string = 'MobAsyncAction'; + + async createController( + panelItem: IPanelItem, + panel: PanelController, + parent: PanelItemController | undefined, + ): Promise { + const c = new MobAsyncActionController(panelItem, panel, parent); + await c.init(); + return c; + } +} diff --git a/src/panel-component/async-action/async-action/async-action.provider.ts b/src/panel-component/async-action/async-action/async-action.provider.ts new file mode 100644 index 00000000..bd1c4cab --- /dev/null +++ b/src/panel-component/async-action/async-action/async-action.provider.ts @@ -0,0 +1,54 @@ +import { IPortalAsyncAction } from '@ibiz-template/core'; +import { IAsyncActionProvider, IModal } from '@ibiz-template/runtime'; +import { VNode, h } from 'vue'; +import { isNil, isObject } from 'lodash-es'; +import { AsyncActionPreview } from '../async-action-preview/async-action-preview'; +import { AsyncActionResult } from '../async-action-result/async-action-result'; +import { AsyncAction } from './async-action'; + +export class AsyncActionProvider implements IAsyncActionProvider { + component = AsyncAction; + + render(props: IData & { action: IPortalAsyncAction }): VNode { + return h(this.component, { + provider: this, + ...props, + }); + } + + async onClick( + asyncAction: IPortalAsyncAction, + _event: MouseEvent, + ): Promise { + if (isNil(asyncAction.actiontype)) { + // 打开模态展示,由于是动态路由,添加其他路由,会导致模版出现问题 + ibiz.overlay.modal( + (modal: IModal) => { + return h(AsyncActionResult, { + asyncAction, + modal, + }); + }, + {}, + { width: '100%', height: '100%' }, + ); + return true; + } + // actionresult有值且是对象时才能打开详情页面 + if (isObject(asyncAction.actionresult)) { + // 打开模态展示,由于是动态路由,添加其他路由,会导致模版出现问题 + ibiz.overlay.modal( + (modal: IModal) => { + return h(AsyncActionPreview, { + asyncAction, + modal, + }); + }, + {}, + { width: '100%', height: '100%' }, + ); + return true; + } + return false; + } +} diff --git a/src/panel-component/async-action/async-action/async-action.scss b/src/panel-component/async-action/async-action/async-action.scss new file mode 100644 index 00000000..232aafe0 --- /dev/null +++ b/src/panel-component/async-action/async-action/async-action.scss @@ -0,0 +1,116 @@ +$async-action: ( + loading-warp-bg-color: linear-gradient(90deg,getCssVar(color, primary, light, hover),getCssVar(color, primary)), +); + +@include b(async-action) { + @include set-component-css-var('async-action', $async-action); + @include flex(row, space-between, center); + + position: relative; + white-space: nowrap; + + @include m(clickable){ + cursor: pointer; + } + + &:hover{ + background-color: getCssVar(color, fill, 1); + } + + @include e(caption){ + margin-bottom: getCssVar('spacing', 'tight'); + font-weight: getCssVar(font-weight, bold); + } + + @include e(error-info){ + margin-bottom: getCssVar('spacing', 'tight'); + color: getCssVar(color, danger); + @include utils-ellipsis; + + } + +} + +@include b(async-action-left) { + flex-grow: 0; + flex-shrink: 0; + width: rem(30px); + font-size: getCssVar(font-size,header-3); + text-align: center; +} + +@include b(async-action-right) { + flex-grow: 0; + flex-shrink: 0; + width: rem(80px); + text-align: center; + +} + +@include b(async-action-center) { + flex-grow: 1; + + // flex-shrink: 1; + width: 342px; +} + +@keyframes breath { + 0% { + opacity: .5 + } + + 10% { + opacity: .6 + } + + 20% { + opacity: .7 + } + + 30% { + opacity: .8 + } + + 40% { + opacity: .9 + } + + 50% { + opacity: 1 + } + + 60% { + opacity: .9 + } + + 70% { + opacity: .8 + } + + 80% { + opacity: .7 + } + + 90% { + opacity: .6 + } + + 100% { + opacity: .5 + } +} + +@include b(async-action-loading-warp) { + position: absolute; + inset: 0; + z-index: -1; + + @include e(inner){ + width: 0; + height: 100%; + text-align: right; + background: getCssVar(async-action,loading-warp-bg-color) !important; + transition: width .5s; + animation: breath 2s ease-in-out infinite; + } +} \ No newline at end of file diff --git a/src/panel-component/async-action/async-action/async-action.tsx b/src/panel-component/async-action/async-action/async-action.tsx new file mode 100644 index 00000000..d6475909 --- /dev/null +++ b/src/panel-component/async-action/async-action/async-action.tsx @@ -0,0 +1,124 @@ +import { computed, defineComponent, PropType } from 'vue'; +import { IPortalAsyncAction, showTitle } from '@ibiz-template/core'; +import { useNamespace } from '@ibiz-template/vue3-util'; +import { isObject, isString } from 'lodash-es'; +import { IAsyncActionProvider } from '@ibiz-template/runtime'; +import './async-action.scss'; + +const stateTexts = { + 10: '未开始', + 20: '执行中', + 30: '已执行', + 40: '执行失败', +}; + +const stateType = { + 10: 'info', + 20: '', + 30: 'success', + 40: 'danger', +}; + +export const AsyncAction = defineComponent({ + name: 'IBizAsyncAction', + props: { + action: { + type: Object as PropType, + required: true, + }, + provider: { + type: Object as PropType, + required: true, + }, + }, + emits: { + close: () => true, + }, + setup(props, { emit }) { + const ns = useNamespace('async-action'); + const hasObjResult = computed(() => isObject(props.action.actionresult)); + + // 没有actiontype或者有并且actionresult是对象的时候 + const clickable = computed(() => { + return !props.action.actiontype || hasObjResult.value; + }); + + const showErrorInfo = computed(() => { + return props.action.actiontype && isString(props.action.actionresult); + }); + + const actionstate = computed(() => { + if (hasObjResult.value) { + const result = props.action.actionresult as IData; + const errorNum = result.total - result.success; + if (errorNum > 0) { + return 40; + } + return 30; + } + return props.action.actionstate; + }); + + const progressText = computed(() => { + return !props.action.completionrate + ? '' + : `(${props.action.completionrate}%)`; + }); + + const onClick = async (event: MouseEvent) => { + if (props.provider.onClick) { + const isClose = await props.provider.onClick(props.action, event); + if (isClose) { + emit('close'); + } + } + }; + + return { ns, showErrorInfo, clickable, actionstate, progressText, onClick }; + }, + render() { + const { asyncacitonname, begintime, stepinfo = '进行中' } = this.action; + return ( +
+
+ +
+
+
{asyncacitonname}
+ {this.showErrorInfo && ( +
+ {this.action.actionresult} +
+ )} + {this.actionstate === 20 ? ( +
+ {stepinfo} + {this.progressText} +
+ ) : ( +
{begintime}
+ )} +
+
+ + {stateTexts[this.actionstate]} + +
+ {this.actionstate === 20 && !!this.action.completionrate && ( +
+
+
+ )} +
+ ); + }, +}); diff --git a/src/panel-component/async-action/index.ts b/src/panel-component/async-action/index.ts new file mode 100644 index 00000000..0b019e0a --- /dev/null +++ b/src/panel-component/async-action/index.ts @@ -0,0 +1,33 @@ +import { withInstall } from '@ibiz-template/vue3-util'; +import { App } from 'vue'; +import { + registerAsyncActionProvider, + registerPanelItemProvider, +} from '@ibiz-template/runtime'; +import { MobAsyncActionProvider } from './async-action.provider'; +import { MobAsyncAction } from './mob-async-action'; +import { AsyncAction } from './async-action/async-action'; +import { AsyncActionTab } from './async-action-tab/async-action-tab'; +import { AsyncActionProvider } from './async-action/async-action.provider'; + +export const IBizMobAsyncAction = withInstall( + MobAsyncAction, + function (v: App) { + v.component(MobAsyncAction.name, MobAsyncAction); + registerPanelItemProvider( + 'RAWITEM_ASYNCACTION', + () => new MobAsyncActionProvider(), + ); + v.component(AsyncAction.name, AsyncAction); + v.component(AsyncActionTab.name, AsyncActionTab); + + // 注册异步导入操作适配器 + registerAsyncActionProvider( + 'DEIMPORTDATA2', + () => new AsyncActionProvider(), + ); + registerAsyncActionProvider('DEFAULT', () => new AsyncActionProvider()); + }, +); + +export default IBizMobAsyncAction; diff --git a/src/panel-component/async-action/mob-async-action.scss b/src/panel-component/async-action/mob-async-action.scss new file mode 100644 index 00000000..1a3c1f83 --- /dev/null +++ b/src/panel-component/async-action/mob-async-action.scss @@ -0,0 +1,11 @@ +@include b(mob-async-action) { + position: relative; + height: 100%; + + @include e(title){ + width: 100%; + padding: rem(8px) 0; + font-size: rem(20px); + text-align: center; + } +} diff --git a/src/panel-component/async-action/mob-async-action.tsx b/src/panel-component/async-action/mob-async-action.tsx new file mode 100644 index 00000000..de2af3e0 --- /dev/null +++ b/src/panel-component/async-action/mob-async-action.tsx @@ -0,0 +1,28 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +import { defineComponent } from 'vue'; +import { getRawProps, useNamespace } from '@ibiz-template/vue3-util'; +import { MobAsyncActionController } from './async-action.controller'; +import { AsyncActionTab } from './async-action-tab/async-action-tab'; +import './mob-async-action.scss'; + +export const MobAsyncAction = defineComponent({ + name: 'MobAsyncAction', + props: getRawProps(), + setup() { + const ns = useNamespace('mob-async-action'); + const noticeController = ibiz.hub.notice; + return { + ns, + noticeController, + }; + }, + render() { + return ( +
+ +
+ ); + }, +}); diff --git a/src/panel-component/index.ts b/src/panel-component/index.ts index f82b4b3b..eb8027a5 100644 --- a/src/panel-component/index.ts +++ b/src/panel-component/index.ts @@ -26,6 +26,7 @@ import IBizPanelCarouse from './panel-carousel'; import IBizPanelVideoPlayer from './panel-video-player'; import IBizAuthUserinfo from './auth-userinfo'; import IBizMobUserMessage from './user-message'; +import IBizMobAsyncAction from './async-action'; export const IBizPanelComponents = { install: (v: App): void => { @@ -54,6 +55,7 @@ export const IBizPanelComponents = { v.use(IBizAuthUserinfo); v.use(IBizPanelItemRender); v.use(IBizMobUserMessage); + v.use(IBizMobAsyncAction); }, }; diff --git a/src/panel-component/user-message.7z b/src/panel-component/user-message.7z new file mode 100644 index 0000000000000000000000000000000000000000..5bbc41d0a0fcc9ec2deb3dfcf2f521ba97204aa5 GIT binary patch literal 10972 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 K0rl-QSO5T}OJO