276 lines
6.3 KiB
Vue
276 lines
6.3 KiB
Vue
<script lang="tsx">
|
|
import { selectionEmits, selectionProps } from 'hoci'
|
|
import type { CSSProperties, PropType } from 'vue'
|
|
import { Teleport, Transition, computed, defineComponent, onDeactivated, ref, watch } from 'vue'
|
|
|
|
import { onClickOutside, useVModel } from '@vueuse/core'
|
|
interface ICascaderItem {
|
|
label: string
|
|
id?: string | number
|
|
value?: any
|
|
children?: ICascaderItem[]
|
|
onSelect?: () => void
|
|
}
|
|
|
|
export default defineComponent({
|
|
name: 'ICascader',
|
|
props: {
|
|
...selectionProps,
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
width: {
|
|
type: Number,
|
|
default: 120,
|
|
},
|
|
emptyLabel: {
|
|
type: String,
|
|
},
|
|
highlight: {
|
|
type: String,
|
|
// warn remind
|
|
default: '',
|
|
},
|
|
items: {
|
|
type: Array as PropType<ICascaderItem[]>,
|
|
default: () => [],
|
|
},
|
|
},
|
|
emits: [...selectionEmits, 'select'],
|
|
setup(props, { emit }) {
|
|
const modelValue = useVModel(props, 'modelValue')
|
|
|
|
const isOpen = ref(false)
|
|
const triggerRef = ref<HTMLElement>()
|
|
const selectRef = ref<HTMLElement>()
|
|
const dropdownRef = ref<HTMLElement>()
|
|
|
|
watch(isOpen, onResize)
|
|
|
|
function collapse() {
|
|
isOpen.value = !isOpen.value && !props.disabled
|
|
}
|
|
|
|
// 下拉框位置
|
|
const dropdownPosition = ref({ x: 0, y: 0, w: 0, z: 0 })
|
|
// 下拉框位置
|
|
const dropdownStyle = computed<CSSProperties>(() => {
|
|
if (dropdownPosition.value.z == 0) {
|
|
return {
|
|
left: `${dropdownPosition.value.x}px`,
|
|
top: `${dropdownPosition.value.y}px`,
|
|
width: `${dropdownPosition.value.w}px`,
|
|
}
|
|
} else {
|
|
return {
|
|
left: `${dropdownPosition.value.x}px`,
|
|
bottom: `${dropdownPosition.value.z}px`,
|
|
width: `${dropdownPosition.value.w}px`,
|
|
}
|
|
}
|
|
})
|
|
|
|
function onResize() {
|
|
if (triggerRef.value) {
|
|
const { width, height, left, top } = triggerRef.value.getBoundingClientRect()
|
|
if (window.innerHeight - top > 160) {
|
|
dropdownPosition.value = {
|
|
w: width,
|
|
x: left,
|
|
y: top + height + 2,
|
|
z: 0,
|
|
}
|
|
} else {
|
|
dropdownPosition.value = {
|
|
w: width,
|
|
x: left,
|
|
z: window.innerHeight - top + 2,
|
|
y: 0,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
window.addEventListener('resize', onResize)
|
|
window.addEventListener('scroll', onResize)
|
|
|
|
onDeactivated(() => {
|
|
window.removeEventListener('resize', onResize)
|
|
window.removeEventListener('scroll', onResize)
|
|
})
|
|
|
|
const selectItem = ref<ICascaderItem>()
|
|
|
|
function onSelect(item: ICascaderItem) {
|
|
if (!item.children?.length) {
|
|
isOpen.value = false
|
|
}
|
|
selectItem.value = item
|
|
emit('select', item.value)
|
|
}
|
|
|
|
function loadNode(item: ICascaderItem) {
|
|
const value = props.modelValue ?? props.defaultValue
|
|
if ((selectItem.value == null || selectItem.value == undefined) && value == item.value) {
|
|
selectItem.value = item
|
|
}
|
|
}
|
|
|
|
onClickOutside(triggerRef, () => (isOpen.value = false), { ignore: [dropdownRef] })
|
|
|
|
return () => {
|
|
return (
|
|
<div
|
|
class={['min-w-20 w-40 i-cascader'].concat(props.highlight)}
|
|
onClick={collapse}
|
|
ref={selectRef}
|
|
>
|
|
<div
|
|
class={{
|
|
'i-cascader-trigger': true,
|
|
disabled: props.disabled,
|
|
}}
|
|
ref={triggerRef}
|
|
>
|
|
<span class="i-cascader-label">{selectItem.value?.label}</span>
|
|
<calc-button icon="dropdown"></calc-button>
|
|
</div>
|
|
<Teleport to="body">
|
|
<Transition name="dropdown" mode="out-in">
|
|
<calc-tree
|
|
{...props}
|
|
v-model={modelValue.value}
|
|
class="py-1 i-cascader-dropdown"
|
|
style={dropdownStyle.value}
|
|
v-show={isOpen.value}
|
|
ref={dropdownRef}
|
|
onLoad={loadNode}
|
|
onSelect={onSelect}
|
|
data={props.items}
|
|
/>
|
|
</Transition>
|
|
</Teleport>
|
|
</div>
|
|
)
|
|
}
|
|
},
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
@use 'sass:color';
|
|
$text-color: #937639;
|
|
.i-cascader {
|
|
min-width: 80px;
|
|
width: 160px;
|
|
user-select: none;
|
|
outline: none;
|
|
height: 16px;
|
|
min-height: 16px;
|
|
line-height: 100%;
|
|
// font-size: 12px;
|
|
color: $text-color;
|
|
background-color: rgba(0, 0, 0, 1);
|
|
border: 1px solid #5b472a;
|
|
border-radius: 2px;
|
|
padding: 0;
|
|
padding-right: 3px;
|
|
margin: 0;
|
|
display: block;
|
|
|
|
.i-cascader-trigger {
|
|
padding: 0;
|
|
display: flex;
|
|
height: 100%;
|
|
opacity: 0.9;
|
|
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
|
|
.i-cascader-label {
|
|
padding-left: 5px;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
text-overflow: ellipsis;
|
|
max-width: calc(100% - 20px);
|
|
}
|
|
|
|
.i-cascader-down-icon {
|
|
background-image: url('./img/select_down.png');
|
|
background-size: cover;
|
|
background-repeat: no-repeat;
|
|
background-position: center;
|
|
border-radius: 2px;
|
|
width: 15px;
|
|
height: 14px;
|
|
}
|
|
|
|
.i-cascader-down-icon:active {
|
|
background-image: url('./img/select_down_clicked.png');
|
|
background-size: cover;
|
|
background-repeat: no-repeat;
|
|
background-position: center;
|
|
border-radius: 2px;
|
|
width: 15px;
|
|
height: 14px;
|
|
}
|
|
|
|
&.disabled {
|
|
color: gray;
|
|
|
|
.i-cascader-down-icon {
|
|
background-image: url('./img/select_down_disabled.png');
|
|
}
|
|
}
|
|
}
|
|
|
|
.i-cascader-trigger:hover {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.remind {
|
|
color: #32e432 !important;
|
|
border: 1px solid #aa8651;
|
|
}
|
|
|
|
.i-cascader-dropdown {
|
|
position: fixed;
|
|
max-height: 160px;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
background: black;
|
|
// font-size: 12px;
|
|
z-index: 888;
|
|
|
|
$hoverColor: #002947;
|
|
$activeColor: color.scale($hoverColor, $lightness: 5%);
|
|
color: $text-color;
|
|
}
|
|
|
|
.dropdown-active {
|
|
animation: dropdown-enter 0.2s ease-in;
|
|
}
|
|
|
|
.dropdown-active {
|
|
animation: dropdown-leave 0.2s ease-in;
|
|
}
|
|
|
|
.dropdown-enter-active {
|
|
animation: dropdown-enter 0.2s ease-in;
|
|
}
|
|
|
|
.dropdown-leave-active {
|
|
animation: dropdown-leave 0.2s ease-in;
|
|
}
|
|
|
|
.dropdown-enter-active {
|
|
animation: dropdown-enter 0.2s ease-in;
|
|
}
|
|
|
|
.dropdown-leave-active {
|
|
animation: dropdown-leave 0.2s ease-in;
|
|
}
|
|
</style>
|