Skip to content

弹窗交互组件

  • 弹窗交互组件包括消息确认弹窗、Modal 弹窗、Message、drawer 抽屉等组件
  • 弹窗类组件全部用命令模式来替换声明式的开发方式,使弹窗跟页面跟符合高内聚低耦合的设计思想
  • 弹窗组件所有交互规范满足 Promise,其中 resolve 是用户在弹窗里有正常的操作后关闭,reject 是没有操作任何消息而关闭或者直接关闭弹窗。

对话确认框

二次确认提示消息弹窗

pop.confirm

代码事例
vue
<template>
	<fk-button type="primary" @click="handleConfirm">确认对话框</fk-button>
</template>
<script setup lang="ts">
import { pop } from '@erp/biz';

const handleConfirm = () => {
	pop.confirm('确认提示信息?')
		.then(() => {
			console.log('确认...');
		})
		.catch(e => {
			console.log('取消...');
		});
};
</script>

打开page弹窗

pop.createPage

vue
<template>
	<fk-button type="primary" @click="handleOpenPage">打开 Page 弹窗</fk-button>
</template>
<script setup>
import { pop } from '@erp/biz';
// pop.container = '.VPDoc';
const handleOpenPage = () => {
	const pagePromise = pop.createPage(import('./custom-page.vue'), { title: '配置弹窗' });
	console.log('pagePromise >>', pagePromise);
};
</script>
vue
<template>
	<fk-modal v-model:visible="visible" width="520px" title="Modal Form" @cancel="handleCancel" @before-ok="handleBeforeOk">
		<fk-form :model="form">
			<fk-form-item field="name" label="Name">
				<fk-input v-model="form.name" />
			</fk-form-item>
			<fk-form-item field="post" label="Post">
				<fk-select v-model="form.post">
					<fk-option value="post1">Post1</fk-option>
					<fk-option value="post2">Post2</fk-option>
					<fk-option value="post3">Post3</fk-option>
					<fk-option value="post4">Post4</fk-option>
				</fk-select>
			</fk-form-item>
		</fk-form>
	</fk-modal>
</template>

<script lang="ts" setup>
import { getCurrentInstance, ref, reactive } from 'vue';

const visible = ref(true);
const form = reactive({
	name: '',
	post: '',
});

const handleBeforeOk = done => {
	console.log(form);
	setTimeout(() => {
		done(form);
		// prevent close
		// done(false)
	}, 1000);
};
const handleCancel = () => {
	visible.value = false;
};
const app = getCurrentInstance();

console.log(app);
</script>

<style lang="scss" scoped></style>

打开modal弹窗

pop.createModal

vue
<template>
	<fk-button type="primary" @click="handleOpenModal">打开 Modal 弹窗</fk-button>
</template>
<script setup>
import { pop } from '@erp/biz';
const data = Array.from({ length: 8 })
	.fill(undefined)
	.map((_, index) => ({
		value: `option${index + 1}`,
		label: `Option ${index + 1}`,
	}));
const value = ['option1', 'option3', 'option5'];
const handleOpenModal = () => {
	pop.createModal(
		import('./columns-config.vue'),
		{
			options: data,
			value,
		},
		{ id: 'columns-config', title: '列表配置' },
	)
		.then(params => {
			console.log('确认...', params);
		})
		.catch(e => {
			console.log('取消...', e);
		});
};
</script>
vue
<template>
	<div class="columns-config">
		<fk-transfer v-model="selected" height="430px" :title="['不显示列', '显示列']" :data="options" />
		<p style="color: var(--color-text-3); margin-top: 12px">勾选字段,点击【&gt;】【&lt;】按钮移入对应列方可生效</p>
	</div>
</template>

<script setup lang="ts">
import { inject, onBeforeUnmount, ref } from 'vue';
import { ModalContextInjectKey } from '@erp/common';
import type { PageExpose } from '@erp/biz';

/**
 * 入参
 */
const props = defineProps(['options', 'value']);

const modalContext = inject(ModalContextInjectKey);

const intervalId = setInterval(() => {
	modalContext.okDisabled = !modalContext.okDisabled;
}, 2000);

/**
 * 触发事件
 */
const emit = defineEmits<{
	(event: 'close', param?: any): void;
	(event: 'ok', param?: any): void;
	(event: 'loading', l: boolean): void;
}>();

