| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- <template>
- <!--
- ss-search-button 搜索按钮组件
- 基于原JSP中的mobileSearch-button样式封装
- 支持多选项popup功能
- -->
- <view class="ss-search-button-container" :class="{ open: showOptionsMenu }">
- <button
- class="ss-search-button"
- :style="buttonStyle"
- @click="handleClick"
- :disabled="disabled"
- >
- <!-- 前置图标插槽 -->
- <view v-if="preIcon || $slots.preIcon" class="ss-search-button__pre-icon">
- <slot name="preIcon">
- <Icon v-if="preIcon" :name="preIcon" :size="iconSize" :color="iconColor" />
- </slot>
- </view>
- <!-- 按钮文本 -->
- <text class="ss-search-button__text">{{ text }}</text>
- <!-- 后置图标插槽 -->
- <view v-if="suffixIcon || $slots.suffixIcon" class="ss-search-button__suffix-icon">
- <slot name="suffixIcon">
- <Icon v-if="suffixIcon" :name="suffixIcon" :size="iconSize" :color="iconColor" />
- </slot>
- </view>
- </button>
- <!-- 选项弹窗菜单 - 参考ss-select样式 -->
- <view
- v-if="showOptionsMenu && hasMultipleOptions"
- class="options-menu"
- @click.stop
- >
- <view
- v-for="(option, index) in options"
- :key="index"
- class="option-item"
- @click="handleOptionClick(option, index)"
- >
- <text v-if="option.icon" :class="['iconfont', option.icon]"></text>
- <text class="option-text">{{ option.text }}</text>
- </view>
- </view>
- </view>
- </template>
- <script setup>
- import { computed, ref, onMounted, onUnmounted } from 'vue'
- import Icon from '@/components/icon/index.vue'
- const props = defineProps({
- // 按钮文本
- text: {
- type: String,
- default: '增加'
- },
- // 是否禁用
- disabled: {
- type: Boolean,
- default: false
- },
- // 按钮高度
- height: {
- type: [String, Number],
- default: '36px'
- },
- // 前置图标名称
- preIcon: {
- type: String,
- default: ''
- },
- // 后置图标名称
- suffixIcon: {
- type: String,
- default: ''
- },
- // 图标大小
- iconSize: {
- type: [String, Number],
- default: '32'
- },
- // 图标颜色
- iconColor: {
- type: String,
- default: '#585d6e'
- },
- // 自定义按钮样式
- customStyle: {
- type: Object,
- default: () => ({})
- },
- // 跳转链接(兼容原JSP用法)
- href: {
- type: String,
- default: ''
- },
- // 选项列表 - 新增
- options: {
- type: Array,
- default: () => []
- }
- })
- const emit = defineEmits(['click', 'optionClick'])
- // 状态
- const showOptionsMenu = ref(false)
- // 计算属性
- const hasOptions = computed(() => {
- return props.options && Array.isArray(props.options) && props.options.length > 0
- })
- const hasMultipleOptions = computed(() => {
- return props.options && props.options.length > 1
- })
- const isSingleOption = computed(() => {
- return props.options && props.options.length === 1
- })
- // 按钮样式
- const buttonStyle = computed(() => ({
- height: typeof props.height === 'number' ? `${props.height}px` : props.height,
- ...props.customStyle
- }))
- /**
- * 处理按钮点击
- */
- const handleClick = () => {
- if (!hasOptions.value) {
- // 没有选项,直接触发点击事件
- emit('click')
- } else if (isSingleOption.value) {
- // 单个选项,直接执行
- handleOptionClick(props.options[0], 0)
- } else if (hasMultipleOptions.value) {
- // 先记录当前状态
- const wasOpen = showOptionsMenu.value
- // 关闭其他按钮的菜单
- uni.$emit('closeAllButtonMenus')
- // 切换当前菜单状态
- showOptionsMenu.value = !wasOpen
- }
- }
- /**
- * 处理选项点击
- */
- const handleOptionClick = (option, index) => {
- showOptionsMenu.value = false
- // 执行选项的回调
- if (option.onclick && typeof option.onclick === 'function') {
- option.onclick()
- }
- // 触发组件事件
- emit('optionClick', { option, index })
- // 兼容原来的click事件
- emit('click', { option, index })
- }
- /**
- * 关闭菜单
- */
- const closeMenu = () => {
- showOptionsMenu.value = false
- }
- /**
- * 监听全局关闭事件
- */
- onMounted(() => {
- uni.$on('closeAllButtonMenus', closeMenu)
- })
- onUnmounted(() => {
- uni.$off('closeAllButtonMenus', closeMenu)
- })
- </script>
- <style lang="scss" scoped>
- .ss-search-button-container {
- position: relative;
- display: inline-block;
- }
- .ss-search-button {
- height: auto;
- padding: 0 1rem;
- border: 1px solid #eceded;
- outline: none;
- background-color: #fff;
- text-align: center;
- font-size: 1rem;
- color: #585d6e;
- letter-spacing: 0.1rem;
- border-radius: 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- -webkit-appearance: none;
- appearance: none;
- box-sizing: border-box;
- transition: all 0.2s ease;
- &:active {
- background-color: #eceded;
- color: #fff;
- border-color: #eceded;
- transform: scale(0.98);
- transition: all 0.1s ease;
- // active 状态下图标样式
- .ss-search-button__pre-icon,
- .ss-search-button__suffix-icon {
- :deep(.iconfont) {
- color: #fff !important;
- }
- :deep(text) {
- color: #fff !important;
- }
- }
- }
- &:disabled {
- opacity: 0.6;
- cursor: not-allowed;
- }
- &__pre-icon {
- margin-right: 0.3rem;
- display: flex;
- align-items: center;
- }
- &__suffix-icon {
- margin-left: 0.3rem;
- display: flex;
- align-items: center;
- }
- &__text {
- font-size: inherit;
- color: inherit;
- }
- }
- // 选项弹窗菜单 - 参考ss-select样式
- .options-menu {
- position: absolute;
- top: 100%;
- left: 0;
- width: 100%;
- background-color: #393D51;
- z-index: 1003; // 比卡片popup稍高
- color: #fff;
- border: 2rpx solid #393D51;
- box-sizing: border-box;
- border-radius: 10rpx;
- overflow: hidden;
- max-height: 600rpx;
- overflow: auto;
- min-width: 200rpx;
- .option-item {
- padding: 20rpx 20rpx 20rpx 32rpx;
- cursor: pointer;
- position: relative;
- display: flex;
- align-items: center;
- // 分隔线
- &::after {
- content: "";
- position: absolute;
- bottom: 0%;
- left: 50%;
- transform: translateX(-50%);
- width: 80%;
- height: 2rpx;
- background-color: #303445;
- }
- &:last-child::after {
- display: none;
- }
- &:active {
- background-color: #fff;
- color: #393D51;
- &::after {
- display: none;
- }
- }
- .iconfont {
- font-size: 28rpx;
- color: inherit;
- margin-right: 16rpx;
- }
- .option-text {
- font-size: 28rpx;
- color: inherit;
- flex: 1;
- }
- }
- }
- // 响应式适配
- @media screen and (max-width: 750px) {
- .ss-search-button {
- font-size: 14px;
- padding: 0 0.8rem;
- }
- }
- </style>
|