This commit is contained in:
2026-01-05 12:47:14 +08:00
commit 1fc846fae3
1614 changed files with 162035 additions and 0 deletions

View File

@@ -0,0 +1,82 @@
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue'
import { CANCEL_EVENT, CHOOSE_EVENT, CLOSE_EVENT, UPDATE_VISIBLE_EVENT } from '../_constants'
import { commonProps, isBoolean, isNumber, makeArrayProp, makeStringProp, truthProp } from '../_utils'
import { popupProps } from '../popup/popup'
export interface ActionSheetOption {
disable?: boolean
loading?: boolean
color?: string
name: string
subname?: string
}
export const actionsheetProps = {
...popupProps,
...commonProps,
/**
* @description 是否显示圆角
*/
round: truthProp,
/**
* @description 是否开启 iPhone 系列全面屏底部安全区适配,仅当 `position` 为 `bottom` 时有效
*/
safeAreaInsetBottom: truthProp,
/**
* @description 遮罩显示时的背景是否锁定
*/
lockScroll: truthProp,
/**
* @description 自定义 popup 弹框样式
*/
popStyle: {
type: Object as PropType<CSSProperties>,
},
/**
* @description 取消文案
*/
cancelTxt: makeStringProp(''),
/**
* @description 设置列表项标题展示使用参数
*/
optionTag: makeStringProp<keyof ActionSheetOption>('name'),
/**
* @description 设置列表项二级标题展示使用参数
*/
optionSubTag: makeStringProp<keyof ActionSheetOption>('subname'),
/**
* @description 设置选中项的值,和 'option-tag' 的值对应
*/
chooseTagValue: makeStringProp(''),
/**
* @description 设置列表项标题
*/
title: makeStringProp(''),
/**
* @description 选中项颜色,当 choose-tag-value == option-tag 的值 生效
*/
customColor: makeStringProp('#ee0a24'),
/**
* @description 设置列表项副标题/描述
*/
description: makeStringProp(''),
/**
* @description 列表项
*/
menuItems: makeArrayProp<ActionSheetOption>([]),
/**
* @description 遮罩层是否可关闭
*/
closeAbled: truthProp,
}
export type ActionsheetProps = ExtractPropTypes<typeof actionsheetProps>
export const actionsheetEmits = {
[CLOSE_EVENT]: () => true,
[UPDATE_VISIBLE_EVENT]: (val: boolean) => isBoolean(val),
[CANCEL_EVENT]: () => true,
[CHOOSE_EVENT]: (item: ActionSheetOption, index: number) => item instanceof Object && isNumber(index),
}
export type ActionsheetEmits = typeof actionsheetEmits

View File