const selected = ref(props.value);

onBeforeUnmount(() => {
	clearInterval(intervalId);
});

/**
 * 给Modal调用的
 */
defineExpose<PageExpose>({
	/**
	 * 返回给调用方
	 */
	getModel(): Promise<Record<string, any>> {
		console.log('getModel >> 123');
		return new Promise((resolve, reject) => {
			setTimeout(() => {
				resolve(selected.value);
			}, 1000);
		});
	},
});
</script>

<style lang="scss" scoped>
.columns-config {
	:deep(.fk-transfer-view) {
		height: 420px;
	}
}
</style>

弹窗内容

其他都已经封装,只需要开发主要的业务内容 `./__demo__/columns-config.vue`

打开drawer弹窗

pop.createDrawer

vue
<template>
	<fk-space direction="vertical">
		<fk-button type="primary" @click="handleOpenDrawer">打开 Drawer 弹窗</fk-button>
		<fk-button type="primary" @click="handleOpenDrawer1">打开 No Mask Drawer 弹窗</fk-button>
		<fk-button type="primary" @click="handleOpenDrawer2">打开之后数据更新弹窗</fk-button>
	</fk-space>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import { pop } from '@erp/biz';

const handleOpenDrawer = () => {
	pop.createDrawer(import('./default.vue'), { title: '配置弹窗' })
		.then(params => {
			console.log('确认...', params);
		})
		.catch(e => {
			console.log('取消...', e);
		});
};

const handleOpenDrawer1 = () => {
	pop.createDrawer(import('./default.vue'), { title: '配置弹窗', mask: false, width: '900px' })
		.then(params => {
			console.log('确认...', params);
		})
		.catch(e => {
			console.log('取消...', e);
		});
};
/**
 * 动态传参
 */
const model = reactive({
	footer: 'footer',
	name: '动态传参 - 配置弹窗',
});
const handleOpenDrawer2 = () => {
	// 唯一标识drawer
	const drawerId = 'drawer-id';
	if (pop.hasDrawer(drawerId)) {
		model.name = `参数传进去了${Date.now()}`;
		return;
	}
	pop.createDrawer(import('./default.vue'), {
		id: drawerId,
		title: '配置弹窗',
		mask: false,
		width: '900px',
		model, // import('./default.vue') 组件的参数
		clickOutsideToClose: true,
		onBeforeOutsideClose(e: MouseEvent) {
			console.log('onBeforeOutsideClose >>', e);
			model.footer = 'onBeforeOutsideClose';
			return !document.querySelector<HTMLElement>('._common_modal_README').contains(e.target as Node);
		},
	})
		.then(params => {
			console.log('确认...', params);
		})
		.catch(e => {
			console.log('取消...', e);
		});
};
</script>
<style lang="less">
.drawer-demo {
	width: 1000px;
	margin-left: auto;
}
</style>
vue
<template>
	<div class="input-demo">
		<h1>{{ name }}</h1>
		<div class="erp-input-demo">
			<h4>复选框</h4>
			<ErpInput v-model="vm.checkbox" type="checkbox" :options="checkboxOptions" />
			<h4>单选</h4>
			<ErpInput v-model="vm.radio" type="radio" :options="checkboxOptions" />
			<h4>文本域</h4>
			<ErpInput v-model="vm.textarea" type="textarea" />
			<h4>简单输入框</h4>
			<ErpInput v-model="vm.text" type="text" placeholder="请输入" />
			<h4>数字输入框</h4>
			<ErpInput v-model="vm.number" type="number" placeholder="请输入" />
			<h4>日期选择</h4>
			<ErpInput v-model="vm.date" type="date" placeholder="请选择" />
			<h4>日期范围选择</h4>
			<ErpInput v-model="vm.rangedate" type="rangedate" />
			<h4>自动补全输入框</h4>
			<ErpInput v-model="vm.autocomplete" type="autocomplete" :options="options" @change="handleSearch" />

			<h4>选择器-单选</h4>
			<ErpInput v-model="vm.select" type="select" :options="selectOptions" />
			<h4>选择器-多选</h4>
			<ErpInput v-model="vm.multipleSelect" type="select" multiple :options="selectOptions" />
			<h4>级联选择-单选</h4>
			<ErpInput v-model="vm.cascader" type="cascader" :options="cascaderOptions" />
			<h4>级联选择-多选</h4>
			<ErpInput v-model="vm.multipleCascader" type="cascader" multiple :options="cascaderOptions" />
			<h4>树形选择-单选</h4>
			<ErpInput v-model="vm.tree" type="tree" :options="treeData" />
			<h4>树形选择-多选</h4>
			<ErpInput v-model="vm.multipleTree" type="tree" multiple :options="treeData" />
		</div>
		<h4>上传-多选</h4>
		<ErpInput v-model="vm.multipleUpload" type="upload" multiple />
		<h4>上传-单选</h4>
		<ErpInput v-model="vm.upload" type="upload" />
		<h4>模态弹窗</h4>
		<fk-space>
			<fk-button type="primary" @click="handleOpenModal">打开弹窗</fk-button>
		</fk-space>
		<h4>数据模型</h4>
		<JsonViewer :data="vm" />
	</div>
	<div name="footer">{{ footer }}</div>
