弹窗交互组件
- 弹窗交互组件包括消息确认弹窗、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">勾选字段,点击【>】【<】按钮移入对应列方可生效</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>