diff --git a/packages/mobile-ui-vue/components/button/src/button.component.tsx b/packages/mobile-ui-vue/components/button/src/button.component.tsx index 41133647674ab7e9b0f3377230c0bd86042d2645..2859928593c85afd99e3a1b3fdee6db6155639ce 100644 --- a/packages/mobile-ui-vue/components/button/src/button.component.tsx +++ b/packages/mobile-ui-vue/components/button/src/button.component.tsx @@ -24,9 +24,9 @@ export default defineComponent({ props: buttonProps, emits: ['click'] as (string[] & ThisType) | undefined, setup(props: ButtonProps, context: SetupContext) { - const slots = context.slots; + const { slots } = context; const { buttonClass, buttonStyle, onClickButton } = useButton(props, context); - const iconName = toRef(props.icon); + const iconName = toRef(props, 'icon'); const renderLoading = () => { return ( diff --git a/packages/mobile-ui-vue/components/hook/index.ts b/packages/mobile-ui-vue/components/hook/index.ts index 9fd82fd174250f4888ee03b9aa4050f617dae0bd..1b13362e46f51a1729165ffc7cdaf81518f7be07 100644 --- a/packages/mobile-ui-vue/components/hook/index.ts +++ b/packages/mobile-ui-vue/components/hook/index.ts @@ -5,6 +5,8 @@ export * from './use-touch'; export * from './use-lay-render'; export * from './use-refs'; export * from './use-rect'; +export * from './use-expose'; +export * from './use-mount-component'; export * from './use-bem'; export * from './use-link'; export * from './use-click-away'; diff --git a/packages/mobile-ui-vue/components/hook/use-expose/index.ts b/packages/mobile-ui-vue/components/hook/use-expose/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..3a06d4ac4612d19a99e2ac5d6b74c74af4a50ee0 --- /dev/null +++ b/packages/mobile-ui-vue/components/hook/use-expose/index.ts @@ -0,0 +1,9 @@ +import { getCurrentInstance } from 'vue'; + +// expose public api +export function useExpose(apis: Record) { + const instance = getCurrentInstance(); + if (instance) { + Object.assign(instance.proxy, apis); + } +} diff --git a/packages/mobile-ui-vue/components/hook/use-mount-component/index.ts b/packages/mobile-ui-vue/components/hook/use-mount-component/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..bef519dafd018c1b67ef03c5ddaffe398ab8a17d --- /dev/null +++ b/packages/mobile-ui-vue/components/hook/use-mount-component/index.ts @@ -0,0 +1,48 @@ +import { useExpose } from "../use-expose"; +import { Component, createApp, nextTick, reactive } from "vue"; + +export function usePopupState() { + const state = reactive({ + show: false, + }); + + const toggle = (show: boolean) => { + state.show = show; + }; + + const open = (props: Record) => { + Object.assign(state, props); + + nextTick(() => { + toggle(true); + }); + }; + + const close = () => { + toggle(false); + }; + + useExpose({ open, close, toggle }); + + return { + open, + close, + state, + toggle, + }; +} + +export function mountComponent(RootComponent: Component) { + const app = createApp(RootComponent); + const root = document.createElement('div'); + + document.body.appendChild(root); + + return { + instance: app.mount(root), + unmount() { + app.unmount(); + document.body.removeChild(root); + }, + }; +} diff --git a/packages/mobile-ui-vue/components/index.scss b/packages/mobile-ui-vue/components/index.scss index 5bcd8a6445cc7a895ad2ca344b6024ea40e724a5..8aae6460d21bd3bf9e0cddcbdc5db176bf670c70 100644 --- a/packages/mobile-ui-vue/components/index.scss +++ b/packages/mobile-ui-vue/components/index.scss @@ -10,5 +10,7 @@ @import './form/src/form-item.scss'; @import './overlay/src/overlay.scss'; @import './popup/src/popup.scss'; +@import './switch/src/switch.scss'; +@import './toast/src/toast.scss'; @import './loading/src/loading.scss'; @import './pull-refresh/src/pull-refresh.scss'; diff --git a/packages/mobile-ui-vue/components/index.ts b/packages/mobile-ui-vue/components/index.ts index d896f82f99b5afdf4b400e3ddf4d35220576a96c..b34529e61191ab0e69a2df58d2b0d7485bd3aa1a 100644 --- a/packages/mobile-ui-vue/components/index.ts +++ b/packages/mobile-ui-vue/components/index.ts @@ -21,6 +21,7 @@ import Icon from './icon'; import Input from './input'; import Navbar from './navbar'; import Switch from './switch'; +import Toast from './toast'; import { Checkbox, CheckboxGroupItem, CheckboxGroup } from './checkbox'; import { RadioGroupItem, RadioGroup } from './radio'; import Rate from './rate'; @@ -43,6 +44,7 @@ const components = [ Input, Navbar, Switch, + Toast, RadioGroupItem, RadioGroup, Rate, @@ -62,6 +64,7 @@ const install = (app: App): void => { }); }; + export { Button, Cell, @@ -72,6 +75,7 @@ export { Input, Navbar, Switch, + Toast, RadioGroupItem, RadioGroup, Rate, diff --git a/packages/mobile-ui-vue/components/toast/index.ts b/packages/mobile-ui-vue/components/toast/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..482d5a789c9e5d0bd1949c0738d265f29c138713 --- /dev/null +++ b/packages/mobile-ui-vue/components/toast/index.ts @@ -0,0 +1,3 @@ +import Toast from './src/index' +export { Toast } +export default Toast \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/toast/src/index.tsx b/packages/mobile-ui-vue/components/toast/src/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0032da0770845984cc0d7081df953c8878e3d5f8 --- /dev/null +++ b/packages/mobile-ui-vue/components/toast/src/index.tsx @@ -0,0 +1,114 @@ +/** + * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import FMToast from './toast.component'; +import { isObject, inBrowser } from '@/components/utils'; +import { mountComponent, usePopupState } from '@/components/hook'; +import { App } from 'vue'; + +let instance; +let timer; +function defaultOptions() { + return { + type: 'default', + message: '', + icon: '', + duration: 3000, + position: 'middle', + overlay: false + }; +} + +function parseOptions(message) { + return isObject(message) ? message : { message }; +} + +const initInstance = () => { + ({ instance } = mountComponent({ + setup() { + const { state, toggle } = usePopupState(); + return () => ; + } + })); +}; + +function Toast(options) { + if (!inBrowser) { + return; + } + + if (!instance) { + initInstance(); + } + + options = { + ...Toast.currentOptions, + ...parseOptions(options) + }; + + instance.open(options); + clearTimeout(timer); + + if (options.duration > 0) { + timer = setTimeout(Toast.clear, options.duration); + } + + return instance; +} + +Toast.clear = () => { + if (instance) { + instance.toggle(false); + } +}; + +Toast.info = (options) => { + return Toast({ ...parseOptions(options), type: 'info' }); +}; + +Toast.success = (options) => { + return Toast({ ...parseOptions(options), type: 'success' }); +}; + +Toast.error = (options) => { + return Toast({ ...parseOptions(options), type: 'error' }); +}; + +Toast.warning = (options) => { + return Toast({ ...parseOptions(options), type: 'warning' }); +}; + +Toast.loading = (options) => { + return Toast({ ...parseOptions(options), type: 'loading', duration: 0 }); +}; + +Toast.currentOptions = defaultOptions(); + +Toast.setDefaultOptions = (options) => { + Object.assign(Toast.currentOptions, options); +}; + +Toast.resetDefaultOptions = () => { + Toast.currentOptions = defaultOptions(); +}; + +Toast.install = (app: App) => { + app.component(FMToast.name, FMToast); + app.config.globalProperties.$toast = Toast; +}; + +Toast.Component = FMToast; + +export default Toast; diff --git a/packages/mobile-ui-vue/components/toast/src/toast.component.tsx b/packages/mobile-ui-vue/components/toast/src/toast.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b447ba9d18f8613925653689a24bfda11509d5c8 --- /dev/null +++ b/packages/mobile-ui-vue/components/toast/src/toast.component.tsx @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { SetupContext, computed, defineComponent } from 'vue'; +import { ToastProps, toastProps } from './toast.props'; +import { useBem } from '@/components/hook'; +import { Icon } from '@/components/icon'; +import { Overlay } from '@/components/overlay'; +import './toast.scss'; + +const name = 'fm-toast'; + +export default defineComponent({ + name, + props: toastProps, + emits: ['click', 'update:modelValue', 'change'], + setup(props: ToastProps, context: SetupContext) { + const { bem } = useBem(name); + + const iconName = computed(() => { + if (props.icon) { + return props.icon; + } + let name = ''; + switch (props.type) { + case 'success': + name = 'successfulhints'; + break; + case 'info': + case 'warning': + name = 'warningmessage'; + break; + case 'error': + name = 'failureprompt'; + break; + default: + name = ''; + break; + } + return name; + }); + + const toastClass = computed(() => ({ + [name]: true, + [bem('', props.type)]: true, + [bem('', props.position)]: true + })); + + const isLoadding = computed(() => { + return props.type === 'loading'; + }); + + const renderLoading = () => { + return ( + + + + + + ); + }; + + const renderIconContent = () => { + const iconSize = '48'; + return ( +
+ +
+ ); + }; + + return () => ( + <> + {true && ( +
+ {isLoadding.value ? renderLoading() : iconName.value ? renderIconContent() : null} + {props.message} +
+ )} + {props.overlay && ( + + )} + + ); + } +}); diff --git a/packages/mobile-ui-vue/components/toast/src/toast.props.ts b/packages/mobile-ui-vue/components/toast/src/toast.props.ts new file mode 100644 index 0000000000000000000000000000000000000000..2522f158212ee6d8ec013a2fce52ceff766c14d9 --- /dev/null +++ b/packages/mobile-ui-vue/components/toast/src/toast.props.ts @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ExtractPropTypes } from 'vue'; + + +export const toastProps = { + show: { + type: Boolean, + default: false, + }, + type: { + type: String, + default: 'info', + validator: function (value: string) { + // 这个值必须匹配下列字符串中的一个 + return ( + ['success', 'warning', 'error', 'info', 'default', `loading`].indexOf( + value + ) !== -1 + ) + }, + }, + position: { + type: String, + default: 'middle', + }, + message: String, + icon: String, + duration: { + type: Number, + default: 3000, + }, + overlay: Boolean +}; + +export type ToastProps = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/toast/src/toast.scss b/packages/mobile-ui-vue/components/toast/src/toast.scss new file mode 100644 index 0000000000000000000000000000000000000000..0334b56ef4bfc432f838ad706352fc5148a4182d --- /dev/null +++ b/packages/mobile-ui-vue/components/toast/src/toast.scss @@ -0,0 +1,74 @@ +@import '@/theme/index.scss'; + +:root { + --fm-toast-zindex: var(--fm-zindex-5); + --fm-toast-color: var(--fm-white); + --fm-toast-font-size: 16px; + --fm-toast-background: var(--fm-gray-7); + --fm-toast-icon-font-size: 48px; +} + +.fm-toast { + position: fixed; + top: 50%; + left: 50%; + display: -webkit-box; + display: -webkit-flex; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + box-sizing: content-box; + max-width: 70%; + padding: 16px; + line-height: 20px; + white-space: pre-wrap; + text-align: center; + word-wrap: break-word; + border-radius: 7px; + transform: translate(-50%, -50%); + color: var(--fm-toast-color); + z-index: var(--fm-toast-zindex); + font-size: var(--fm-toast-font-size); + background-color: var(--fm-toast-background); + &--top { + top: 50px; + transform: translate(-50%, 0); + } + &--bottom { + top: auto; + bottom: 50px; + } + &--info, + &--success, + &--warning, + &--error { + width: 88px; + min-height: 88px; + } + &__icon-wrapper { + margin-bottom: 8px; + } + &--default { + padding: 8px 16px; + } + &--loading { + min-width: 84px; + min-height: 84px; + } + &--loading-icon { + position: relative; + display: inline-block; + width: 30px; + height: 30px; + margin-bottom: 8px; + vertical-align: middle; + animation: fm-rotate 2s linear infinite; + circle { + animation: fm-circular 1.5s ease-in-out infinite; + stroke: currentColor; + stroke-width: 4; + stroke-linecap: round; + } + } +} diff --git a/packages/mobile-ui-vue/demos/button/index.html b/packages/mobile-ui-vue/demos/button/index.html index 7193af54cd53910153f54acd4dd30ac7c1b53274..ad1dc7fcf5e9cb55edb921cdc131e36298964031 100644 --- a/packages/mobile-ui-vue/demos/button/index.html +++ b/packages/mobile-ui-vue/demos/button/index.html @@ -47,12 +47,12 @@

添加按钮

- +

图标按钮

- - 按钮 + + 按钮

自定义颜色

diff --git a/packages/mobile-ui-vue/demos/toast/index.html b/packages/mobile-ui-vue/demos/toast/index.html new file mode 100644 index 0000000000000000000000000000000000000000..ce87a4ae012ac8ea393fbdd81f80bda6864f6d6f --- /dev/null +++ b/packages/mobile-ui-vue/demos/toast/index.html @@ -0,0 +1,10 @@ +
+

基础用法

+
+ + + + + + + \ No newline at end of file diff --git a/packages/mobile-ui-vue/demos/toast/index.js b/packages/mobile-ui-vue/demos/toast/index.js new file mode 100644 index 0000000000000000000000000000000000000000..ee3d2e6cf266c079f17e6ea2d71d2553cc1fb314 --- /dev/null +++ b/packages/mobile-ui-vue/demos/toast/index.js @@ -0,0 +1,44 @@ +import { Toast } from '../../components/toast/index'; +export default { + name: 'toast-demo', + inheritAttrs: false, + components: [Toast], + setup() { + const showInfo = () => { + Toast.info('info'); + }; + const showSuccess = () => { + Toast.success('成功'); + }; + const showWarning = () => { + Toast.warning('警告'); + }; + const showError = () => { + Toast.error('错误'); + }; + const showCustom = () => { + Toast('自定义'); + }; + const showCustomIcon = () => { + Toast({ + message: '自定义图标', + icon: 'back' + }); + }; + const showLoading = () => { + Toast.loading({ message: '加载中...', overlay: true }); + setTimeout(() => { + Toast.clear(); + }, 2000); + }; + return { + showInfo, + showSuccess, + showWarning, + showError, + showCustom, + showCustomIcon, + showLoading + }; + } +}; diff --git a/packages/mobile-ui-vue/demos/toast/index.vue b/packages/mobile-ui-vue/demos/toast/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..69956a31f495ad36aa94c46a622e031503e13247 --- /dev/null +++ b/packages/mobile-ui-vue/demos/toast/index.vue @@ -0,0 +1,2 @@ + + diff --git a/packages/mobile-ui-vue/docs/components/button/index.md b/packages/mobile-ui-vue/docs/components/button/index.md index dfb353bae0cc1316a04b6bb8d229e1ffad7ebe41..b20105c69e4bc1e9f6df2de85c7285665b5aa012 100644 --- a/packages/mobile-ui-vue/docs/components/button/index.md +++ b/packages/mobile-ui-vue/docs/components/button/index.md @@ -83,14 +83,13 @@ demo: button ### 添加按钮 ```html - + ``` ### 图标按钮 ```html - -按钮 + 按钮 ``` ### 自定义颜色 @@ -100,9 +99,7 @@ demo: button ```html 单色按钮 单色按钮 -渐变色按钮 +渐变色按钮 ``` ## API diff --git a/packages/mobile-ui-vue/src/menu-data.ts b/packages/mobile-ui-vue/src/menu-data.ts index c6b6c1ebc796d27e51239b4ace4d67a06944b6a1..9f5156273e4f5b3b71f4f466ac2f6beb793aceef 100644 --- a/packages/mobile-ui-vue/src/menu-data.ts +++ b/packages/mobile-ui-vue/src/menu-data.ts @@ -80,6 +80,12 @@ export default { url: "/demos/switch", component: "/switch", }, + { + title: "轻提示", + name: "toast", + url: "/demos/toast", + component: "/toast", + } ], }, {