v2.0.0
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<button
|
||||
:class="[$style.btn, {[$style.min]: min}, {[$style.outline]: outline}]"
|
||||
:disabled="disabled">
|
||||
<slot />
|
||||
</button>
|
||||
<button
|
||||
:class="[$style.btn, {[$style.min]: min}, {[$style.outline]: outline}]"
|
||||
tabindex="0"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -34,13 +36,14 @@ export default {
|
||||
border-radius: @form-radius;
|
||||
cursor: pointer;
|
||||
padding: 8px 15px;
|
||||
color: @color-btn;
|
||||
color: var(--color-button-font);
|
||||
outline: none;
|
||||
transition: background-color 0.2s ease;
|
||||
background-color: @color-btn-background;
|
||||
background-color: var(--color-button-background);
|
||||
font-size: 14px;
|
||||
&[disabled] {
|
||||
opacity: .4;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.outline {
|
||||
@@ -48,10 +51,10 @@ export default {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @color-btn-hover;
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-btn-active;
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,22 +63,4 @@ export default {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.btn {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
background-color: ~'@{color-@{value}-btn-background}';
|
||||
&.outline {
|
||||
background-color: transparent;
|
||||
}
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-btn-active}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,50 +1,60 @@
|
||||
<template lang="pug">
|
||||
div(:class="$style.checkbox")
|
||||
input(:type="need ? 'radio' : 'checkbox'" :class="$style.input" :id="id" :disabled="disabled" :value="value" :name="name" @change="change" v-model="bool")
|
||||
label(:for="id" :class="$style.content")
|
||||
div(v-if="indeterminate" :class="$style.container")
|
||||
svg(v-show="indeterminate" :class="$style.icon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
|
||||
use(xlink:href='#icon-check-indeterminate')
|
||||
svg(v-show="!indeterminate" :class="$style.icon" version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 0 448 512' space='preserve')
|
||||
use(xlink:href='#icon-check-true')
|
||||
div(v-else :class="$style.container")
|
||||
svg(version='1.1' :class="$style.icon" xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' width="100%" viewBox='0 32 448 448' space='preserve')
|
||||
use(xlink:href='#icon-check-true')
|
||||
span(v-if="label != null" v-html="label" :class="$style.label")
|
||||
<template>
|
||||
<div :class="$style.checkbox">
|
||||
<input
|
||||
:id="id" :type="need ? 'radio' : 'checkbox'" :aria-hidden="true" :checked="checked"
|
||||
:class="$style.input" :disabled="disabled" :value="value" :name="name" @input="handleInput"
|
||||
>
|
||||
<label :for="id" :class="$style.content">
|
||||
<div :class="$style.container" role="checkbox" tabindex="0" :aria-label="ariaLabel || label" :aria-checked="checked" :aria-disabled="disabled">
|
||||
<svg version="1.1" :class="$style.icon" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" width="100%" viewBox="0 32 448 448" space="preserve">
|
||||
<use xlink:href="#icon-check-true" />
|
||||
</svg>
|
||||
</div>
|
||||
<span v-if="label != null" :class="$style.label" v-text="label" />
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
modelValue: {},
|
||||
value: {},
|
||||
modelValue: {
|
||||
type: [String, Boolean, Number, Array],
|
||||
required: true,
|
||||
},
|
||||
value: {
|
||||
type: [String, Boolean, Number, Array],
|
||||
default: undefined,
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
need: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
ariaLabel: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
indeterminate: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
data() {
|
||||
return {
|
||||
bool: false,
|
||||
checked: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -56,57 +66,38 @@ export default {
|
||||
this.setValue(this.modelValue)
|
||||
},
|
||||
methods: {
|
||||
change() {
|
||||
let checked
|
||||
handleInput(event) {
|
||||
const checked = event.target.checked
|
||||
let modelValue
|
||||
if (Array.isArray(this.modelValue)) {
|
||||
checked = [...this.modelValue]
|
||||
const index = checked.indexOf(this.value)
|
||||
if (index < 0) {
|
||||
checked.push(this.value)
|
||||
} else {
|
||||
checked.splice(index, 1)
|
||||
modelValue = [...this.modelValue]
|
||||
if (checked) modelValue.push(this.value)
|
||||
else {
|
||||
const index = modelValue.indexOf(this.value)
|
||||
if (index > -1) modelValue.splice(index, 1)
|
||||
}
|
||||
} else {
|
||||
let bool = this.bool
|
||||
switch (typeof this.modelValue) {
|
||||
case 'boolean':
|
||||
if (this.indeterminate) {
|
||||
bool = true
|
||||
this.$nextTick(() => {
|
||||
this.bool = bool
|
||||
})
|
||||
}
|
||||
checked = bool
|
||||
break
|
||||
case 'number':
|
||||
checked = this.value
|
||||
break
|
||||
default:
|
||||
checked = bool ? this.value : ''
|
||||
break
|
||||
}
|
||||
if (typeof this.modelValue == 'boolean') {
|
||||
modelValue = checked
|
||||
} else modelValue = checked ? this.value : ''
|
||||
}
|
||||
this.$emit('update:modelValue', checked)
|
||||
this.$emit('change', checked)
|
||||
this.$emit('update:modelValue', modelValue)
|
||||
this.$emit('change', modelValue)
|
||||
},
|
||||
setValue(value) {
|
||||
let bool
|
||||
let checked
|
||||
if (Array.isArray(value)) {
|
||||
bool = value.includes(this.value)
|
||||
checked = value.includes(this.value)
|
||||
} else {
|
||||
switch (typeof value) {
|
||||
case 'boolean':
|
||||
bool = value
|
||||
break
|
||||
// case 'string':
|
||||
// case 'number':
|
||||
default:
|
||||
bool = value === this.value
|
||||
break
|
||||
}
|
||||
if (typeof this.modelValue == 'boolean') {
|
||||
checked = this.modelValue
|
||||
} else if (value == null) checked = this.modelValue != ''
|
||||
else checked = this.modelValue == this.value
|
||||
}
|
||||
|
||||
this.bool = this.need ? bool && this.value : bool
|
||||
// console.log(this.need, this.value, checked)
|
||||
// this.checked = this.need ? checked && this.value : checked
|
||||
if (this.checked == checked) return
|
||||
this.checked = checked
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -125,13 +116,16 @@ export default {
|
||||
&[disabled] {
|
||||
+ .content {
|
||||
opacity: .5;
|
||||
.container, .label {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:checked {
|
||||
+ .content {
|
||||
.container {
|
||||
&:after {
|
||||
border-color: @color-theme;
|
||||
border-color: var(--color-primary-font);
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
@@ -143,19 +137,16 @@ export default {
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
align-items: center;
|
||||
}
|
||||
.container {
|
||||
flex: none;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
color: @color-theme;
|
||||
margin-top: .25em;
|
||||
color: var(--color-primary);
|
||||
// border: 1px solid #ccc;
|
||||
&:after {
|
||||
position: absolute;
|
||||
@@ -164,7 +155,7 @@ export default {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border: 1px solid @color-theme_2-font-label;
|
||||
border: 1px solid var(--color-font-label);
|
||||
transition: border-color 0.2s ease;
|
||||
border-radius: 2px;
|
||||
}
|
||||
@@ -179,36 +170,9 @@ export default {
|
||||
|
||||
.label {
|
||||
flex: auto;
|
||||
line-height: 1;
|
||||
margin-left: 5px;
|
||||
line-height: 1.5;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.checkbox {
|
||||
> input {
|
||||
&:checked {
|
||||
+ .content {
|
||||
.container {
|
||||
&:after {
|
||||
border-color: ~'@{color-@{value}-theme}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.content {
|
||||
.container {
|
||||
color: ~'@{color-@{value}-theme}';
|
||||
// border: 1px solid #ccc;
|
||||
&:after {
|
||||
border-color: ~'@{color-@{value}-theme_2-font-label}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</style>
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<template>
|
||||
<input
|
||||
:class="$style.input"
|
||||
ref="dom_input"
|
||||
:type="type"
|
||||
:placeholder="placeholder"
|
||||
:value="modelValue"
|
||||
:disabled="disabled"
|
||||
@input="handleInput"
|
||||
@change="$emit('change', $event.target.value.trim())"
|
||||
@keyup.enter="$emit('submit', $event.target.value.trim())"
|
||||
/>
|
||||
<input
|
||||
ref="dom_input"
|
||||
:class="$style.input"
|
||||
:type="type"
|
||||
:placeholder="placeholder"
|
||||
:value="modelValue"
|
||||
:disabled="disabled"
|
||||
tabindex="0"
|
||||
@input="handleInput"
|
||||
@change="$emit('change', $event.target.value.trim())"
|
||||
@keyup.enter="$emit('submit', $event.target.value.trim())"
|
||||
>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -55,10 +56,10 @@ export default {
|
||||
border: none;
|
||||
border-radius: @form-radius;
|
||||
padding: 7px 8px;
|
||||
color: @color-btn;
|
||||
color: var(--color-button-font);
|
||||
outline: none;
|
||||
transition: background-color 0.2s ease;
|
||||
background-color: @color-btn-background;
|
||||
background-color: var(--color-primary-background);
|
||||
font-size: 13.3px;
|
||||
|
||||
&::-webkit-outer-spin-button,
|
||||
@@ -72,10 +73,10 @@ export default {
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
background-color: @color-theme_2-hover;
|
||||
background-color: var(--color-primary-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-theme_2-active;
|
||||
background-color: var(--color-primary-background-active);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,19 +85,4 @@ export default {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.input {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
background-color: ~'@{color-@{value}-btn-background}';
|
||||
&:hover, &:focus {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,22 +1,42 @@
|
||||
<template>
|
||||
<ul :class="$style.list" :style="listStyles" ref="dom_list" role="toolbar" :aria-hidden="!show">
|
||||
<li v-for="item in menus"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
:aria-label="item[itemName]"
|
||||
ignore-tip
|
||||
:key="item.action"
|
||||
@click="handleClick(item)"
|
||||
v-show="!item.hide && (item.action == 'download' ? setting.download.enable : true)"
|
||||
:disabled="item.disabled ? true : null">{{item[itemName]}}</li>
|
||||
</ul>
|
||||
<teleport to="#root">
|
||||
<ul ref="dom_menu" :class="$style.list" :style="menuStyles" role="toolbar" :aria-hidden="!modelValue">
|
||||
<li
|
||||
v-for="item in menus"
|
||||
v-show="!item.hide && (item.action == 'download' ? appSetting['download.enable'] : true)"
|
||||
:key="item.action"
|
||||
:class="$style.listItem"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
:aria-label="item[itemName]"
|
||||
ignore-tip
|
||||
:disabled="item.disabled ? true : null"
|
||||
@click="menuClick(item)"
|
||||
>
|
||||
{{ item[itemName] }}
|
||||
</li>
|
||||
</ul>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { computed } from '@common/utils/vueTools'
|
||||
import useMenuLocation from '@renderer/utils/compositions/useMenuLocation'
|
||||
|
||||
import { appSetting } from '@renderer/store/setting'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'MenuToolBar',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
xy: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
menus: {
|
||||
type: Array,
|
||||
default() {
|
||||
@@ -27,103 +47,34 @@ export default {
|
||||
type: String,
|
||||
default: 'name',
|
||||
},
|
||||
isShow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
location: {
|
||||
type: Object,
|
||||
default() {
|
||||
return { x: 0, y: 0 }
|
||||
},
|
||||
},
|
||||
},
|
||||
emits: ['menu-click'],
|
||||
computed: {
|
||||
...mapGetters(['setting']),
|
||||
},
|
||||
watch: {
|
||||
isShow: {
|
||||
handler(n) {
|
||||
n ? this.handleShow() : this.handleHide()
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
location: {
|
||||
handler(n) {
|
||||
this.listStyles.left = n.x + 2 + 'px'
|
||||
this.listStyles.top = n.y + 'px'
|
||||
if (this.show) {
|
||||
if (this.listStyles.transitionProperty != this.transition2) this.listStyles.transitionProperty = this.transition2
|
||||
this.listStyles.transform = `scaleY(1) translate(${this.handleGetOffsetXY(n.x, n.y)})`
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
listStyles: {
|
||||
left: 0,
|
||||
top: 0,
|
||||
opacity: 0,
|
||||
transitionProperty: 'transform, opacity',
|
||||
transform: 'scale(0) translate(0,0)',
|
||||
},
|
||||
transition1: 'transform, opacity',
|
||||
transition2: 'transform, opacity, top, left',
|
||||
transitionProperty: 'transform, opacity',
|
||||
emits: ['update:modelValue', 'menu-click'],
|
||||
setup(props, { emit }) {
|
||||
const visible = computed(() => props.modelValue)
|
||||
const location = computed(() => props.xy)
|
||||
|
||||
const onHide = () => {
|
||||
emit('update:modelValue', false)
|
||||
menuClick(null)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.handleDocumentClick)
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('click', this.handleDocumentClick)
|
||||
},
|
||||
methods: {
|
||||
handleShow() {
|
||||
this.show = true
|
||||
this.listStyles.opacity = 1
|
||||
this.listStyles.transform = `scaleY(1) translate(${this.handleGetOffsetXY(this.location.x, this.location.y)})`
|
||||
},
|
||||
handleHide(e) {
|
||||
this.listStyles.opacity = 0
|
||||
this.listStyles.transform = 'scale(0) translate(0, 0)'
|
||||
this.show = false
|
||||
},
|
||||
handleGetOffsetXY(left, top) {
|
||||
const listWidth = this.$refs.dom_list.clientWidth
|
||||
const listHeight = this.$refs.dom_list.clientHeight
|
||||
const dom_container = this.$refs.dom_list.offsetParent
|
||||
const containerWidth = dom_container.clientWidth
|
||||
const containerHeight = dom_container.clientHeight
|
||||
const offsetWidth = containerWidth - left - listWidth
|
||||
const offsetHeight = containerHeight - top - listHeight
|
||||
let x = 0
|
||||
let y = 0
|
||||
if (containerWidth > listWidth && offsetWidth < 17) {
|
||||
x = offsetWidth - 17
|
||||
}
|
||||
if (containerHeight > listHeight && offsetHeight < 5) {
|
||||
y = offsetHeight - 5
|
||||
}
|
||||
return `${x}px, ${y}px`
|
||||
},
|
||||
handleDocumentClick(event) {
|
||||
if (!this.show) return
|
||||
|
||||
if (event.target == this.$refs.dom_list || this.$refs.dom_list.contains(event.target)) return
|
||||
const { dom_menu, menuStyles } = useMenuLocation({
|
||||
visible,
|
||||
location,
|
||||
onHide,
|
||||
})
|
||||
|
||||
if (this.show && this.listStyles.transitionProperty != this.transition1) this.listStyles.transitionProperty = this.transition1
|
||||
this.handleClick(null)
|
||||
},
|
||||
handleClick(item) {
|
||||
const menuClick = (item) => {
|
||||
if (item && item.disabled) return
|
||||
emit('menu-click', item)
|
||||
}
|
||||
|
||||
this.$emit('menu-click', item)
|
||||
},
|
||||
return {
|
||||
dom_menu,
|
||||
menuStyles,
|
||||
menuClick,
|
||||
appSetting,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -138,60 +89,43 @@ export default {
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
transform-origin: 0 0 0;
|
||||
transition: .25s ease;
|
||||
transition: .14s ease;
|
||||
transition-property: transform, opacity;
|
||||
border-radius: @radius-border;
|
||||
background-color: @color-theme_2-background_2;
|
||||
background-color: var(--color-content-background);
|
||||
box-shadow: 0 1px 8px 0 rgba(0,0,0,.2);
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
// will-change: transform;
|
||||
li {
|
||||
cursor: pointer;
|
||||
min-width: 96px;
|
||||
line-height: 34px;
|
||||
// color: @color-btn;
|
||||
padding: 0 10px;
|
||||
text-align: center;
|
||||
outline: none;
|
||||
transition: @transition-theme;
|
||||
transition-property: background-color, opacity;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
}
|
||||
.listItem {
|
||||
cursor: pointer;
|
||||
min-width: 96px;
|
||||
line-height: 34px;
|
||||
// color: var(--color-button-font);
|
||||
padding: 0 10px;
|
||||
text-align: center;
|
||||
outline: none;
|
||||
transition: @transition-normal;
|
||||
transition-property: background-color, opacity;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
// background-color: var(--color-primary-light-600-alpha-800);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-primary-background-active);
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
cursor: default;
|
||||
opacity: .4;
|
||||
&:hover {
|
||||
background-color: @color-theme_2-hover;
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-theme_2-active;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
cursor: default;
|
||||
opacity: .4;
|
||||
&:hover {
|
||||
background: none !important;
|
||||
}
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.list {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
li {
|
||||
// color: ~'@{color-@{value}-btn}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<component :is="containerEl" ref="dom_scrollContainer" :class="containerClass" tabindex="0" style="outline: none; height: 100%; overflow-y: auto; position: relative; display: block; contain: strict;">
|
||||
<component :is="contentEl" ref="dom_list" :class="contentClass" :style="contentStyle">
|
||||
<!-- <div v-for="item in views" :key="item.key" :style="item.style">
|
||||
<slot name="default" v-bind="{ item: item.item, index: item.index }" />
|
||||
</div> -->
|
||||
</component>
|
||||
<slot name="footer" />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
computed,
|
||||
ref,
|
||||
nextTick,
|
||||
watch,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
render,
|
||||
// h,
|
||||
} from 'vue'
|
||||
|
||||
const easeInOutQuad = (t, b, c, d) => {
|
||||
t /= d / 2
|
||||
if (t < 1) return (c / 2) * t * t + b
|
||||
t--
|
||||
return (-c / 2) * (t * (t - 2) - 1) + b
|
||||
}
|
||||
const handleScroll = (element, to, duration = 300, callback = () => {}, onCancel = () => {}) => {
|
||||
if (!element) return callback()
|
||||
const start = element.scrollTop || element.scrollY || 0
|
||||
let cancel = false
|
||||
if (to > start) {
|
||||
let maxScrollTop = element.scrollHeight - element.clientHeight
|
||||
if (to > maxScrollTop) to = maxScrollTop
|
||||
} else if (to < start) {
|
||||
if (to < 0) to = 0
|
||||
} else return callback()
|
||||
const change = to - start
|
||||
const increment = 10
|
||||
if (!change) return callback()
|
||||
|
||||
let currentTime = 0
|
||||
let val
|
||||
let cancelCallback
|
||||
|
||||
const animateScroll = () => {
|
||||
currentTime += increment
|
||||
val = parseInt(easeInOutQuad(currentTime, start, change, duration))
|
||||
if (element.scrollTo) {
|
||||
element.scrollTo(0, val)
|
||||
} else {
|
||||
element.scrollTop = val
|
||||
}
|
||||
if (currentTime < duration) {
|
||||
if (cancel) {
|
||||
cancelCallback()
|
||||
onCancel()
|
||||
return
|
||||
}
|
||||
setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
animateScroll()
|
||||
return (callback) => {
|
||||
cancelCallback = callback
|
||||
cancel = true
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'VirtualizedList',
|
||||
props: {
|
||||
containerEl: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
default: 'virtualized-list',
|
||||
},
|
||||
contentEl: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
contentClass: {
|
||||
type: String,
|
||||
default: 'virtualized-list-content',
|
||||
},
|
||||
itemHeight: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
keyName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['scroll'],
|
||||
setup(props, { emit, slots }) {
|
||||
const views = ref([])
|
||||
const dom_scrollContainer = ref(null)
|
||||
const dom_list = ref(null)
|
||||
let startIndex = -1
|
||||
let endIndex = -1
|
||||
let scrollTop = -1
|
||||
let cachedList = []
|
||||
let cancelScroll = null
|
||||
let isScrolling = false
|
||||
let scrollToValue = 0
|
||||
|
||||
const createList = (startIndex, endIndex) => {
|
||||
if (startIndex == endIndex) return []
|
||||
console.log(startIndex, endIndex)
|
||||
const cache = cachedList.slice(startIndex, endIndex)
|
||||
const list = props.list.slice(startIndex, endIndex).map((item, i) => {
|
||||
if (cache[i]) return cache[i]
|
||||
const top = (startIndex + i) * props.itemHeight
|
||||
const index = startIndex + i
|
||||
return cachedList[index] = {
|
||||
item,
|
||||
top,
|
||||
style: { position: 'absolute', left: 0, right: 0, top: top + 'px', height: props.itemHeight + 'px' },
|
||||
index,
|
||||
key: item[props.keyName],
|
||||
}
|
||||
})
|
||||
return list
|
||||
}
|
||||
|
||||
const renderListItem = (list, type) => {
|
||||
if (!list.length) return
|
||||
console.log(list)
|
||||
// const dom = document.createDocumentFragment()
|
||||
switch (type) {
|
||||
case 'up':
|
||||
break
|
||||
case 'down':
|
||||
break
|
||||
default:
|
||||
console.log(render(null, slots.default(list[0])[0]))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const updateView = (force = false, currentScrollTop = dom_scrollContainer.value.scrollTop) => {
|
||||
// const currentScrollTop = this.$refs.dom_scrollContainer.scrollTop
|
||||
const itemHeight = props.itemHeight
|
||||
const currentStartIndex = Math.floor(currentScrollTop / itemHeight)
|
||||
const scrollContainerHeight = dom_scrollContainer.value.clientHeight
|
||||
const currentEndIndex = currentStartIndex + Math.ceil(scrollContainerHeight / itemHeight)
|
||||
const continuous = currentStartIndex <= endIndex && currentEndIndex >= startIndex
|
||||
const currentStartRenderIndex = Math.max(currentStartIndex, 0)
|
||||
const currentEndRenderIndex = currentEndIndex + 1
|
||||
// console.log(continuous, currentStartIndex, endIndex, currentEndIndex, startIndex)
|
||||
// debugger
|
||||
if (!force && continuous) {
|
||||
// if (Math.abs(currentScrollTop - this.scrollTop) < this.itemHeight * 0.6) return
|
||||
// console.log('update')
|
||||
if (currentScrollTop > scrollTop) { // scroll down
|
||||
console.log('scroll down')
|
||||
renderListItem(createList(endIndex + 1, currentEndRenderIndex))
|
||||
// // views.value.push(...list.slice(list.indexOf(views.value[views.value.length - 1]) + 1))
|
||||
// // // if (this.views.length > 100) {
|
||||
// // nextTick(() => {
|
||||
// // views.value.splice(0, views.value.indexOf(list[0]))
|
||||
// // })
|
||||
// // }
|
||||
} else if (currentScrollTop < scrollTop) { // scroll up
|
||||
console.log('scroll up')
|
||||
renderListItem(createList(currentStartRenderIndex, startIndex))
|
||||
// views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
} else return
|
||||
// if (currentScrollTop == scrollTop && endIndex >= currentEndIndex) return
|
||||
// views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
} else {
|
||||
renderListItem(createList(currentStartRenderIndex, currentEndRenderIndex))
|
||||
}
|
||||
startIndex = currentStartIndex
|
||||
endIndex = currentEndIndex
|
||||
scrollTop = currentScrollTop
|
||||
}
|
||||
|
||||
const onScroll = event => {
|
||||
const currentScrollTop = dom_scrollContainer.value.scrollTop
|
||||
if (Math.abs(currentScrollTop - scrollTop) > props.itemHeight * 0.6) {
|
||||
updateView(false, currentScrollTop)
|
||||
}
|
||||
emit('scroll', event)
|
||||
}
|
||||
|
||||
const scrollTo = (scrollTop, animate = false) => {
|
||||
return new Promise(resolve => {
|
||||
if (cancelScroll) {
|
||||
cancelScroll(resolve)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
}).then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (animate) {
|
||||
isScrolling = true
|
||||
scrollToValue = scrollTop
|
||||
cancelScroll = handleScroll(dom_scrollContainer.value, scrollTop, 300, () => {
|
||||
cancelScroll = null
|
||||
isScrolling = false
|
||||
resolve()
|
||||
}, () => {
|
||||
cancelScroll = null
|
||||
isScrolling = false
|
||||
reject('canceled')
|
||||
})
|
||||
} else {
|
||||
dom_scrollContainer.value.scrollTop = scrollTop
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const scrollToIndex = (index, offset = 0, animate = false) => {
|
||||
return scrollTo(Math.max(index * props.itemHeight + offset, 0), animate)
|
||||
}
|
||||
|
||||
const getScrollTop = () => {
|
||||
return isScrolling ? scrollToValue : dom_scrollContainer.value.scrollTop
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
setTimeout(updateView)
|
||||
}
|
||||
|
||||
const contentStyle = computed(() => ({
|
||||
display: 'block',
|
||||
height: props.list.length * props.itemHeight + 'px',
|
||||
}))
|
||||
|
||||
const handleReset = list => {
|
||||
cachedList = Array(list.length)
|
||||
startIndex = -1
|
||||
endIndex = -1
|
||||
nextTick(() => {
|
||||
updateView(true)
|
||||
})
|
||||
}
|
||||
watch(() => props.itemHeight, () => {
|
||||
handleReset(props.list)
|
||||
})
|
||||
watch(() => props.list, (list) => {
|
||||
handleReset(list)
|
||||
}, {
|
||||
deep: true,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
dom_scrollContainer.value.addEventListener('scroll', onScroll, false)
|
||||
cachedList = Array(props.list.length)
|
||||
startIndex = -1
|
||||
endIndex = -1
|
||||
updateView(true)
|
||||
window.addEventListener('resize', handleResize)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
dom_scrollContainer.value.removeEventListener('scroll', onScroll)
|
||||
window.removeEventListener('resize', handleResize)
|
||||
if (cancelScroll) cancelScroll()
|
||||
})
|
||||
|
||||
return {
|
||||
views,
|
||||
dom_scrollContainer,
|
||||
dom_list,
|
||||
contentStyle,
|
||||
scrollTo,
|
||||
scrollToIndex,
|
||||
getScrollTop,
|
||||
}
|
||||
},
|
||||
// render() {
|
||||
// console.log('render')
|
||||
// // const list = this.views.map((item) => {
|
||||
// // return h('div', { style: item.style, key: item.key }, this.$slots.default(item))
|
||||
// // })
|
||||
// return h(this.containerEl, {
|
||||
// ref: 'dom_scrollContainer',
|
||||
// class: this.containerClass,
|
||||
// style: 'outline: none; height: 100%; overflow-y: auto; position: relative; display: block; contain: strict;',
|
||||
// }, [
|
||||
// h(this.contentEl, { style: this.contentStyle, class: this.contentClass }, this.views.map((item) => {
|
||||
// return h('div', { style: item.style, key: item.key }, 'this.$slots.default(item)')
|
||||
// })),
|
||||
// ])
|
||||
// },
|
||||
}
|
||||
</script>
|
||||
@@ -1,167 +0,0 @@
|
||||
<template>
|
||||
<div :class="[$style.select, {[$style.active]: show}]">
|
||||
<div :class="$style.label" ref="dom_btn" @click="handleShow">{{modelValue ? itemName ? list.find(l => l.id == modelValue).name : modelValue : ''}}</div>
|
||||
<ul :class="$style.list">
|
||||
<li v-for="(item, index) in list" :key="index" @click="handleClick(itemKey ? item[itemKey] : item)">{{itemName ? item[itemName] : item}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
},
|
||||
itemName: {
|
||||
type: String,
|
||||
},
|
||||
itemKey: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.handleHide)
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('click', this.handleHide)
|
||||
},
|
||||
methods: {
|
||||
handleHide(e) {
|
||||
// if (e && e.target.parentNode != this.$refs.dom_list && this.show) return this.show = false
|
||||
if (e && e.target == this.$refs.dom_btn) return
|
||||
setTimeout(() => {
|
||||
this.show = false
|
||||
}, 50)
|
||||
},
|
||||
handleClick(item) {
|
||||
if (item === this.modelValue) return
|
||||
this.$emit('update:modelValue', item)
|
||||
this.$emit('change', item)
|
||||
},
|
||||
handleShow() {
|
||||
this.show = !this.show
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.select {
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
.list {
|
||||
transform: scaleY(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
padding: 8px 15px;
|
||||
// background-color: @color-btn-background;
|
||||
transition: background-color @transition-theme;
|
||||
border-top: 2px solid @color-tab-border-bottom;
|
||||
border-left: 2px solid @color-tab-border-bottom;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
border-top-left-radius: @radius-border;
|
||||
color: @color-btn;
|
||||
cursor: pointer;
|
||||
.mixin-ellipsis-1;
|
||||
|
||||
&:hover {
|
||||
background-color: @color-btn-hover;
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-btn-active;
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
border-bottom: 2px solid @color-tab-border-bottom;
|
||||
border-left: 2px solid @color-tab-border-bottom;
|
||||
border-bottom-left-radius: @radius-border;
|
||||
background-color: @color-theme_2-background_2;
|
||||
opacity: 0;
|
||||
transform: scaleY(0);
|
||||
transform-origin: 0 0 0;
|
||||
transition: .25s ease;
|
||||
transition-property: transform, opacity;
|
||||
z-index: 10;
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
padding: 8px 15px;
|
||||
// color: @color-btn;
|
||||
text-align: center;
|
||||
outline: none;
|
||||
transition: background-color @transition-theme;
|
||||
background-color: @color-btn-background;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
|
||||
&:hover {
|
||||
background-color: @color-btn-hover;
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-btn-active;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.label {
|
||||
border-top-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-btn-active}';
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
border-bottom-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
li {
|
||||
// color: ~'@{color-@{value}-btn}';
|
||||
background-color: ~'@{color-@{value}-btn-background}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-btn-active}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
@@ -1,18 +1,22 @@
|
||||
<template>
|
||||
<div class="content" :class="[$style.select, show ? $style.active : '']">
|
||||
<div class="label-content" :class="$style.label" ref="dom_btn" @click="handleShow">
|
||||
<span class="label">{{label}}</span>
|
||||
<div class="icon" :class="$style.icon">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.847 451.847" space="preserve">
|
||||
<use xlink:href="#icon-down"></use>
|
||||
</svg>
|
||||
<div class="content" :class="[$style.select, show ? $style.active : '']">
|
||||
<div ref="dom_btn" class="label-content" :class="$style.label" @click="handleShow">
|
||||
<span class="label">{{ label }}</span>
|
||||
<div class="icon" :class="$style.icon">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" height="100%" viewBox="0 0 451.847 451.847" space="preserve">
|
||||
<use xlink:href="#icon-down" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<ul v-if="show" ref="dom_list" class="selection-list scroll" :class="$style.list" :style="listStyles">
|
||||
<li
|
||||
v-for="(item, index) in list" :key="index" :class="[$style.listItem, (itemKey ? item[itemKey] : item) == modelValue ? $style.active : null]"
|
||||
:aria-label="itemName ? item[itemName] : item" @click="handleClick(item)"
|
||||
>
|
||||
{{ itemName ? item[itemName] : item }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul class="selection-list scroll" :class="$style.list" :style="listStyles" ref="dom_list">
|
||||
<li v-for="(item, index) in list" :key="index" :class="(itemKey ? item[itemKey] : item) == modelValue ? $style.active : null"
|
||||
@click="handleClick(item)" :aria-label="itemName ? item[itemName] : item">{{itemName ? item[itemName] : item}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -27,14 +31,18 @@ export default {
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
itemName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
itemKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
@@ -43,12 +51,6 @@ export default {
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.handleHide)
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('click', this.handleHide)
|
||||
},
|
||||
computed: {
|
||||
label() {
|
||||
if (this.modelValue == null) return ''
|
||||
@@ -58,8 +60,15 @@ export default {
|
||||
return item[this.itemName]
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('click', this.handleHide, true)
|
||||
},
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('click', this.handleHide, true)
|
||||
},
|
||||
methods: {
|
||||
handleHide(e) {
|
||||
if (!this.show) return
|
||||
// if (e && e.target.parentNode != this.$refs.dom_list && this.show) return this.show = false
|
||||
if (e && (e.target == this.$refs.dom_btn || this.$refs.dom_btn.contains(e.target))) return
|
||||
this.listStyles.transform = 'scaleY(0) translateY(0)'
|
||||
@@ -75,7 +84,9 @@ export default {
|
||||
},
|
||||
handleShow() {
|
||||
this.show = true
|
||||
this.listStyles.transform = `scaleY(1) translateY(${this.handleGetOffset()}px)`
|
||||
this.$nextTick(() => {
|
||||
this.listStyles.transform = `scaleY(1) translateY(${this.handleGetOffset()}px)`
|
||||
})
|
||||
},
|
||||
handleGetOffset() {
|
||||
const listHeight = this.$refs.dom_list.clientHeight
|
||||
@@ -105,7 +116,7 @@ export default {
|
||||
|
||||
&.active {
|
||||
.label {
|
||||
background-color: @color-btn-background;
|
||||
background-color: var(--color-button-background);
|
||||
}
|
||||
.list {
|
||||
opacity: 1;
|
||||
@@ -119,16 +130,17 @@ export default {
|
||||
}
|
||||
|
||||
.label {
|
||||
background-color: @color-btn-background;
|
||||
background-color: var(--color-button-background);
|
||||
padding: 0 10px;
|
||||
transition: background-color @transition-theme;
|
||||
transition: background-color @transition-normal;
|
||||
height: @selection-height;
|
||||
line-height: 27px;
|
||||
// line-height: 27px;
|
||||
box-sizing: border-box;
|
||||
color: @color-btn;
|
||||
color: var(--color-button-font);
|
||||
border-radius: @form-radius;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
flex: auto;
|
||||
@@ -146,10 +158,10 @@ export default {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: @color-btn-hover;
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-btn-active;
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +170,7 @@ export default {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: @color-theme_2-background_2;
|
||||
background-color: var(--color-content-background);
|
||||
opacity: 0;
|
||||
transform: scaleY(0) translateY(0);
|
||||
transform-origin: 0 (@selection-height / 2) 0;
|
||||
@@ -169,61 +181,28 @@ export default {
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, .15);
|
||||
overflow: auto;
|
||||
max-height: 200px;
|
||||
}
|
||||
.listItem {
|
||||
cursor: pointer;
|
||||
padding: 0 10px;
|
||||
line-height: @selection-height;
|
||||
// color: var(--color-button-font);
|
||||
outline: none;
|
||||
transition: background-color @transition-normal;
|
||||
background-color: var(--color-button-font)-background;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
padding: 0 10px;
|
||||
line-height: @selection-height;
|
||||
// color: @color-btn;
|
||||
outline: none;
|
||||
transition: background-color @transition-theme;
|
||||
background-color: @color-btn-background;
|
||||
box-sizing: border-box;
|
||||
.mixin-ellipsis-1;
|
||||
|
||||
&:hover {
|
||||
background-color: @color-btn-hover;
|
||||
}
|
||||
&:active {
|
||||
background-color: @color-btn-active;
|
||||
}
|
||||
&.active {
|
||||
color: @color-btn;
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--color-button-background-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-button-background-active);
|
||||
}
|
||||
&.active {
|
||||
color: var(--color-button-font);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.label {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
background-color: ~'@{color-@{value}-btn-background}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-btn-active}';
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_2}';
|
||||
li {
|
||||
// color: ~'@{color-@{value}-btn}';
|
||||
background-color: ~'@{color-@{value}-btn-background}';
|
||||
&:hover {
|
||||
background-color: ~'@{color-@{value}-btn-hover}';
|
||||
}
|
||||
&:active {
|
||||
background-color: ~'@{color-@{value}-btn-active}';
|
||||
}
|
||||
&.active {
|
||||
color: ~'@{color-@{value}-btn}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
<template lang="pug">
|
||||
div.scroll(:class="$style.tab")
|
||||
//- div(:class="$style.content")
|
||||
ul
|
||||
li.ignore-to-rem(v-for="item in list" :key="itemKey ? item[itemKey] : item" :class="modelValue === (itemKey ? item[itemKey] : item) ? $style.active : ''")
|
||||
button(type="button"
|
||||
@click="handleClick(itemKey ? item[itemKey] : item)") {{ itemName ? item[itemName] : item }}
|
||||
//- div(:class="$style.control")
|
||||
div(:class="$style.left")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
|
||||
use(xlink:href='#icon-left')
|
||||
div(:class="$style.right")
|
||||
svg(version='1.1' xmlns='http://www.w3.org/2000/svg' xlink='http://www.w3.org/1999/xlink' height='100%' viewBox='0 0 451.846 451.847' space='preserve')
|
||||
use(xlink:href='#icon-right')
|
||||
<template>
|
||||
<ul :class="[$style.list, $style[align]]" role="tablist">
|
||||
<li
|
||||
v-for="item in list" :key="item[itemKey]" :class="[$style.listItem, {[$style.active]: modelValue == item[itemKey]}]" tabindex="-1"
|
||||
role="tab" :aria-label="item[itemLabel]" ignore-tip :aria-selected="modelValue == item[itemKey]"
|
||||
@click="handleToggle(item[itemKey])"
|
||||
>
|
||||
<span :class="$style.label">{{ item[itemLabel] }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
list: {
|
||||
@@ -23,203 +20,101 @@ export default {
|
||||
return []
|
||||
},
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
},
|
||||
itemName: {
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left',
|
||||
},
|
||||
itemKey: {
|
||||
type: String,
|
||||
default: 'id',
|
||||
},
|
||||
itemLabel: {
|
||||
type: String,
|
||||
default: 'label',
|
||||
},
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClick(item) {
|
||||
if (item === this.modelValue) return
|
||||
this.$emit('update:modelValue', item)
|
||||
this.$emit('change', item)
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
setup(props, { emit }) {
|
||||
const handleToggle = id => {
|
||||
if (id == props.modelValue) return
|
||||
emit('update:modelValue', id)
|
||||
emit('change', id)
|
||||
}
|
||||
|
||||
return {
|
||||
handleToggle,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" module>
|
||||
@import '@renderer/assets/styles/layout.less';
|
||||
|
||||
.tab {
|
||||
// overflow: hidden;
|
||||
// display: flex;
|
||||
// flex-flow: row nowrap;
|
||||
overflow-x: auto;
|
||||
ul {
|
||||
display: flex;
|
||||
flex: none;
|
||||
border-bottom: 2px solid @color-tab-border-bottom;
|
||||
transition: border-bottom-color @transition-theme;
|
||||
li {
|
||||
position: relative;
|
||||
flex: none;
|
||||
border-top: 2px solid @color-tab-border-top;
|
||||
border-left: 2px solid @color-tab-btn-background;
|
||||
border-right: 2px solid @color-tab-btn-background;
|
||||
// box-sizing: border-box;
|
||||
transition: border-color @transition-theme;
|
||||
.list {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
font-size: 12px;
|
||||
gap: 25px;
|
||||
padding: 0 15px;
|
||||
|
||||
&:global(.ignore-to-rem) {
|
||||
margin-left: -2px;
|
||||
margin-bottom: -2px;
|
||||
&:after, &:before {
|
||||
height: 2px;
|
||||
}
|
||||
}
|
||||
&.left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
&.center {
|
||||
justify-content: center;
|
||||
}
|
||||
&.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
.listItem {
|
||||
display: block;
|
||||
// padding: 5px 15px;
|
||||
cursor: pointer;
|
||||
transition: color @transition-normal;
|
||||
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
|
||||
&.active {
|
||||
color: var(--color-primary);
|
||||
cursor: default;
|
||||
|
||||
>.label {
|
||||
&:after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
width: 50%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: @color-tab-border-bottom;
|
||||
transition: width @transition-theme;
|
||||
}
|
||||
&:before {
|
||||
content: ' ';
|
||||
display: block;
|
||||
width: 50%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-color: @color-tab-border-bottom;
|
||||
transition: width @transition-theme;
|
||||
}
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
margin-left: 0;
|
||||
button {
|
||||
border-top-left-radius: 3px;
|
||||
// border-bottom-left-radius: @radius-border;
|
||||
}
|
||||
}
|
||||
&:last-child {
|
||||
border-right: 2px solid @color-tab-border-top;
|
||||
border-top-right-radius: @radius-border;
|
||||
button {
|
||||
border-top-right-radius: 3px;
|
||||
// border-bottom-right-radius: @radius-border;
|
||||
}
|
||||
}
|
||||
button {
|
||||
display: inline-block;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0 10px;
|
||||
font-size: 12px;
|
||||
min-width: 56px;
|
||||
// color: @color-btn;
|
||||
outline: none;
|
||||
transition: background-color @transition-theme;
|
||||
background-color: @color-tab-btn-background;
|
||||
line-height: 28px;
|
||||
}
|
||||
&:hover {
|
||||
// border-left-color: @color-theme_2-hover;
|
||||
// border-right-color: @color-theme_2-hover;
|
||||
button {
|
||||
background-color: @color-tab-btn-background-hover;
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
// border-left-color: @color-theme_2-active;
|
||||
// border-right-color: @color-theme_2-active;
|
||||
button {
|
||||
background-color: @color-theme_2-active;
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
border-bottom-color: @color-theme_2-background_1;
|
||||
border-top-color: @color-tab-border-bottom;
|
||||
border-left-color: @color-tab-border-bottom;
|
||||
border-right-color: @color-tab-border-bottom;
|
||||
&::after {
|
||||
width: 0;
|
||||
}
|
||||
&::before {
|
||||
width: 0;
|
||||
}
|
||||
button {
|
||||
background-color: @color-theme_2-background_1;
|
||||
}
|
||||
// background-color: var(--color-primary);
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// .control {
|
||||
// flex: none;
|
||||
// border-bottom: 2px solid @color-tab-border-bottom;
|
||||
// width: 1em;
|
||||
// svg {
|
||||
// width: .85em;
|
||||
// }
|
||||
// }
|
||||
// .left, .right {
|
||||
// line-height: 0;
|
||||
// }
|
||||
// .content {
|
||||
// flex: auto;
|
||||
// overflow: hidden;
|
||||
// }
|
||||
|
||||
each(@themes, {
|
||||
:global(#root.@{value}) {
|
||||
.tab {
|
||||
ul {
|
||||
border-bottom-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
li {
|
||||
border-top-color: ~'@{color-@{value}-tab-border-top}';
|
||||
border-left-color: ~'@{color-@{value}-tab-btn-background}';
|
||||
border-right-color: ~'@{color-@{value}-tab-btn-background}';
|
||||
|
||||
&::after {
|
||||
background-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
}
|
||||
&::before {
|
||||
background-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
}
|
||||
&:last-child {
|
||||
border-right-color: ~'@{color-@{value}-tab-border-top}';
|
||||
}
|
||||
button {
|
||||
// color: ~'@{color-@{value}-btn}';
|
||||
background-color: ~'@{color-@{value}-tab-btn-background}';
|
||||
}
|
||||
&:hover {
|
||||
// border-left-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
// border-right-color: ~'@{color-@{value}-theme_2-hover}';
|
||||
button {
|
||||
background-color: ~'@{color-@{value}-tab-btn-background-hover}';
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
// border-left-color: ~'@{color-@{value}-theme_2-active}';
|
||||
// border-right-color: ~'@{color-@{value}-theme_2-active}';
|
||||
button {
|
||||
background-color: ~'@{color-@{value}-theme_2-active}';
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
border-bottom-color: ~'@{color-@{value}-theme_2}';
|
||||
border-top-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
border-left-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
border-right-color: ~'@{color-@{value}-tab-border-bottom}';
|
||||
button {
|
||||
background-color: ~'@{color-@{value}-theme_2-background_1}';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.label {
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: 8px 0;
|
||||
&:after {
|
||||
.mixin-after;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
border-radius: 20px;
|
||||
background-color: transparent;
|
||||
transform: translateY(-4px);
|
||||
opacity: 0;
|
||||
background-color: var(--color-primary-alpha-300);
|
||||
transition: @transition-fast;
|
||||
transition-property: transform, opacity;
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<component :is="containerEl" :class="containerClass" tabindex="0" ref="dom_scrollContainer" style="outline: none; height: 100%; overflow: auto; position: relative; display: block; contain: strict;">
|
||||
<component :is="containerEl" ref="dom_scrollContainer" :class="containerClass" tabindex="0" style="outline: none; height: 100%; overflow-y: auto; position: relative; display: block; contain: strict;">
|
||||
<component :is="contentEl" :class="contentClass" :style="contentStyle">
|
||||
<div v-for="item in views" :key="item.key" :style="item.style">
|
||||
<slot name="default" v-bind="{ item: item.item, index: item.index }" />
|
||||
@@ -57,7 +57,7 @@ const handleScroll = (element, to, duration = 300, callback = () => {}, onCancel
|
||||
onCancel()
|
||||
return
|
||||
}
|
||||
setTimeout(animateScroll, increment)
|
||||
window.setTimeout(animateScroll, increment)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
@@ -88,21 +88,17 @@ export default {
|
||||
type: String,
|
||||
default: 'virtualized-list-content',
|
||||
},
|
||||
outsideNum: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
itemHeight: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
keyName: {
|
||||
type: String,
|
||||
require: true,
|
||||
required: true,
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
require: true,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['scroll'],
|
||||
@@ -141,12 +137,12 @@ export default {
|
||||
const scrollContainerHeight = dom_scrollContainer.value.clientHeight
|
||||
const currentEndIndex = currentStartIndex + Math.ceil(scrollContainerHeight / itemHeight)
|
||||
const continuous = currentStartIndex <= endIndex && currentEndIndex >= startIndex
|
||||
const currentStartRenderIndex = Math.max(currentStartIndex - props.outsideNum, 0)
|
||||
const currentEndRenderIndex = currentEndIndex + props.outsideNum + 1
|
||||
const currentStartRenderIndex = Math.max(currentStartIndex, 0)
|
||||
const currentEndRenderIndex = currentEndIndex + 1
|
||||
// console.log(continuous)
|
||||
// debugger
|
||||
if (continuous) {
|
||||
// if (Math.abs(currentScrollTop - this.scrollTop) < this.itemHeight * this.outsideNum * 0.6) return
|
||||
// if (Math.abs(currentScrollTop - this.scrollTop) < this.itemHeight * 0.6) return
|
||||
// console.log('update')
|
||||
// if (currentScrollTop > scrollTop) { // scroll down
|
||||
// // console.log('scroll down')
|
||||
@@ -162,9 +158,13 @@ export default {
|
||||
// views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
// } else return
|
||||
if (currentScrollTop == scrollTop && endIndex >= currentEndIndex) return
|
||||
views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
requestAnimationFrame(() => {
|
||||
views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
})
|
||||
} else {
|
||||
views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
requestAnimationFrame(() => {
|
||||
views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
})
|
||||
}
|
||||
startIndex = currentStartIndex
|
||||
endIndex = currentEndIndex
|
||||
@@ -173,42 +173,47 @@ export default {
|
||||
|
||||
const onScroll = event => {
|
||||
const currentScrollTop = dom_scrollContainer.value.scrollTop
|
||||
if (Math.abs(currentScrollTop - scrollTop) > props.itemHeight * props.outsideNum * 0.6) {
|
||||
if (Math.abs(currentScrollTop - scrollTop) > props.itemHeight * 0.6) {
|
||||
updateView(currentScrollTop)
|
||||
}
|
||||
emit('scroll', event)
|
||||
}
|
||||
|
||||
const scrollTo = (scrollTop, animate = false) => {
|
||||
return new Promise(resolve => {
|
||||
if (cancelScroll) {
|
||||
cancelScroll(resolve)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
}).then(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const scrollTo = (scrollTop, animate = false, onScrollEnd) => {
|
||||
if (onScrollEnd) {
|
||||
new Promise(resolve => {
|
||||
if (cancelScroll) {
|
||||
cancelScroll(resolve)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
}).then(() => {
|
||||
if (animate) {
|
||||
isScrolling = true
|
||||
scrollToValue = scrollTop
|
||||
cancelScroll = handleScroll(dom_scrollContainer.value, scrollTop, 300, () => {
|
||||
cancelScroll = null
|
||||
isScrolling = false
|
||||
resolve()
|
||||
onScrollEnd(true)
|
||||
}, () => {
|
||||
cancelScroll = null
|
||||
isScrolling = false
|
||||
reject('canceled')
|
||||
onScrollEnd('canceled')
|
||||
})
|
||||
} else {
|
||||
dom_scrollContainer.value.scrollTop = scrollTop
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
dom_scrollContainer.value.scrollTo({
|
||||
top: scrollTop,
|
||||
behavior: animate ? 'smooth' : 'instant',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const scrollToIndex = (index, offset = 0, animate = false) => {
|
||||
return scrollTo(Math.max(index * props.itemHeight + offset, 0), animate)
|
||||
const scrollToIndex = (index, offset = 0, animate = false, onScrollEnd) => {
|
||||
scrollTo(Math.max(index * props.itemHeight + offset, 0), animate, onScrollEnd)
|
||||
}
|
||||
|
||||
const getScrollTop = () => {
|
||||
@@ -216,7 +221,7 @@ export default {
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
setTimeout(updateView)
|
||||
window.setTimeout(updateView)
|
||||
}
|
||||
|
||||
const contentStyle = computed(() => ({
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
// import {
|
||||
// computed,
|
||||
// ref,
|
||||
// nextTick,
|
||||
// watch,
|
||||
// onMounted,
|
||||
// onBeforeUnmount,
|
||||
// } from 'vue'
|
||||
// import { scrollTo } from '@common/utils/renderer'
|
||||
|
||||
// interface ListItem {
|
||||
// item: LX.Music.MusicInfo
|
||||
// top: number
|
||||
// style: {
|
||||
// position: string
|
||||
// left: number
|
||||
// right: number
|
||||
// top: string
|
||||
// height: string
|
||||
// }
|
||||
// index: number
|
||||
// key: string
|
||||
// }
|
||||
|
||||
// export default (props: { list: LX.Music.MusicInfo[], itemHeight: number }) => {
|
||||
// const dom_scrollContainer = ref<HTMLElement | null>(null)
|
||||
// const dom_list = ref<HTMLElement | null>(null)
|
||||
// let startIndex = -1
|
||||
// let endIndex = -1
|
||||
// let scrollTop = -1
|
||||
// let cachedList: ListItem[] = []
|
||||
// let cancelScroll: null | (() => void) = null
|
||||
// let isScrolling = false
|
||||
// let scrollToValue = 0
|
||||
|
||||
// const createList = (startIndex: number, endIndex: number) => {
|
||||
// if (startIndex == endIndex) return []
|
||||
// console.log(startIndex, endIndex)
|
||||
// const cache = cachedList.slice(startIndex, endIndex)
|
||||
// const list = props.list.slice(startIndex, endIndex).map((item, i) => {
|
||||
// if (cache[i]) return cache[i]
|
||||
// const top = (startIndex + i) * props.itemHeight
|
||||
// const index = startIndex + i
|
||||
// return cachedList[index] = {
|
||||
// item,
|
||||
// top,
|
||||
// style: { position: 'absolute', left: 0, right: 0, top: `${top}px`, height: `${props.itemHeight}px` },
|
||||
// index,
|
||||
// key: item.id,
|
||||
// }
|
||||
// })
|
||||
// return list
|
||||
// }
|
||||
|
||||
// // div.list-item(@click="handleListItemClick($event, index)" @contextmenu="handleListItemRightClick($event, index)"
|
||||
// // :class="[{ selected: rightClickSelectedIndex == index }, { active: selectedList.includes(item) }]")
|
||||
// // div.list-item-cell.nobreak.center(:style="{ width: rowWidth.r1 }" style="padding-left: 3px; padding-right: 3px;" :class="$style.noSelect" @click.stop) {{index + 1}}
|
||||
// // div.list-item-cell.auto(:style="{ width: rowWidth.r2 }" :aria-label="item.name + (item.meta._qualitys.flac32bit ? ` - ${$t('tag__lossless_24bit')}` : (item.meta._qualitys.ape || item.meta._qualitys.flac || item.meta._qualitys.wav) ? ` - ${$t('tag__lossless')}` : item.meta._qualitys['320k'] ? ` - ${$t('tag__high_quality')}` : '') + (sourceTag ? ` - ${item.source}` : '')")
|
||||
// // span.select {{item.name}}
|
||||
// // span.badge.badge-theme-primary(:class="[$style.labelQuality, $style.noSelect]" v-if="item.meta._qualitys.flac32bit") {{$t('tag__lossless_24bit')}}
|
||||
// // span.badge.badge-theme-primary(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item.meta._qualitys.ape || item.meta._qualitys.flac || item.meta._qualitys.wav") {{$t('tag__lossless')}}
|
||||
// // span.badge.badge-theme-secondary(:class="[$style.labelQuality, $style.noSelect]" v-else-if="item.meta._qualitys['320k']") {{$t('tag__high_quality')}}
|
||||
// // span.badge.badge-theme-tertiary(:class="[$style.labelQuality, $style.noSelect]" v-if="sourceTag") {{item.source}}
|
||||
// // div.list-item-cell(:style="{ width: rowWidth.r3 }" :aria-label="item.singer")
|
||||
// // span.select {{item.singer}}
|
||||
// // div.list-item-cell(:style="{ width: rowWidth.r4 }" :aria-label="item.albumName")
|
||||
// // span.select {{item.meta.albumName}}
|
||||
// // div.list-item-cell(:style="{ width: rowWidth.r5 }")
|
||||
// // span(:class="[$style.time, $style.noSelect]") {{item.meta.interval || '--/--'}}
|
||||
// // div.list-item-cell(:style="{ width: rowWidth.r6 }" style="padding-left: 0; padding-right: 0;")
|
||||
// // material-list-buttons(:index="index" :class="$style.btns"
|
||||
// // :remove-btn="false" @btn-click="handleListBtnClick"
|
||||
// // :download-btn="assertApiSupport(item.source)")
|
||||
// const renderListItem = (list: ListItem) => {
|
||||
// const dom_listItem = document.createElement('div')
|
||||
// dom_listItem.className = 'list-item'
|
||||
// }
|
||||
|
||||
// const renderList = (list: ListItem[], type?: 'up' | 'down') => {
|
||||
// if (!list.length) return
|
||||
// console.log(list)
|
||||
// const dom = document.createDocumentFragment()
|
||||
// for (const item of list) {
|
||||
// dom.appendChild(renderListItem(item))
|
||||
// }
|
||||
// switch (type) {
|
||||
// case 'up':
|
||||
// break
|
||||
// case 'down':
|
||||
// break
|
||||
// default:
|
||||
// // console.log()
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// const updateView = (force = false, currentScrollTop = dom_scrollContainer.value.scrollTop) => {
|
||||
// // const currentScrollTop = this.$refs.dom_scrollContainer.scrollTop
|
||||
// const itemHeight = props.itemHeight
|
||||
// const currentStartIndex = Math.floor(currentScrollTop / itemHeight)
|
||||
// const scrollContainerHeight = dom_scrollContainer.value.clientHeight
|
||||
// const currentEndIndex = currentStartIndex + Math.ceil(scrollContainerHeight / itemHeight)
|
||||
// const continuous = currentStartIndex <= endIndex && currentEndIndex >= startIndex
|
||||
// const currentStartRenderIndex = Math.max(currentStartIndex, 0)
|
||||
// const currentEndRenderIndex = currentEndIndex + 1
|
||||
// // console.log(continuous, currentStartIndex, endIndex, currentEndIndex, startIndex)
|
||||
// // debugger
|
||||
// if (!force && continuous) {
|
||||
// // if (Math.abs(currentScrollTop - this.scrollTop) < this.itemHeight * 0.6) return
|
||||
// // console.log('update')
|
||||
// if (currentScrollTop > scrollTop) { // scroll down
|
||||
// console.log('scroll down')
|
||||
// renderList(createList(endIndex + 1, currentEndRenderIndex))
|
||||
// // // views.value.push(...list.slice(list.indexOf(views.value[views.value.length - 1]) + 1))
|
||||
// // // // if (this.views.length > 100) {
|
||||
// // // nextTick(() => {
|
||||
// // // views.value.splice(0, views.value.indexOf(list[0]))
|
||||
// // // })
|
||||
// // // }
|
||||
// } else if (currentScrollTop < scrollTop) { // scroll up
|
||||
// console.log('scroll up')
|
||||
// renderList(createList(currentStartRenderIndex, startIndex))
|
||||
// // views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
// } else return
|
||||
// // if (currentScrollTop == scrollTop && endIndex >= currentEndIndex) return
|
||||
// // views.value = createList(currentStartRenderIndex, currentEndRenderIndex)
|
||||
// } else {
|
||||
// renderList(createList(currentStartRenderIndex, currentEndRenderIndex))
|
||||
// }
|
||||
// startIndex = currentStartIndex
|
||||
// endIndex = currentEndIndex
|
||||
// scrollTop = currentScrollTop
|
||||
// }
|
||||
|
||||
// const onScroll = event => {
|
||||
// const currentScrollTop = dom_scrollContainer.value.scrollTop
|
||||
// if (Math.abs(currentScrollTop - scrollTop) > props.itemHeight * 0.6) {
|
||||
// updateView(false, currentScrollTop)
|
||||
// }
|
||||
// emit('scroll', event)
|
||||
// }
|
||||
|
||||
// const scrollTo = async(scrollTop, animate = false) => {
|
||||
// return await new Promise(resolve => {
|
||||
// if (cancelScroll) {
|
||||
// cancelScroll(resolve)
|
||||
// } else {
|
||||
// resolve()
|
||||
// }
|
||||
// }).then(async() => {
|
||||
// return await new Promise((resolve, reject) => {
|
||||
// if (animate) {
|
||||
// isScrolling = true
|
||||
// scrollToValue = scrollTop
|
||||
// cancelScroll = handleScroll(dom_scrollContainer.value, scrollTop, 300, () => {
|
||||
// cancelScroll = null
|
||||
// isScrolling = false
|
||||
// resolve()
|
||||
// }, () => {
|
||||
// cancelScroll = null
|
||||
// isScrolling = false
|
||||
// reject('canceled')
|
||||
// })
|
||||
// } else {
|
||||
// dom_scrollContainer.value.scrollTop = scrollTop
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
|
||||
// const scrollToIndex = async(index, offset = 0, animate = false) => {
|
||||
// return await scrollTo(Math.max(index * props.itemHeight + offset, 0), animate)
|
||||
// }
|
||||
|
||||
// const getScrollTop = () => {
|
||||
// return isScrolling ? scrollToValue : dom_scrollContainer.value.scrollTop
|
||||
// }
|
||||
|
||||
// const handleResize = () => {
|
||||
// setTimeout(updateView)
|
||||
// }
|
||||
|
||||
// const contentStyle = computed(() => ({
|
||||
// display: 'block',
|
||||
// height: props.list.length * props.itemHeight + 'px',
|
||||
// }))
|
||||
|
||||
// const handleReset = list => {
|
||||
// cachedList = Array(list.length)
|
||||
// startIndex = -1
|
||||
// endIndex = -1
|
||||
// void nextTick(() => {
|
||||
// updateView(true)
|
||||
// })
|
||||
// }
|
||||
// watch(() => props.itemHeight, () => {
|
||||
// handleReset(props.list)
|
||||
// })
|
||||
// watch(() => props.list, (list) => {
|
||||
// handleReset(list)
|
||||
// }, {
|
||||
// deep: true,
|
||||
// })
|
||||
|
||||
// onMounted(() => {
|
||||
// dom_scrollContainer.value!.addEventListener('scroll', onScroll, false)
|
||||
// cachedList = Array(props.list.length)
|
||||
// startIndex = -1
|
||||
// endIndex = -1
|
||||
// updateView(true)
|
||||
// window.addEventListener('resize', handleResize)
|
||||
// })
|
||||
// onBeforeUnmount(() => {
|
||||
// dom_scrollContainer.value!.removeEventListener('scroll', onScroll)
|
||||
// window.removeEventListener('resize', handleResize)
|
||||
// if (cancelScroll) cancelScroll()
|
||||
// })
|
||||
|
||||
// return {
|
||||
// dom_scrollContainer,
|
||||
// dom_list,
|
||||
// contentStyle,
|
||||
// scrollTo,
|
||||
// scrollToIndex,
|
||||
// getScrollTop,
|
||||
// }
|
||||
// }
|
||||
Reference in New Issue
Block a user