diff --git a/packages/mobile-ui-vue/components/button-group/src/button-group.component.tsx b/packages/mobile-ui-vue/components/button-group/src/button-group.component.tsx index e79694a3b03d2f6ae7a247fbc6e1ba9de55063af..6f692a10867dad6cbff4537930eabd727c7aa35b 100644 --- a/packages/mobile-ui-vue/components/button-group/src/button-group.component.tsx +++ b/packages/mobile-ui-vue/components/button-group/src/button-group.component.tsx @@ -40,7 +40,7 @@ export default defineComponent({ function isButtonItemPlain(buttonItem: ButtonItem): boolean { if (isDefaultMode.value) { - return buttonItem.plain; + return !!buttonItem.plain; } const shouldDefaultToPlain = props.mode === 'outline' || props.mode === 'text'; return buttonItem.plain ?? shouldDefaultToPlain; @@ -55,7 +55,7 @@ export default defineComponent({ function isButtonItemNoBorder(buttonItem: ButtonItem): boolean { if (isDefaultMode.value) { - return buttonItem.noBorder; + return !!buttonItem.noBorder; } return props.mode === 'text'; } @@ -65,6 +65,7 @@ export default defineComponent({ [bem('item')]: true, [bem('item', 'bare-vertical')]: buttonItem.variant === 'bare-vertical', [bem('item', 'disabled')]: isButtonItemDisabled(buttonItem), + [buttonItem.customClass ?? '']: typeof buttonItem.customClass === 'string' && buttonItem.customClass, }; }; @@ -79,7 +80,9 @@ export default defineComponent({ function renderDefaultButton(buttonItem: ButtonItem) { return ( onButtonItemClick(buttonItem)} > {buttonItem.icon && } diff --git a/packages/mobile-ui-vue/components/button-group/src/button-group.props.ts b/packages/mobile-ui-vue/components/button-group/src/button-group.props.ts index 1c053e9fc569fea847257953022219b32ba58a0f..b81fbbb39b69bb0f9041c8a9a2ba7a5f4e972bca 100644 --- a/packages/mobile-ui-vue/components/button-group/src/button-group.props.ts +++ b/packages/mobile-ui-vue/components/button-group/src/button-group.props.ts @@ -9,12 +9,15 @@ export const BUTTON_GROUP_NAME = 'fm-button-group'; export type ButtonGroupMode = 'default' | 'group' | 'outline' | 'text'; export type ButtonItemVariant = 'default' | 'bare-vertical'; -export type ButtonItem = ButtonProps & { +export type ButtonItem = Partial void; -}; + customClass: string; + customStyle: string; + onClick: (eventParam?: any) => void; +}>; export const buttonGroupProps = { diff --git a/packages/mobile-ui-vue/components/button-group/src/button-group.scss b/packages/mobile-ui-vue/components/button-group/src/button-group.scss index 6a73cfc0a49633e321263e3e1a6b8079bbefdf95..dc524668874abca33160ebbff4fbf0dc6ac1f1b5 100644 --- a/packages/mobile-ui-vue/components/button-group/src/button-group.scss +++ b/packages/mobile-ui-vue/components/button-group/src/button-group.scss @@ -96,12 +96,26 @@ border-top: 0; } - &--horizontal#{&}--mode-text &__item:not(:last-child) { - border-right: var(--fm-button-border-width) solid currentColor !important; - } - - &--vertical#{&}--mode-text &__item:not(:last-child) { - border-bottom: var(--fm-button-border-width) solid currentColor !important; + &--horizontal#{&}--mode-text &__item:not(:last-child)::after, + &--vertical#{&}--mode-text &__item:not(:last-child)::after { + content: ''; + display: block; + position: absolute; + background-color: var(--fm-gray-3); + } + + &--horizontal#{&}--mode-text &__item:not(:last-child)::after { + width: 1px; + right: 0; + top: var(--fm-padding-xs); + bottom: var(--fm-padding-xs); + } + + &--vertical#{&}--mode-text &__item:not(:last-child)::after { + height: 1px; + bottom: 0; + left: var(--fm-padding-xs); + right: var(--fm-padding-xs); } &--fill { diff --git a/packages/mobile-ui-vue/components/card/index.ts b/packages/mobile-ui-vue/components/card/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..65abf564a6c01d648176bef136eac9dc8a5288e5 --- /dev/null +++ b/packages/mobile-ui-vue/components/card/index.ts @@ -0,0 +1,22 @@ +import { Plugin } from 'vue'; +import { withInstall } from '@components/common'; +import CardInstallless from './src/card.component'; +import { propsResolver } from './src/card.props'; + +const CARD_REGISTERED_NAME = 'card'; + +const Card = withInstall(CardInstallless); + +Card.register = ( + componentMap: Record, + propsResolverMap: Record, + configResolverMap: Record, + resolverMap: Record, +) => { + componentMap[CARD_REGISTERED_NAME] = Card; + propsResolverMap[CARD_REGISTERED_NAME] = propsResolver; +}; + +export * from './src/card.props'; +export { Card }; +export default Card as typeof Card & Plugin; diff --git a/packages/mobile-ui-vue/components/card/src/card.component.tsx b/packages/mobile-ui-vue/components/card/src/card.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6639d7b7e7df0e7fad63eb59b1b269963d1f682b --- /dev/null +++ b/packages/mobile-ui-vue/components/card/src/card.component.tsx @@ -0,0 +1,69 @@ +import { defineComponent, computed } from 'vue'; +import { useBem } from '@components/common'; +import { ButtonGroup } from '@components/button-group'; +import { CARD_NAME, cardProps, CardProps } from './card.props'; + +export default defineComponent({ + name: CARD_NAME, + props: cardProps, + emits: [], + setup(props: CardProps, context) { + const { bem } = useBem(CARD_NAME); + const { slots } = context; + + const toolbarItemCount = computed(() => props.toolbarItems?.length ?? 0); + + const shouldRenderHeader = computed(() => { + const isHeaderEmpty = !slots.header && !props.title; + return props.showHeader && !isHeaderEmpty; + }); + + const shouldRenderFooter = computed(() => { + const isFooterEmpty = !slots.footer && toolbarItemCount.value === 0; + return props.showFooter && !isFooterEmpty; + }); + + function renderHeader() { + if (slots.header) { + return slots.header(); + } + return ( +
+ {props.title} +
+ ); + } + + function renderContent() { + return ( +
+ {slots.content ? slots.content?.() : slots.default?.()} +
+ ); + } + + function renderFooter() { + if (slots.footer) { + return slots.footer(); + } + return ( +
+ +
+ ); + } + + return () => ( +
+ {shouldRenderHeader.value && renderHeader()} + {renderContent()} + {shouldRenderFooter.value && renderFooter()} +
+ ); + + } +}); diff --git a/packages/mobile-ui-vue/components/card/src/card.props.ts b/packages/mobile-ui-vue/components/card/src/card.props.ts new file mode 100644 index 0000000000000000000000000000000000000000..76b4b1ef63bd4b0d1771adbcaab65acfed463e98 --- /dev/null +++ b/packages/mobile-ui-vue/components/card/src/card.props.ts @@ -0,0 +1,32 @@ +import { ExtractPropTypes } from 'vue'; +import { createPropsResolver } from '@components/dynamic-resolver'; +import { ButtonItem } from '@components/button-group'; +import cardSchema from './schema/card.schema.json'; +import { schemaMapper } from './schema/schema-mapper'; +import { schemaResolver } from './schema/schema-resolver'; + +export const CARD_NAME = 'fm-card'; + +export const cardProps = { + + /** 标题 */ + title: { type: String, default: '' }, + + /** 是否显示头部区域 */ + showHeader: { type: Boolean, default: true }, + + /** 是否显示尾部区域 */ + showFooter: { type: Boolean, default: true }, + + /** 按钮工具栏 */ + toolbarItems: { type: Array, default: [] }, +}; + +export const propsResolver = createPropsResolver( + cardProps, + cardSchema, + schemaMapper, + schemaResolver, +); + +export type CardProps = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/card/src/card.scss b/packages/mobile-ui-vue/components/card/src/card.scss new file mode 100644 index 0000000000000000000000000000000000000000..a8106b291a0a333c8d56ddb4dfda610be17f2901 --- /dev/null +++ b/packages/mobile-ui-vue/components/card/src/card.scss @@ -0,0 +1,43 @@ +@use '../../common/src/style/mixins/index.scss' as *; + +:root { + --fm-card-background: var(--fm-white); + --fm-card-title-color: var(--fm-gray-7); +} + +.fm-card { + background-color: var(--fm-card-background); + + &__header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 11px 16px 10px; + @include hairline('bottom'); + } + + &__title { + position: relative; + padding-left: var(--fm-padding-md); + color: var(--fm-card-title-color); + font-size: var(--fm-font-size-lg); + line-height: var(--fm-line-height-lg); + font-weight: var(--fm-font-bold-light); + @include ellipsis(); + + &::before { + content: ''; + position: absolute; + left: 0; + top: 50%; + width: 3px; + height: 14px; + margin: -7px 0 0; + background: var(--fm-primary-color); + } + } + + &__footer { + @include hairline('top'); + } +} \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/card/src/schema/card.schema.json b/packages/mobile-ui-vue/components/card/src/schema/card.schema.json new file mode 100644 index 0000000000000000000000000000000000000000..4d1160f7ec4c205f9dcd0144bcbe83989bc81e5c --- /dev/null +++ b/packages/mobile-ui-vue/components/card/src/schema/card.schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://farris-design.gitee.io/card.schema.json", + "title": "card", + "description": "", + "type": "object", + "properties": { + "id": { + "description": "唯一标识", + "type": "string" + }, + "type": { + "description": "组件类型", + "type": "string", + "default": "card" + }, + "title": { + "description": "显示的标题", + "type": "string", + "default": "" + }, + "showHeader": { + "description": "是否显示头部区域", + "type": "boolean", + "default": true + }, + "showFooter": { + "description": "是否显示尾部区域", + "type": "boolean", + "default": true + }, + "toolbarItems": { + "description": "按钮工具栏", + "type": "array", + "default": [] + } + }, + "required": [ + "id", + "type" + ] +} \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/card/src/schema/schema-mapper.ts b/packages/mobile-ui-vue/components/card/src/schema/schema-mapper.ts new file mode 100644 index 0000000000000000000000000000000000000000..47415f8117f46655672d6ec2dfa97608db48b7bb --- /dev/null +++ b/packages/mobile-ui-vue/components/card/src/schema/schema-mapper.ts @@ -0,0 +1,5 @@ +import { resolveAppearance, MapperFunction } from '../../../dynamic-resolver'; + +export const schemaMapper = new Map([ + ['appearance', resolveAppearance], +]); diff --git a/packages/mobile-ui-vue/components/card/src/schema/schema-resolver.ts b/packages/mobile-ui-vue/components/card/src/schema/schema-resolver.ts new file mode 100644 index 0000000000000000000000000000000000000000..c1bf8da88a08c01a3c8d6e9f3d81859d84d87aa6 --- /dev/null +++ b/packages/mobile-ui-vue/components/card/src/schema/schema-resolver.ts @@ -0,0 +1,5 @@ +import { DynamicResolver } from "../../../dynamic-resolver"; + +export function schemaResolver(resolver: DynamicResolver, schema: Record, context: Record): Record { + return schema; +} diff --git a/packages/mobile-ui-vue/components/common/src/style/variables.scss b/packages/mobile-ui-vue/components/common/src/style/variables.scss index 8d7e07e4e7594c62b37004e8e77411801a9963b0..8bca70a58c56bb2e8fb3b5df64ec334799c2fc96 100644 --- a/packages/mobile-ui-vue/components/common/src/style/variables.scss +++ b/packages/mobile-ui-vue/components/common/src/style/variables.scss @@ -73,6 +73,7 @@ --fm-font-bold: 600; --fm-font-size: 16px; --fm-line-height: 1.2; + --fm-line-height-lg: 1.5; --fm-font-size-xs: 10px; --fm-font-size-sm: 12px; diff --git a/packages/mobile-ui-vue/components/icon/src/icon.component.tsx b/packages/mobile-ui-vue/components/icon/src/icon.component.tsx index 98f0c4797fa079ab36187be71e1581d4b1d090ec..d74cb826eed947eb8fa751d063fe91c25dc77358 100644 --- a/packages/mobile-ui-vue/components/icon/src/icon.component.tsx +++ b/packages/mobile-ui-vue/components/icon/src/icon.component.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { defineComponent, computed, CSSProperties, ComputedRef, SetupContext } from 'vue'; +import { defineComponent, computed, CSSProperties } from 'vue'; import { addUnit } from '@components/common'; import { IconProps, iconProps } from './icon.props'; @@ -22,22 +22,25 @@ export default defineComponent({ name: 'FmIcon', props: iconProps, emits: ['click'], - setup(props: IconProps, context: SetupContext) { + setup(props: IconProps, context) { const { emit } = context; - const iconClass: ComputedRef = computed(() => { + + const iconClass = computed(() => { const { classPrefix, name } = props; return [classPrefix, name ? `${classPrefix}-${name}` : '']; }); - const iconStyle: ComputedRef = computed(() => { - return { color: props.color, 'font-size': props.size ? addUnit(props.size) : '' }; - }); + const iconStyle = computed(() => ({ + color: props.color, + 'font-size': props.size ? addUnit(props.size) : '', + })); + + function onClick(event: MouseEvent): void { + emit('click', event); + } return () => ( - emit('click', event)}> + ); } }); diff --git a/packages/mobile-ui-vue/components/index.scss b/packages/mobile-ui-vue/components/index.scss index 5d371b8e9ab82891968130c6048b599f1bc2a310..67be30e79038df79466a14b6469e40551e9695b8 100644 --- a/packages/mobile-ui-vue/components/index.scss +++ b/packages/mobile-ui-vue/components/index.scss @@ -2,6 +2,7 @@ @use './button/src/button.scss'; @use './button-edit/src/button-edit.scss'; @use './button-group/src/button-group.scss'; +@use './card/src/card.scss'; @use './cell/src/cell.scss'; @use './checkbox-group/src/checkbox-group.scss'; @use './checkbox/src/checkbox.scss'; diff --git a/packages/mobile-ui-vue/components/index.ts b/packages/mobile-ui-vue/components/index.ts index 69d269a675048613ad1ab959c660cfcf8e2c5989..e43e6372636017c687deaae5d5b5207680bafae1 100644 --- a/packages/mobile-ui-vue/components/index.ts +++ b/packages/mobile-ui-vue/components/index.ts @@ -2,6 +2,7 @@ import { App, Plugin } from 'vue'; import Button from './button'; import ButtonEdit from './button-edit'; import ButtonGroup from './button-group'; +import Card from './card'; import Cell from './cell'; import Icon from './icon'; import InputGroup from './input-group'; @@ -59,6 +60,7 @@ const components = [ Button, ButtonEdit, ButtonGroup, + Card, Cell, Checkbox, CheckboxGroup, @@ -122,6 +124,7 @@ export { Button, ButtonEdit, ButtonGroup, + Card, Cell, Checker, Checkbox, diff --git a/packages/mobile-ui-vue/components/list-view/src/components/list-view-item.component.tsx b/packages/mobile-ui-vue/components/list-view/src/components/list-view-item.component.tsx index 25e7b6a3891932b5ba853285afdcc705aaf4f135..5befcc70bd7a4b69429515ea2446acbe9ee32043 100644 --- a/packages/mobile-ui-vue/components/list-view/src/components/list-view-item.component.tsx +++ b/packages/mobile-ui-vue/components/list-view/src/components/list-view-item.component.tsx @@ -1,4 +1,5 @@ -import { SetupContext, toRefs } from "vue"; +import { computed, SetupContext, toRefs } from "vue"; +import { SwipeCell } from '@components/swipe-cell'; import { LIST_VIEW_NAME, ListViewProps } from '../list-view.props'; import { UseData, UseSelection } from '../composition/types'; import { useBem } from '@components/common'; @@ -18,16 +19,20 @@ export default function ( const { isMultiSelectMode, isItemSelected, toggleSelectItem } = useSelectionComposition; const { checkboxProps } = toRefs(props); + const shouldRenderSwipeCell = computed(() => { + return Array.isArray(props.swipeToolbar) && props.swipeToolbar.length > 0; + }); + const shouldDisableSwipeCell = computed(() => isMultiSelectMode.value); - const onListItemClick = (item: any, index: number) => { + function onListItemClick(item: any, index: number): void { emit('clickItem', { data: item, index }); - }; + } - const onCheckboxClick = (evt: MouseEvent) => { - evt && evt.stopPropagation(); - }; + function onCheckboxClick(event: MouseEvent): void { + event && event.stopPropagation(); + } - const renderCheckbox = (item: any) => { + function renderCheckbox(item: any) { return ( ); - }; + } - const renderItemContent = (item: any, index: number, disabled: boolean) => { + function renderItemContent(item: any, index: number, disabled: boolean) { if (slots.item) { return slots.item({ item, index, disabled }); } return (
{getItemText(item)}
); - }; + } - const renderSingleItem = (item: any, index: number, isChildItem?: boolean, parentItemIndex?: number) => { + function renderSwipeCell(item: any, index: number, disabled: boolean) { + if (!shouldRenderSwipeCell.value) { + return renderItemContent(item, index, disabled); + } + return ( + + {renderItemContent(item, index, disabled)} + + ); + } + + function renderSingleItem(item: any, index: number, isChildItem?: boolean, parentItemIndex?: number) { const itemClass = { [bem('item')]: true, [bem('item', 'child')]: isChildItem, @@ -59,20 +78,20 @@ export default function ( const rootItemIndex = isChildItem ? parentItemIndex : index; return ( -
onListItemClick(item, rootItemIndex)}> +
onListItemClick(item, rootItemIndex!)}> {isMultiSelectMode.value && (
{shouldShowCheckbox && renderCheckbox(item)}
)}
- {renderItemContent(item, index, shouldDisableItem)} + {renderSwipeCell(item, index, shouldDisableItem)}
); - }; + } - const renderItem = (item: any, index: number) => { + function renderItem(item: any, index: number): void { const children = getItemChildren(item); const hasChildren = children.length > 0; @@ -90,7 +109,7 @@ export default function (
); - }; + } return { renderItem }; } diff --git a/packages/mobile-ui-vue/components/list-view/src/composition/use-data.ts b/packages/mobile-ui-vue/components/list-view/src/composition/use-data.ts index a2fbc44a41d25887e01f1bf5856257e4200873dc..89d58e42e26ee5240e98729d89d8e1867ee6ebca 100644 --- a/packages/mobile-ui-vue/components/list-view/src/composition/use-data.ts +++ b/packages/mobile-ui-vue/components/list-view/src/composition/use-data.ts @@ -1,22 +1,22 @@ -import { SetupContext, computed } from 'vue'; +import { computed } from 'vue'; import { ListViewProps } from '../list-view.props'; import { UseData } from './types'; -export function useData(props: ListViewProps, _context: SetupContext): UseData { +export function useData(props: ListViewProps): UseData { - const getFieldPathArr = (fullPath: string): string[] => { + function getFieldPathArr(fullPath: string): string[] { if (!fullPath || typeof fullPath !== 'string') { return []; } return fullPath.split('.').filter(path => !!path); - }; + } const idFieldPathArr = computed(() => getFieldPathArr(props.idField)); const textFieldPathArr = computed(() => getFieldPathArr(props.textField)); const disableFieldPathArr = computed(() => getFieldPathArr(props.disableField)); const childFieldPathArr = computed(() => getFieldPathArr(props.childField)); - const getFieldByPathArr = (object: any, fieldPathArr: string[]): any => { + function getFieldByPathArr(object: any, fieldPathArr: string[]): any { if (!object || !fieldPathArr || !fieldPathArr.length) { return undefined; } @@ -24,9 +24,9 @@ export function useData(props: ListViewProps, _context: SetupContext): UseData { return currentObject ? currentObject[path] : currentObject; }, object); return fieldValue; - }; + } - const setFieldByPathArr = (object: any, fieldPathArr: string[], value: any): any => { + function setFieldByPathArr(object: any, fieldPathArr: string[], value: any): any { if (!object || !fieldPathArr || !fieldPathArr.length) { return object; } @@ -42,25 +42,25 @@ export function useData(props: ListViewProps, _context: SetupContext): UseData { } } return object; - }; + } - const getItemID = (item: any): any => { + function getItemID(item: any): any { return getFieldByPathArr(item, idFieldPathArr.value); - }; + } - const getItemText = (item: any): any => { + function getItemText(item: any): any { return getFieldByPathArr(item, textFieldPathArr.value) || ''; - }; + } const isItemDisabled = (item: any): boolean => { return !!getFieldByPathArr(item, disableFieldPathArr.value); }; - const getItemChildren = (item: any): any[] => { + function getItemChildren(item: any): any[] { const children = getFieldByPathArr(item, childFieldPathArr.value); const isChildrenValid = Array.isArray(children); return isChildrenValid ? children : []; - }; + } const listItems = computed(() => { const originalItems = props.data || []; diff --git a/packages/mobile-ui-vue/components/list-view/src/composition/use-selection.ts b/packages/mobile-ui-vue/components/list-view/src/composition/use-selection.ts index eb97d25f80f4c8bcac96934618b748b44836f4c5..79376bc17c75044c0e6321a3dd773416a96d90d8 100644 --- a/packages/mobile-ui-vue/components/list-view/src/composition/use-selection.ts +++ b/packages/mobile-ui-vue/components/list-view/src/composition/use-selection.ts @@ -15,13 +15,13 @@ export function useSelection( return props.enableMultiSelect && props.multiSelect; }); - const toggleMultiSelect = (value: boolean) => { + function toggleMultiSelect(value: boolean): void { emit('update:multiSelect', value); - }; + } const { listItems, getItemID, getItemChildren } = useDataCompostion; - const getId2ItemMap = () => { + function getId2ItemMap(): Map { const id2Item = new Map(); listItems.value.forEach((item) => { id2Item.set(getItemID(item), item); @@ -33,9 +33,9 @@ export function useSelection( }); }); return id2Item; - }; + } - const getItemsFromIDs = (ids: string[]): any[] => { + function getItemsFromIDs(ids: string[]): any[] { const items: any[] = []; const id2Item = getId2ItemMap(); ids.forEach((id) => { @@ -46,7 +46,7 @@ export function useSelection( targetItem && items.push(targetItem); }); return items; - }; + } const selectedItems: Ref = ref(getItemsFromIDs(props.selectedValues)); @@ -54,27 +54,27 @@ export function useSelection( selectedItems.value = ref(getItemsFromIDs(newValues)).value; }); - const getItemIndexFromSelectedItems = (item: any): number => { + function getItemIndexFromSelectedItems(item: any): number { const targetID = getItemID(item); if (!isDef(targetID)) { return -1; } return selectedItems.value.findIndex(i => getItemID(i) === targetID); - }; + } - const isItemSelected = (item: any) => { + function isItemSelected(item: any): boolean { return getItemIndexFromSelectedItems(item) >= 0; - }; + } - const getSelectedItems = () => { + function getSelectedItems(): any[] { return [...selectedItems.value]; - }; + } - const emitSelectionChange = () => { + function emitSelectionChange(): void { emit('selectionChange', getSelectedItems()); - }; + } - const toggleSelectItem = (item: any) => { + function toggleSelectItem(item: any): void { const targetID = getItemID(item); if (!isDef(targetID)) { return; @@ -86,12 +86,12 @@ export function useSelection( selectedItems.value.push(item); } emitSelectionChange(); - }; + } - const clearSelection = () => { + function clearSelection(): void { selectedItems.value = []; emitSelectionChange(); - }; + } return { isMultiSelectMode, diff --git a/packages/mobile-ui-vue/components/list-view/src/list-view.component.tsx b/packages/mobile-ui-vue/components/list-view/src/list-view.component.tsx index a3ec5488852a54974a8002aaffc990a5481b10f7..11e34782d78844474ba923d357215c8471d7ac16 100644 --- a/packages/mobile-ui-vue/components/list-view/src/list-view.component.tsx +++ b/packages/mobile-ui-vue/components/list-view/src/list-view.component.tsx @@ -44,7 +44,7 @@ export default defineComponent({ const { emit, slots, expose } = context; const listComponentRef = ref(); - const useDataComposition = useData(props, context); + const useDataComposition = useData(props); const { listItems, isEmpty } = useDataComposition; const useSelectionComposition = useSelection(props, context, useDataComposition); @@ -90,20 +90,20 @@ export default defineComponent({ return props.emptyMessage || '暂无数据'; }); - const renderEmptyTemplate = () => { + function renderEmptyTemplate() { if (slots.empty) { return slots.empty(); } return ( {emptyMessage.value} ); - }; + } - const handleListContentLongPress = () => { + function handleListContentLongPress(): void { toggleMultiSelect(true); - }; + } - const renderListContent = () => { + function renderListContent() { const listContentClass = { [bem('content')]: true, [bem('content', 'split')]: shouldShowSplitLine.value, @@ -120,9 +120,9 @@ export default defineComponent({ ]])} ); - }; + } - const renderList = () => { + function renderList() { const listSlots = { finished: slots.finished, loading: slots.loading, @@ -149,9 +149,9 @@ export default defineComponent({ {slots.default?.()} ); - }; + } - const renderListWithPullRefresh = () => { + function renderListWithPullRefresh() { if (!enablePullRefresh.value) { return renderList(); } @@ -175,9 +175,9 @@ export default defineComponent({ {renderList()} ); - }; + } - const renderToolbar = () => { + function renderToolbar() { return (
@@ -199,11 +199,11 @@ export default defineComponent({
); - }; + } - const checkListNeedLoadMore = () => { + function checkListNeedLoadMore(): void { listComponentRef.value?.check(); - }; + } expose({ check: checkListNeedLoadMore, diff --git a/packages/mobile-ui-vue/components/list-view/src/list-view.props.ts b/packages/mobile-ui-vue/components/list-view/src/list-view.props.ts index 229a5883e355a58ddb128877eb406f084b1fbb96..57f5046c75c74d1f4e051f69844ad80b321d2e6c 100644 --- a/packages/mobile-ui-vue/components/list-view/src/list-view.props.ts +++ b/packages/mobile-ui-vue/components/list-view/src/list-view.props.ts @@ -18,6 +18,7 @@ import { ExtractPropTypes, PropType } from 'vue'; import { ListProps } from '@components/list'; import { PullRefreshProps } from '@components/pull-refresh'; import { CheckboxProps } from '@components/checkbox/src/checkbox.props'; +import { SwipeCellButton } from '@components/swipe-cell'; import { createPropsResolver } from '../../dynamic-resolver'; import { schemaMapper } from './schema/schema-mapper'; import listViewSchema from './schema/list-view.schema.json'; @@ -87,6 +88,9 @@ export const listViewProps = { /** 多选状态下,勾选框的属性 */ checkboxProps: { type: Object as PropType, default: { shape: 'round' } }, + + /** 列表行的滑动工具栏按钮 */ + swipeToolbar: { type: Array, default: [] }, }; /** 工具栏按钮 */ diff --git a/packages/mobile-ui-vue/components/list-view/src/schema/list-view.schema.json b/packages/mobile-ui-vue/components/list-view/src/schema/list-view.schema.json index 0816ea49c4dda3ed8b3dff65f31c9cb1ed8b0d77..b1fdd677d493decff49a3e7cca01703af434a869 100644 --- a/packages/mobile-ui-vue/components/list-view/src/schema/list-view.schema.json +++ b/packages/mobile-ui-vue/components/list-view/src/schema/list-view.schema.json @@ -47,7 +47,6 @@ "type": "string", "default": "" }, - "itemTemplate": { "description": "列表行模板", "type": "string", diff --git a/packages/mobile-ui-vue/components/navbar/README.md b/packages/mobile-ui-vue/components/navbar/README.md deleted file mode 100644 index c8d3bce2fdf63d88b0a773b1e2cb39c8d483de7a..0000000000000000000000000000000000000000 --- a/packages/mobile-ui-vue/components/navbar/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# fm-navbar -## API - -### Props - -| 参数 | 说明 | 类型 | 默认值 | -| --- | --- | --- | --- | -| title | 标题 | string | `''` | -| left-text | 左侧文案 | string | `''` | -| right-text | 右侧文案 | string | `''` | -| left-arrow | 是否显示左侧箭头 | boolean | `false` | -| border | 是否显示下边框 | boolean | `true` | -| fixed | 是否固定在顶部 | boolean | `false` | - -### Slots - -| 名称 | 说明 | -| ----- | ------------------ | -| title | 自定义标题 | -| left | 自定义左侧区域内容 | -| right | 自定义右侧区域内容 | - -### Events - -| 事件名 | 说明 | 回调参数 | -| ----------- | ------------------ | -------- | -| clickLeft | 点击左侧按钮时触发 | - | -| clickRight | 点击右侧按钮时触发 | - | \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/navbar/index.ts b/packages/mobile-ui-vue/components/navbar/index.ts index 9860ea71a68be644e021eb6bd467e0af6cdc17e7..3984a04985fbd21d95018785dc294af853e254f2 100644 --- a/packages/mobile-ui-vue/components/navbar/index.ts +++ b/packages/mobile-ui-vue/components/navbar/index.ts @@ -7,14 +7,25 @@ const NAVBAR_REGISTERED_NAME = 'navigation-bar'; const Navbar = withInstall(NavbarInstallless); -Navbar.register = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record) => { - componentMap[NAVBAR_REGISTERED_NAME] = Navbar; - propsResolverMap[NAVBAR_REGISTERED_NAME] = propsResolver; +Navbar.register = ( + componentMap: Record, + propsResolverMap: Record, + configResolverMap: Record, + resolverMap: Record, +) => { + componentMap[NAVBAR_REGISTERED_NAME] = Navbar; + propsResolverMap[NAVBAR_REGISTERED_NAME] = propsResolver; }; -Navbar.registerDesigner = (componentMap: Record, propsResolverMap: Record, configResolverMap: Record, resolverMap: Record) => { - componentMap[NAVBAR_REGISTERED_NAME] = NavbarDesign; - propsResolverMap[NAVBAR_REGISTERED_NAME] = propsResolver; +Navbar.registerDesigner = ( + componentMap: Record, + propsResolverMap: Record, + configResolverMap: Record, + resolverMap: Record, +) => { + componentMap[NAVBAR_REGISTERED_NAME] = NavbarDesign; + propsResolverMap[NAVBAR_REGISTERED_NAME] = propsResolver; }; + export { Navbar }; export default Navbar; diff --git a/packages/mobile-ui-vue/components/navbar/src/designer/nav.design.component.tsx b/packages/mobile-ui-vue/components/navbar/src/designer/nav.design.component.tsx index e2a066b8b42c842fbe77b8021eaf1574e7945b8e..08f46e49e04c23a8a6cc5b87c620a1212f2be9a8 100644 --- a/packages/mobile-ui-vue/components/navbar/src/designer/nav.design.component.tsx +++ b/packages/mobile-ui-vue/components/navbar/src/designer/nav.design.component.tsx @@ -1,4 +1,3 @@ - /** * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd. * @@ -22,30 +21,30 @@ import { useDesignerRules } from './use-designer-rules'; import { DesignerHostService, DesignerItemContext, useDesignerComponent } from '@/components/designer-canvas';; export default defineComponent({ - name: 'FNavDesign', - props: navbarProps, - emits: ['nav'] as (string[] & ThisType) | undefined, - setup(props: NavbarProps, context: SetupContext) { - const elementRef = ref(); - const designItemContext = inject('design-item-context') as DesignerItemContext; - const designerHostService = inject('designer-host-service'); - const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); - const componentInstance = useDesignerComponent(elementRef, designItemContext,designerRulesComposition); - - onMounted(() => { - elementRef.value.componentInstance = componentInstance; - }); + name: 'FNavDesign', + props: navbarProps, + emits: ['nav'] as (string[] & ThisType) | undefined, + setup(props: NavbarProps, context: SetupContext) { + const elementRef = ref(); + const designItemContext = inject('design-item-context') as DesignerItemContext; + const designerHostService = inject('designer-host-service'); + const designerRulesComposition = useDesignerRules(designItemContext, designerHostService); + const componentInstance = useDesignerComponent(elementRef, designItemContext, designerRulesComposition); - context.expose(componentInstance.value); + onMounted(() => { + elementRef.value.componentInstance = componentInstance; + }); - const navbarProps = computed(() => ({ - ...props, - })); + context.expose(componentInstance.value); - return () => { - return ( - - ); - }; - } + const navbarProps = computed(() => ({ + ...props, + })); + + return () => { + return ( + + ); + }; + } }); diff --git a/packages/mobile-ui-vue/components/navbar/src/designer/use-designer-rules.ts b/packages/mobile-ui-vue/components/navbar/src/designer/use-designer-rules.ts index 00566e382cc5582906676db94e046b186d445e18..240365ea981c50e83c8be4e9566d9368c5f7009f 100644 --- a/packages/mobile-ui-vue/components/navbar/src/designer/use-designer-rules.ts +++ b/packages/mobile-ui-vue/components/navbar/src/designer/use-designer-rules.ts @@ -1,6 +1,6 @@ import { DesignerItemContext, UseDesignerRules } from "@/components/designer-canvas"; import { DraggingResolveContext } from "@/components/designer-canvas/src/composition/types"; -import { nextTick, ref } from "vue"; +import { ref } from "vue"; import { NavBarProperty } from "../property-config/navbar.property-config"; export function useDesignerRules(designItemContext: DesignerItemContext, designerHostService): UseDesignerRules { @@ -14,14 +14,13 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe const isInFixedContextRules = true; function canAccepts(draggingContext: DraggingResolveContext): boolean { - return false; } - function checkCanMoveComponent() { return true; } + function checkCanDeleteComponent() { return true; } @@ -31,7 +30,6 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe } function getStyles(): string { - // return 'border-radius: 12px'; return ' '; } @@ -47,7 +45,6 @@ export function useDesignerRules(designItemContext: DesignerItemContext, designe const { schema } = designItemContext; return componentProp.getPropertyConfig(schema); } - return { canAccepts, diff --git a/packages/mobile-ui-vue/components/navbar/src/navbar.component.tsx b/packages/mobile-ui-vue/components/navbar/src/navbar.component.tsx index 3b0436073a51480ff0bc9851eca168dbf5661168..04c1d32cfcbad25da2d73cf716fb7280b3c5b32b 100644 --- a/packages/mobile-ui-vue/components/navbar/src/navbar.component.tsx +++ b/packages/mobile-ui-vue/components/navbar/src/navbar.component.tsx @@ -13,54 +13,122 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { SetupContext, defineComponent } from 'vue'; +import { defineComponent, computed, ref } from 'vue'; +import { useBem } from '@components/common'; +import { Icon } from '@components/icon'; +import { Popover } from '@components/popover'; +import { ButtonItem } from '@components/button-group'; import { NAVBAR_NAME, NavbarProps, navbarProps } from './navbar.props'; export default defineComponent({ name: NAVBAR_NAME, props: navbarProps, emits: ['click-left', 'click-right'], - setup(props: NavbarProps, context: SetupContext) { + setup(props: NavbarProps, context) { + const { bem } = useBem(NAVBAR_NAME); const { emit, slots } = context; + const showToolbarPopover = ref(false); - const handlerClickLeft = (event) => { + function onLeftClick(event: MouseEvent): void { emit('click-left', event); - }; + } - const handlerClickRight = (event) => { + function onRightClick(event: MouseEvent): void { emit('click-right', event); - }; + } - const navbarClass = { - 'fm-navbar': true, - 'fm-navbar-fixed': props.fixed, - 'fm-navbar-border-bottom': props.border - }; + const navbarClass = computed(() => ({ + [bem()]: true, + [bem('', 'fixed')]: props.fixed, + [bem('', 'bottom-border')]: props.showBottomBorder, + })); - return () => ( - <> -
- {(props.leftArrow || props.leftText) && ( -
- {slots.left ? ( - slots.left() - ) : ( - <> - {props.leftArrow && ( - - )} - {props.leftText && {props.leftText}} - - )} + const hasToolbarItems = computed(() => { + return Array.isArray(props.toolbarItems) && props.toolbarItems.length > 0; + }); + const shouldRenderLeft = computed(() => { + return props.leftArrow || !!props.leftText || !!slots.left; + }); + const shouldRenderRight = computed(() => { + return !!props.rightText || hasToolbarItems.value || !!slots.right; + }); + + function toolbarItemClass(item: ButtonItem) { + return { + [bem('toolbar-item')]: true, + [bem('toolbar-item', 'disabled')]: item.disabled, + }; + } + + function onToolbarItemClick(item: ButtonItem): void { + if (item.disabled) { + return; + } + showToolbarPopover.value = false; + item.onClick?.(); + } + + function renderLeftContent() { + return ( +
+ {slots.left ? ( + slots.left() + ) : ( + <> + {props.leftArrow && } + {props.leftText && {props.leftText}} + + )} +
+ ); + } + + function renderToolbar() { + return ( + {{ + triggerElement: () => ( + + ), + content: () => props.toolbarItems.filter((item) => { + return item.visible || item.visible === undefined; + }).map((item) => ( +
onToolbarItemClick(item)}> + {item.text}
+ )), + }}
+ ); + } + + function renderRightContent() { + return ( +
+ {slots.right ? ( + slots.right() + ) : ( + <> + {hasToolbarItems.value && renderToolbar()} + {!hasToolbarItems.value && {props.rightText}} + )} -
{slots.title ? slots.title() : props.title}
-
- {slots.right ? slots.right() : {props.rightText}} -
- + ); + } + + return () => ( +
+ {shouldRenderLeft.value && renderLeftContent()} +
{slots.title ? slots.title() : props.title}
+ {shouldRenderRight.value && renderRightContent()} +
); } }); diff --git a/packages/mobile-ui-vue/components/navbar/src/navbar.props.ts b/packages/mobile-ui-vue/components/navbar/src/navbar.props.ts index bd3add9711b0763ab3cb367fccde8f2f799c0315..4ac0d4fe22f28e309bcbe044ba123da67f905ff8 100644 --- a/packages/mobile-ui-vue/components/navbar/src/navbar.props.ts +++ b/packages/mobile-ui-vue/components/navbar/src/navbar.props.ts @@ -15,40 +15,41 @@ */ import { ExtractPropTypes } from 'vue'; import { createPropsResolver } from '../../dynamic-resolver'; +import { ButtonItem } from '@components/button-group'; import navbarSchema from './schema/navbar.schema.json'; import { schemaMapper } from './schema/schema-mapper'; import { schemaResolver } from './schema/schema-resolver'; -export const NAVBAR_NAME = 'fm-navbar'; +export const NAVBAR_NAME = 'fm-navbar'; export const navbarProps = { - title: { - type: String - }, - fixed: { - type: Boolean, - default: false - }, - leftText: { - type: String - }, - rightText: { - type: String - }, - leftArrow: { - type: Boolean, - default: true - }, - border: { - type: Boolean, - default: false - } + + /** 标题 */ + title: { type: String, default: '' }, + + /** 是否固定在页面顶部 */ + fixed: { type: Boolean, default: false }, + + /** 左侧的文本 */ + leftText: { type: String, default: '' }, + + /** 右侧的文本 */ + rightText: { type: String, default: '' }, + + /** 是否显示左侧的箭头图标 */ + leftArrow: { type: Boolean, default: true }, + + /** 是否在下方显示分隔线 */ + showBottomBorder: { type: Boolean, default: true }, + + /** 右侧的下拉按钮工具栏 */ + toolbarItems: { type: Array, default: [] }, }; export const propsResolver = createPropsResolver( - navbarProps, - navbarSchema, - schemaMapper, - schemaResolver + navbarProps, + navbarSchema, + schemaMapper, + schemaResolver ); export type NavbarProps = ExtractPropTypes; diff --git a/packages/mobile-ui-vue/components/navbar/src/navbar.scss b/packages/mobile-ui-vue/components/navbar/src/navbar.scss index e0e44693bca0189840f6066f60a7a89008113476..2d8988936d287f8b54d9d01af0b0311b62efd5aa 100644 --- a/packages/mobile-ui-vue/components/navbar/src/navbar.scss +++ b/packages/mobile-ui-vue/components/navbar/src/navbar.scss @@ -1,72 +1,104 @@ @use '../../common/src/style/mixins/index.scss' as *; :root { - --fm-navbar-background: var(--fm-background-white); - --fm-navbar-color: var(--fm-text-color); - --fm-navbar-height: 44px; - --fm-navbar-title-size: 17px; - --fm-navbar-side-size: 16px; - --fm-navbar-arrow-size: 18px; + --fm-navbar-background: var(--fm-background-white); + --fm-navbar-color: var(--fm-text-color); + --fm-navbar-height: 44px; + --fm-navbar-title-size: 17px; + --fm-navbar-side-size: 16px; + --fm-navbar-arrow-size: 16px; + --fm-navbar-toolbar-item-height: 46px; } -.fm-navbar{ - position: relative; - z-index: 9; + +.fm-navbar { + position: relative; + z-index: 9; + display: flex; + align-items: center; + line-height: 1.5; + text-align: center; + user-select: none; + height: var(--fm-navbar-height); + color: var(--fm-navbar-color); + background-color: var(--fm-navbar-background); + + &--fixed { + position: fixed; + top: 0; + left: 0; + width: 100%; + } + + &--bottom-border { + @include hairline('bottom', #e6e6e6); + } + + &__title { + max-width: 60%; + margin: 0 auto; + font-weight: 500; + font-size: var(--fm-navbar-title-size); + @include ellipsis(); + } + + &__left, + &__right { + position: absolute; + top: 0; + bottom: 0; display: flex; align-items: center; - line-height: 1.5; - text-align: center; - user-select: none; - height: var(--fm-navbar-height); - color: var(--fm-navbar-color); - background-color: var(--fm-navbar-background); - &.fm-navbar-fixed { - position: fixed; - top: 0; - left: 0; - width: 100%; - } - &.fm-navbar-border-bottom{ - @include hairline('bottom', #eee); - } - &-title { - max-width: 60%; - margin: 0 auto; - font-weight: 500; - font-size: var(--fm-navbar-title-size); - @include ellipsis(); - } + max-width: 30%; + padding: 0 16px; + font-size: var(--fm-navbar-side-size); + @include ellipsis(); + cursor: pointer; + } + + &__left { + left: 0; - &-left, - &-right { - position: absolute; - top: 0; - bottom: 0; - display: flex; - align-items: center; - max-width: 30%; - padding: 0 16px; - font-size: var(--fm-navbar-side-size); - @include ellipsis(); - cursor: pointer; + &:active { + opacity: var(--fm-active-opacity); } - &-left { - left: 0; - &:active{ - opacity: var(--fm-active-opacity); - } - &.fm-navbar-left-padding{ - padding-left: 14px; - } + + &.fm-navbar-left-padding { + padding-left: 14px; } - &-right { - right: 0; - .fm-navbar-text:active { - opacity: var(--fm-active-opacity); - } + } + + &__right { + right: 0; + + .fm-navbar-text:active { + opacity: var(--fm-active-opacity); } - .fm-navbar-left-arrow{ - min-width: 1em; - margin-right: 4px; - font-size: var(--fm-navbar-arrow-size); + } + + &__left-arrow { + min-width: 1em; + margin-right: 4px; + font-size: var(--fm-navbar-arrow-size); + } + + &__toolbar { + padding: 0 !important; + + &-item { + padding: 0 var(--fm-padding-sm); + height: var(--fm-navbar-toolbar-item-height); + line-height: var(--fm-navbar-toolbar-item-height); + user-select: none; + cursor: pointer; + + &:not(:last-child) { + @include hairline('bottom'); + } + + &--disabled { + opacity: var(--fm-disabled-opacity); + cursor: not-allowed; + } } + } } \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/navbar/src/property-config/navbar.property-config.ts b/packages/mobile-ui-vue/components/navbar/src/property-config/navbar.property-config.ts index e6a0c287c425c47b71cd83a70da622bae53c1000..9dd0f7ef285568fddce7ed76eff690223887509b 100644 --- a/packages/mobile-ui-vue/components/navbar/src/property-config/navbar.property-config.ts +++ b/packages/mobile-ui-vue/components/navbar/src/property-config/navbar.property-config.ts @@ -1,31 +1,31 @@ import { BaseControlProperty } from "@/components/property-panel"; export class NavBarProperty extends BaseControlProperty { - constructor(componentId: string, designerHostService: any) { - super(componentId, designerHostService); - } - public getPropertyConfig(propertyData: any) { - // 基本信息 - this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); - // 外观 - this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); - // 行为 - this.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); + constructor(componentId: string, designerHostService: any) { + super(componentId, designerHostService); + } + public getPropertyConfig(propertyData: any) { + // 基本信息 + this.propertyConfig.categories['basic'] = this.getBasicPropConfig(propertyData); + // 外观 + this.propertyConfig.categories['appearance'] = this.getAppearanceConfig(propertyData); + // 行为 + this.propertyConfig.categories['behavior'] = this.getBehaviorConfig(propertyData); - return this.propertyConfig; - } + return this.propertyConfig; + } - private getBehaviorConfig(propertyData) { - return { - description: "基本信息", - title: "行为", - properties: { - title: { - title: "标题", - type: "string", - } - } - }; - } + private getBehaviorConfig(propertyData) { + return { + description: "基本信息", + title: "行为", + properties: { + title: { + title: "标题", + type: "string", + } + } + }; + } } diff --git a/packages/mobile-ui-vue/components/navbar/src/schema/navbar.schema.json b/packages/mobile-ui-vue/components/navbar/src/schema/navbar.schema.json index 4a06ab6f5e7e1bbcd30c4c53d84da7df8a8c51c4..70b1eae89e19a405b13a7412698af551117137d6 100644 --- a/packages/mobile-ui-vue/components/navbar/src/schema/navbar.schema.json +++ b/packages/mobile-ui-vue/components/navbar/src/schema/navbar.schema.json @@ -1,45 +1,45 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://farris-design.gitee.io/navbar.schema.json", - "title": "navigation-bar", - "description": "页面导航组件", - "type": "object", - "properties": { - "id": { - "description": "唯一标志", - "type": "string" - }, - "type": { - "description": "组件类型", - "type": "string", - "default": "navbar" - }, - "appearance": { - "description": "导航条自定义样式", - "type": "object", - "properties": { - "class": { - "type": "string" - }, - "style": { - "type": "string" - } - }, - "default": {} - }, - "title": { - "description": "标题", - "type": "string", - "default": "" + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://farris-design.gitee.io/navbar.schema.json", + "title": "navigation-bar", + "description": "页面导航组件", + "type": "object", + "properties": { + "id": { + "description": "唯一标识", + "type": "string" + }, + "type": { + "description": "组件类型", + "type": "string", + "default": "navbar" + }, + "appearance": { + "description": "导航条自定义样式", + "type": "object", + "properties": { + "class": { + "type": "string" }, - "leftArrow": { - "description": "是否显示左侧返回按钮", - "type": "boolean", - "default": "true" + "style": { + "type": "string" } + }, + "default": {} + }, + "title": { + "description": "标题", + "type": "string", + "default": "" }, - "required": [ - "id", - "type" - ] + "leftArrow": { + "description": "是否显示左侧返回按钮", + "type": "boolean", + "default": "true" + } + }, + "required": [ + "id", + "type" + ] } \ No newline at end of file diff --git a/packages/mobile-ui-vue/components/navbar/src/schema/schema-mapper.ts b/packages/mobile-ui-vue/components/navbar/src/schema/schema-mapper.ts index bc768d5bab9395cbd5d4faa5f1d2f86552f57980..90dc1493e556aff6b291ceb243c0d3e5ab96dc91 100644 --- a/packages/mobile-ui-vue/components/navbar/src/schema/schema-mapper.ts +++ b/packages/mobile-ui-vue/components/navbar/src/schema/schema-mapper.ts @@ -1,5 +1,5 @@ import { resolveAppearance, MapperFunction } from '../../../dynamic-resolver'; export const schemaMapper = new Map([ - ['appearance', resolveAppearance] + ['appearance', resolveAppearance] ]); diff --git a/packages/mobile-ui-vue/components/navbar/src/schema/schema-resolver.ts b/packages/mobile-ui-vue/components/navbar/src/schema/schema-resolver.ts index b02bdf93eec9060948f579c53aa81e3963a7d706..c1bf8da88a08c01a3c8d6e9f3d81859d84d87aa6 100644 --- a/packages/mobile-ui-vue/components/navbar/src/schema/schema-resolver.ts +++ b/packages/mobile-ui-vue/components/navbar/src/schema/schema-resolver.ts @@ -1,5 +1,5 @@ import { DynamicResolver } from "../../../dynamic-resolver"; export function schemaResolver(resolver: DynamicResolver, schema: Record, context: Record): Record { - return schema; + return schema; } diff --git a/packages/mobile-ui-vue/components/swipe-cell/src/composition/type.ts b/packages/mobile-ui-vue/components/swipe-cell/src/composition/type.ts index e41b9f6c3a974001e99cc4afdfc4f87afcfd0951..f94d637b5f689c4c59e5b3a0cc62966ca5dbaf00 100644 --- a/packages/mobile-ui-vue/components/swipe-cell/src/composition/type.ts +++ b/packages/mobile-ui-vue/components/swipe-cell/src/composition/type.ts @@ -1,24 +1,9 @@ -import type { VNode } from 'vue'; +import { ButtonItem } from '@components/button-group'; -export interface SwipeCellButton { - - /** 按钮文本 */ - text?: string; - - /** 图标 */ - icon?: string | VNode; - - /** 自定义类名 */ - className?: string; - - /** 自定义样式 */ - style?: string; - - /** 点击事件回调方法 */ - onClick?: (close: (() => void)) => void; - - [key: string]: any; -} +export type SwipeCellButton = Pick< + ButtonItem, + 'id' | 'text' | 'icon' | 'visible' | 'disabled' | 'customClass' | 'customStyle' | 'onClick' +>; export type SwipeCellSide = 'left' | 'right'; diff --git a/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.component.tsx b/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.component.tsx index 146911c40b61437d67fb83b989ab9c7982d1719c..ac1618815688bb64f9c6f49594fd827a3cdc7173 100644 --- a/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.component.tsx +++ b/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.component.tsx @@ -15,7 +15,6 @@ */ import { defineComponent, - SetupContext, ref, Ref, reactive, @@ -23,6 +22,7 @@ import { h, onMounted, computed, + watch, } from 'vue'; import { SWIPE_CELL_NAME, swipeCellProps, SwipeCellProps } from './swipe-cell.props'; import { @@ -43,7 +43,7 @@ export default defineComponent({ emits: ['click', 'open', 'close'], - setup(props: SwipeCellProps, context: SetupContext) { + setup(props: SwipeCellProps, context) { const { bem } = useBem(SWIPE_CELL_NAME); const { emit, slots, expose } = context; @@ -71,33 +71,33 @@ export default defineComponent({ /** 滑动时禁用点击 */ const forbidClick = ref(false); - const getElementWidth = (elementRef: Ref) => { + function getElementWidth(elementRef: Ref): number { return elementRef.value ? useRect(elementRef).width : 0; - }; + } - const setSideContentWidth = () => { + function setSideContentWidth(): void { state.leftSideWidth = getElementWidth(leftSideRef); state.rightSideWidth = getElementWidth(rightSideRef); - }; + } onMounted(() => { setSideContentWidth(); }); - const keepInRange = (num: number, min: number, max: number) => { + function keepInRange(num: number, min: number, max: number): number { return Math.min(Math.max(num, min), max); - }; + } - const open = (side: SwipeCellSide) => { + function open(side: SwipeCellSide): void { state.offset = side === 'left' ? state.leftSideWidth : -state.rightSideWidth; if (!state.opened) { state.opened = true; emit('open', side); } - }; + } - const close = (clickPosition?: SwipeCellClickPosition) => { + function close(clickPosition?: SwipeCellClickPosition): void { const side: SwipeCellSide = state.offset > 0 ? 'left' : 'right'; state.offset = 0; @@ -105,9 +105,9 @@ export default defineComponent({ state.opened = false; emit('close', { side, clickPosition }); } - }; + } - const handleSwipeEnd = () => { + function handleSwipeEnd(): void { const side: SwipeCellSide = state.offset > 0 ? 'left' : 'right'; const sideWidth = side === 'left' ? state.leftSideWidth : state.rightSideWidth; const offset = Math.abs(state.offset); @@ -119,18 +119,18 @@ export default defineComponent({ } else { close(); } - }; + } - const onTouchStart = (event: TouchEvent) => { + function onTouchStart(event: TouchEvent): void { if (props.disabled) { return; } setSideContentWidth(); state.startOffset = state.offset; touch.start(event); - }; + } - const onTouchMove = (event: TouchEvent) => { + function onTouchMove(event: TouchEvent): void { if (props.disabled) { return; } @@ -149,9 +149,9 @@ export default defineComponent({ } const newOffset = state.startOffset + deltaX.value; state.offset = keepInRange(newOffset, -state.rightSideWidth, state.leftSideWidth); - }; + } - const onTouchEnd = () => { + function onTouchEnd(): void { if (!state.moving) { return; } @@ -161,15 +161,20 @@ export default defineComponent({ setTimeout(() => { forbidClick.value = false; }); - }; + } - const onClick = (position: SwipeCellClickPosition, button?: SwipeCellButton) => { - if (forbidClick.value) { + function onClick(position: SwipeCellClickPosition, button?: SwipeCellButton, event?: MouseEvent): void { + if (forbidClick.value || button?.disabled) { return; } if (button && button.onClick) { - const closeFunc = () => close(position); - button.onClick(closeFunc); + const clickEventParam = { + close: () => close(position), + position, + button, + event, + }; + button.onClick(clickEventParam); } emit('click', { position, button }); const fromSideButton = position === 'left' || position === 'right'; @@ -182,9 +187,9 @@ export default defineComponent({ ) { close(position); } - }; + } - const getClickHandler = (position: SwipeCellClickPosition, button?: SwipeCellButton) => { + function getClickHandler(position: SwipeCellClickPosition, button?: SwipeCellButton) { return (event: MouseEvent) => { if ( position === 'left' @@ -193,11 +198,11 @@ export default defineComponent({ ) { event.stopPropagation(); } - onClick(position, button); + onClick(position, button, event); }; - }; + } - const renderButtonIcon = (button: SwipeCellButton) => { + function renderButtonIcon(button: SwipeCellButton) { const { icon } = button; if (typeof icon === 'string') { return ( @@ -207,13 +212,16 @@ export default defineComponent({ if (isVNode(icon)) { return h(icon, { class: bem('icon') }); } - }; + } - const renderSideButtons = (side: SwipeCellSide, buttons: SwipeCellButton[]) => { - return buttons.map((button, index) => ( + function renderSideButtons(side: SwipeCellSide, buttons: SwipeCellButton[]) { + return buttons.filter((button) => { + return !!button.visible || button.visible === undefined; + }).map((button, index) => (
@@ -221,9 +229,9 @@ export default defineComponent({ {button.text}
)); - }; + } - const renderSideContent = (side: SwipeCellSide, elementRef: Ref) => { + function renderSideContent(side: SwipeCellSide, elementRef: Ref) { const sideContentSlot = slots[side]; const slotParam = { close: () => close(side) }; const sideButtons = side === 'left' ? props.leftButtons : props.rightButtons; @@ -241,19 +249,28 @@ export default defineComponent({ {!sideContentSlot && renderSideButtons(side, sideButtons)}
); - }; + } + + watch( + () => props.disabled, + () => { + if (props.disabled && state.opened) { + close(); + } + }, + ); expose({ open, close: () => close(), }); - useEventListener('touchstart', onTouchStart, { target: rootRef, passive: true }); - useEventListener('touchmove', onTouchMove, { target: rootRef }); + useEventListener('touchstart', onTouchStart as EventListener, { target: rootRef, passive: true }); + useEventListener('touchmove', onTouchMove as EventListener, { target: rootRef }); useEventListener('touchend', onTouchEnd, { target: rootRef }); useEventListener('touchcancel', onTouchEnd, { target: rootRef }); - useEventListener('click', getClickHandler('cell'), { target: rootRef }); + useEventListener('click', getClickHandler('cell') as EventListener, { target: rootRef }); useClickAway(rootRef, () => onClick('outside'), { eventName: 'touchstart' }); diff --git a/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.props.ts b/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.props.ts index 71d000b8ac01e90c0df6462ba738e39d2433b073..b0cfafdbef36b9e6977c25cf9237aa2cc7cede83 100644 --- a/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.props.ts +++ b/packages/mobile-ui-vue/components/swipe-cell/src/swipe-cell.props.ts @@ -49,3 +49,4 @@ export const swipeCellProps = { }; export type SwipeCellProps = ExtractPropTypes; +export type { SwipeCellButton }; diff --git a/packages/mobile-ui-vue/demos/card/base.vue b/packages/mobile-ui-vue/demos/card/base.vue new file mode 100644 index 0000000000000000000000000000000000000000..c9fbc6e741690dcf6ec8bcdf11cf431ea3ad9157 --- /dev/null +++ b/packages/mobile-ui-vue/demos/card/base.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/packages/mobile-ui-vue/demos/card/index.vue b/packages/mobile-ui-vue/demos/card/index.vue new file mode 100644 index 0000000000000000000000000000000000000000..3010a50ba932ec7481ab8f015b9322447be5f72b --- /dev/null +++ b/packages/mobile-ui-vue/demos/card/index.vue @@ -0,0 +1,11 @@ + + + + + diff --git a/packages/mobile-ui-vue/demos/list-view/index.vue b/packages/mobile-ui-vue/demos/list-view/index.vue index 57b2fb963c56eecdfa4ac2a39a7e443019b70dd4..ae9847676d6481f92dd4fa2b0ed1f1dad69f09ff 100644 --- a/packages/mobile-ui-vue/demos/list-view/index.vue +++ b/packages/mobile-ui-vue/demos/list-view/index.vue @@ -6,12 +6,16 @@ + + + diff --git a/packages/mobile-ui-vue/demos/list-view/swipe-cell.vue b/packages/mobile-ui-vue/demos/list-view/swipe-cell.vue new file mode 100644 index 0000000000000000000000000000000000000000..8bfac2dba6fef2ba997a0fd090263e4816b100d6 --- /dev/null +++ b/packages/mobile-ui-vue/demos/list-view/swipe-cell.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/packages/mobile-ui-vue/demos/navbar/index.vue b/packages/mobile-ui-vue/demos/navbar/index.vue index e7afdf085e958252a7436af7e73bfe72049de55c..d7634ae55c07340a932085cd796da12aa1ee3465 100644 --- a/packages/mobile-ui-vue/demos/navbar/index.vue +++ b/packages/mobile-ui-vue/demos/navbar/index.vue @@ -1,19 +1,23 @@ diff --git a/packages/mobile-ui-vue/demos/navbar/toolbar.vue b/packages/mobile-ui-vue/demos/navbar/toolbar.vue new file mode 100644 index 0000000000000000000000000000000000000000..41c86e044cd3eb9ac4c7e90a40a8a4547c1e94fd --- /dev/null +++ b/packages/mobile-ui-vue/demos/navbar/toolbar.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/packages/mobile-ui-vue/demos/swipe-cell/async-close.vue b/packages/mobile-ui-vue/demos/swipe-cell/async-close.vue index 9fef557c86613ceb1e977b7ebc3953f6d537ee32..642899ee5ca4d2ae39d9ad909b1530085e05f784 100644 --- a/packages/mobile-ui-vue/demos/swipe-cell/async-close.vue +++ b/packages/mobile-ui-vue/demos/swipe-cell/async-close.vue @@ -28,8 +28,8 @@ const handleDelete = () => { const rightSideButtons = [ { text: '删除', - style: 'background-color: #F24645', - onClick: (close: () => void) => { + customStyle: 'background-color: #F24645', + onClick: ({ close }) => { Toast({ message: '正在删除...', overlay: true, duration: 1500 }); setTimeout(() => { close(); diff --git a/packages/mobile-ui-vue/demos/swipe-cell/base.vue b/packages/mobile-ui-vue/demos/swipe-cell/base.vue index 40a1ab896888be968e4c31cde614c2c5466b903b..54c40fa17bfd9d5a2904d75a8ed329a2f137d6ee 100644 --- a/packages/mobile-ui-vue/demos/swipe-cell/base.vue +++ b/packages/mobile-ui-vue/demos/swipe-cell/base.vue @@ -12,7 +12,7 @@ import { Toast } from '../../components/toast'; const leftSideButtons = [ { text: '选择', - style: 'background-color: #3A90FF', + customStyle: 'background-color: #3A90FF', onClick: () => { Toast({ message: '选择' }); } @@ -23,7 +23,7 @@ const rightSideButtons = [ { text: '收藏', icon: 's-star-o', - style: 'background-color: #ED7B2F', + customStyle: 'background-color: #ED7B2F', onClick: () => { Toast({ message: '收藏' }); } @@ -31,7 +31,7 @@ const rightSideButtons = [ { text: '删除', icon: h(TrashIcon), - style: 'background-color: #F24645', + customStyle: 'background-color: #F24645', onClick: () => { Toast({ message: '删除' }); } diff --git a/packages/mobile-ui-vue/src/menu-data.ts b/packages/mobile-ui-vue/src/menu-data.ts index 4c6d0b06ee51e54390478098617d0c23e6704bf3..1574f8bde9cb12036efe24ed12600c01bd5867a1 100644 --- a/packages/mobile-ui-vue/src/menu-data.ts +++ b/packages/mobile-ui-vue/src/menu-data.ts @@ -192,6 +192,12 @@ export default { url: "/demos/list-view", component: "/list-view", }, + { + title: "卡片", + name: "card", + url: "/demos/card", + component: "/card", + }, { title: "标签栏", name: "tab-bar",