</template>

<script setup lang="ts">
import { reactive, ref } from 'vue';
import { Input as ErpInput, pop } from '@erp/biz';

const props = defineProps<{
	footer?: string;
	name?: string;
}>();

const vm = reactive({
	text: '',
	textarea: '',
	number: '',
	date: '',
	rangedate: [],
	autocomplete: '',
	checkbox: [3, 5],
	radio: 2,
	select: '',
	multipleSelect: [],
	cascader: '',
	multipleCascader: [],
	tree: '',
	multipleTree: [],
	upload: {
		uid: '-2',
		name: '103937.png',
		url: '//test.img.fukerp.com/default/FLIrNHVfnvjvOXN40Rm1l5dugEwsjUDsxEDW3kIP.jpg',
	},
	multipleUpload: [
		{
			uid: '-2',
			name: '103937.png',
			url: '//test.img.fukerp.com/default/FLIrNHVfnvjvOXN40Rm1l5dugEwsjUDsxEDW3kIP.jpg',
		},
		{
			uid: '-1',
			name: 'hahhahahahaha.png',
			url: '//test.img.fukerp.com/default/j44G9LTLBxBkNlLu84HOSGF6qgzQLjg7kK3WuuVg.jpg',
		},
		{
			uid: '1',
			name: '103937.png',
			url: '//test.img.fukerp.com/default/FLIrNHVfnvjvOXN40Rm1l5dugEwsjUDsxEDW3kIP.jpg',
		},
		{
			uid: '2',
			name: 'hahhahahahaha.png',
			url: '//test.img.fukerp.com/default/j44G9LTLBxBkNlLu84HOSGF6qgzQLjg7kK3WuuVg.jpg',
		},
	],
});

const options = ref([]);
const checkboxOptions = [
	{
		label: '未选中项',
		value: 1,
	},
	{
		label: '未选悬停项',
		value: 2,
	},
	{
		label: '选中项',
		value: 3,
	},
	{
		label: '未选禁用项',
		value: 4,
	},
	{
		label: '选中禁用项',
		value: 5,
		disabled: true,
	},
];
const selectOptions = [
	{
		label: '未选中项',
		value: 1,
	},
	{
		label: '未选悬停项',
		value: 2,
	},
	{
		label: '选中项',
		value: 3,
	},
	{
		label: '未选禁用项',
		value: 4,
	},
	{
		label: '选中禁用项',
		value: '5',
		disabled: true,
	},
];

for (let i = 0; i < 10; i++) {
	selectOptions.push({
		label: String(i * 10),
		value: i * 10,
	});
}

const handleSearch = value => {
	if (value) {
		options.value = [...Array.from({ length: 5 })].map((_, index) => `${value}-${index}`);
		console.log(options.value);
	} else {
		options.value = [];
	}
};

const cascaderOptions = [
	{
		value: 'beijing',
		label: 'Beijing',
		children: [
			{
				value: 'chaoyang',
				label: 'ChaoYang',
				children: [
					{
						value: 'datunli',
						label: 'Datunli',
					},
				],
			},
			{
				value: 'haidian',
				label: 'Haidian',
			},
			{
				value: 'dongcheng',
				label: 'Dongcheng',
			},
			{
				value: 'xicheng',
				label: 'Xicheng',
				children: [
					{
						value: 'jinrongjie',
						label: 'Jinrongjie',
					},
					{
						value: 'tianqiao',
						label: 'Tianqiao',
					},
				],
			},
		],
	},
	{
		value: 'shanghai',
		label: 'Shanghai',
		children: [
			{
				value: 'huangpu',
				label: 'Huangpu',
			},
		],
	},
];

