Compare commits
3 Commits
4f59eb52e1
...
6abcba798f
Author | SHA1 | Date | |
---|---|---|---|
|
6abcba798f | ||
|
850a3169c2 | ||
|
bd2225f59b |
339
src/components/YearMonthWheelPicker.vue
Normal file
339
src/components/YearMonthWheelPicker.vue
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
<template>
|
||||||
|
<div class="picker-mask" @click.self="emit('close')">
|
||||||
|
<div class="picker-panel">
|
||||||
|
<div class="picker-title">
|
||||||
|
Select
|
||||||
|
<svg
|
||||||
|
@click="emit('close')"
|
||||||
|
style="cursor: pointer"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M0.666016 9.74935C0.666016 4.59469 4.84469 0.416016 9.99935 0.416016C15.154 0.416016 19.3327 4.59469 19.3327 9.74935C19.3327 14.904 15.154 19.0827 9.99935 19.0827C4.84469 19.0827 0.666016 14.904 0.666016 9.74935Z"
|
||||||
|
fill="#CCCCCC"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M12.833 5.84961C13.1273 5.55596 13.6042 5.55565 13.8965 5.84863C14.1907 6.14223 14.1893 6.61848 13.8965 6.91211L11.0615 9.74609L13.9043 12.5898C14.1973 12.8848 14.1986 13.3607 13.9043 13.6543C13.6114 13.947 13.1344 13.9471 12.8408 13.6543L9.99707 10.8105L7.1582 13.6504C6.86386 13.9444 6.38729 13.9446 6.09375 13.6504C5.8002 13.3574 5.80045 12.8809 6.09473 12.5859L8.93359 9.74707L6.10254 6.91602C5.80956 6.62236 5.80889 6.1452 6.10254 5.85156C6.39486 5.55817 6.87209 5.55802 7.16699 5.85156L9.99805 8.68262L12.833 5.84961Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="picker-columns"
|
||||||
|
:style="{ height: wheelViewportHeight + 'px' }"
|
||||||
|
>
|
||||||
|
<div class="center-lines" :style="{ height: wheelItemHeight + 'px' }">
|
||||||
|
<div class="line"></div>
|
||||||
|
<div class="line"></div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="picker-col year-col"
|
||||||
|
ref="yearColRef"
|
||||||
|
@scroll.passive="(e) => handleWheelScroll('year', e)"
|
||||||
|
@wheel.prevent="(e) => handleWheelStep('year', e)"
|
||||||
|
:style="{
|
||||||
|
scrollPaddingTop: wheelCenterPad + 'px',
|
||||||
|
scrollPaddingBottom: wheelCenterPad + 'px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div :style="{ height: wheelCenterPad + 'px' }"></div>
|
||||||
|
<div
|
||||||
|
v-for="y in years"
|
||||||
|
:key="'y' + y"
|
||||||
|
class="picker-item"
|
||||||
|
:class="{ active: y === localYear }"
|
||||||
|
:style="{
|
||||||
|
height: wheelItemHeight + 'px',
|
||||||
|
}"
|
||||||
|
@click="localYear = y"
|
||||||
|
>
|
||||||
|
{{ y }}
|
||||||
|
</div>
|
||||||
|
<div :style="{ height: wheelCenterPad + 'px' }"></div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="picker-col month-col"
|
||||||
|
ref="monthColRef"
|
||||||
|
@scroll.passive="(e) => handleWheelScroll('month', e)"
|
||||||
|
@wheel.prevent="(e) => handleWheelStep('month', e)"
|
||||||
|
:style="{
|
||||||
|
scrollPaddingTop: wheelCenterPad + 'px',
|
||||||
|
scrollPaddingBottom: wheelCenterPad + 'px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div :style="{ height: wheelCenterPad + 'px' }"></div>
|
||||||
|
<div
|
||||||
|
v-for="m in months"
|
||||||
|
:key="'m' + m"
|
||||||
|
class="picker-item"
|
||||||
|
:class="{ active: m === localMonth }"
|
||||||
|
:style="{
|
||||||
|
height: wheelItemHeight + 'px',
|
||||||
|
}"
|
||||||
|
@click="localMonth = m"
|
||||||
|
>
|
||||||
|
{{ m }}
|
||||||
|
</div>
|
||||||
|
<div :style="{ height: wheelCenterPad + 'px' }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="picker-actions">
|
||||||
|
<button class="picker-confirm" @click="confirm">
|
||||||
|
Confirm Selection
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, watch, nextTick } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: { type: Object, required: true }, // { year, month }
|
||||||
|
minYear: { type: Number, default: 2009 },
|
||||||
|
maxYear: { type: Number, default: new Date().getFullYear() },
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["update:modelValue", "close", "confirm"]);
|
||||||
|
|
||||||
|
const wheelItemBase = 8 * 5.12;
|
||||||
|
const wheelItemHeight = Math.round(wheelItemBase); // use integer px to avoid fractional rounding
|
||||||
|
const wheelViewportHeight = wheelItemHeight * 5;
|
||||||
|
const wheelCenterPad = (wheelViewportHeight - wheelItemHeight) / 2;
|
||||||
|
const isUserScrolling = ref(false);
|
||||||
|
|
||||||
|
const years = computed(() =>
|
||||||
|
Array.from(
|
||||||
|
{ length: props.maxYear - props.minYear + 1 },
|
||||||
|
(_, i) => props.minYear + i
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const months = Array.from({ length: 12 }, (_, i) => i + 1);
|
||||||
|
|
||||||
|
const localYear = ref(props.modelValue.year);
|
||||||
|
const localMonth = ref(props.modelValue.month);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(v) => {
|
||||||
|
localYear.value = v.year;
|
||||||
|
localMonth.value = v.month;
|
||||||
|
nextTick(syncWheelPositions);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const yearColRef = ref(null);
|
||||||
|
const monthColRef = ref(null);
|
||||||
|
|
||||||
|
watch([localYear, localMonth], () => {
|
||||||
|
if (!isUserScrolling.value) {
|
||||||
|
nextTick(syncWheelPositions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function syncWheelPositions() {
|
||||||
|
const yearIdx = years.value.indexOf(localYear.value);
|
||||||
|
const monthIdx = localMonth.value - 1;
|
||||||
|
if (yearColRef.value) yearColRef.value.scrollTop = yearIdx * wheelItemHeight;
|
||||||
|
if (monthColRef.value)
|
||||||
|
monthColRef.value.scrollTop = monthIdx * wheelItemHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollTimers = { year: null, month: null };
|
||||||
|
const isAnimating = { year: false, month: false };
|
||||||
|
|
||||||
|
function clamp(n, min, max) {
|
||||||
|
return Math.max(min, Math.min(n, max));
|
||||||
|
}
|
||||||
|
function getCenteredIndexByRect(el) {
|
||||||
|
if (!el) return 0;
|
||||||
|
const items = el.querySelectorAll(".picker-item");
|
||||||
|
if (!items || items.length === 0) return 0;
|
||||||
|
const containerRect = el.getBoundingClientRect();
|
||||||
|
const centerY = containerRect.top + containerRect.height / 2;
|
||||||
|
let nearestIdx = 0;
|
||||||
|
let nearestDist = Number.POSITIVE_INFINITY;
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const r = items[i].getBoundingClientRect();
|
||||||
|
const mid = r.top + r.height / 2;
|
||||||
|
const dist = Math.abs(mid - centerY);
|
||||||
|
if (dist < nearestDist) {
|
||||||
|
nearestDist = dist;
|
||||||
|
nearestIdx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nearestIdx;
|
||||||
|
}
|
||||||
|
function handleWheelStep(type, e) {
|
||||||
|
const refMap = { year: yearColRef, month: monthColRef };
|
||||||
|
const el = refMap[type].value;
|
||||||
|
if (!el || isAnimating[type]) return;
|
||||||
|
const direction = e.deltaY > 0 ? 1 : -1;
|
||||||
|
let idx = getCenteredIndexByRect(el);
|
||||||
|
if (type === "year") {
|
||||||
|
idx = clamp(idx + direction, 0, years.value.length - 1);
|
||||||
|
localYear.value = years.value[idx];
|
||||||
|
} else if (type === "month") {
|
||||||
|
idx = clamp(idx + direction, 0, 11);
|
||||||
|
localMonth.value = idx + 1;
|
||||||
|
}
|
||||||
|
isAnimating[type] = true;
|
||||||
|
isUserScrolling.value = true;
|
||||||
|
el.scrollTo({ top: idx * wheelItemHeight, behavior: "smooth" });
|
||||||
|
setTimeout(() => {
|
||||||
|
isAnimating[type] = false;
|
||||||
|
isUserScrolling.value = false;
|
||||||
|
}, 180);
|
||||||
|
}
|
||||||
|
function handleWheelScroll(type, e) {
|
||||||
|
clearTimeout(scrollTimers[type]);
|
||||||
|
isUserScrolling.value = true;
|
||||||
|
scrollTimers[type] = setTimeout(() => {
|
||||||
|
const el = e.target;
|
||||||
|
const idx = getCenteredIndexByRect(el);
|
||||||
|
if (type === "year") {
|
||||||
|
localYear.value = years.value[clamp(idx, 0, years.value.length - 1)];
|
||||||
|
} else if (type === "month") {
|
||||||
|
localMonth.value = clamp(idx + 1, 1, 12);
|
||||||
|
}
|
||||||
|
isUserScrolling.value = false;
|
||||||
|
}, 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirm() {
|
||||||
|
if (yearColRef.value) {
|
||||||
|
const idx = clamp(
|
||||||
|
getCenteredIndexByRect(yearColRef.value),
|
||||||
|
0,
|
||||||
|
years.value.length - 1
|
||||||
|
);
|
||||||
|
localYear.value = years.value[idx];
|
||||||
|
}
|
||||||
|
if (monthColRef.value) {
|
||||||
|
const idx = clamp(getCenteredIndexByRect(monthColRef.value) + 1, 1, 12);
|
||||||
|
localMonth.value = idx;
|
||||||
|
}
|
||||||
|
const payload = {
|
||||||
|
year: localYear.value,
|
||||||
|
month: localMonth.value,
|
||||||
|
};
|
||||||
|
emit("update:modelValue", payload);
|
||||||
|
nextTick(() => emit("confirm", payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTick(syncWheelPositions);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.picker-mask {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
.picker-panel {
|
||||||
|
width: 311 * 5.12px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8 * 5.12px;
|
||||||
|
box-shadow: 0 3 * 5.12px 14 * 5.12px rgba(0, 0, 0, 0.16);
|
||||||
|
padding: 16 * 5.12px;
|
||||||
|
}
|
||||||
|
.picker-title {
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
color: #455363;
|
||||||
|
margin-bottom: 16 * 5.12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.picker-columns {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
column-gap: 32 * 5.12px;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-items: stretch;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.center-lines {
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.center-lines .line {
|
||||||
|
position: absolute;
|
||||||
|
left: 16 * 5.12px;
|
||||||
|
right: 16 * 5.12px;
|
||||||
|
height: 1 * 5.12px;
|
||||||
|
background: #ededed;
|
||||||
|
}
|
||||||
|
.center-lines .line:first-child {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.center-lines .line:last-child {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.picker-col {
|
||||||
|
position: relative;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 1;
|
||||||
|
scroll-snap-type: y mandatory;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
|
.picker-col::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.picker-item {
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
color: #9da3ad;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
scroll-snap-align: center;
|
||||||
|
scroll-snap-stop: always;
|
||||||
|
}
|
||||||
|
.year-col .picker-item {
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding-left: 16 * 5.12px;
|
||||||
|
}
|
||||||
|
.month-col .picker-item {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-item.active {
|
||||||
|
color: #000;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.picker-actions {
|
||||||
|
margin-top: 16 * 5.12px;
|
||||||
|
}
|
||||||
|
.picker-confirm {
|
||||||
|
width: 100%;
|
||||||
|
height: 44 * 5.12px;
|
||||||
|
background: #ff7bac;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8 * 5.12px;
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
}
|
||||||
|
</style>
|
286
src/components/YearWheelPicker.vue
Normal file
286
src/components/YearWheelPicker.vue
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
<template>
|
||||||
|
<Teleport to="body">
|
||||||
|
<div class="picker-mask" @click.self="emit('close')">
|
||||||
|
<div class="picker-panel">
|
||||||
|
<div class="picker-title">
|
||||||
|
Select
|
||||||
|
<svg
|
||||||
|
@click="emit('close')"
|
||||||
|
style="cursor: pointer"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M0.666016 9.74935C0.666016 4.59469 4.84469 0.416016 9.99935 0.416016C15.154 0.416016 19.3327 4.59469 19.3327 9.74935C19.3327 14.904 15.154 19.0827 9.99935 19.0827C4.84469 19.0827 0.666016 14.904 0.666016 9.74935Z"
|
||||||
|
fill="#CCCCCC"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="M12.833 5.84961C13.1273 5.55596 13.6042 5.55565 13.8965 5.84863C14.1907 6.14223 14.1893 6.61848 13.8965 6.91211L11.0615 9.74609L13.9043 12.5898C14.1973 12.8848 14.1986 13.3607 13.9043 13.6543C13.6114 13.947 13.1344 13.9471 12.8408 13.6543L9.99707 10.8105L7.1582 13.6504C6.86386 13.9444 6.38729 13.9446 6.09375 13.6504C5.8002 13.3574 5.80045 12.8809 6.09473 12.5859L8.93359 9.74707L6.10254 6.91602C5.80956 6.62236 5.80889 6.1452 6.10254 5.85156C6.39486 5.55817 6.87209 5.55802 7.16699 5.85156L9.99805 8.68262L12.833 5.84961Z"
|
||||||
|
fill="white"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="picker-columns"
|
||||||
|
:style="{ height: wheelViewportHeight + 'px' }"
|
||||||
|
>
|
||||||
|
<div class="center-lines" :style="{ height: wheelItemHeight + 'px' }">
|
||||||
|
<div class="line"></div>
|
||||||
|
<div class="line"></div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="picker-col year-col"
|
||||||
|
ref="yearColRef"
|
||||||
|
@scroll.passive="handleWheelScroll"
|
||||||
|
@wheel.prevent="handleWheelStep"
|
||||||
|
:style="{
|
||||||
|
scrollPaddingTop: wheelCenterPad + 'px',
|
||||||
|
scrollPaddingBottom: wheelCenterPad + 'px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div :style="{ height: wheelCenterPad + 'px' }"></div>
|
||||||
|
<div
|
||||||
|
v-for="opt in props.options"
|
||||||
|
:key="opt.value"
|
||||||
|
class="picker-item"
|
||||||
|
:class="{ active: opt.value === localYear }"
|
||||||
|
:style="{ height: wheelItemHeight + 'px' }"
|
||||||
|
@click="localYear = opt.value"
|
||||||
|
>
|
||||||
|
{{ opt.label }}
|
||||||
|
</div>
|
||||||
|
<div :style="{ height: wheelCenterPad + 'px' }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="picker-actions">
|
||||||
|
<button class="picker-confirm" @click="confirm">
|
||||||
|
Confirm Selection
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, nextTick, Teleport } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: { type: [String, Number], required: true },
|
||||||
|
options: { type: Array, required: true },
|
||||||
|
});
|
||||||
|
const emit = defineEmits(["update:modelValue", "close", "confirm"]);
|
||||||
|
|
||||||
|
const wheelItemBase = 8 * 5.12;
|
||||||
|
const wheelItemHeight = Math.round(wheelItemBase);
|
||||||
|
const wheelViewportHeight = wheelItemHeight * 5;
|
||||||
|
const wheelCenterPad = (wheelViewportHeight - wheelItemHeight) / 2;
|
||||||
|
|
||||||
|
const localYear = ref(props.modelValue);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(v) => {
|
||||||
|
localYear.value = v;
|
||||||
|
nextTick(syncWheelPositions);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const yearColRef = ref(null);
|
||||||
|
|
||||||
|
watch(localYear, () => {
|
||||||
|
if (!isUserScrolling.value) {
|
||||||
|
nextTick(syncWheelPositions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function syncWheelPositions() {
|
||||||
|
const yearIdx = props.options.findIndex(
|
||||||
|
(opt) => opt.value === localYear.value
|
||||||
|
);
|
||||||
|
if (yearColRef.value) {
|
||||||
|
yearColRef.value.scrollTop = yearIdx * wheelItemHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollTimer = ref(null);
|
||||||
|
const isAnimating = ref(false);
|
||||||
|
const isUserScrolling = ref(false);
|
||||||
|
|
||||||
|
function clamp(n, min, max) {
|
||||||
|
return Math.max(min, Math.min(n, max));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCenteredIndexByRect(el) {
|
||||||
|
if (!el) return 0;
|
||||||
|
const items = el.querySelectorAll(".picker-item");
|
||||||
|
if (!items || items.length === 0) return 0;
|
||||||
|
const containerRect = el.getBoundingClientRect();
|
||||||
|
const centerY = containerRect.top + containerRect.height / 2;
|
||||||
|
let nearestIdx = 0;
|
||||||
|
let nearestDist = Number.POSITIVE_INFINITY;
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const r = items[i].getBoundingClientRect();
|
||||||
|
const mid = r.top + r.height / 2;
|
||||||
|
const dist = Math.abs(mid - centerY);
|
||||||
|
if (dist < nearestDist) {
|
||||||
|
nearestDist = dist;
|
||||||
|
nearestIdx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nearestIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWheelStep(e) {
|
||||||
|
const el = yearColRef.value;
|
||||||
|
if (!el || isAnimating.value) return;
|
||||||
|
const direction = e.deltaY > 0 ? 1 : -1;
|
||||||
|
let idx = getCenteredIndexByRect(el);
|
||||||
|
const options = props.options;
|
||||||
|
|
||||||
|
idx = clamp(idx + direction, 0, options.length - 1);
|
||||||
|
localYear.value = options[idx].value;
|
||||||
|
|
||||||
|
isAnimating.value = true;
|
||||||
|
isUserScrolling.value = true;
|
||||||
|
el.scrollTo({ top: idx * wheelItemHeight, behavior: "smooth" });
|
||||||
|
setTimeout(() => {
|
||||||
|
isAnimating.value = false;
|
||||||
|
isUserScrolling.value = false;
|
||||||
|
}, 180);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWheelScroll(e) {
|
||||||
|
clearTimeout(scrollTimer.value);
|
||||||
|
isUserScrolling.value = true;
|
||||||
|
scrollTimer.value = setTimeout(() => {
|
||||||
|
const el = e.target;
|
||||||
|
const idx = getCenteredIndexByRect(el);
|
||||||
|
const options = props.options;
|
||||||
|
localYear.value = options[clamp(idx, 0, options.length - 1)].value;
|
||||||
|
isUserScrolling.value = false;
|
||||||
|
}, 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirm() {
|
||||||
|
if (yearColRef.value) {
|
||||||
|
const idx = getCenteredIndexByRect(yearColRef.value);
|
||||||
|
const options = props.options;
|
||||||
|
if (options[idx]) {
|
||||||
|
localYear.value = options[idx].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit("update:modelValue", localYear.value);
|
||||||
|
nextTick(() => emit("confirm", localYear.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTick(syncWheelPositions);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.picker-mask {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
.picker-panel {
|
||||||
|
width: 311 * 5.12px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8 * 5.12px;
|
||||||
|
box-shadow: 0 3 * 5.12px 14 * 5.12px rgba(0, 0, 0, 0.16);
|
||||||
|
padding: 16 * 5.12px;
|
||||||
|
}
|
||||||
|
.picker-title {
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
color: #455363;
|
||||||
|
margin-bottom: 16 * 5.12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.picker-columns {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-items: stretch;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.center-lines {
|
||||||
|
pointer-events: none;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.center-lines .line {
|
||||||
|
position: absolute;
|
||||||
|
left: 16 * 5.12px;
|
||||||
|
right: 16 * 5.12px;
|
||||||
|
height: 1 * 5.12px;
|
||||||
|
background: #ededed;
|
||||||
|
}
|
||||||
|
.center-lines .line:first-child {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.center-lines .line:last-child {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.picker-col {
|
||||||
|
position: relative;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 1;
|
||||||
|
scroll-snap-type: y mandatory;
|
||||||
|
overscroll-behavior: contain;
|
||||||
|
}
|
||||||
|
.picker-col::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.picker-item {
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
color: #9da3ad;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
scroll-snap-align: center;
|
||||||
|
scroll-snap-stop: always;
|
||||||
|
}
|
||||||
|
.year-col .picker-item {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-item.active {
|
||||||
|
color: #000;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.picker-actions {
|
||||||
|
margin-top: 16 * 5.12px;
|
||||||
|
}
|
||||||
|
.picker-confirm {
|
||||||
|
width: 100%;
|
||||||
|
height: 44 * 5.12px;
|
||||||
|
background: #ff7bac;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8 * 5.12px;
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,73 +1,162 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { NCarousel, NDivider, NMarquee, NPopselect } from "naive-ui";
|
||||||
import { onUnmounted, ref, watch, onMounted, computed } from "vue";
|
import { onUnmounted, ref, watch, onMounted, computed } from "vue";
|
||||||
|
|
||||||
function copyEmail() {
|
function copyEmail() {
|
||||||
navigator.clipboard.writeText("fiee@dlkadvisory.com");
|
navigator.clipboard.writeText("fiee@dlkadvisory.com");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main
|
<div class="contact-container">
|
||||||
ref="main"
|
<!-- Title Section -->
|
||||||
class="flex flex-col items-center from-primary to-accent w-[100vw] mt-8 animate-fade-in px-4 py-8 pt-500px"
|
<div class="title-section">
|
||||||
>
|
<div class="title-decoration"></div>
|
||||||
<div class="w-full flex flex-col items-center gap-4 px-2">
|
<div class="contact-title">Investor Contacts</div>
|
||||||
<h1
|
|
||||||
class="text-2xl font-bold text-primary animate-fade-in-down animate-delay-0"
|
|
||||||
>
|
|
||||||
Investor Contacts
|
|
||||||
</h1>
|
|
||||||
<div
|
|
||||||
class="text-lg font-semibold text-gray-800 animate-fade-in-down animate-delay-200"
|
|
||||||
>
|
|
||||||
FiEE Inc.
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="text-base text-#ff7bac animate-fade-in-down animate-delay-400"
|
<!-- Card Section -->
|
||||||
>
|
<div class="contact-card">
|
||||||
Investor Relations
|
<img
|
||||||
</div>
|
class="card-overlay"
|
||||||
<div
|
src="@/assets/image/375/contacts-bg.png"
|
||||||
class="text-sm text-gray-600 flex items-center gap-1 animate-fade-in-down animate-delay-600"
|
alt=""
|
||||||
>
|
/>
|
||||||
|
<div class="logo-text">FiEE Inc.</div>
|
||||||
|
<div class="relation-text">Investor Relations</div>
|
||||||
|
<div class="email-section">
|
||||||
<span>Email:</span>
|
<span>Email:</span>
|
||||||
<span
|
<span class="email-address" @click="copyEmail"
|
||||||
class="transition-colors duration-300 cursor-pointer text-#00baff hover:text-primary active:text-secondary select-all"
|
|
||||||
@click="copyEmail"
|
|
||||||
>fiee@dlkadvisory.com</span
|
>fiee@dlkadvisory.com</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.contact-container {
|
||||||
|
width: 343 * 5.12px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-section {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16 * 5.12px;
|
||||||
|
padding: 0 16 * 5.12px;
|
||||||
|
margin-top: 43 * 5.12px;
|
||||||
|
margin-bottom: 32 * 5.12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-decoration {
|
||||||
|
width: 58 * 5.12px;
|
||||||
|
height: 7 * 5.12px;
|
||||||
|
background: #ff7bac;
|
||||||
|
margin: auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-title {
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 24 * 5.12px;
|
||||||
|
line-height: normal;
|
||||||
|
letter-spacing: 0 * 5.12px;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-card {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 524 * 5.12px;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4 * 5.12px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 16 * 5.12px;
|
||||||
|
box-shadow: 0 * 5.12px 3 * 5.12px 14 * 5.12px 0 * 5.12px rgba(0, 0, 0, 0.16);
|
||||||
|
position: relative;
|
||||||
|
margin: 0 16 * 5.12px;
|
||||||
|
animation: fade-in 0.8s cubic-bezier(0.23, 1, 0.32, 1) both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-feature-settings: "liga" off, "clig" off;
|
||||||
|
font-family: "PingFang SC";
|
||||||
|
font-size: 64 * 5.12px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: normal;
|
||||||
|
letter-spacing: 0.48 * 5.12px;
|
||||||
|
background: linear-gradient(90deg, #ff7bac 0%, #0ff 100%);
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
color: transparent;
|
||||||
|
animation: fade-in-down 0.8s cubic-bezier(0.23, 1, 0.32, 1) both;
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.relation-text {
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-size: 20 * 5.12px;
|
||||||
|
color: #000000;
|
||||||
|
font-weight: 600;
|
||||||
|
animation: fade-in-down 0.8s cubic-bezier(0.23, 1, 0.32, 1) both;
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-section {
|
||||||
|
font-size: 16 * 5.12px;
|
||||||
|
color: #455363;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8 * 5.12px;
|
||||||
|
letter-spacing: 1.2 * 5.12px;
|
||||||
|
animation: fade-in-down 0.8s cubic-bezier(0.23, 1, 0.32, 1) both;
|
||||||
|
animation-delay: 0.6s;
|
||||||
|
margin-top: 0 * 5.12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-address {
|
||||||
|
color: #ff7bac;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-overlay {
|
||||||
|
position: absolute;
|
||||||
|
left: 43 * 5.12px;
|
||||||
|
top: 174 * 5.12px;
|
||||||
|
width: 258 * 5.12px;
|
||||||
|
height: 176 * 5.12px;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes fade-in-down {
|
@keyframes fade-in-down {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(-20px);
|
transform: translateY(-20 * 5.12px);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animate-fade-in-down {
|
|
||||||
animation: fade-in-down 0.8s cubic-bezier(0.23, 1, 0.32, 1) both;
|
|
||||||
}
|
|
||||||
.animate-delay-0 {
|
|
||||||
animation-delay: 0s;
|
|
||||||
}
|
|
||||||
.animate-delay-200 {
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
}
|
|
||||||
.animate-delay-400 {
|
|
||||||
animation-delay: 0.4s;
|
|
||||||
}
|
|
||||||
.animate-delay-600 {
|
|
||||||
animation-delay: 0.6s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.max-w-343px {
|
|
||||||
max-width: 343px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,32 +1,57 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="events-calendar-page">
|
<main class="page-container">
|
||||||
<customDefaultPage>
|
<!-- 标题区域 -->
|
||||||
<template #content>
|
<div class="title-section">
|
||||||
<main class="p-[35px] max-w-[1800px] mx-auto">
|
<div class="title-decoration"></div>
|
||||||
<div class="title mb-[20px]">
|
<div class="events-title">
|
||||||
{{ t("events_calendar.title") }}
|
{{ t("events_calendar.title") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="search-container">
|
|
||||||
<n-date-picker
|
|
||||||
v-model:value="state.selectedDateValue"
|
|
||||||
type="date"
|
|
||||||
class="search-date-picker"
|
|
||||||
></n-date-picker>
|
|
||||||
<n-button @click="handleSearch" class="search-button">
|
|
||||||
{{ t("events_calendar.search.button") }}
|
|
||||||
</n-button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 日期选择区域 -->
|
||||||
|
<div class="date-selector" @click="showPicker = true">
|
||||||
|
<span class="date-label">Date</span>
|
||||||
|
<div class="date-value">
|
||||||
|
<span>{{ selectedDateText }}</span>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="10"
|
||||||
|
height="10"
|
||||||
|
viewBox="0 0 10 10"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M2.58687 9.69047C2.38438 9.48798 2.36188 9.17365 2.51937 8.9463L2.58687 8.86552L6.45252 5.00022L2.58687 1.13492C2.38438 0.932428 2.36188 0.618098 2.51937 0.390748L2.58687 0.309958C2.78936 0.107469 3.10369 0.0849685 3.33104 0.242458L3.41183 0.309958L7.68961 4.58774C7.8921 4.79023 7.9146 5.10456 7.75711 5.33191L7.68961 5.4127L3.41183 9.69047C3.18402 9.91828 2.81468 9.91828 2.58687 9.69047Z"
|
||||||
|
fill="#B6B6B6"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 内容区域 -->
|
||||||
|
<div class="content-area">
|
||||||
|
<img
|
||||||
|
src="@/assets/image/375/events-calendar-bg.png"
|
||||||
|
alt="No Events"
|
||||||
|
class="empty-state-image"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<YearMonthWheelPicker
|
||||||
|
v-if="showPicker"
|
||||||
|
v-model="selectedDate"
|
||||||
|
@close="showPicker = false"
|
||||||
|
@confirm="handleDateConfirm"
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
</customDefaultPage>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import customDefaultPage from "@/components/customDefaultPage/index.vue";
|
import { reactive, ref, computed } from "vue";
|
||||||
import { reactive } from "vue";
|
|
||||||
import { NDatePicker, NButton } from "naive-ui";
|
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
|
import YearMonthWheelPicker from "@/components/YearMonthWheelPicker.vue";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
@ -34,55 +59,104 @@ const state = reactive({
|
|||||||
selectedDateValue: null, //选中值
|
selectedDateValue: null, //选中值
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSearch = () => {
|
const showPicker = ref(false);
|
||||||
// 搜索处理逻辑
|
const now = new Date();
|
||||||
// console.log('搜索:', state.selectedDateValue)
|
const selectedDate = ref({
|
||||||
};
|
year: now.getFullYear(),
|
||||||
|
month: now.getMonth() + 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedDateText = computed(() => {
|
||||||
|
if (selectedDate.value) {
|
||||||
|
return `${selectedDate.value.year}-${String(
|
||||||
|
selectedDate.value.month
|
||||||
|
).padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
return "Select Date";
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleDateConfirm(date) {
|
||||||
|
selectedDate.value = date;
|
||||||
|
showPicker.value = false;
|
||||||
|
// TODO: Add logic to fetch events for the selected date
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.title {
|
.page-container {
|
||||||
font-size: 113px;
|
width: 343 * 5.12px;
|
||||||
font-weight: bold;
|
margin: 0 auto;
|
||||||
color: #333;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
}
|
||||||
|
.title-section {
|
||||||
.search-container {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
|
gap: 16 * 5.12px;
|
||||||
|
margin-top: 43 * 5.12px;
|
||||||
|
padding: 0 16 * 5.12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-decoration {
|
||||||
|
width: 58 * 5.12px;
|
||||||
|
height: 7 * 5.12px;
|
||||||
|
background: #ff7bac;
|
||||||
|
margin: auto 0;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.events-title {
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 24 * 5.12px;
|
||||||
|
line-height: 1;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-selector {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: #f6f7f9;
|
height: 20 * 5.12px;
|
||||||
border-radius: 8px;
|
margin: 32 * 5.12px 0;
|
||||||
padding: 8px;
|
padding: 0 16 * 5.12px;
|
||||||
gap: 16px;
|
.date-label {
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: 0.48 * 5.12px;
|
||||||
|
color: #000;
|
||||||
}
|
}
|
||||||
|
.date-value {
|
||||||
.search-date-picker {
|
display: flex;
|
||||||
width: 100%;
|
align-items: center;
|
||||||
|
gap: 16 * 5.12px;
|
||||||
|
span {
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: 0.48 * 5.12px;
|
||||||
|
color: #b6b6b6;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
width: 10 * 5.12px;
|
||||||
|
height: 10 * 5.12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.n-date-picker) {
|
|
||||||
width: 100%;
|
|
||||||
.n-input__input {
|
|
||||||
padding: 4px 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.n-button) {
|
.content-area {
|
||||||
width: 260px;
|
background: #fff;
|
||||||
padding: 20px 16px;
|
height: 456 * 5.12px;
|
||||||
border-radius: 4px;
|
box-shadow: 0 * 5.12px 3 * 5.12px 14 * 5.12px 0 * 5.12px rgba(0, 0, 0, 0.16);
|
||||||
}
|
border-radius: 16 * 5.12px;
|
||||||
.search-button {
|
display: flex;
|
||||||
background: #ff7bac;
|
justify-content: center;
|
||||||
color: #fff;
|
align-items: center;
|
||||||
&:hover {
|
|
||||||
background: #ff7bac;
|
|
||||||
color: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.empty-state-image {
|
||||||
|
width: 243 * 5.12px;
|
||||||
|
height: 138 * 5.12px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -7,11 +7,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<n-select
|
<div class="search-select" @click="openYearPicker">
|
||||||
:options="state.selectOptions"
|
<div class="search-select-label">Year</div>
|
||||||
v-model:value="state.selectedValue"
|
<div class="search-select-icon">
|
||||||
class="search-select"
|
<span class="selected-year-label">{{ selectedYearLabel }}</span>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="10"
|
||||||
|
height="10"
|
||||||
|
viewBox="0 0 10 10"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M2.58882 9.69047C2.38633 9.48798 2.36383 9.17365 2.52132 8.9463L2.58882 8.86552L6.45447 5.00022L2.58882 1.13492C2.38633 0.932428 2.36383 0.618098 2.52132 0.390748L2.58882 0.309958C2.79132 0.107469 3.10565 0.0849685 3.33299 0.242458L3.41378 0.309958L7.69156 4.58774C7.89405 4.79023 7.91655 5.10456 7.75906 5.33191L7.69156 5.4127L3.41378 9.69047C3.18597 9.91828 2.81663 9.91828 2.58882 9.69047Z"
|
||||||
|
fill="black"
|
||||||
/>
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
v-model="state.inputValue"
|
v-model="state.inputValue"
|
||||||
type="text"
|
type="text"
|
||||||
@ -167,17 +182,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="goto-section">
|
|
||||||
<span>Goto</span>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
v-model="state.gotoPage"
|
|
||||||
class="goto-input"
|
|
||||||
:min="1"
|
|
||||||
:max="totalPages"
|
|
||||||
@keyup.enter="handleGoto"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pagination-info" v-if="state.total > 0">
|
<div class="pagination-info" v-if="state.total > 0">
|
||||||
@ -185,10 +189,18 @@
|
|||||||
{{ state.total }} results
|
{{ state.total }} results
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<year-wheel-picker
|
||||||
|
v-if="state.showYearPicker"
|
||||||
|
v-model="state.selectedValue"
|
||||||
|
:options="state.selectOptions"
|
||||||
|
@close="closeYearPicker"
|
||||||
|
@confirm="onYearConfirm"
|
||||||
|
></year-wheel-picker>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import customDefaultPage from "@/components/customDefaultPage/index.vue";
|
import customDefaultPage from "@/components/customDefaultPage/index.vue";
|
||||||
|
import YearWheelPicker from "@/components/YearWheelPicker.vue";
|
||||||
import {
|
import {
|
||||||
reactive,
|
reactive,
|
||||||
onMounted,
|
onMounted,
|
||||||
@ -198,7 +210,7 @@ import {
|
|||||||
computed,
|
computed,
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import { NSelect, NInput, NButton, NTooltip } from "naive-ui";
|
import { NInput, NButton, NTooltip } from "naive-ui";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
@ -225,12 +237,33 @@ const state = reactive({
|
|||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
total: 0,
|
total: 0,
|
||||||
gotoPage: 1,
|
gotoPage: 1,
|
||||||
|
showYearPicker: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const showPageSizeMenu = ref(false);
|
const showPageSizeMenu = ref(false);
|
||||||
|
|
||||||
const titleRefs = ref([]);
|
const titleRefs = ref([]);
|
||||||
|
|
||||||
|
const selectedYearLabel = computed(() => {
|
||||||
|
const option = state.selectOptions.find(
|
||||||
|
(opt) => opt.value === state.selectedValue
|
||||||
|
);
|
||||||
|
return option ? option.label : "";
|
||||||
|
});
|
||||||
|
|
||||||
|
const openYearPicker = () => {
|
||||||
|
state.showYearPicker = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeYearPicker = () => {
|
||||||
|
state.showYearPicker = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onYearConfirm = (year) => {
|
||||||
|
state.selectedValue = year;
|
||||||
|
closeYearPicker();
|
||||||
|
};
|
||||||
|
|
||||||
const setTitleRef = (el, idx) => {
|
const setTitleRef = (el, idx) => {
|
||||||
if (el) titleRefs.value[idx] = el;
|
if (el) titleRefs.value[idx] = el;
|
||||||
};
|
};
|
||||||
@ -390,39 +423,15 @@ const togglePageSizeMenu = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getVisiblePages = () => {
|
const getVisiblePages = () => {
|
||||||
const current = state.currentPage;
|
|
||||||
const total = totalPages.value;
|
const total = totalPages.value;
|
||||||
|
if (total <= 4) {
|
||||||
const pages = [];
|
const pages = [];
|
||||||
|
|
||||||
if (total <= 7) {
|
|
||||||
for (let i = 1; i <= total; i++) {
|
for (let i = 1; i <= total; i++) {
|
||||||
pages.push(i);
|
pages.push(i);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
pages.push(1);
|
|
||||||
|
|
||||||
if (current <= 4) {
|
|
||||||
for (let i = 2; i <= 5; i++) {
|
|
||||||
pages.push(i);
|
|
||||||
}
|
|
||||||
pages.push("...");
|
|
||||||
pages.push(total);
|
|
||||||
} else if (current >= total - 3) {
|
|
||||||
pages.push("...");
|
|
||||||
for (let i = total - 4; i <= total; i++) {
|
|
||||||
pages.push(i);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pages.push("...");
|
|
||||||
for (let i = current - 1; i <= current + 1; i++) {
|
|
||||||
pages.push(i);
|
|
||||||
}
|
|
||||||
pages.push("...");
|
|
||||||
pages.push(total);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pages;
|
return pages;
|
||||||
|
}
|
||||||
|
return [1, 2, "...", total];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 点击外部关闭页面大小选择菜单
|
// 点击外部关闭页面大小选择菜单
|
||||||
@ -460,9 +469,10 @@ const handleClickOutside = (event) => {
|
|||||||
margin-top: 32 * 5.12px;
|
margin-top: 32 * 5.12px;
|
||||||
margin-bottom: 20 * 5.12px;
|
margin-bottom: 20 * 5.12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: space-between;
|
||||||
|
flex-flow: wrap;
|
||||||
gap: 16 * 5.12px;
|
gap: 16 * 5.12px;
|
||||||
padding: 0 16 * 5.12px;
|
padding: 0 16 * 5.12px;
|
||||||
}
|
}
|
||||||
@ -470,10 +480,22 @@ const handleClickOutside = (event) => {
|
|||||||
.search-select {
|
.search-select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 34 * 5.12px;
|
height: 34 * 5.12px;
|
||||||
|
padding: 0 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
color: #455363;
|
||||||
|
}
|
||||||
|
.search-select-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input {
|
.search-input {
|
||||||
width: 100%;
|
width: 191 * 5.12px;
|
||||||
height: 34 * 5.12px;
|
height: 34 * 5.12px;
|
||||||
padding: 7 * 5.12px 12 * 5.12px;
|
padding: 7 * 5.12px 12 * 5.12px;
|
||||||
border: 1 * 5.12px solid #e0e0e6;
|
border: 1 * 5.12px solid #e0e0e6;
|
||||||
@ -511,7 +533,7 @@ const handleClickOutside = (event) => {
|
|||||||
border-radius: 4 * 5.12px;
|
border-radius: 4 * 5.12px;
|
||||||
}
|
}
|
||||||
.search-button {
|
.search-button {
|
||||||
width: 100%;
|
width: 104 * 5.12px;
|
||||||
height: 34 * 5.12px;
|
height: 34 * 5.12px;
|
||||||
padding: 7 * 5.12px 12 * 5.12px;
|
padding: 7 * 5.12px 12 * 5.12px;
|
||||||
background: #ff7bac;
|
background: #ff7bac;
|
||||||
@ -536,7 +558,6 @@ const handleClickOutside = (event) => {
|
|||||||
gap: 4 * 5.12px;
|
gap: 4 * 5.12px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0 16 * 5.12px;
|
|
||||||
margin-top: 40 * 5.12px;
|
margin-top: 40 * 5.12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -596,7 +617,7 @@ const handleClickOutside = (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.arrow-icon {
|
.arrow-icon {
|
||||||
margin-left: auto;
|
margin-right: 16 * 5.12px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -615,9 +636,8 @@ const handleClickOutside = (event) => {
|
|||||||
line-height: 1.375em;
|
line-height: 1.375em;
|
||||||
letter-spacing: 0.48 * 5.12px;
|
letter-spacing: 0.48 * 5.12px;
|
||||||
color: #455363;
|
color: #455363;
|
||||||
margin: 0;
|
margin: 8 * 5.12px 0;
|
||||||
padding: 0;
|
padding: 0 16 * 5.12px;
|
||||||
margin-top: 8 * 5.12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.news-item-date {
|
.news-item-date {
|
||||||
@ -634,7 +654,8 @@ const handleClickOutside = (event) => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 4 * 5.12px 0 * 5.12px;
|
padding: 0 16 * 5.12px;
|
||||||
|
margin-bottom: 8 * 5.12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator-line {
|
.separator-line {
|
||||||
@ -654,7 +675,7 @@ const handleClickOutside = (event) => {
|
|||||||
.pagination-container {
|
.pagination-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 40 * 5.12px 0;
|
margin: 16 * 5.12px 0;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
padding: 0 16 * 5.12px;
|
padding: 0 16 * 5.12px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -809,4 +830,20 @@ const handleClickOutside = (event) => {
|
|||||||
border-color: #ff7bac;
|
border-color: #ff7bac;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selected-year-label {
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
line-height: 1.428em;
|
||||||
|
color: #000;
|
||||||
|
margin-right: 16 * 5.12px;
|
||||||
|
}
|
||||||
|
.search-select-label {
|
||||||
|
font-family: "PingFang SC", sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14 * 5.12px;
|
||||||
|
line-height: 1.428em;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user