@@ -0,0 +1,120 @@
<script setup lang="ts">
import { computed, defineComponent, useSlots } from 'vue'
import { CANCEL_EVENT, CHOOSE_EVENT, CLOSE_EVENT, PREFIX, UPDATE_VISIBLE_EVENT } from '../_constants'
import { getMainClass } from '../_utils'
import NutIcon from '../icon/icon.vue'
import NutPopup from '../popup/popup.vue'
import type { ActionSheetOption } from './actionsheet'
import { actionsheetEmits, actionsheetProps } from './actionsheet'
const props = defineProps(actionsheetProps)
const emit = defineEmits(actionsheetEmits)
const slotDefault = !!useSlots().default
const classes = computed(() => {
return getMainClass(props, componentName)
})
function isHighlight(item: ActionSheetOption) {
return props.chooseTagValue && props.chooseTagValue === item[props.optionTag] ? props.customColor : ''
}
function cancelActionSheet() {
emit(CANCEL_EVENT)
emit(UPDATE_VISIBLE_EVENT, false)
}
function chooseItem(item: ActionSheetOption, index: number) {
if (!item.disable && !item.loading) {
emit(CHOOSE_EVENT, item, index)
emit(UPDATE_VISIBLE_EVENT, false)
}
}
function close() {
if (props.closeAbled) {
emit(CLOSE_EVENT)
emit(UPDATE_VISIBLE_EVENT, false)
}
}
</script>
<script lang="ts">
const componentName = `${PREFIX}-action-sheet`
export default defineComponent({
name: componentName,
options: {
virtualHost: true,
addGlobalClass: true,
styleIsolation: 'shared',
},
})
</script>
<template>
<NutPopup
:pop-class="props.popClass"
:custom-style="props.popStyle"
:visible="props.visible"
position="bottom"
:overlay="props.overlay"
:round="props.round"
:safe-area-inset-bottom="props.safeAreaInsetBottom"
:z-index="props.zIndex"
:duration="props.duration"
:overlay-class="props.overlayClass"
:overlay-style="props.overlayStyle"
:lock-scroll="props.lockScroll"
:close-on-click-overlay="props.closeAbled"
@click-overlay="close"
>
<view :class="classes" :style="props.customStyle">
<view v-if="props.title" class="nut-action-sheet__title">
{{ props.title }}
</view>
<slot />
<view v-if="!slotDefault">
<view v-if="props.description" class="nut-action-sheet__item nut-action-sheet__desc">
{{ props.description }}
</view>
<view v-if="props.menuItems.length" class="nut-action-sheet__menu">
<view
v-for="(item, index) of props.menuItems"
:key="index"
class="nut-action-sheet__item"
:class="{
'nut-action-sheet__item--disabled': item.disable,
'nut-action-sheet__item--loading': item.loading,
}"
:style="{ color: isHighlight(item) || item.color }"
@click="chooseItem(item, index)"
>
<NutIcon v-if="item.loading" name="loading" />
<view v-else>
{{ item[props.optionTag] }}
</view>
<view class="nut-action-sheet__subdesc">
{{ item[props.optionSubTag] }}
</view>
</view>
</view>
<view v-if="props.cancelTxt" class="nut-action-sheet__cancel" @click="cancelActionSheet">
{{ props.cancelTxt }}
</view>
</view>
</view>
</NutPopup>
</template>
<style lang="scss">
@import './index';
</style>

View File

@@ -0,0 +1,82 @@
@import "../popup/index";
.nut-theme-dark {
.nut-action-sheet {
.nut-action-sheet__cancel {
border-top: 1px solid $dark-background2;
}
.nut-action-sheet__title {
border-bottom: 1px solid $dark-background2;
}
.nut-action-sheet__cancel,
.nut-action-sheet__item,
.nut-action-sheet__title {
color: $dark-color;
background: $dark-background;
}
}
}
.nut-action-sheet {
display: block;
.nut-action-sheet__title {
display: block;
padding: 10px;
margin: 0;
font-size: $font-size-base;
color: $title-color;
text-align: center;
background-color: $white;
border-bottom: 1px solid $actionsheet-light-color;
}
.nut-action-sheet__menu {
display: block;
padding: 0;
margin: 0;
list-style: none;
}
.nut-action-sheet__cancel,
.nut-action-sheet__item {
display: block;
padding: 10px;
font-size: $actionsheet-item-font-size;
line-height: $actionsheet-item-line-height;
color: $actionsheet-item-font-color;
text-align: center;
cursor: pointer;
background-color: #fff;
border-bottom: $actionsheet-item-border-bottom;
}
.nut-action-sheet__desc {
font-size: $actionsheet-item-font-size;
color: #999;
cursor: default;
}
.nut-action-sheet__subdesc {
display: block;
font-size: $actionsheet-item-subdesc-font-size;
color: #999;
}
.nut-action-sheet__item--disabled {
color: #e1e1e1 !important;
cursor: not-allowed;
}
.nut-action-sheet__item--loading {
cursor: default;
}
.nut-action-sheet__cancel {
margin-top: 5px;
border-top: $actionsheet-item-cancel-border-top;
}
}

View File

@@ -0,0 +1 @@
export * from './actionsheet'