const treeData = [
	{
		value: 'node1',
		label: '一级树',
		children: [
			{
				value: 'node2',
				label: '二级树',
				disabled: true,
			},
		],
	},
	{
		value: 'node3',
		label: '三级树',
		children: [
			{
				value: 'node4',
				label: '四级树',
			},
			{
				value: 'node5',
				label: '五级树',
			},
			{
				value: 'node6',
				label: '六级树',
			},
			{
				value: 'node7',
				label: '六级树',
			},
		],
	},
];
const data = Array.from({ length: 8 })
	.fill(undefined)
	.map((_, index) => ({
		value: `option${index + 1}`,
		label: `Option ${index + 1}`,
	}));
const value = ['option1', 'option3', 'option5'];
const handleOpenModal = () => {
	pop.createModal(
		import('./columns-config.vue'),
		{
			options: data,
			value,
		},
		{ title: '列表配置' },
	)
		.then(params => {
			console.log('确认...', params);
		})
		.catch(e => {
			console.log('取消...', e);
		});
};
</script>

<style lang="scss" scoped>
.input-demo {
	width: 80%;
}

.erp-input-demo {
	width: 240px;

	h4 {
		font-size: 16px;
		margin: 12px 0 6px;
	}

	:deep(.fk-picker) {
		width: 100%;
	}

	:deep(.fk-upload-wrapper) {
		width: 400px;
	}

	pre {
		word-break: break-all;
	}
}
</style>

打开message消息提示弹窗

pop.info() pop.success() pop.warning() pop.error()

代码事例
vue
<template>
	<fk-space>
		<fk-button @click="handleInfo">普通消息</fk-button>
		<fk-button status="success" @click="handleSuccess">成功消息 </fk-button>
		<fk-button status="warning" @click="handleWarning">警告消息 </fk-button>
		<fk-button status="danger" @click="handleError">错误消息</fk-button>
	</fk-space>
</template>
<script setup lang="ts">
import { pop } from '@erp/biz';

const handleSuccess = () => {
	pop.success('This is a success message!');
};

const handleInfo = () => {
	pop.info('This is an info message!');
};

const handleWarning = () => {
	pop.warning('This is an warning message!');
};

const handleError = () => {
	pop.error('This is an error message!');
};
</script>

打开notification消息通知弹窗

pop.inf() pop.succ() pop.warn() pop.err()

代码事例
vue
<template>
	<fk-space>
		<fk-button @click="handleInf">普通通知</fk-button>
		<fk-button status="success" @click="handleSucc">成功通知 </fk-button>
		<fk-button status="warning" @click="handleWarn">警告通知 </fk-button>
		<fk-button status="danger" @click="handleErr">错误通知</fk-button>
	</fk-space>
</template>

<script setup lang="ts">
import { pop } from '@erp/biz';

const handleSucc = () => {
	pop.succ('This is a success Notification!');
};

const handleInf = () => {
	pop.inf('This is an info Notification!');
};

const handleWarn = () => {
	pop.warn('This is an warning Notification!');
};

const handleErr = () => {
	pop.err('This is an error Notification!');
};
</script>

打开 loading 全局弹窗

代码事例
vue
<template>
	<fk-space>
		<fk-button @click="handleLoading">普通全局loading</fk-button>
        <fk-button @click="handle3Loading">Loading3s后关闭</fk-button>
        <fk-button @click="handleSpin">普通全局Spin</fk-button>
	</fk-space>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { pop } from '@erp/biz';

let loading;

const handleLoading = () => {
    if (loading) {
        loading.close();
        loading = null;
        return;
    }
	loading = pop.loading();
};

const handle3Loading = () => {
	const loading = pop.loading({
        duration: 3000
    });
};

const handleSpin = () => {
    const spin = pop.spin();
    console.log(spin);
    setTimeout(() => {
        spin.close();
    }, 3000)
}

</script>

基于 MIT 许可发布