Compare commits
19 Commits
533c24fa8e
...
e75e8adcd3
Author | SHA1 | Date | |
---|---|---|---|
e75e8adcd3 | |||
|
75344b2ee3 | ||
|
6abcba798f | ||
|
850a3169c2 | ||
|
bd2225f59b | ||
|
4f59eb52e1 | ||
|
ad91c54d8d | ||
|
364a7e4e3e | ||
|
f1af717483 | ||
|
c11364fa42 | ||
|
62f9b8f2e1 | ||
|
7360392044 | ||
|
63c39cfb9a | ||
|
6679da97f6 | ||
|
60d228c3d8 | ||
|
000b23f4a8 | ||
|
51d2364e38 | ||
|
52d9083813 | ||
|
54b1d1551d |
BIN
src/assets/image/375/bg-contacts.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
src/assets/image/375/bg-events-calendar.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
src/assets/image/375/bg-news.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
src/assets/image/375/bg-pc.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
src/assets/image/375/bg-stock-quote.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
src/assets/image/375/contacts-bg.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
src/assets/image/375/email-alerts-submit.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
src/assets/image/375/events-calendar-bg.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/image/375/menu-close.png
Normal file
After Width: | Height: | Size: 851 B |
BIN
src/assets/image/375/menu-open.png
Normal file
After Width: | Height: | Size: 640 B |
BIN
src/assets/image/375/product-introduction-icon1.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
src/assets/image/375/product-introduction-icon2.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
src/assets/image/375/product-introduction-icon3.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
src/assets/image/375/product-introduction-img1.png
Normal file
After Width: | Height: | Size: 145 KiB |
BIN
src/assets/image/375/product-introduction-img2.png
Normal file
After Width: | Height: | Size: 136 KiB |
BIN
src/assets/image/375/product-introduction-img3.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/assets/image/375/product-introduction-img4.png
Normal file
After Width: | Height: | Size: 858 KiB |
BIN
src/assets/image/375/product-introduction-img5.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/image/375/product-introduction-img6.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/image/768/menu-close.png
Normal file
After Width: | Height: | Size: 851 B |
BIN
src/assets/image/768/menu-open.png
Normal file
After Width: | Height: | Size: 640 B |
397
src/components/DateWheelPicker.vue
Normal file
@ -0,0 +1,397 @@
|
||||
<template>
|
||||
<div class="picker-mask" @click.self="emit('close')">
|
||||
<div class="picker-panel">
|
||||
<div class="picker-title">
|
||||
Select Time
|
||||
<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
|
||||
class="picker-col day-col"
|
||||
ref="dayColRef"
|
||||
@scroll.passive="(e) => handleWheelScroll('day', e)"
|
||||
@wheel.prevent="(e) => handleWheelStep('day', e)"
|
||||
:style="{
|
||||
scrollPaddingTop: wheelCenterPad + 'px',
|
||||
scrollPaddingBottom: wheelCenterPad + 'px',
|
||||
}"
|
||||
>
|
||||
<div :style="{ height: wheelCenterPad + 'px' }"></div>
|
||||
<div
|
||||
v-for="d in daysInMonth(localYear, localMonth)"
|
||||
:key="'d' + d"
|
||||
class="picker-item"
|
||||
:class="{ active: d === localDay }"
|
||||
:style="{
|
||||
height: wheelItemHeight + 'px',
|
||||
}"
|
||||
@click="localDay = d"
|
||||
>
|
||||
{{ d }}
|
||||
</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, day }
|
||||
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);
|
||||
const localDay = ref(props.modelValue.day);
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(v) => {
|
||||
localYear.value = v.year;
|
||||
localMonth.value = v.month;
|
||||
localDay.value = v.day;
|
||||
nextTick(syncWheelPositions);
|
||||
}
|
||||
);
|
||||
|
||||
const yearColRef = ref(null);
|
||||
const monthColRef = ref(null);
|
||||
const dayColRef = ref(null);
|
||||
|
||||
function daysInMonth(year, month) {
|
||||
return new Date(year, month, 0).getDate();
|
||||
}
|
||||
|
||||
watch([localYear, localMonth], () => {
|
||||
const dim = daysInMonth(localYear.value, localMonth.value);
|
||||
if (localDay.value > dim) localDay.value = dim;
|
||||
if (!isUserScrolling.value) {
|
||||
nextTick(syncWheelPositions);
|
||||
}
|
||||
});
|
||||
|
||||
function syncWheelPositions() {
|
||||
const yearIdx = years.value.indexOf(localYear.value);
|
||||
const monthIdx = localMonth.value - 1;
|
||||
const dayIdx = localDay.value - 1;
|
||||
if (yearColRef.value) yearColRef.value.scrollTop = yearIdx * wheelItemHeight;
|
||||
if (monthColRef.value)
|
||||
monthColRef.value.scrollTop = monthIdx * wheelItemHeight;
|
||||
if (dayColRef.value) dayColRef.value.scrollTop = dayIdx * wheelItemHeight;
|
||||
}
|
||||
|
||||
const scrollTimers = { year: null, month: null, day: null };
|
||||
const isAnimating = { year: false, month: false, day: false };
|
||||
function getCenteredIndex(el) {
|
||||
if (!el) return 0;
|
||||
const raw = (el.scrollTop - wheelCenterPad) / wheelItemHeight;
|
||||
return Math.round(raw);
|
||||
}
|
||||
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, day: dayColRef };
|
||||
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;
|
||||
} else {
|
||||
const dim = daysInMonth(localYear.value, localMonth.value);
|
||||
idx = clamp(idx + direction, 0, dim - 1);
|
||||
localDay.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);
|
||||
} else if (type === "day") {
|
||||
const dim = daysInMonth(localYear.value, localMonth.value);
|
||||
localDay.value = clamp(idx + 1, 1, dim);
|
||||
}
|
||||
isUserScrolling.value = false;
|
||||
}, 120);
|
||||
}
|
||||
|
||||
function confirm() {
|
||||
// 以滚轮当前“水平线”居中项为最终值(基于 DOM 位置,避免像素取整误差)
|
||||
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;
|
||||
}
|
||||
if (dayColRef.value) {
|
||||
const dim = daysInMonth(localYear.value, localMonth.value);
|
||||
const idx = clamp(getCenteredIndexByRect(dayColRef.value) + 1, 1, dim);
|
||||
localDay.value = idx;
|
||||
}
|
||||
// 输出固定为本地日期,不跨时区
|
||||
const payload = {
|
||||
year: localYear.value,
|
||||
month: localMonth.value,
|
||||
day: localDay.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 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; /* align with design padding */
|
||||
right: 16 * 5.12px;
|
||||
height: 1px; /* crisp hairline */
|
||||
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; /* paint above guide lines */
|
||||
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;
|
||||
}
|
||||
.day-col .picker-item {
|
||||
justify-content: flex-end;
|
||||
padding-right: 16 * 5.12px;
|
||||
}
|
||||
.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>
|
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
@ -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>
|
@ -3,7 +3,7 @@ import { computed } from "vue";
|
||||
import { useWindowSize } from "@vueuse/core";
|
||||
|
||||
import size375 from "@/components/customEcharts/size375/index.vue";
|
||||
import size768 from "@/components/customEcharts/size375/index.vue";
|
||||
import size768 from "@/components/customEcharts/size768/index.vue";
|
||||
import size1440 from "@/components/customEcharts/size1440/index.vue";
|
||||
import size1920 from "@/components/customEcharts/size1920/index.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
@ -15,9 +15,9 @@ const { t } = useI18n();
|
||||
|
||||
const viewComponent = computed(() => {
|
||||
const viewWidth = width.value;
|
||||
if (viewWidth <= 500) {
|
||||
if (viewWidth <= 450) {
|
||||
return size375;
|
||||
} else if (viewWidth <= 960) {
|
||||
} else if (viewWidth <= 1100) {
|
||||
return size768;
|
||||
} else if (viewWidth <= 1500) {
|
||||
return size1440;
|
||||
|
624
src/components/customEcharts/size768/index.vue
Normal file
@ -0,0 +1,624 @@
|
||||
<template>
|
||||
<div class="custom-echarts">
|
||||
<div>
|
||||
<div class="echarts-header">
|
||||
<!-- 标题区域 -->
|
||||
<div class="title-section">
|
||||
<div class="title-decoration"></div>
|
||||
<div class="title-text">
|
||||
<span>FiEE, Inc. Stock Price History</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="echarts-search-area">
|
||||
<div class="echarts-search-byRange">
|
||||
<text style="font-size: 0.9rem; font-weight: 400; color: #666666">
|
||||
Range
|
||||
</text>
|
||||
<div class="search-range-list">
|
||||
<div
|
||||
class="search-range-list-each"
|
||||
v-for="(item, index) in state.searchRange"
|
||||
:key="index"
|
||||
:class="{ activeRange: state.activeRange === item }"
|
||||
@click="changeSearchRange(item)"
|
||||
>
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="echarts-search-byDate">
|
||||
<n-date-picker
|
||||
v-model:value="state.dateRange"
|
||||
type="daterange"
|
||||
:is-date-disabled="isDateDisabled"
|
||||
@update:value="handleDateRangeChange"
|
||||
input-readonly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="myEcharts" class="myChart"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, watch, reactive } from "vue";
|
||||
import * as echarts from "echarts";
|
||||
import { NDatePicker, NIcon } from "naive-ui";
|
||||
import { ArrowForwardOutline } from "@vicons/ionicons5";
|
||||
import axios from "axios";
|
||||
const state = reactive({
|
||||
searchRange: ["1m", "3m", "YTD", "1Y", "5Y", "10Y", "Max"],
|
||||
dateRange: [new Date("2009-10-07").getTime(), new Date().getTime()],
|
||||
activeRange: "",
|
||||
});
|
||||
|
||||
let myCharts = null;
|
||||
let historicData = [];
|
||||
let xAxisData = [];
|
||||
|
||||
//初始化eCharts
|
||||
const initEcharts = (data) => {
|
||||
historicData = data;
|
||||
xAxisData = data.map((item) => {
|
||||
return new Date(item.date).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
});
|
||||
const yAxisData = data.map((item) => item.price);
|
||||
// console.error(xAxisData, yAxisData)
|
||||
// 基于准备好的dom,初始化echarts实例
|
||||
myCharts = echarts.init(document.getElementById("myEcharts"), null, {
|
||||
renderer: "canvas",
|
||||
useDirtyRect: true,
|
||||
});
|
||||
// 绘制图表
|
||||
myCharts.setOption({
|
||||
animation: false,
|
||||
progressive: 500,
|
||||
progressiveThreshold: 3000,
|
||||
// title: {
|
||||
// text: 'FiEE, Inc. Stock Price History',
|
||||
// },
|
||||
grid: {
|
||||
left: "8%", // 或 '2%',根据实际情况调整
|
||||
right: "12%", // 给右侧y轴留空间,数值可根据y轴label宽度调整
|
||||
},
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "line",
|
||||
label: {
|
||||
backgroundColor: "#6a7985",
|
||||
},
|
||||
},
|
||||
formatter: function (params) {
|
||||
const p = params[0];
|
||||
return `<span style="font-size: 1.1rem; font-weight: 600;">${p.axisValue}</span><br/><span style="font-size: 0.9rem; font-weight: 400;">Price: ${p.data}</span>`;
|
||||
},
|
||||
triggerOn: "mousemove",
|
||||
confine: true,
|
||||
hideDelay: 1500,
|
||||
},
|
||||
xAxis: {
|
||||
data: xAxisData,
|
||||
type: "category",
|
||||
boundaryGap: false,
|
||||
inverse: true,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: "#CCD6EB",
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: "#323232",
|
||||
fontWeight: "bold",
|
||||
interval: "auto",
|
||||
hideOverlap: true,
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: "value",
|
||||
position: "right",
|
||||
interval: 25,
|
||||
// max: 75.0,
|
||||
show: true,
|
||||
axisLabel: {
|
||||
color: "#323232",
|
||||
fontWeight: "bold",
|
||||
formatter: function (value) {
|
||||
return value > 0 ? value.toFixed(2) : value;
|
||||
},
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: yAxisData,
|
||||
type: "line",
|
||||
sampling: "lttb",
|
||||
symbol: "none",
|
||||
lineStyle: {
|
||||
color: "#CC346C",
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: "linear",
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: "#CC346C",
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: "#F4F6F8",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
markPoint: {
|
||||
symbol: "circle",
|
||||
symbolSize: 20,
|
||||
itemStyle: {
|
||||
color: {
|
||||
type: "radial",
|
||||
x: 0.5,
|
||||
y: 0.5,
|
||||
r: 0.5,
|
||||
colorStops: [
|
||||
{ offset: 0, color: "#CC346C" },
|
||||
{ offset: 0.4, color: "white" },
|
||||
{ offset: 0.4, color: "white" },
|
||||
{ offset: 0.6, color: "rgba(204, 52, 108, 0.30)" },
|
||||
{ offset: 0.8, color: "rgba(204, 52, 108, 0.30)" },
|
||||
{ offset: 1, color: "rgba(255, 123, 172, 0)" },
|
||||
],
|
||||
},
|
||||
},
|
||||
data: [],
|
||||
},
|
||||
progressive: 500,
|
||||
progressiveThreshold: 3000,
|
||||
large: true,
|
||||
largeThreshold: 2000,
|
||||
},
|
||||
],
|
||||
|
||||
dataZoom: [
|
||||
{
|
||||
type: "inside",
|
||||
},
|
||||
{
|
||||
type: "slider",
|
||||
show: true,
|
||||
dataBackground: {
|
||||
lineStyle: {
|
||||
color: "#CC346C",
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: "linear",
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 1, color: "#CC346C" },
|
||||
{ offset: 0, color: "#F4F6F8" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
selectedDataBackground: {
|
||||
lineStyle: {
|
||||
color: "#CC346C",
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: "linear",
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{ offset: 1, color: "#CC346C" },
|
||||
{ offset: 0, color: "#F4F6F8" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
fillerColor: "rgba(44, 98, 136, 0.3)",
|
||||
realtime: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// 监听 showTip 事件,动态显示 markPoint
|
||||
myCharts.on("showTip", function (params) {
|
||||
if (params) {
|
||||
const dataIndex = params.dataIndex;
|
||||
const x = myCharts.getOption().xAxis[0].data[dataIndex];
|
||||
const y = myCharts.getOption().series[0].data[dataIndex];
|
||||
myCharts.setOption({
|
||||
series: [
|
||||
{
|
||||
markPoint: {
|
||||
data: [{ coord: [x, y] }],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
});
|
||||
// 鼠标移出时,清除 markPoint
|
||||
myCharts.on("globalout", function () {
|
||||
myCharts.setOption({
|
||||
series: [
|
||||
{
|
||||
markPoint: {
|
||||
data: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
myCharts.on("dataZoom", function (params) {
|
||||
// 获取当前 dataZoom 范围
|
||||
const option = myCharts.getOption();
|
||||
const xAxisData = option.xAxis[0].data;
|
||||
const dataZoom = option.dataZoom[1] || option.dataZoom[0];
|
||||
|
||||
// 获取 dataZoom 的 startValue 和 endValue
|
||||
let startValue = dataZoom.endValue;
|
||||
let endValue = dataZoom.startValue;
|
||||
|
||||
// 如果是索引,转为日期
|
||||
if (typeof startValue === "number") {
|
||||
startValue = xAxisData[startValue];
|
||||
}
|
||||
if (typeof endValue === "number") {
|
||||
endValue = xAxisData[endValue];
|
||||
}
|
||||
|
||||
// 更新日期选择器
|
||||
state.dateRange = [
|
||||
new Date(startValue).getTime(),
|
||||
new Date(endValue).getTime(),
|
||||
];
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getHistoricalData();
|
||||
});
|
||||
|
||||
//获取历史数据
|
||||
const getHistoricalData = async () => {
|
||||
let now = new Date();
|
||||
let toDate =
|
||||
now.getFullYear() +
|
||||
"-" +
|
||||
String(now.getMonth() + 1).padStart(2, "0") +
|
||||
"-" +
|
||||
String(now.getDate()).padStart(2, "0");
|
||||
let url =
|
||||
"https://common.szjixun.cn/api/stock/history/base/list?from=2009-10-07&to=" +
|
||||
toDate;
|
||||
const res = await axios.get(url);
|
||||
if (res.status === 200) {
|
||||
if (res.data.status === 0) {
|
||||
initEcharts(res.data.data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 适配倒序数据,返回大于等于目标日期的最近一天索引
|
||||
function findClosestDateIndex(data, targetDateStr) {
|
||||
let left = 0,
|
||||
right = data.length - 1;
|
||||
const target = new Date(targetDateStr).getTime();
|
||||
let res = data.length - 1; // 默认返回最后一个
|
||||
while (left <= right) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
const midTime = new Date(data[mid].date).getTime();
|
||||
if (midTime > target) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
res = mid;
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 适配倒序数据,返回小于等于目标日期的最近一天索引
|
||||
function findClosestDateIndexDescLeft(data, targetDateStr) {
|
||||
let left = 0,
|
||||
right = data.length - 1;
|
||||
const target = new Date(targetDateStr).getTime();
|
||||
let res = -1;
|
||||
while (left <= right) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
const midTime = new Date(data[mid].date).getTime();
|
||||
if (midTime > target) {
|
||||
left = mid + 1; // mid 比目标新,往更旧的方向找
|
||||
} else {
|
||||
res = mid; // mid <= target,记录下来,继续往更新的方向找
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
//点击切换搜索区间
|
||||
const changeSearchRange = (range, dateTime) => {
|
||||
state.activeRange = range;
|
||||
const now = new Date();
|
||||
let startDate = "";
|
||||
let endDate = "";
|
||||
if (range === "1m") {
|
||||
const last = new Date(now);
|
||||
last.setMonth(now.getMonth() - 1);
|
||||
startDate = last.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
endDate = new Date(new Date()).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
} else if (range === "3m") {
|
||||
const last = new Date(now);
|
||||
last.setMonth(now.getMonth() - 3);
|
||||
startDate = last.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
endDate = new Date(new Date()).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
} else if (range === "YTD") {
|
||||
startDate = new Date(now.getFullYear(), 0, 1).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
endDate = new Date(new Date()).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
} else if (range === "1Y") {
|
||||
const last = new Date(now);
|
||||
last.setFullYear(now.getFullYear() - 1);
|
||||
startDate = last.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
endDate = new Date(new Date()).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
} else if (range === "5Y") {
|
||||
const last = new Date(now);
|
||||
last.setFullYear(now.getFullYear() - 5);
|
||||
startDate = last.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
endDate = new Date(new Date()).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
} else if (range === "10Y") {
|
||||
const last = new Date(now);
|
||||
last.setFullYear(now.getFullYear() - 10);
|
||||
startDate = last.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
endDate = new Date(new Date()).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
} else if (range === "Max") {
|
||||
startDate = "";
|
||||
endDate = "";
|
||||
} else if (range === "startDateTime") {
|
||||
startDate = dateTime;
|
||||
endDate = "";
|
||||
} else if (range === "endDateTime") {
|
||||
startDate = "";
|
||||
endDate = dateTime;
|
||||
}
|
||||
if (startDate || endDate) {
|
||||
// historicData 和 xAxisData 需在 initEcharts 作用域可用
|
||||
if (
|
||||
typeof historicData !== "undefined" &&
|
||||
typeof xAxisData !== "undefined"
|
||||
) {
|
||||
const zoomOptions = {};
|
||||
let newStartTs = state.dateRange[0];
|
||||
let newEndTs = state.dateRange[1];
|
||||
|
||||
if (startDate) {
|
||||
const idx = findClosestDateIndex(historicData, startDate);
|
||||
const startValue = new Date(historicData[idx].date).toLocaleDateString(
|
||||
"en-US",
|
||||
{
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
}
|
||||
);
|
||||
zoomOptions.endValue = startValue;
|
||||
newStartTs = new Date(startValue).getTime();
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
const idx = findClosestDateIndexDescLeft(historicData, endDate);
|
||||
const endValue = new Date(historicData[idx].date).toLocaleDateString(
|
||||
"en-US",
|
||||
{
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
}
|
||||
);
|
||||
zoomOptions.startValue = endValue;
|
||||
newEndTs = new Date(endValue).getTime();
|
||||
}
|
||||
|
||||
if (Object.keys(zoomOptions).length > 0) {
|
||||
myCharts.setOption({ dataZoom: [zoomOptions, zoomOptions] });
|
||||
}
|
||||
|
||||
state.dateRange = [newStartTs, newEndTs];
|
||||
}
|
||||
} else {
|
||||
myCharts.setOption({
|
||||
dataZoom: {
|
||||
startValue: "",
|
||||
endValue: "",
|
||||
},
|
||||
});
|
||||
|
||||
state.dateRange = [new Date("2009-10-07").getTime(), new Date().getTime()];
|
||||
}
|
||||
};
|
||||
|
||||
// 禁用日期
|
||||
const isDateDisabled = (ts, type, range) => {
|
||||
const minDate = new Date("2009-10-06").getTime();
|
||||
const maxDate = new Date().getTime();
|
||||
|
||||
if (ts < minDate || ts > maxDate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type === "end" && range && range[0]) {
|
||||
return ts < range[0];
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// 切换搜索区间
|
||||
const handleDateRangeChange = (range) => {
|
||||
if (range && range[0] && range[1]) {
|
||||
const startDate = new Date(range[0]).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
const endDate = new Date(range[1]).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
});
|
||||
|
||||
changeSearchRange("startDateTime", startDate);
|
||||
changeSearchRange("endDateTime", endDate);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.custom-echarts {
|
||||
.myChart {
|
||||
width: 100%;
|
||||
height: 25rem;
|
||||
}
|
||||
|
||||
.echarts-header {
|
||||
.title-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16 * 2.5px;
|
||||
margin-bottom: 32 * 2.5px;
|
||||
margin-top: 43 * 2.5px;
|
||||
padding: 0 16 * 2.5px;
|
||||
}
|
||||
|
||||
.title-decoration {
|
||||
width: 58 * 2.5px;
|
||||
height: 7 * 2.5px;
|
||||
background: #ff7bac;
|
||||
margin: auto 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 32 * 2.5px;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.03em;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.echarts-search-area {
|
||||
padding: 0 16 * 2.5px 0 16 * 2.5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 10 * 2.5px;
|
||||
|
||||
.echarts-search-byRange {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 10 * 2.5px;
|
||||
.search-range-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 16 * 2.5px;
|
||||
.search-range-list-each {
|
||||
padding: 7 * 2.5px 22 * 2.5px;
|
||||
border-radius: 5px;
|
||||
background-color: #f3f4f6;
|
||||
cursor: pointer;
|
||||
span {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
.activeRange {
|
||||
color: #fff;
|
||||
background: #ff7bac;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.echarts-search-byDate {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 0.4rem;
|
||||
.n-date-picker {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -15,7 +15,7 @@ const { t } = useI18n();
|
||||
|
||||
const viewComponent = computed(() => {
|
||||
const viewWidth = width.value;
|
||||
if (viewWidth <= 500) {
|
||||
if (viewWidth <= 450) {
|
||||
return size375;
|
||||
} else if (viewWidth <= 1100) {
|
||||
return size768;
|
||||
|
@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<!-- 通用页脚 -->
|
||||
<div class="custom-footer">
|
||||
<span>© 2025 FiEE, Inc. All Rights Reserved.</span>
|
||||
<div class="footer-links-box">
|
||||
<div class="footer-links">
|
||||
<span @click="handleLink('privacyPolicy')">Privacy Policy</span>
|
||||
@ -9,6 +8,7 @@
|
||||
<span @click="handleLink('siteMap')">Site Map</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-copyright">2025 FiEE, Inc. All Rights Reserved.</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -29,7 +29,7 @@ const handleLink = (link) => {
|
||||
// } else if (link === "siteMap") {
|
||||
// window.open(siteMap, "_blank");
|
||||
// }
|
||||
router.push(link)
|
||||
router.push(link);
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -50,14 +50,13 @@ const handleLink = (link) => {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0.6rem 0 0;
|
||||
|
||||
.footer-links {
|
||||
span {
|
||||
border-right: 1px solid #d2d2d7;
|
||||
padding: 0 0.8rem;
|
||||
padding: 0 16 * 5.12px;
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
font-size: 14 * 5.12px;
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
}
|
||||
@ -66,5 +65,11 @@ const handleLink = (link) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer-copyright {
|
||||
margin-top: 12 * 5.12px;
|
||||
font-size: 14 * 5.12px;
|
||||
color: 455363;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -160,13 +160,13 @@ const handleToHome = () => {
|
||||
position: relative;
|
||||
margin: 0 10px;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 12px;
|
||||
min-width: 120px;
|
||||
text-align: center;
|
||||
font-family: "PingFang SC";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: normal;
|
||||
font-size: 16px;
|
||||
min-width: 120px;
|
||||
text-align: center;
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
@ -153,12 +153,13 @@ const handleToHome = () => {
|
||||
position: relative;
|
||||
margin: 0 20px;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 700;
|
||||
// font-size: 16px;
|
||||
font-size: 1.05rem;
|
||||
min-width: 120px;
|
||||
text-align: center;
|
||||
|
||||
font-family: "PingFang SC";
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: normal;
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
@ -17,12 +17,18 @@
|
||||
:class="{ 'menu-open': showMenu }"
|
||||
@click="toggleMenu"
|
||||
>
|
||||
<n-icon size="28" class="menu-icon menu-icon-menu">
|
||||
<menu-sharp />
|
||||
</n-icon>
|
||||
<n-icon size="28" class="menu-icon menu-icon-close">
|
||||
<close-sharp />
|
||||
</n-icon>
|
||||
<img
|
||||
v-if="showMenu"
|
||||
src="@/assets/image/375/menu-close.png"
|
||||
alt="menu"
|
||||
class="menu-icon"
|
||||
/>
|
||||
<img
|
||||
v-else
|
||||
src="@/assets/image/375/menu-open.png"
|
||||
alt="menu"
|
||||
class="menu-icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</NLayoutHeader>
|
||||
@ -53,11 +59,11 @@ import { useRouter } from "vue-router";
|
||||
import { useHeaderMenuConfig } from "@/config/headerMenuConfig";
|
||||
const themeOverrides = {
|
||||
Menu: {
|
||||
itemTextColorHover: "#ff7bac",
|
||||
itemTextColorActive: "#ff7bac",
|
||||
itemTextColorActiveHover: "#ff7bac",
|
||||
itemColorHover: "#fff8fb",
|
||||
itemColorActive: "#fff8fb",
|
||||
itemTextColorHover: "#000",
|
||||
itemTextColorActive: "#FF7BAC",
|
||||
itemTextColorActiveHover: "#fff8fb",
|
||||
itemColorHover: "#FDDFE9",
|
||||
itemColorActive: "#fff",
|
||||
itemColorActiveHover: "#fff8fb",
|
||||
},
|
||||
};
|
||||
@ -125,7 +131,7 @@ const handleToHome = () => {
|
||||
.custom-header {
|
||||
transition: all 0.3s ease;
|
||||
background: transparent;
|
||||
height: 320px;
|
||||
height: 60 * 5.12px;
|
||||
|
||||
&.header-scrolled {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
@ -152,8 +158,7 @@ const handleToHome = () => {
|
||||
}
|
||||
|
||||
.logo :deep(.n-image) {
|
||||
max-width: 100px;
|
||||
height: auto;
|
||||
width: 94 * 5.12px;
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
@ -168,6 +173,7 @@ const handleToHome = () => {
|
||||
user-select: none;
|
||||
transition: background 0.2s;
|
||||
position: relative;
|
||||
width: 56 * 5.12px;
|
||||
|
||||
.menu-icon {
|
||||
position: absolute;
|
||||
@ -198,15 +204,14 @@ const handleToHome = () => {
|
||||
|
||||
.mobile-menu-wrapper {
|
||||
position: fixed;
|
||||
top: 320px;
|
||||
top: 60 * 5.12px;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
z-index: 1100;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
|
||||
padding: 40px 0 80px 0;
|
||||
// max-height: 1500px;
|
||||
// box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
|
||||
padding: 40 * 5.12px 0;
|
||||
max-height: 1500 * 5.12px;
|
||||
overflow-y: auto;
|
||||
|
||||
// 设置CSS变量,只影响当前组件的菜单
|
||||
|
@ -13,12 +13,18 @@
|
||||
:class="{ 'menu-open': showMenu }"
|
||||
@click="toggleMenu"
|
||||
>
|
||||
<n-icon size="28" class="menu-icon menu-icon-menu">
|
||||
<menu-sharp />
|
||||
</n-icon>
|
||||
<n-icon size="28" class="menu-icon menu-icon-close">
|
||||
<close-sharp />
|
||||
</n-icon>
|
||||
<img
|
||||
v-if="showMenu"
|
||||
src="@/assets/image/768/menu-close.png"
|
||||
alt="menu"
|
||||
class="menu-icon"
|
||||
/>
|
||||
<img
|
||||
v-else
|
||||
src="@/assets/image/768/menu-open.png"
|
||||
alt="menu"
|
||||
class="menu-icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</NLayoutHeader>
|
||||
@ -164,6 +170,7 @@ const handleToHome = () => {
|
||||
user-select: none;
|
||||
transition: background 0.2s;
|
||||
position: relative;
|
||||
width: 56 * 2.5px;
|
||||
|
||||
.menu-icon {
|
||||
position: absolute;
|
||||
|
@ -74,7 +74,7 @@ function copyEmail() {
|
||||
gap: 4px;
|
||||
background-color: white;
|
||||
border-radius: 1rem;
|
||||
background-image: url("@/assets/image/1920/contacts-bg.png");
|
||||
background-image: url("@/assets/image/1440/contacts-bg.png");
|
||||
background-size: 64% auto;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
|
@ -1,73 +1,162 @@
|
||||
<script setup>
|
||||
import { NCarousel, NDivider, NMarquee, NPopselect } from "naive-ui";
|
||||
import { onUnmounted, ref, watch, onMounted, computed } from "vue";
|
||||
|
||||
function copyEmail() {
|
||||
navigator.clipboard.writeText("fiee@dlkadvisory.com");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main
|
||||
ref="main"
|
||||
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="w-full flex flex-col items-center gap-4 px-2">
|
||||
<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 class="contact-container">
|
||||
<!-- Title Section -->
|
||||
<div class="title-section">
|
||||
<div class="title-decoration"></div>
|
||||
<div class="contact-title">Investor Contacts</div>
|
||||
</div>
|
||||
<div
|
||||
class="text-base text-#ff7bac animate-fade-in-down animate-delay-400"
|
||||
>
|
||||
Investor Relations
|
||||
</div>
|
||||
<div
|
||||
class="text-sm text-gray-600 flex items-center gap-1 animate-fade-in-down animate-delay-600"
|
||||
>
|
||||
|
||||
<!-- Card Section -->
|
||||
<div class="contact-card">
|
||||
<img
|
||||
class="card-overlay"
|
||||
src="@/assets/image/375/contacts-bg.png"
|
||||
alt=""
|
||||
/>
|
||||
<div class="logo-text">FiEE Inc.</div>
|
||||
<div class="relation-text">Investor Relations</div>
|
||||
<div class="email-section">
|
||||
<span>Email:</span>
|
||||
<span
|
||||
class="transition-colors duration-300 cursor-pointer text-#00baff hover:text-primary active:text-secondary select-all"
|
||||
@click="copyEmail"
|
||||
<span class="email-address" @click="copyEmail"
|
||||
>fiee@dlkadvisory.com</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
transform: translateY(-20 * 5.12px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
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>
|
||||
|
@ -1,67 +1,148 @@
|
||||
<script setup>
|
||||
import { NCarousel, NDivider, NMarquee, NPopselect } from "naive-ui";
|
||||
import { onUnmounted, ref, watch, onMounted, computed } from "vue";
|
||||
|
||||
function copyEmail() {
|
||||
navigator.clipboard.writeText("fiee@dlkadvisory.com");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main
|
||||
ref="main"
|
||||
class="flex flex-col items-center from-primary to-accent w-[100vw] mt-12 animate-fade-in px-6 py-10 pt-500px"
|
||||
>
|
||||
<div class="w-full flex flex-col items-center gap-5 px-4">
|
||||
<h1
|
||||
class="text-3xl font-bold text-primary animate-fade-in-down animate-delay-0"
|
||||
>
|
||||
Investor Contacts
|
||||
</h1>
|
||||
<div
|
||||
class="text-xl font-semibold text-gray-800 animate-fade-in-down animate-delay-200"
|
||||
>
|
||||
FiEE Inc.
|
||||
<div class="contact-container">
|
||||
<!-- Title Section -->
|
||||
<div class="title-section">
|
||||
<div class="title-decoration"></div>
|
||||
<div class="contact-title">Investor Contacts</div>
|
||||
</div>
|
||||
<div class="text-lg text-#ff7bac animate-fade-in-down animate-delay-400">
|
||||
Investor Relations
|
||||
</div>
|
||||
<div
|
||||
class="text-base text-gray-600 flex items-center gap-2 animate-fade-in-down animate-delay-600"
|
||||
>
|
||||
|
||||
<!-- Card Section -->
|
||||
<div class="contact-card">
|
||||
<div class="logo-text">FiEE Inc.</div>
|
||||
<div class="relation-text">Investor Relations</div>
|
||||
<div class="email-section">
|
||||
<span>Email:</span>
|
||||
<span
|
||||
class="transition-colors duration-300 cursor-pointer text-#00baff hover:text-primary active:text-secondary select-all"
|
||||
@click="copyEmail"
|
||||
<span class="email-address" @click="copyEmail"
|
||||
>fiee@dlkadvisory.com</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.contact-container {
|
||||
width: 650 * 2.5px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16 * 2.5px;
|
||||
padding: 0 16 * 2.5px;
|
||||
margin-bottom: 32 * 2.5px;
|
||||
}
|
||||
|
||||
.title-decoration {
|
||||
width: 58 * 2.5px;
|
||||
height: 7 * 2.5px;
|
||||
background: #ff7bac;
|
||||
margin: auto 0;
|
||||
margin-top: 43 * 2.5px;
|
||||
}
|
||||
|
||||
.contact-title {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 32 * 2.5px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 0.03em;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.contact-card {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 511 * 2.5px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background-color: white;
|
||||
border-radius: 1rem;
|
||||
background-image: url("@/assets/image/768/contacts-bg.png");
|
||||
background-size: 64% auto;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
box-shadow: 0px 3 * 2.5px 14 * 2.5px 0px rgba(0, 0, 0, 0.16);
|
||||
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: 110 * 2.5px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: normal;
|
||||
letter-spacing: 0.48 * 2.5px;
|
||||
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: 24 * 2.5px;
|
||||
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: 18 * 2.5px;
|
||||
color: #4a5568;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8 * 2.5px;
|
||||
animation: fade-in-down 0.8s cubic-bezier(0.23, 1, 0.32, 1) both;
|
||||
animation-delay: 0.6s;
|
||||
margin-top: 16 * 2.5px;
|
||||
}
|
||||
|
||||
.email-address {
|
||||
color: #ff7bac;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-in-down {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
transform: translateY(-20 * 2.5px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
|
@ -245,7 +245,7 @@ async function handleSubmit(e) {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url("@/assets/image/1920/email-alerts-submit.png");
|
||||
background-image: url("@/assets/image/1440/email-alerts-submit.png");
|
||||
background-repeat: no-repeat;
|
||||
background-position: bottom;
|
||||
background-size: 100%;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { NCarousel, NDivider, NMarquee, NPopselect } from "naive-ui";
|
||||
import { onUnmounted, ref, watch, onMounted, computed } from "vue";
|
||||
import { ref } from "vue";
|
||||
|
||||
import axios from "axios";
|
||||
const form = ref({
|
||||
firstName: "",
|
||||
@ -8,9 +8,8 @@ const form = ref({
|
||||
email: "",
|
||||
company: "",
|
||||
phone: "",
|
||||
alertType: "all",
|
||||
});
|
||||
const submitted = ref(false);
|
||||
const submitted = ref(true);
|
||||
|
||||
async function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
@ -26,132 +25,271 @@ async function handleSubmit(e) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main ref="main">
|
||||
<main
|
||||
class="min-h-70vh flex flex-col items-center justify-center relative px-6 py-10"
|
||||
>
|
||||
<div class="alerts-container">
|
||||
<!-- Title Section -->
|
||||
<!-- 未提交 -->
|
||||
<div v-if="!submitted" class="title-section">
|
||||
<div class="title-decoration"></div>
|
||||
<div class="title">E-Mail Alerts</div>
|
||||
<div class="subtitle">* Required Fields</div>
|
||||
</div>
|
||||
<!-- 已提交 -->
|
||||
<div v-else class="title-section">
|
||||
<div class="title-decoration"></div>
|
||||
<div class="title">Submitted successfully!</div>
|
||||
<div class="subtitle">The information you submitted is as follows:</div>
|
||||
</div>
|
||||
<!-- Form Card -->
|
||||
<div
|
||||
class="w-[840px] max-w-90vw p-6 bg-white/95 rounded-2xl shadow-lg animate-bounce-in"
|
||||
class="form-card relative"
|
||||
:style="{
|
||||
width: '100%',
|
||||
height: submitted ? '593px' : 'auto',
|
||||
}"
|
||||
>
|
||||
<template v-if="!submitted">
|
||||
<h2
|
||||
class="text-2xl font-bold text-#ff7bac mb-3 text-center tracking-wide"
|
||||
>
|
||||
E-Mail Alerts
|
||||
</h2>
|
||||
<p class="text-sm text-gray-500 mb-5 text-center">
|
||||
* Required Fields
|
||||
</p>
|
||||
<form class="flex flex-col gap-4" @submit="handleSubmit">
|
||||
<div>
|
||||
<label class="block text-gray-700 font-semibold mb-1.5 text-base"
|
||||
>* First Name</label
|
||||
>
|
||||
<form class="form-content" @submit="handleSubmit">
|
||||
<div class="form-group mt-[36px]">
|
||||
<label for="firstName">* First Name</label>
|
||||
<input
|
||||
id="firstName"
|
||||
v-model="form.firstName"
|
||||
type="text"
|
||||
class="w-full px-3 py-2 rounded-lg ring-4 ring-#ff7bac/20) transition-all duration-300 outline-none bg-white/90 border-none"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-gray-700 font-semibold mb-1.5 text-base"
|
||||
>* Last Name</label
|
||||
>
|
||||
<input
|
||||
v-model="form.lastName"
|
||||
type="text"
|
||||
class="w-full px-3 py-2 rounded-lg ring-4 ring-#ff7bac/20) transition-all duration-300 outline-none bg-white/90 border-none"
|
||||
/>
|
||||
<div class="form-group">
|
||||
<label for="lastName">* Last Name</label>
|
||||
<input id="lastName" v-model="form.lastName" type="text" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-gray-700 font-semibold mb-1.5 text-base"
|
||||
>* Email</label
|
||||
>
|
||||
<input
|
||||
v-model="form.email"
|
||||
type="email"
|
||||
class="w-full px-3 py-2 rounded-lg ring-4 ring-#ff7bac/20) transition-all duration-300 outline-none bg-white/90 border-none"
|
||||
/>
|
||||
<div class="form-group">
|
||||
<label for="email">* Email</label>
|
||||
<input id="email" v-model="form.email" type="email" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-gray-700 font-semibold mb-1.5 text-base"
|
||||
>* Company</label
|
||||
>
|
||||
<input
|
||||
v-model="form.company"
|
||||
type="text"
|
||||
class="w-full px-3 py-2 rounded-lg ring-4 ring-#ff7bac/20) transition-all duration-300 outline-none bg-white/90 border-none"
|
||||
/>
|
||||
<div class="form-group">
|
||||
<label for="company">* Company</label>
|
||||
<input id="company" v-model="form.company" type="text" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-gray-700 font-semibold mb-1.5 text-base"
|
||||
>Phone</label
|
||||
>
|
||||
<input
|
||||
v-model="form.phone"
|
||||
type="tel"
|
||||
class="w-full px-3 py-2 rounded-lg ring-4 ring-#ff7bac/20) transition-all duration-300 outline-none bg-white/90 border-none"
|
||||
/>
|
||||
<div class="form-group">
|
||||
<label for="phone">* Phone</label>
|
||||
<input id="phone" v-model="form.phone" type="tel" />
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full py-3.5 rounded-xl text-white font-bold text-lg active:scale-95 transition-all duration-200 animate-bounce-in animate-delay-200 mt-3 submit-btn"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
<button type="submit" class="submit-btn">Submit</button>
|
||||
</form>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
class="flex flex-col items-center justify-center min-h-[240px] animate-bounce-in"
|
||||
>
|
||||
<span
|
||||
class="i-mdi:check-circle-outline text-green-500 text-5xl mb-4"
|
||||
></span>
|
||||
<h2 class="text-xl font-bold text-#ff7bac mb-3">
|
||||
Submitted successfully!
|
||||
</h2>
|
||||
<div class="text-gray-700 text-base mb-4">
|
||||
The information you submitted is as follows:
|
||||
<div class="submitted-data">
|
||||
<div class="submitted-data-content">
|
||||
<div class="submitted-row">
|
||||
<span class="label">First Name:</span>
|
||||
<span class="value">{{ form.firstName || "Not filled in" }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="w-full bg-white/90 rounded-xl shadow p-4 space-y-2 text-gray-800 text-base"
|
||||
>
|
||||
<div>
|
||||
<span class="font-semibold">First Name:</span
|
||||
>{{ form.firstName }}
|
||||
<div class="submitted-row">
|
||||
<span class="label">Last Name:</span>
|
||||
<span class="value">{{ form.lastName || "Not filled in" }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-semibold">Last Name:</span
|
||||
>{{ form.lastName }}
|
||||
<div class="submitted-row">
|
||||
<span class="label">Email:</span>
|
||||
<span class="value">{{ form.email || "Not filled in" }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-semibold">Email:</span>{{ form.email }}
|
||||
<div class="submitted-row">
|
||||
<span class="label">Company:</span>
|
||||
<span class="value">{{ form.company || "Not filled in" }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-semibold">Company:</span>{{ form.company }}
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-semibold">Phone:</span
|
||||
>{{ form.phone || "(Not filled)" }}
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-semibold">Alert Type:</span
|
||||
>{{
|
||||
form.alertType === "all" ? "All Alerts" : "Customize Alerts"
|
||||
}}
|
||||
<div class="submitted-row">
|
||||
<span class="label">Phone:</span>
|
||||
<span class="value">{{ form.phone || "Not filled in" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="submitted-bg"></div>
|
||||
</template>
|
||||
</div>
|
||||
</main>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* Keep tablet background simple */
|
||||
.alerts-container {
|
||||
width: 650 * 2.5px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
margin-top: 38 * 2.5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8 * 2.5px;
|
||||
padding: 0 16 * 2.5px;
|
||||
margin-bottom: 32 * 2.5px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.title-decoration {
|
||||
width: 58 * 2.5px;
|
||||
height: 7 * 2.5px;
|
||||
background: #ff7bac;
|
||||
margin: auto 0;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 32 * 2.5px;
|
||||
line-height: 1.4em;
|
||||
color: #000000;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.375em;
|
||||
color: #455363;
|
||||
letter-spacing: 0.48 * 2.5px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
border-radius: 16 * 2.5px;
|
||||
padding: 24 * 2.5px;
|
||||
box-shadow: 0px 3 * 2.5px 14 * 2.5px 0px rgba(0, 0, 0, 0.16);
|
||||
animation: fade-in 0.8s cubic-bezier(0.23, 1, 0.32, 1) both;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16 * 2.5px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8 * 2.5px;
|
||||
|
||||
label {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.375em;
|
||||
color: #000000;
|
||||
letter-spacing: 0.48 * 2.5px;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 38 * 2.5px;
|
||||
border: 1 * 2.5px solid #e0e0e6;
|
||||
border-radius: 8 * 2.5px;
|
||||
padding: 0 12 * 2.5px;
|
||||
font-size: 14 * 2.5px;
|
||||
outline: none;
|
||||
transition: border-color 0.3s;
|
||||
|
||||
&:focus {
|
||||
border-color: #ff7bac;
|
||||
}
|
||||
}
|
||||
}
|
||||
.submit-btn {
|
||||
background: linear-gradient(to right, #ff7bac, #00ffff);
|
||||
height: 60 * 2.5px;
|
||||
background: #ff7bac;
|
||||
border-radius: 8 * 2.5px;
|
||||
border: none;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 24 * 2.5px;
|
||||
line-height: 32 * 2.5px;
|
||||
color: white;
|
||||
letter-spacing: 1.2 * 2.5px;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s;
|
||||
margin-top: 8 * 2.5px; // 16px (from figma form-group gap) + 8px = 24px
|
||||
margin-bottom: 16 * 2.5px;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.success-title {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #ff7bac;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.success-info {
|
||||
margin-bottom: 24px;
|
||||
color: #455363;
|
||||
}
|
||||
|
||||
.submitted-data {
|
||||
padding: 36 * 2.5px 24 * 2.5px 24 * 2.5px;
|
||||
border-radius: 16 * 2.5px;
|
||||
width: 678 * 2.5px;
|
||||
height: 428 * 2.5px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.submitted-bg {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url("@/assets/image/768/email-alerts-submit.png");
|
||||
background-repeat: no-repeat;
|
||||
background-position: bottom;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.submitted-data-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16 * 2.5px;
|
||||
padding: 16 * 2.5px 0;
|
||||
}
|
||||
|
||||
.submitted-row {
|
||||
display: flex;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.375em;
|
||||
letter-spacing: 0.48 * 2.5px;
|
||||
width: 280 * 2.5px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-weight: 500;
|
||||
color: #000000;
|
||||
width: 110 * 2.5px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-weight: 400;
|
||||
color: #455363;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -5,7 +5,7 @@
|
||||
</template>
|
||||
<script setup>
|
||||
import size375 from "@/views/events-calendar/size375/index.vue";
|
||||
import size768 from "@/views/events-calendar/size375/index.vue";
|
||||
import size768 from "@/views/events-calendar/size768/index.vue";
|
||||
import size1440 from "@/views/events-calendar/size1440/index.vue";
|
||||
import size1920 from "@/views/events-calendar/size1920/index.vue";
|
||||
import { computed } from "vue";
|
||||
@ -16,7 +16,7 @@ const viewComponent = computed(() => {
|
||||
const viewWidth = width.value;
|
||||
if (viewWidth <= 450) {
|
||||
return size375;
|
||||
} else if (viewWidth <= 768) {
|
||||
} else if (viewWidth <= 1100) {
|
||||
return size768;
|
||||
} else if (viewWidth <= 1500) {
|
||||
return size1440;
|
||||
|
@ -79,7 +79,7 @@
|
||||
<!-- 背景图片区域 -->
|
||||
<div class="background-image-container">
|
||||
<img
|
||||
src="@/assets/image/1920/events-calendar-bg.png"
|
||||
src="@/assets/image/1440/events-calendar-bg.png"
|
||||
alt="Events Calendar Background"
|
||||
class="background-image"
|
||||
/>
|
||||
@ -200,7 +200,7 @@ const handleSearch = () => {
|
||||
.background-image-container {
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
height: 800px;
|
||||
height: 518 * 1.33px;
|
||||
box-shadow: 0px 3px 14px 0px rgba(0, 0, 0, 0.16);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
|
@ -1,32 +1,57 @@
|
||||
<template>
|
||||
<div class="events-calendar-page">
|
||||
<customDefaultPage>
|
||||
<template #content>
|
||||
<main class="p-[35px] max-w-[1800px] mx-auto">
|
||||
<div class="title mb-[20px]">
|
||||
<main class="page-container">
|
||||
<!-- 标题区域 -->
|
||||
<div class="title-section">
|
||||
<div class="title-decoration"></div>
|
||||
<div class="events-title">
|
||||
{{ t("events_calendar.title") }}
|
||||
</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 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>
|
||||
</template>
|
||||
</customDefaultPage>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import customDefaultPage from "@/components/customDefaultPage/index.vue";
|
||||
import { reactive } from "vue";
|
||||
import { NDatePicker, NButton } from "naive-ui";
|
||||
import { reactive, ref, computed } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import YearMonthWheelPicker from "@/components/YearMonthWheelPicker.vue";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@ -34,55 +59,104 @@ const state = reactive({
|
||||
selectedDateValue: null, //选中值
|
||||
});
|
||||
|
||||
const handleSearch = () => {
|
||||
// 搜索处理逻辑
|
||||
// console.log('搜索:', state.selectedDateValue)
|
||||
};
|
||||
const showPicker = ref(false);
|
||||
const now = new Date();
|
||||
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>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.title {
|
||||
font-size: 113px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
.page-container {
|
||||
width: 343 * 5.12px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
margin-bottom: 24px;
|
||||
.title-section {
|
||||
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;
|
||||
background-color: #f6f7f9;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-date-picker {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.n-date-picker) {
|
||||
width: 100%;
|
||||
.n-input__input {
|
||||
padding: 4px 0;
|
||||
border-radius: 4px;
|
||||
height: 20 * 5.12px;
|
||||
margin: 32 * 5.12px 0;
|
||||
padding: 0 16 * 5.12px;
|
||||
.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 {
|
||||
display: flex;
|
||||
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-button) {
|
||||
width: 260px;
|
||||
padding: 20px 16px;
|
||||
border-radius: 4px;
|
||||
.content-area {
|
||||
background: #fff;
|
||||
height: 456 * 5.12px;
|
||||
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;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.search-button {
|
||||
background: #ff7bac;
|
||||
color: #fff;
|
||||
&:hover {
|
||||
background: #ff7bac;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.empty-state-image {
|
||||
width: 243 * 5.12px;
|
||||
height: 138 * 5.12px;
|
||||
}
|
||||
</style>
|
||||
|
218
src/views/events-calendar/size768/index.vue
Normal file
@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div class="events-calendar-page">
|
||||
<main class="page-container">
|
||||
<div class="events-container">
|
||||
<!-- 标题区域 -->
|
||||
<div class="title-section">
|
||||
<div class="title-decoration"></div>
|
||||
<div class="events-title">
|
||||
{{ t("events_calendar.title") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-container">
|
||||
<div class="date-picker-wrapper">
|
||||
<n-date-picker
|
||||
v-model:value="state.selectedDateValue"
|
||||
type="date"
|
||||
class="search-date-picker"
|
||||
placeholder="Select Date"
|
||||
>
|
||||
<template #prefix>
|
||||
<svg
|
||||
class="calendar-icon"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
>
|
||||
<g clip-path="url(#clip0_134_3094)">
|
||||
<path
|
||||
d="M17.5 3.33398H2.5C2.08333 3.33398 1.66667 3.66732 1.66667 4.08398V17.5007C1.66667 17.9173 2.08333 18.2507 2.5 18.2507H17.5C17.9167 18.2507 18.3333 17.9173 18.3333 17.5007V4.08398C18.3333 3.66732 17.9167 3.33398 17.5 3.33398Z"
|
||||
stroke="#78777B"
|
||||
stroke-width="1.25"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="M13.3333 1.66602V5.00018"
|
||||
stroke="#78777B"
|
||||
stroke-width="1.25"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="M6.66669 1.66602V5.00018"
|
||||
stroke="#78777B"
|
||||
stroke-width="1.25"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path>
|
||||
<path
|
||||
d="M1.66669 8.33398H18.3334"
|
||||
stroke="#78777B"
|
||||
stroke-width="1.25"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
></path>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_134_3094">
|
||||
<rect
|
||||
width="20"
|
||||
height="20"
|
||||
fill="white"
|
||||
transform="translate(0 0.000976562)"
|
||||
></rect>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
||||
</n-date-picker>
|
||||
</div>
|
||||
<button @click="handleSearch" class="search-button">
|
||||
{{ t("events_calendar.search.button") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 背景图片区域 -->
|
||||
<div class="background-image-container">
|
||||
<img
|
||||
src="@/assets/image/768/events-calendar-bg.png"
|
||||
alt="Events Calendar Background"
|
||||
class="background-image"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from "vue";
|
||||
import { NDatePicker, NButton } from "naive-ui";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const state = reactive({
|
||||
selectedDateValue: null, //选中值
|
||||
});
|
||||
|
||||
const handleSearch = () => {
|
||||
// 搜索处理逻辑
|
||||
// console.log('搜索:', state.selectedDateValue)
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
background-size: 100% 100%;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.events-container {
|
||||
width: 650 * 2.5px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16 * 2.5px;
|
||||
margin-bottom: 32 * 2.5px;
|
||||
padding: 0 16 * 2.5px;
|
||||
}
|
||||
|
||||
.title-decoration {
|
||||
width: 58 * 2.5px;
|
||||
height: 7 * 2.5px;
|
||||
background: #ff7bac;
|
||||
margin: auto 0;
|
||||
margin-top: 43 * 2.5px;
|
||||
}
|
||||
|
||||
.events-title {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 32 * 2.5px;
|
||||
line-height: 1.4em;
|
||||
letter-spacing: 0.03em;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
margin-bottom: 20 * 2.5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16 * 2.5px;
|
||||
padding: 0 16 * 2.5px;
|
||||
}
|
||||
|
||||
.date-picker-wrapper {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-date-picker {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.n-input) {
|
||||
height: 34 * 2.5px;
|
||||
border-radius: 3 * 2.5px;
|
||||
border: 1 * 2.5px solid #e0e0e6;
|
||||
&:hover {
|
||||
border-color: #ff7bac;
|
||||
}
|
||||
}
|
||||
:deep(.n-input--focus) {
|
||||
border-color: #ff7bac;
|
||||
box-shadow: 0 0 0 2px rgba(255, 123, 172, 0.2);
|
||||
}
|
||||
|
||||
.calendar-icon {
|
||||
margin-left: 12 * 2.5px;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
height: 34 * 2.5px;
|
||||
padding: 7 * 2.5px 12 * 2.5px;
|
||||
color: #fff;
|
||||
background-color: #ff7bac;
|
||||
border: none;
|
||||
border-radius: 3 * 2.5px;
|
||||
cursor: pointer;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.375em;
|
||||
letter-spacing: 0.03em;
|
||||
min-width: 160 * 2.5px;
|
||||
|
||||
&:hover {
|
||||
background-color: #e66f9a;
|
||||
}
|
||||
}
|
||||
|
||||
.background-image-container {
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
height: 511 * 2.5px;
|
||||
margin-top: 20 * 2.5px;
|
||||
box-shadow: 0px 3px 14px 0px rgba(0, 0, 0, 0.16);
|
||||
border-radius: 16 * 2.5px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.background-image {
|
||||
width: 300 * 2.5px;
|
||||
height: 170 * 2.5px;
|
||||
flex-shrink: 0;
|
||||
aspect-ratio: 83/47;
|
||||
}
|
||||
</style>
|
@ -498,7 +498,7 @@ const handleClickOutside = (event) => {
|
||||
letter-spacing: 0.03em;
|
||||
color: #455363;
|
||||
margin: 0;
|
||||
padding-left: 16px;
|
||||
padding: 0 16px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
|
@ -496,7 +496,7 @@ const handleClickOutside = (event) => {
|
||||
letter-spacing: 3%;
|
||||
color: #455363;
|
||||
margin: 0;
|
||||
padding-left: 16px;
|
||||
padding: 0 16px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
|
@ -493,7 +493,7 @@ const handleClickOutside = (event) => {
|
||||
letter-spacing: 0.03em;
|
||||
color: #455363;
|
||||
margin: 0;
|
||||
padding-left: 16 * 2.5px;
|
||||
padding: 0 16 * 2.5px;
|
||||
margin-top: 8 * 2.5px;
|
||||
}
|
||||
|
||||
|
@ -1,94 +1,184 @@
|
||||
<template>
|
||||
<div class="historic-data-container" style="margin-bottom: 40px">
|
||||
<div class="historic-data-container">
|
||||
<div class="echarts-container">
|
||||
<customEcharts></customEcharts>
|
||||
</div>
|
||||
<div class="header">
|
||||
<!-- 标题区域 -->
|
||||
<div class="title-section">
|
||||
<div class="title-decoration"></div>
|
||||
<div class="title-text">Historical Data</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header mt-[80px]">
|
||||
<div class="title">Historical Data</div>
|
||||
<div class="filter-container">
|
||||
<!-- <n-dropdown
|
||||
trigger="click"
|
||||
:options="periodOptions"
|
||||
@select="handlePeriodChange"
|
||||
:value="state.selectedPeriod"
|
||||
<span class="range-label">Range</span>
|
||||
<div class="filter-row">
|
||||
<div
|
||||
v-for="option in durationOptions"
|
||||
:key="option.key"
|
||||
class="filter-option"
|
||||
:class="{ active: state.selectedDuration === option.key }"
|
||||
@click="handleDurationChange(option.key)"
|
||||
>
|
||||
<n-button>
|
||||
{{ state.selectedPeriod }}
|
||||
<n-icon><chevron-down-outline /></n-icon>
|
||||
</n-button>
|
||||
</n-dropdown> -->
|
||||
|
||||
<n-dropdown
|
||||
trigger="click"
|
||||
:options="durationOptions"
|
||||
@select="handleDurationChange"
|
||||
:value="state.selectedDuration"
|
||||
{{
|
||||
option.label
|
||||
.replace(" Months", "m")
|
||||
.replace(" Years", "Y")
|
||||
.replace(" Year", "Y")
|
||||
.replace(" to Date", "TD")
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- reports-table from annualreports -->
|
||||
<div class="reports-table">
|
||||
<div class="table-container">
|
||||
<div class="table-header">
|
||||
<div
|
||||
class="column"
|
||||
v-for="col in columns"
|
||||
:key="col.key"
|
||||
:style="{
|
||||
width: col.width ? col.width + 'px' : 'auto',
|
||||
flex: col.width ? '0 0 ' + col.width + 'px' : '1 0 116px',
|
||||
'text-align': col.align,
|
||||
}"
|
||||
>
|
||||
<n-button>
|
||||
{{ state.selectedDuration }}
|
||||
<n-icon><chevron-down-outline /></n-icon>
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
{{ col.title }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="reports-list">
|
||||
<div
|
||||
class="table-row"
|
||||
v-for="(row, index) in paginatedData"
|
||||
:key="index"
|
||||
>
|
||||
<div
|
||||
class="column"
|
||||
v-for="col in columns"
|
||||
:key="col.key"
|
||||
:style="{
|
||||
width: col.width ? col.width + 'px' : 'auto',
|
||||
flex: col.width ? '0 0 ' + col.width + 'px' : '1 0 116px',
|
||||
'text-align': col.align,
|
||||
}"
|
||||
>
|
||||
<span
|
||||
v-if="col.key === 'change'"
|
||||
:style="{
|
||||
color:
|
||||
parseFloat(row.change) < 0
|
||||
? '#cf3050'
|
||||
: parseFloat(row.change) > 0
|
||||
? '#18a058'
|
||||
: '',
|
||||
}"
|
||||
>
|
||||
{{ row[col.key] }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ row[col.key] }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<n-data-table
|
||||
:columns="columns"
|
||||
:data="paginatedData"
|
||||
:bordered="false"
|
||||
:single-line="false"
|
||||
:scroll-x="600"
|
||||
/>
|
||||
|
||||
<!-- pagination-container from annualreports -->
|
||||
<div class="pagination-container">
|
||||
<n-button class="page-btn prev-btn" @click="handlePrevPage">
|
||||
<n-icon><chevron-back-outline /></n-icon>
|
||||
</n-button>
|
||||
|
||||
<div class="page-info mr-[40px]">
|
||||
{{ state.currentPage }} of {{ totalPages }}
|
||||
</div>
|
||||
|
||||
<div class="right-controls">
|
||||
<n-dropdown
|
||||
trigger="click"
|
||||
:options="pageSizeOptions"
|
||||
@select="handlePageSizeChange"
|
||||
<div class="pagination-controls">
|
||||
<div class="pagination-buttons">
|
||||
<button
|
||||
class="page-btn prev-btn"
|
||||
:disabled="state.currentPage === 1"
|
||||
@click="goToPrevPage"
|
||||
>
|
||||
<n-button class="rows-dropdown">
|
||||
{{ state.pageSize }} Rows
|
||||
<n-icon><chevron-down-outline /></n-icon>
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
<svg width="5" height="9" viewBox="0 0 5 9" fill="none">
|
||||
<path
|
||||
d="M4 1L1 4.5L4 8"
|
||||
stroke="#455363"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<n-button class="page-btn next-btn" @click="handleNextPage">
|
||||
<n-icon><chevron-forward-outline /></n-icon>
|
||||
</n-button>
|
||||
<template v-for="page in getVisiblePages()" :key="page">
|
||||
<button
|
||||
v-if="page !== '...'"
|
||||
class="page-btn"
|
||||
:class="{ active: page === state.currentPage }"
|
||||
@click="goToPage(page)"
|
||||
>
|
||||
{{ page }}
|
||||
</button>
|
||||
<button v-else class="page-btn disabled" disabled>...</button>
|
||||
</template>
|
||||
|
||||
<button
|
||||
class="page-btn next-btn"
|
||||
:disabled="state.currentPage === totalPages"
|
||||
@click="goToNextPage"
|
||||
>
|
||||
<svg width="5" height="9" viewBox="0 0 5 9" fill="none">
|
||||
<path
|
||||
d="M1 1L4 4.5L1 8"
|
||||
stroke="#455363"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="page-size-selector" @click="togglePageSizeMenu">
|
||||
<span>{{ state.pageSize }}/page</span>
|
||||
<svg width="10" height="5" viewBox="0 0 10 5" fill="none">
|
||||
<path
|
||||
d="M1 1L5 4L9 1"
|
||||
stroke="#455363"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<div v-if="showPageSizeMenu" class="page-size-menu">
|
||||
<div
|
||||
v-for="size in [10, 50, 100, 500, 1000]"
|
||||
:key="size"
|
||||
class="page-size-option"
|
||||
:class="{ active: state.pageSize === size }"
|
||||
@click="handlePageSizeChange(size)"
|
||||
>
|
||||
{{ size }}/page
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pagination-info">
|
||||
Displaying {{ displayRange.start }} - {{ displayRange.end }} of
|
||||
{{ state.tableData.length }} results
|
||||
</div>
|
||||
|
||||
<div class="back-to-top-link">
|
||||
<!-- <div class="back-to-top-link">
|
||||
<a href="#" @click.prevent="scrollToTop">
|
||||
Back to Top
|
||||
<n-icon><arrow-up-outline /></n-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { NDataTable, NButton, NDropdown, NIcon } from "naive-ui";
|
||||
import { reactive, onMounted, h, computed } from "vue";
|
||||
import { NDropdown, NIcon } from "naive-ui";
|
||||
import { reactive, onMounted, h, computed, ref, watch, onUnmounted } from "vue";
|
||||
import axios from "axios";
|
||||
import {
|
||||
ChevronDownOutline,
|
||||
ChevronBackOutline,
|
||||
ChevronForwardOutline,
|
||||
ArrowUpOutline,
|
||||
} from "@vicons/ionicons5";
|
||||
import { ChevronDownOutline, ArrowUpOutline } from "@vicons/ionicons5";
|
||||
import defaultTableData from "../data";
|
||||
// console.log('defaultTableData', defaultTableData)
|
||||
import customEcharts from "@/components/customEcharts/index.vue";
|
||||
|
||||
// 数据筛选选项
|
||||
@ -103,15 +193,15 @@ const periodOptions = [
|
||||
const durationOptions = [
|
||||
{ label: "3 Months", key: "3 Months" },
|
||||
{ label: "6 Months", key: "6 Months" },
|
||||
{ label: "Year to Date", key: "Year to Date" },
|
||||
{ label: "YTD", key: "Year to Date" },
|
||||
{ label: "1 Year", key: "1 Year" },
|
||||
{ label: "5 Years", key: "5 Years" },
|
||||
{ label: "10 Years", key: "10 Years" },
|
||||
// { label: 'Full History', key: 'Full History', disabled: true },
|
||||
];
|
||||
|
||||
// 分页大小选项
|
||||
const pageSizeOptions = [
|
||||
{ label: "10", key: 10 },
|
||||
{ label: "50", key: 50 },
|
||||
{ label: "100", key: 100 },
|
||||
{ label: "500", key: 500 },
|
||||
@ -123,9 +213,12 @@ const state = reactive({
|
||||
selectedDuration: "6 Months",
|
||||
tableData: [],
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
pageSize: 10,
|
||||
gotoPage: 1,
|
||||
});
|
||||
|
||||
const showPageSizeMenu = ref(false);
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(state.tableData.length / state.pageSize);
|
||||
@ -145,47 +238,49 @@ const columns = [
|
||||
key: "date",
|
||||
align: "left",
|
||||
fixed: "left",
|
||||
width: 150,
|
||||
width: 152,
|
||||
},
|
||||
{
|
||||
title: "Open",
|
||||
key: "open",
|
||||
align: "center",
|
||||
width: 116,
|
||||
},
|
||||
{
|
||||
title: "High",
|
||||
key: "high",
|
||||
align: "center",
|
||||
width: 116,
|
||||
},
|
||||
{
|
||||
title: "Low",
|
||||
key: "low",
|
||||
align: "center",
|
||||
width: 116,
|
||||
},
|
||||
{
|
||||
title: "Close",
|
||||
key: "close",
|
||||
align: "center",
|
||||
width: 116,
|
||||
},
|
||||
{
|
||||
title: "Adj. Close",
|
||||
key: "adjClose",
|
||||
align: "center",
|
||||
width: 116,
|
||||
},
|
||||
{
|
||||
title: "Change",
|
||||
key: "change",
|
||||
align: "center",
|
||||
render(row) {
|
||||
const value = parseFloat(row.change);
|
||||
const color = value < 0 ? "#ff4d4f" : value > 0 ? "#52c41a" : "";
|
||||
return h("span", { style: { color } }, row.change);
|
||||
},
|
||||
width: 116,
|
||||
},
|
||||
{
|
||||
title: "Volume",
|
||||
key: "volume",
|
||||
align: "center",
|
||||
width: 116,
|
||||
},
|
||||
];
|
||||
|
||||
@ -213,26 +308,62 @@ const handleDurationChange = (key) => {
|
||||
getPageData();
|
||||
};
|
||||
|
||||
// 处理分页
|
||||
const handlePrevPage = () => {
|
||||
if (state.currentPage === 1) {
|
||||
return;
|
||||
const displayRange = computed(() => {
|
||||
const start = (state.currentPage - 1) * state.pageSize + 1;
|
||||
const end = Math.min(
|
||||
state.currentPage * state.pageSize,
|
||||
state.tableData.length
|
||||
);
|
||||
return { start, end };
|
||||
});
|
||||
|
||||
const goToPage = (page) => {
|
||||
if (page >= 1 && page <= totalPages.value) {
|
||||
state.currentPage = page;
|
||||
}
|
||||
};
|
||||
|
||||
const goToPrevPage = () => {
|
||||
if (state.currentPage > 1) {
|
||||
state.currentPage--;
|
||||
};
|
||||
|
||||
const handleNextPage = () => {
|
||||
if (state.currentPage >= totalPages.value) {
|
||||
return;
|
||||
}
|
||||
state.currentPage++;
|
||||
};
|
||||
|
||||
const goToNextPage = () => {
|
||||
if (state.currentPage < totalPages.value) {
|
||||
state.currentPage++;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理分页
|
||||
const handlePageSizeChange = (size) => {
|
||||
state.pageSize = size;
|
||||
state.currentPage = 1; // 重置到第一页
|
||||
};
|
||||
|
||||
const handleGoto = () => {
|
||||
const page = parseInt(state.gotoPage);
|
||||
if (page >= 1 && page <= totalPages.value) {
|
||||
state.currentPage = page;
|
||||
}
|
||||
};
|
||||
|
||||
const togglePageSizeMenu = () => {
|
||||
showPageSizeMenu.value = !showPageSizeMenu.value;
|
||||
};
|
||||
|
||||
const getVisiblePages = () => {
|
||||
const total = totalPages.value;
|
||||
if (total <= 4) {
|
||||
const pages = [];
|
||||
for (let i = 1; i <= total; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
return pages;
|
||||
}
|
||||
return [1, 2, "...", total];
|
||||
};
|
||||
|
||||
// 回到顶部
|
||||
const scrollToTop = () => {
|
||||
// 尝试多种方法
|
||||
@ -248,8 +379,32 @@ const scrollToTop = () => {
|
||||
};
|
||||
onMounted(() => {
|
||||
getPageData();
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
});
|
||||
|
||||
const handleClickOutside = (event) => {
|
||||
if (!event.target.closest(".page-size-selector")) {
|
||||
showPageSizeMenu.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => state.pageSize,
|
||||
() => {
|
||||
state.currentPage = 1;
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => state.currentPage,
|
||||
(newPage) => {
|
||||
state.gotoPage = newPage;
|
||||
}
|
||||
);
|
||||
|
||||
const getPageDefaultData = async () => {
|
||||
try {
|
||||
let url =
|
||||
@ -341,24 +496,25 @@ const getPageData = async () => {
|
||||
String(fromDate.getMonth() + 1).padStart(2, "0") +
|
||||
"-" +
|
||||
String(fromDate.getDate()).padStart(2, "0");
|
||||
// let url = `https://stockanalysis.com/api/symbol/a/OTC-MINM/history?period=${state.selectedPeriod}&range=${range}`
|
||||
let url =
|
||||
"https://common.szjixun.cn/api/stock/history/list?from=" +
|
||||
finalFromDate +
|
||||
"&to=" +
|
||||
toDate;
|
||||
const res = await axios.get(url);
|
||||
// console.error(res)
|
||||
if (res.status === 200) {
|
||||
if (res.data.status === 0) {
|
||||
// 转换为日期格式:"Nov 26, 2024"
|
||||
let resultData = res.data.data.map((item) => {
|
||||
return {
|
||||
date: new Date(item.date).toLocaleDateString("en-US", {
|
||||
date: new Date(item.date.replace(/-/g, "/")).toLocaleDateString(
|
||||
"en-US",
|
||||
{
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
}),
|
||||
}
|
||||
),
|
||||
open: item.open != null ? Number(item.open).toFixed(2) : "",
|
||||
high: item.high != null ? Number(item.high).toFixed(2) : "",
|
||||
low: item.low != null ? Number(item.low).toFixed(2) : "",
|
||||
@ -379,82 +535,83 @@ const getPageData = async () => {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.historic-data-container {
|
||||
padding: 80px;
|
||||
width: 343 * 5.12px;
|
||||
margin: 0 auto;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.title {
|
||||
font-size: 80px;
|
||||
font-size: 40 * 2.5 * 5.12px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
}
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8 * 5.12px;
|
||||
padding: 0 16 * 5.12px;
|
||||
margin-bottom: 32 * 5.12px;
|
||||
|
||||
.range-label {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0.48 * 5.12px;
|
||||
color: #455363;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
.filter-row {
|
||||
width: 311 * 5.12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 60px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 4px;
|
||||
background-color: #ffffff;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 16 * 5.12px;
|
||||
row-gap: 8 * 5.12px;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
.filter-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 6px 12px;
|
||||
font-size: 92px;
|
||||
justify-content: center;
|
||||
height: 34 * 5.12px;
|
||||
border-radius: 3 * 5.12px;
|
||||
background-color: #efefef;
|
||||
cursor: pointer;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.4;
|
||||
color: #000000;
|
||||
transition: all 0.2s ease;
|
||||
width: 93 * 5.12px;
|
||||
|
||||
&.prev-btn {
|
||||
margin-right: auto;
|
||||
&:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
&.next-btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.page-info {
|
||||
font-size: 72px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.right-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.rows-dropdown {
|
||||
font-size: 72px;
|
||||
&.active {
|
||||
background-color: #ff7bac;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.back-to-top-link {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 56px;
|
||||
margin-top: 16 * 5.12px;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
gap: 5 * 5.12px;
|
||||
color: #2563eb;
|
||||
font-size: 92px;
|
||||
font-size: 20 * 5.12px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
|
||||
@ -463,11 +620,300 @@ const getPageData = async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.n-data-table) {
|
||||
.n-data-table-td {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
.reports-table {
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
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);
|
||||
padding: 16 * 5.12px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.table-container::-webkit-scrollbar {
|
||||
height: 8 * 5.12px;
|
||||
}
|
||||
|
||||
.table-container::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4 * 5.12px;
|
||||
}
|
||||
|
||||
.table-container::-webkit-scrollbar-thumb {
|
||||
background: #ccc;
|
||||
border-radius: 4 * 5.12px;
|
||||
}
|
||||
|
||||
.table-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #fff0f5;
|
||||
cursor: pointer;
|
||||
}
|
||||
.column {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0.48 * 5.12px;
|
||||
color: #455363;
|
||||
padding: 16 * 5.12px;
|
||||
position: relative;
|
||||
font-variant-numeric: tabular-nums; /* 让数字等宽对齐 */
|
||||
}
|
||||
.table-header {
|
||||
display: flex;
|
||||
border-radius: 8 * 5.12px;
|
||||
margin-bottom: 4 * 5.12px;
|
||||
align-items: center;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
|
||||
.column {
|
||||
background: #fff0f5;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0.48 * 5.12px;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border-radius: 8 * 5.12px;
|
||||
&:hover .column {
|
||||
background: #fff8fb;
|
||||
}
|
||||
|
||||
// &:last-child {
|
||||
// border-bottom: none;
|
||||
// }
|
||||
|
||||
&:nth-child(even) {
|
||||
margin: 4 * 5.12px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.reports-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4 * 5.12px;
|
||||
}
|
||||
|
||||
.table-row .column:not(:last-child)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
height: 40 * 5.12px;
|
||||
border-right: 1 * 5.12px dashed #e0e0e6;
|
||||
}
|
||||
|
||||
.table-header .column:first-child {
|
||||
border-radius: 8 * 5.12px 0 0 8 * 5.12px;
|
||||
}
|
||||
.table-header .column:last-child,
|
||||
.table-row .column:last-child {
|
||||
border-radius: 0 8 * 5.12px 8 * 5.12px 0;
|
||||
}
|
||||
|
||||
.table-row .column:first-child {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.table-row:hover .column:first-child {
|
||||
background: #fff8fb;
|
||||
}
|
||||
|
||||
// 分页器样式
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 16 * 5.12px;
|
||||
justify-content: flex-end;
|
||||
padding: 0 4 * 5.12px;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.4;
|
||||
color: #455363;
|
||||
text-align: right;
|
||||
margin-top: 16 * 5.12px;
|
||||
margin-bottom: 16 * 5.12px;
|
||||
padding: 0 4 * 5.12px;
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8 * 5.12px;
|
||||
}
|
||||
|
||||
.pagination-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8 * 5.12px;
|
||||
}
|
||||
.page-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28 * 5.12px;
|
||||
height: 28 * 5.12px;
|
||||
border: 1 * 5.12px solid #e0e0e6;
|
||||
border-radius: 3 * 5.12px;
|
||||
background: #ffffff;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.4;
|
||||
color: #455363;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: #ff7bac;
|
||||
color: #ff7bac;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #ff7bac;
|
||||
color: #ff7bac;
|
||||
background: #fff0f5;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.page-size-selector {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18 * 5.12px;
|
||||
padding: 4 * 5.12px 12 * 5.12px;
|
||||
height: 28 * 5.12px;
|
||||
border: 1 * 5.12px solid #e0e0e6;
|
||||
border-radius: 3 * 5.12px;
|
||||
background: #ffffff;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.4;
|
||||
color: #455363;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: #ff7bac;
|
||||
}
|
||||
}
|
||||
|
||||
.page-size-menu {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ffffff;
|
||||
border: 1 * 5.12px solid #e0e0e6;
|
||||
border-radius: 3 * 5.12px;
|
||||
box-shadow: 0 2 * 5.12px 8 * 5.12px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
margin-bottom: 5 * 5.12px;
|
||||
}
|
||||
|
||||
.page-size-option {
|
||||
padding: 8 * 5.12px 12 * 5.12px;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.4;
|
||||
color: #455363;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: #fff0f5;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #fff0f5;
|
||||
color: #ff7bac;
|
||||
}
|
||||
}
|
||||
|
||||
.goto-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8 * 5.12px;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.4;
|
||||
color: #455363;
|
||||
margin-right: 16 * 5.12px;
|
||||
}
|
||||
|
||||
.goto-input {
|
||||
width: 60 * 5.12px;
|
||||
height: 28 * 5.12px;
|
||||
padding: 4 * 5.12px 12 * 5.12px;
|
||||
border: 1 * 5.12px solid #e0e0e6;
|
||||
border-radius: 3 * 5.12px;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.4;
|
||||
color: #455363;
|
||||
text-align: center;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #ff7bac;
|
||||
}
|
||||
}
|
||||
|
||||
.title-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16 * 5.12px;
|
||||
margin-bottom: 32 * 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;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 24 * 5.12px;
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0.03em;
|
||||
color: #000000;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,96 +1,193 @@
|
||||
<template>
|
||||
<div class="historic-data-container" style="margin-bottom: 40px">
|
||||
<div class="historic-data-container">
|
||||
<div class="echarts-container">
|
||||
<customEcharts></customEcharts>
|
||||
</div>
|
||||
<div class="header">
|
||||
<!-- 标题区域 -->
|
||||
<div class="title-section">
|
||||
<div class="title-decoration"></div>
|
||||
<div class="title-text">Historical Data</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header mt-[20px]">
|
||||
<div class="title">Historical Data</div>
|
||||
<div class="filter-container">
|
||||
<!-- <n-dropdown
|
||||
trigger="click"
|
||||
:options="periodOptions"
|
||||
@select="handlePeriodChange"
|
||||
:value="state.selectedPeriod"
|
||||
<span class="range-label">Range</span>
|
||||
<div
|
||||
v-for="option in durationOptions"
|
||||
:key="option.key"
|
||||
class="filter-option"
|
||||
:class="{ active: state.selectedDuration === option.key }"
|
||||
@click="handleDurationChange(option.key)"
|
||||
>
|
||||
<n-button>
|
||||
{{ state.selectedPeriod }}
|
||||
<n-icon><chevron-down-outline /></n-icon>
|
||||
</n-button>
|
||||
</n-dropdown> -->
|
||||
|
||||
<n-dropdown
|
||||
trigger="click"
|
||||
:options="durationOptions"
|
||||
@select="handleDurationChange"
|
||||
:value="state.selectedDuration"
|
||||
{{
|
||||
option.label
|
||||
.replace(" Months", "m")
|
||||
.replace(" Years", "Y")
|
||||
.replace(" Year", "Y")
|
||||
.replace(" to Date", "TD")
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- reports-table from annualreports -->
|
||||
<div class="reports-table">
|
||||
<div class="table-container">
|
||||
<div class="table-header">
|
||||
<div
|
||||
class="column"
|
||||
v-for="col in columns"
|
||||
:key="col.key"
|
||||
:style="{
|
||||
width: col.width ? col.width + 'px' : 'auto',
|
||||
flex: col.width ? 'none' : '1 0 120px',
|
||||
'text-align': col.align,
|
||||
}"
|
||||
>
|
||||
<n-button>
|
||||
{{ state.selectedDuration }}
|
||||
<n-icon><chevron-down-outline /></n-icon>
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
{{ col.title }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="reports-list">
|
||||
<div
|
||||
class="table-row"
|
||||
v-for="(row, index) in paginatedData"
|
||||
:key="index"
|
||||
>
|
||||
<div
|
||||
class="column"
|
||||
v-for="col in columns"
|
||||
:key="col.key"
|
||||
:style="{
|
||||
width: col.width ? col.width + 'px' : 'auto',
|
||||
flex: col.width ? 'none' : '1 0 120px',
|
||||
'text-align': col.align,
|
||||
}"
|
||||
>
|
||||
<span
|
||||
v-if="col.key === 'change'"
|
||||
:style="{
|
||||
color:
|
||||
parseFloat(row.change) < 0
|
||||
? '#cf3050'
|
||||
: parseFloat(row.change) > 0
|
||||
? '#18a058'
|
||||
: '',
|
||||
}"
|
||||
>
|
||||
{{ row[col.key] }}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ row[col.key] }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<n-data-table
|
||||
:columns="columns"
|
||||
:data="paginatedData"
|
||||
:bordered="false"
|
||||
:single-line="false"
|
||||
:scroll-x="1200"
|
||||
/>
|
||||
|
||||
<!-- pagination-container from annualreports -->
|
||||
<div class="pagination-container">
|
||||
<n-button class="page-btn prev-btn" @click="handlePrevPage">
|
||||
<n-icon><chevron-back-outline /></n-icon>
|
||||
Previous
|
||||
</n-button>
|
||||
|
||||
<div class="page-info">
|
||||
Page {{ state.currentPage }} of {{ totalPages }}
|
||||
</div>
|
||||
|
||||
<div class="right-controls">
|
||||
<n-dropdown
|
||||
trigger="click"
|
||||
:options="pageSizeOptions"
|
||||
@select="handlePageSizeChange"
|
||||
<div class="pagination-controls">
|
||||
<div class="pagination-buttons">
|
||||
<button
|
||||
class="page-btn prev-btn"
|
||||
:disabled="state.currentPage === 1"
|
||||
@click="goToPrevPage"
|
||||
>
|
||||
<n-button class="rows-dropdown">
|
||||
{{ state.pageSize }} Rows
|
||||
<n-icon><chevron-down-outline /></n-icon>
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
<svg width="5" height="9" viewBox="0 0 5 9" fill="none">
|
||||
<path
|
||||
d="M4 1L1 4.5L4 8"
|
||||
stroke="#455363"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<n-button class="page-btn next-btn" @click="handleNextPage">
|
||||
Next
|
||||
<n-icon><chevron-forward-outline /></n-icon>
|
||||
</n-button>
|
||||
<template v-for="page in getVisiblePages()" :key="page">
|
||||
<button
|
||||
v-if="page !== '...'"
|
||||
class="page-btn"
|
||||
:class="{ active: page === state.currentPage }"
|
||||
@click="goToPage(page)"
|
||||
>
|
||||
{{ page }}
|
||||
</button>
|
||||
<button v-else class="page-btn disabled" disabled>...</button>
|
||||
</template>
|
||||
|
||||
<button
|
||||
class="page-btn next-btn"
|
||||
:disabled="state.currentPage === totalPages"
|
||||
@click="goToNextPage"
|
||||
>
|
||||
<svg width="5" height="9" viewBox="0 0 5 9" fill="none">
|
||||
<path
|
||||
d="M1 1L4 4.5L1 8"
|
||||
stroke="#455363"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="page-size-selector" @click="togglePageSizeMenu">
|
||||
<span>{{ state.pageSize }}/page</span>
|
||||
<svg width="10" height="5" viewBox="0 0 10 5" fill="none">
|
||||
<path
|
||||
d="M1 1L5 4L9 1"
|
||||
stroke="#455363"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<div v-if="showPageSizeMenu" class="page-size-menu">
|
||||
<div
|
||||
v-for="size in [10, 50, 100, 500, 1000]"
|
||||
:key="size"
|
||||
class="page-size-option"
|
||||
:class="{ active: state.pageSize === size }"
|
||||
@click="handlePageSizeChange(size)"
|
||||
>
|
||||
{{ size }}/page
|
||||
</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 class="pagination-info">
|
||||
Displaying {{ displayRange.start }} - {{ displayRange.end }} of
|
||||
{{ state.tableData.length }} results
|
||||
</div>
|
||||
|
||||
<div class="back-to-top-link">
|
||||
<!-- <div class="back-to-top-link">
|
||||
<a href="#" @click.prevent="scrollToTop">
|
||||
Back to Top
|
||||
<n-icon><arrow-up-outline /></n-icon>
|
||||
</a>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { NDataTable, NButton, NDropdown, NIcon } from "naive-ui";
|
||||
import { reactive, onMounted, h, computed } from "vue";
|
||||
import { NDropdown, NIcon } from "naive-ui";
|
||||
import { reactive, onMounted, h, computed, ref, watch, onUnmounted } from "vue";
|
||||
import axios from "axios";
|
||||
import {
|
||||
ChevronDownOutline,
|
||||
ChevronBackOutline,
|
||||
ChevronForwardOutline,
|
||||
ArrowUpOutline,
|
||||
} from "@vicons/ionicons5";
|
||||
import { ChevronDownOutline, ArrowUpOutline } from "@vicons/ionicons5";
|
||||
import defaultTableData from "../data";
|
||||
// console.log('defaultTableData', defaultTableData)
|
||||
import customEcharts from "@/components/customEcharts/index.vue";
|
||||
|
||||
// 数据筛选选项
|
||||
@ -105,15 +202,15 @@ const periodOptions = [
|
||||
const durationOptions = [
|
||||
{ label: "3 Months", key: "3 Months" },
|
||||
{ label: "6 Months", key: "6 Months" },
|
||||
{ label: "Year to Date", key: "Year to Date" },
|
||||
{ label: "YTD", key: "Year to Date" },
|
||||
{ label: "1 Year", key: "1 Year" },
|
||||
{ label: "5 Years", key: "5 Years" },
|
||||
{ label: "10 Years", key: "10 Years" },
|
||||
// { label: 'Full History', key: 'Full History', disabled: true },
|
||||
];
|
||||
|
||||
// 分页大小选项
|
||||
const pageSizeOptions = [
|
||||
{ label: "10", key: 10 },
|
||||
{ label: "50", key: 50 },
|
||||
{ label: "100", key: 100 },
|
||||
{ label: "500", key: 500 },
|
||||
@ -125,9 +222,12 @@ const state = reactive({
|
||||
selectedDuration: "6 Months",
|
||||
tableData: [],
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
pageSize: 10,
|
||||
gotoPage: 1,
|
||||
});
|
||||
|
||||
const showPageSizeMenu = ref(false);
|
||||
|
||||
// 计算总页数
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(state.tableData.length / state.pageSize);
|
||||
@ -173,16 +273,12 @@ const columns = [
|
||||
title: "Adj. Close",
|
||||
key: "adjClose",
|
||||
align: "center",
|
||||
width: 115,
|
||||
},
|
||||
{
|
||||
title: "Change",
|
||||
key: "change",
|
||||
align: "center",
|
||||
render(row) {
|
||||
const value = parseFloat(row.change);
|
||||
const color = value < 0 ? "#ff4d4f" : value > 0 ? "#52c41a" : "";
|
||||
return h("span", { style: { color } }, row.change);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Volume",
|
||||
@ -215,26 +311,91 @@ const handleDurationChange = (key) => {
|
||||
getPageData();
|
||||
};
|
||||
|
||||
// 处理分页
|
||||
const handlePrevPage = () => {
|
||||
if (state.currentPage === 1) {
|
||||
return;
|
||||
const displayRange = computed(() => {
|
||||
const start = (state.currentPage - 1) * state.pageSize + 1;
|
||||
const end = Math.min(
|
||||
state.currentPage * state.pageSize,
|
||||
state.tableData.length
|
||||
);
|
||||
return { start, end };
|
||||
});
|
||||
|
||||
const goToPage = (page) => {
|
||||
if (page >= 1 && page <= totalPages.value) {
|
||||
state.currentPage = page;
|
||||
}
|
||||
};
|
||||
|
||||
const goToPrevPage = () => {
|
||||
if (state.currentPage > 1) {
|
||||
state.currentPage--;
|
||||
};
|
||||
|
||||
const handleNextPage = () => {
|
||||
if (state.currentPage >= totalPages.value) {
|
||||
return;
|
||||
}
|
||||
state.currentPage++;
|
||||
};
|
||||
|
||||
const goToNextPage = () => {
|
||||
if (state.currentPage < totalPages.value) {
|
||||
state.currentPage++;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理分页
|
||||
const handlePageSizeChange = (size) => {
|
||||
state.pageSize = size;
|
||||
state.currentPage = 1; // 重置到第一页
|
||||
};
|
||||
|
||||
const handleGoto = () => {
|
||||
const page = parseInt(state.gotoPage);
|
||||
if (page >= 1 && page <= totalPages.value) {
|
||||
state.currentPage = page;
|
||||
}
|
||||
};
|
||||
|
||||
const togglePageSizeMenu = () => {
|
||||
showPageSizeMenu.value = !showPageSizeMenu.value;
|
||||
};
|
||||
|
||||
const getVisiblePages = () => {
|
||||
const current = state.currentPage;
|
||||
const total = totalPages.value;
|
||||
const pages = [];
|
||||
|
||||
if (total <= 7) {
|
||||
// 如果总页数小于等于7,显示所有页码
|
||||
for (let i = 1; i <= total; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
} else {
|
||||
// 显示第一页
|
||||
pages.push(1);
|
||||
|
||||
if (current <= 4) {
|
||||
// 当前页在前4页
|
||||
for (let i = 2; i <= 5; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
pages.push("...");
|
||||
pages.push(total);
|
||||
} else if (current >= total - 3) {
|
||||
// 当前页在后4页
|
||||
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;
|
||||
};
|
||||
|
||||
// 回到顶部
|
||||
const scrollToTop = () => {
|
||||
// 尝试多种方法
|
||||
@ -250,8 +411,32 @@ const scrollToTop = () => {
|
||||
};
|
||||
onMounted(() => {
|
||||
getPageData();
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
});
|
||||
|
||||
const handleClickOutside = (event) => {
|
||||
if (!event.target.closest(".page-size-selector")) {
|
||||
showPageSizeMenu.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => state.pageSize,
|
||||
() => {
|
||||
state.currentPage = 1;
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => state.currentPage,
|
||||
(newPage) => {
|
||||
state.gotoPage = newPage;
|
||||
}
|
||||
);
|
||||
|
||||
const getPageDefaultData = async () => {
|
||||
try {
|
||||
let url =
|
||||
@ -343,14 +528,12 @@ const getPageData = async () => {
|
||||
String(fromDate.getMonth() + 1).padStart(2, "0") +
|
||||
"-" +
|
||||
String(fromDate.getDate()).padStart(2, "0");
|
||||
// let url = `https://stockanalysis.com/api/symbol/a/OTC-MINM/history?period=${state.selectedPeriod}&range=${range}`
|
||||
let url =
|
||||
"https://common.szjixun.cn/api/stock/history/list?from=" +
|
||||
finalFromDate +
|
||||
"&to=" +
|
||||
toDate;
|
||||
const res = await axios.get(url);
|
||||
// console.error(res)
|
||||
if (res.status === 200) {
|
||||
if (res.data.status === 0) {
|
||||
// 转换为日期格式:"Nov 26, 2024"
|
||||
@ -381,83 +564,71 @@ const getPageData = async () => {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.historic-data-container {
|
||||
max-width: calc(100% - 300px);
|
||||
width: 650 * 2.5px;
|
||||
margin: 0 auto;
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.title {
|
||||
font-size: 85px;
|
||||
font-size: 40 * 2.5px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 4px;
|
||||
background-color: #ffffff;
|
||||
gap: 16 * 2.5px;
|
||||
padding: 0 16 * 2.5px;
|
||||
margin-bottom: 32 * 2.5px;
|
||||
margin-top: 32 * 2.5px;
|
||||
|
||||
.page-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 6px 12px;
|
||||
font-size: 50px;
|
||||
|
||||
&.prev-btn {
|
||||
margin-right: auto;
|
||||
.range-label {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 16 * 2.5px;
|
||||
line-height: 1.375em;
|
||||
letter-spacing: 3%;
|
||||
color: #455363;
|
||||
}
|
||||
|
||||
&.next-btn {
|
||||
margin-left: 10px;
|
||||
.filter-option {
|
||||
padding: 7 * 2.5px 28 * 2.5px;
|
||||
border-radius: 3 * 2.5px;
|
||||
background-color: #efefef;
|
||||
cursor: pointer;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 16 * 2.5px;
|
||||
line-height: 1.375em;
|
||||
color: #000000;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.page-info {
|
||||
font-size: 40px;
|
||||
color: #374151;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.right-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.rows-dropdown {
|
||||
font-size: 40px;
|
||||
&.active {
|
||||
background-color: #ff7bac;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.back-to-top-link {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
margin-top: 16 * 2.5px;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
gap: 5 * 2.5px;
|
||||
color: #2563eb;
|
||||
font-size: 50px;
|
||||
font-size: 20 * 2.5px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
|
||||
@ -466,11 +637,305 @@ const getPageData = async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.n-data-table) {
|
||||
.n-data-table-td {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
.reports-table {
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
border-radius: 16 * 2.5px;
|
||||
box-shadow: 0px 3px 14px 0px rgba(0, 0, 0, 0.16);
|
||||
padding: 16 * 2.5px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.table-container::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.table-container::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.table-container::-webkit-scrollbar-thumb {
|
||||
background: #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.table-container::-webkit-scrollbar-thumb:hover {
|
||||
background: #fff0f5;
|
||||
cursor: pointer;
|
||||
}
|
||||
.column {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 16 * 2.5px;
|
||||
line-height: 1.375em;
|
||||
letter-spacing: 3%;
|
||||
color: #455363;
|
||||
padding: 16 * 2.5px 16 * 2.5px;
|
||||
position: relative;
|
||||
}
|
||||
.table-header {
|
||||
display: flex;
|
||||
border-radius: 8 * 2.5px;
|
||||
margin-bottom: 4 * 2.5px;
|
||||
align-items: center;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
|
||||
.column {
|
||||
background: #fff0f5;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 16 * 2.5px;
|
||||
line-height: 1.375em;
|
||||
letter-spacing: 3%;
|
||||
color: #000000;
|
||||
}
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border-radius: 8 * 2.5px;
|
||||
&:hover .column {
|
||||
background: #fff8fb;
|
||||
}
|
||||
|
||||
// &:last-child {
|
||||
// border-bottom: none;
|
||||
// }
|
||||
|
||||
&:nth-child(even) {
|
||||
margin: 4px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.reports-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4 * 2.5px;
|
||||
}
|
||||
|
||||
.table-row .column:not(:last-child)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
height: 24 * 2.5px;
|
||||
border-right: 1px dashed #e0e0e6;
|
||||
}
|
||||
|
||||
.table-header .column:first-child,
|
||||
.table-row .column:first-child {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.table-header .column:first-child {
|
||||
border-radius: 8 * 2.5px 0 0 8 * 2.5px;
|
||||
}
|
||||
.table-header .column:last-child,
|
||||
.table-row .column:last-child {
|
||||
border-radius: 0 8 * 2.5px 8 * 2.5px 0;
|
||||
}
|
||||
|
||||
.table-row .column:first-child {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.table-row:hover .column:first-child {
|
||||
background: #fff8fb;
|
||||
}
|
||||
|
||||
// 分页器样式
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 20 * 2.5px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 16 * 2.5px;
|
||||
line-height: 1.4375em;
|
||||
color: #455363;
|
||||
text-align: right;
|
||||
padding: 0 16 * 2.5px;
|
||||
margin-top: 16 * 2.5px;
|
||||
margin-bottom: 16 * 2.5px;
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8 * 2.5px;
|
||||
}
|
||||
|
||||
.pagination-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8 * 2.5px;
|
||||
}
|
||||
.page-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28 * 2.5px;
|
||||
height: 28 * 2.5px;
|
||||
border: 1px solid #e0e0e6;
|
||||
border-radius: 3 * 2.5px;
|
||||
background: #ffffff;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.428em;
|
||||
color: #455363;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: #ff7bac;
|
||||
color: #ff7bac;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #ff7bac;
|
||||
color: #ff7bac;
|
||||
background: #fff0f5;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.page-size-selector {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18 * 2.5px;
|
||||
padding: 4px 12 * 2.5px;
|
||||
height: 28 * 2.5px;
|
||||
border: 1px solid #e0e0e6;
|
||||
border-radius: 3 * 2.5px;
|
||||
background: #ffffff;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.428em;
|
||||
color: #455363;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: #ff7bac;
|
||||
}
|
||||
}
|
||||
|
||||
.page-size-menu {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e0e0e6;
|
||||
border-radius: 3 * 2.5px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
margin-bottom: 2 * 2.5px;
|
||||
}
|
||||
|
||||
.page-size-option {
|
||||
padding: 8px 12 * 2.5px;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.428em;
|
||||
color: #455363;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: #fff0f5;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #fff0f5;
|
||||
color: #ff7bac;
|
||||
}
|
||||
}
|
||||
|
||||
.goto-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8 * 2.5px;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.428em;
|
||||
color: #455363;
|
||||
margin-right: 16 * 2.5px;
|
||||
}
|
||||
|
||||
.goto-input {
|
||||
width: 60 * 2.5px;
|
||||
height: 28 * 2.5px;
|
||||
padding: 4px 12 * 2.5px;
|
||||
border: 1px solid #e0e0e6;
|
||||
border-radius: 3 * 2.5px;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.428em;
|
||||
color: #455363;
|
||||
text-align: center;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #ff7bac;
|
||||
}
|
||||
}
|
||||
|
||||
.title-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16 * 2.5px;
|
||||
margin-bottom: 32 * 2.5px;
|
||||
margin-top: 43 * 2.5px;
|
||||
padding: 0 16 * 2.5px;
|
||||
}
|
||||
|
||||
.title-decoration {
|
||||
width: 58 * 2.5px;
|
||||
height: 7 * 2.5px;
|
||||
background: #ff7bac;
|
||||
margin: auto 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 32 * 2.5px;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.03em;
|
||||
color: #000000;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,24 +1,21 @@
|
||||
<script setup>
|
||||
import size375 from '@/views/index/size375/index.vue'
|
||||
import size768 from '@/views/index/size1920/index.vue'
|
||||
import size1440 from '@/views/index/size1920/index.vue'
|
||||
import size1920 from '@/views/index/size1920/index.vue'
|
||||
import { computed } from 'vue'
|
||||
import { useWindowSize } from '@vueuse/core'
|
||||
import size1920 from "@/views/index/size1920/index.vue";
|
||||
import { computed } from "vue";
|
||||
import { useWindowSize } from "@vueuse/core";
|
||||
|
||||
const { width } = useWindowSize()
|
||||
const { width } = useWindowSize();
|
||||
const viewComponent = computed(() => {
|
||||
const viewWidth = width.value
|
||||
if (viewWidth <= 450) {
|
||||
return size375
|
||||
} else if (viewWidth <= 1100) {
|
||||
return size768
|
||||
} else if (viewWidth <= 1500) {
|
||||
return size1440
|
||||
} else if (viewWidth <= 1920 || viewWidth > 1920) {
|
||||
return size1920
|
||||
}
|
||||
})
|
||||
// const viewWidth = width.value
|
||||
// if (viewWidth <= 450) {
|
||||
// return size375
|
||||
// } else if (viewWidth <= 1100) {
|
||||
// return size768
|
||||
// } else if (viewWidth <= 1500) {
|
||||
// return size1440
|
||||
// } else if (viewWidth <= 1920 || viewWidth > 1920) {
|
||||
return size1920;
|
||||
// }
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,21 +0,0 @@
|
||||
<script setup>
|
||||
import customHeader from "@/components/customHeader/index.vue";
|
||||
import customFooter from "@/components/customFooter/index.vue";
|
||||
import { NScrollbar } from "naive-ui";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col h-100svh">
|
||||
<customHeader />
|
||||
<n-scrollbar
|
||||
class="bg-[url('@/assets/image/bg-mobile.png')] bg-cover bg-center flex-1"
|
||||
>
|
||||
<div>
|
||||
<router-view />
|
||||
</div>
|
||||
</n-scrollbar>
|
||||
<customFooter />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
@ -16,7 +16,7 @@ const viewComponent = computed(() => {
|
||||
const viewWidth = width.value;
|
||||
if (viewWidth <= 450) {
|
||||
return size375;
|
||||
} else if (viewWidth <= 768) {
|
||||
} else if (viewWidth <= 1100) {
|
||||
return size768;
|
||||
} else if (viewWidth <= 1500) {
|
||||
return size1440;
|
||||
|
@ -16,7 +16,7 @@ const viewComponent = computed(() => {
|
||||
const viewWidth = width.value;
|
||||
if (viewWidth <= 450) {
|
||||
return size375;
|
||||
} else if (viewWidth <= 768) {
|
||||
} else if (viewWidth <= 1100) {
|
||||
return size768;
|
||||
} else if (viewWidth <= 1500) {
|
||||
return size1440;
|
||||
|
@ -619,7 +619,7 @@ const handleClickOutside = (event) => {
|
||||
letter-spacing: 0.03em;
|
||||
color: #455363;
|
||||
margin: 0;
|
||||
padding-left: 21px;
|
||||
padding: 0 21px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
|
@ -615,7 +615,7 @@ const handleClickOutside = (event) => {
|
||||
letter-spacing: 3%;
|
||||
color: #455363;
|
||||
margin: 0;
|
||||
padding-left: 17px;
|
||||
padding: 0 16px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
|
@ -1,38 +1,59 @@
|
||||
<template>
|
||||
<div class="press-releases-page">
|
||||
<n-infinite-scroll :distance="0" @load="doLoadMore">
|
||||
<main class="p-[80px] mx-auto" style="max-width: 100vw; min-width: 285px">
|
||||
<div class="title mb-[24px]">
|
||||
<div class="title-section">
|
||||
<div class="title-decoration"></div>
|
||||
<div class="title">
|
||||
{{ t("press_releases.title") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-container">
|
||||
<n-select
|
||||
:options="state.selectOptions"
|
||||
v-model:value="state.selectedValue"
|
||||
class="search-select"
|
||||
:font-size="72"
|
||||
<div class="search-select" @click="openYearPicker">
|
||||
<div class="search-select-label">Year</div>
|
||||
<div class="search-select-icon">
|
||||
<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"
|
||||
/>
|
||||
<n-input
|
||||
v-model:value="state.inputValue"
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
v-model="state.inputValue"
|
||||
type="text"
|
||||
:placeholder="t('press_releases.search.placeholder')"
|
||||
class="search-input"
|
||||
clearable
|
||||
:font-size="72"
|
||||
/>
|
||||
<n-button @click="handleSearch" class="search-button" :font-size="72">
|
||||
<button @click="handleSearch" class="search-button">
|
||||
{{ t("press_releases.search.button") }}
|
||||
</n-button>
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="(item, idx) in state.filterNewsData" :key="idx">
|
||||
<div class="news-item mt-[10px]">
|
||||
<div class="news-item-date">{{ item.date }}</div>
|
||||
<div class="reports-list">
|
||||
<div
|
||||
class="news-item-title text-[#0078d7] cursor-pointer"
|
||||
v-for="(item, idx) in state.filterNewsData"
|
||||
:key="idx"
|
||||
class="news-item table-row"
|
||||
>
|
||||
<div class="content">
|
||||
<div class="file-content">
|
||||
<div class="file-info">
|
||||
<div class="vertical-line"></div>
|
||||
<div
|
||||
class="news-item-title text-[#000] cursor-pointer"
|
||||
style="
|
||||
word-break: break-word;
|
||||
word-break: break-all;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-line-clamp: 1;
|
||||
line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -41,19 +62,34 @@
|
||||
>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<n-tooltip
|
||||
trigger="click"
|
||||
:disabled="!item.showTooltip"
|
||||
width="trigger"
|
||||
<svg
|
||||
class="arrow-icon"
|
||||
width="7"
|
||||
height="14"
|
||||
viewBox="0 0 7 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@click="handleNewClick(item)"
|
||||
>
|
||||
<path
|
||||
d="M1 1L6 7L1 13"
|
||||
stroke="#FF7BAC"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<n-tooltip trigger="hover" :disabled="true" width="trigger">
|
||||
<template #trigger>
|
||||
<div
|
||||
:ref="(el) => setTitleRef(el, idx)"
|
||||
class="news-item-content"
|
||||
class="news-item-content file-description"
|
||||
style="
|
||||
word-break: break-word;
|
||||
word-break: break-all;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -66,17 +102,115 @@
|
||||
{{ item.summary }}
|
||||
</div>
|
||||
</n-tooltip>
|
||||
<div class="download-section">
|
||||
<div class="news-item-date">{{ item.date }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</n-infinite-scroll>
|
||||
</div>
|
||||
<div class="separator-line"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页器 -->
|
||||
<div class="pagination-container" v-if="state.total > 0">
|
||||
<div class="pagination-controls">
|
||||
<div class="pagination-buttons">
|
||||
<button
|
||||
class="page-btn prev-btn"
|
||||
:disabled="state.currentPage === 1"
|
||||
@click="goToPrevPage"
|
||||
>
|
||||
<svg width="5" height="9" viewBox="0 0 5 9" fill="none">
|
||||
<path
|
||||
d="M4 1L1 4.5L4 8"
|
||||
stroke="#455363"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<template v-for="page in getVisiblePages()" :key="page">
|
||||
<button
|
||||
v-if="page !== '...'"
|
||||
class="page-btn"
|
||||
:class="{ active: page === state.currentPage }"
|
||||
@click="goToPage(page)"
|
||||
>
|
||||
{{ page }}
|
||||
</button>
|
||||
<button v-else class="page-btn disabled" disabled>...</button>
|
||||
</template>
|
||||
|
||||
<button
|
||||
class="page-btn next-btn"
|
||||
:disabled="state.currentPage === totalPages"
|
||||
@click="goToNextPage"
|
||||
>
|
||||
<svg width="5" height="9" viewBox="0 0 5 9" fill="none">
|
||||
<path
|
||||
d="M1 1L4 5L1 8"
|
||||
stroke="#455363"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="page-size-selector" @click="togglePageSizeMenu">
|
||||
<span>{{ state.pageSize }}/page</span>
|
||||
<svg width="10" height="5" viewBox="0 0 10 5" fill="none">
|
||||
<path
|
||||
d="M1 1L5 4L9 1"
|
||||
stroke="#455363"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<div v-if="showPageSizeMenu" class="page-size-menu">
|
||||
<div
|
||||
v-for="size in [10, 20, 50]"
|
||||
:key="size"
|
||||
class="page-size-option"
|
||||
:class="{ active: state.pageSize === size }"
|
||||
@click="changePageSize(size)"
|
||||
>
|
||||
{{ size }}/page
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pagination-info" v-if="state.total > 0">
|
||||
Displaying {{ displayRange.start }} - {{ displayRange.end }} of
|
||||
{{ state.total }} results
|
||||
</div>
|
||||
</div>
|
||||
<year-wheel-picker
|
||||
v-if="state.showYearPicker"
|
||||
v-model="state.selectedValue"
|
||||
:options="state.selectOptions"
|
||||
@close="closeYearPicker"
|
||||
@confirm="onYearConfirm"
|
||||
></year-wheel-picker>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import customDefaultPage from "@/components/customDefaultPage/index.vue";
|
||||
import { reactive, onMounted, watch, nextTick, ref } from "vue";
|
||||
import { NSelect, NInput, NButton, NInfiniteScroll, NTooltip } from "naive-ui";
|
||||
import YearWheelPicker from "@/components/YearWheelPicker.vue";
|
||||
import {
|
||||
reactive,
|
||||
onMounted,
|
||||
watch,
|
||||
nextTick,
|
||||
ref,
|
||||
computed,
|
||||
onUnmounted,
|
||||
} from "vue";
|
||||
import { NInput, NButton, NTooltip } from "naive-ui";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import axios from "axios";
|
||||
|
||||
@ -97,34 +231,39 @@ const state = reactive({
|
||||
}),
|
||||
], //下拉选项
|
||||
inputValue: "", //输入值
|
||||
newsData: [
|
||||
{
|
||||
date: "June 3, 2025",
|
||||
title: "FiEE, Inc. seized market opportunities through 2025 Osaka Expo",
|
||||
content:
|
||||
"Hong Kong, 3 June 2025 — FiEE, Inc. (NASDAQ:FIEE) (“FiEE, Inc.” or the “Company”), a technology company integrating IoT, connectivity and AI to redefine brand management solutions in the digital era, is pleased to announce significant business updates....",
|
||||
},
|
||||
{
|
||||
date: "June 2, 2025",
|
||||
title: "FiEE, Inc. Closes Its First Day of Trading on NASDAQ",
|
||||
content:
|
||||
"Hong Kong, 2 June 2025 — FiEE, Inc. (NASDAQ:FIEE) (“FiEE, Inc.” or the “Company”), a technology company integrating IoT, connectivity and AI to redefine brand management solutions in the digital era, commenced...",
|
||||
},
|
||||
{
|
||||
date: "May 30, 2025",
|
||||
title: "FiEE, Inc. Announces Reinitiation of Trading on Nasdaq",
|
||||
content:
|
||||
"Hong Kong, May 30, 2025 — FiEE, Inc. (“FiEE, Inc.” or the “Company”), a technology company integrating IoT, connectivity and AI to redefine brand management solutions...",
|
||||
},
|
||||
],
|
||||
filterNewsData: [],
|
||||
loading: false, //是否正在加载数据
|
||||
hasMore: true, //是否还有更多数据
|
||||
currentPage: 1, //当前页码
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
gotoPage: 1,
|
||||
showYearPicker: false,
|
||||
});
|
||||
|
||||
const showPageSizeMenu = ref(false);
|
||||
|
||||
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) => {
|
||||
if (el) titleRefs.value[idx] = el;
|
||||
};
|
||||
@ -142,14 +281,18 @@ const checkAllTitleOverflow = () => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// state.filterNewsData = state.newsData;
|
||||
getPressReleasesDisplay();
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
|
||||
nextTick(() => {
|
||||
checkAllTitleOverflow();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => state.filterNewsData,
|
||||
() => {
|
||||
@ -162,20 +305,21 @@ watch(
|
||||
|
||||
// 获取新闻列表
|
||||
const getPressReleasesDisplay = () => {
|
||||
state.loading = true;
|
||||
let url = "https://erpapi.fiee.com/api/fiee/pressreleases/display";
|
||||
let params = {
|
||||
query: state.inputValue,
|
||||
page: state.currentPage,
|
||||
pageSize: 10,
|
||||
pageSize: state.pageSize,
|
||||
timeStart: state.selectedValue
|
||||
? state.selectedValue === "all_years"
|
||||
? null
|
||||
: new Date(state.selectedValue).getTime()
|
||||
: null,
|
||||
};
|
||||
// console.log(params)
|
||||
axios.post(url, params).then((res) => {
|
||||
// console.log(res)
|
||||
axios
|
||||
.post(url, params)
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
if (res.data.status === 0) {
|
||||
res.data.data?.data?.forEach((item) => {
|
||||
@ -185,70 +329,44 @@ const getPressReleasesDisplay = () => {
|
||||
year: "numeric",
|
||||
});
|
||||
});
|
||||
if (state.currentPage === 1) {
|
||||
state.filterNewsData = res.data.data?.data || [];
|
||||
} else {
|
||||
state.filterNewsData = [
|
||||
...state.filterNewsData,
|
||||
...(res.data.data?.data || []),
|
||||
];
|
||||
}
|
||||
if (state.filterNewsData.length < (res.data.data?.total || 0)) {
|
||||
state.hasMore = true;
|
||||
} else {
|
||||
state.hasMore = false;
|
||||
}
|
||||
state.total = res.data.data?.total || 0;
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.loading = false;
|
||||
});
|
||||
};
|
||||
|
||||
const handleFilter = () => {
|
||||
// 筛选逻辑
|
||||
let filteredData = [...state.newsData];
|
||||
|
||||
// 按年份筛选
|
||||
if (state.selectedValue !== "all_years") {
|
||||
filteredData = filteredData.filter((item) => {
|
||||
// 从日期字符串中提取年份,假设日期格式为 "May 30, 2025"
|
||||
const dateMatch = item.date.match(/\b\d{4}\b/);
|
||||
if (dateMatch) {
|
||||
const year = dateMatch[0];
|
||||
return year === state.selectedValue;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// 按输入内容进行模糊查询(title 和 content)
|
||||
if (state.inputValue && state.inputValue.trim() !== "") {
|
||||
const searchText = state.inputValue.toLowerCase().trim();
|
||||
filteredData = filteredData.filter((item) => {
|
||||
const titleMatch = item.title.toLowerCase().includes(searchText);
|
||||
const contentMatch = item.content.toLowerCase().includes(searchText);
|
||||
return titleMatch || contentMatch;
|
||||
});
|
||||
}
|
||||
|
||||
state.filterNewsData = filteredData;
|
||||
};
|
||||
|
||||
// 添加 watcher 来实现自动筛选
|
||||
watch(
|
||||
() => [state.selectedValue, state.inputValue],
|
||||
() => {
|
||||
// handleFilter();
|
||||
state.currentPage = 1;
|
||||
getPressReleasesDisplay();
|
||||
}
|
||||
);
|
||||
|
||||
const handleSearch = () => {
|
||||
// 手动触发筛选(保留这个函数以保持兼容性)
|
||||
// handleFilter();
|
||||
watch(
|
||||
() => state.pageSize,
|
||||
() => {
|
||||
state.currentPage = 1;
|
||||
getPressReleasesDisplay();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => state.currentPage,
|
||||
(newPage) => {
|
||||
state.gotoPage = newPage;
|
||||
getPressReleasesDisplay();
|
||||
}
|
||||
);
|
||||
|
||||
const handleSearch = () => {
|
||||
state.currentPage = 1;
|
||||
getPressReleasesDisplay();
|
||||
// console.log("筛选结果:", state.filterNewsData);
|
||||
};
|
||||
|
||||
const handleNewClick = (item) => {
|
||||
@ -260,102 +378,472 @@ const handleNewClick = (item) => {
|
||||
});
|
||||
};
|
||||
|
||||
//加载更多数据
|
||||
const doLoadMore = () => {
|
||||
if (!state.hasMore || state.loading) {
|
||||
return;
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(state.total / state.pageSize) || 1;
|
||||
});
|
||||
|
||||
const displayRange = computed(() => {
|
||||
if (state.total === 0) return { start: 0, end: 0 };
|
||||
const start = (state.currentPage - 1) * state.pageSize + 1;
|
||||
const end = Math.min(state.currentPage * state.pageSize, state.total);
|
||||
return { start, end };
|
||||
});
|
||||
|
||||
const goToPage = (page) => {
|
||||
if (page >= 1 && page <= totalPages.value) {
|
||||
state.currentPage = page;
|
||||
}
|
||||
// console.log('触底了')
|
||||
state.loading = true;
|
||||
};
|
||||
|
||||
const goToPrevPage = () => {
|
||||
if (state.currentPage > 1) {
|
||||
state.currentPage--;
|
||||
}
|
||||
};
|
||||
|
||||
const goToNextPage = () => {
|
||||
if (state.currentPage < totalPages.value) {
|
||||
state.currentPage++;
|
||||
getPressReleasesDisplay().finally(() => {
|
||||
state.loading = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const changePageSize = (size) => {
|
||||
state.pageSize = size;
|
||||
};
|
||||
|
||||
const handleGoto = () => {
|
||||
const page = parseInt(state.gotoPage);
|
||||
if (page >= 1 && page <= totalPages.value) {
|
||||
state.currentPage = page;
|
||||
}
|
||||
};
|
||||
|
||||
const togglePageSizeMenu = () => {
|
||||
showPageSizeMenu.value = !showPageSizeMenu.value;
|
||||
};
|
||||
|
||||
const getVisiblePages = () => {
|
||||
const total = totalPages.value;
|
||||
if (total <= 4) {
|
||||
const pages = [];
|
||||
for (let i = 1; i <= total; i++) {
|
||||
pages.push(i);
|
||||
}
|
||||
return pages;
|
||||
}
|
||||
return [1, 2, "...", total];
|
||||
};
|
||||
|
||||
// 点击外部关闭页面大小选择菜单
|
||||
const handleClickOutside = (event) => {
|
||||
if (!event.target.closest || !event.target.closest(".page-size-selector")) {
|
||||
showPageSizeMenu.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.press-releases-page {
|
||||
width: 343 * 5.12px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
}
|
||||
.title-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16 * 5.12px;
|
||||
padding: 0 16 * 5.12px;
|
||||
}
|
||||
.title-decoration {
|
||||
width: 58 * 5.12px;
|
||||
height: 7 * 5.12px;
|
||||
background: #ff7bac;
|
||||
margin-top: 43 * 5.12px;
|
||||
}
|
||||
.title {
|
||||
font-size: 113px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
font-size: 32 * 5.12px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
margin-bottom: 24px;
|
||||
margin-top: 32 * 5.12px;
|
||||
margin-bottom: 20 * 5.12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background-color: #f6f7f9;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
gap: 16px;
|
||||
justify-content: space-between;
|
||||
flex-flow: wrap;
|
||||
gap: 16 * 5.12px;
|
||||
padding: 0 16 * 5.12px;
|
||||
}
|
||||
|
||||
.search-select {
|
||||
width: 1000px;
|
||||
:deep(.n-base-selection) {
|
||||
padding: 4px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
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 {
|
||||
width: 191 * 5.12px;
|
||||
height: 34 * 5.12px;
|
||||
padding: 7 * 5.12px 12 * 5.12px;
|
||||
border: 1 * 5.12px solid #e0e0e6;
|
||||
border-radius: 3 * 5.12px;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.375em;
|
||||
letter-spacing: 0.48 * 5.12px;
|
||||
color: #455363;
|
||||
|
||||
&::placeholder {
|
||||
color: #b6b6b6;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.n-input) {
|
||||
.n-input__input {
|
||||
padding: 4px 0;
|
||||
border-radius: 4px;
|
||||
padding: 4 * 5.12px 0;
|
||||
// border: 1*5.12px solid #ccc;
|
||||
border-radius: 4 * 5.12px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.n-select) {
|
||||
.n-select__input {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
padding: 8 * 5.12px 12 * 5.12px;
|
||||
border: 1 * 5.12px solid #ccc;
|
||||
border-radius: 4 * 5.12px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.n-button) {
|
||||
width: 260px;
|
||||
padding: 20px 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.news-item {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.news-item-date {
|
||||
font-size: 72px;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.news-item-title {
|
||||
font-size: 92px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.news-item-content {
|
||||
font-size: 72px;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
padding: 20 * 5.12px 16 * 5.12px;
|
||||
border-radius: 4 * 5.12px;
|
||||
}
|
||||
.search-button {
|
||||
width: 104 * 5.12px;
|
||||
height: 34 * 5.12px;
|
||||
padding: 7 * 5.12px 12 * 5.12px;
|
||||
background: #ff7bac;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 3 * 5.12px;
|
||||
cursor: pointer;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.375em;
|
||||
letter-spacing: 0.48 * 5.12px;
|
||||
&:hover {
|
||||
background: #ff7bac;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.reports-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4 * 5.12px;
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
margin-top: 40 * 5.12px;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
border-radius: 8 * 5.12px;
|
||||
|
||||
// &:last-child {
|
||||
// .separator-line {
|
||||
// display: none;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16 * 5.12px;
|
||||
}
|
||||
|
||||
.table-row .content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0;
|
||||
&:hover {
|
||||
background: #fff8fb;
|
||||
}
|
||||
}
|
||||
|
||||
.file-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16 * 5.12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.news-item-title {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.375em;
|
||||
letter-spacing: 0.48 * 5.12px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
margin-right: 16 * 5.12px;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vertical-line {
|
||||
width: 1 * 5.12px;
|
||||
height: 20 * 5.12px;
|
||||
background: #ff7bac;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.file-description {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.375em;
|
||||
letter-spacing: 0.48 * 5.12px;
|
||||
color: #455363;
|
||||
margin: 8 * 5.12px 0;
|
||||
padding: 0 16 * 5.12px;
|
||||
}
|
||||
|
||||
.news-item-date {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.375em;
|
||||
letter-spacing: 0.48 * 5.12px;
|
||||
color: #455363;
|
||||
}
|
||||
|
||||
.download-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
padding: 0 16 * 5.12px;
|
||||
margin-bottom: 8 * 5.12px;
|
||||
}
|
||||
|
||||
.separator-line {
|
||||
width: 100%;
|
||||
height: 1 * 5.12px;
|
||||
background: repeating-linear-gradient(
|
||||
to right,
|
||||
#e6eaee 0 * 5.12px,
|
||||
#e6eaee 2 * 5.12px,
|
||||
transparent 2 * 5.12px,
|
||||
transparent 4 * 5.12px
|
||||
);
|
||||
margin-top: 16 * 5.12px;
|
||||
}
|
||||
|
||||
// 分页器样式
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 16 * 5.12px 0;
|
||||
justify-content: flex-end;
|
||||
padding: 0 16 * 5.12px;
|
||||
flex-wrap: wrap;
|
||||
gap: 16 * 5.12px;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.4375em;
|
||||
color: #455363;
|
||||
text-align: right;
|
||||
margin-bottom: 30 * 5.12px;
|
||||
padding: 0 16 * 5.12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8 * 5.12px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.pagination-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8 * 5.12px;
|
||||
}
|
||||
.page-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28 * 5.12px;
|
||||
height: 28 * 5.12px;
|
||||
border: 1 * 5.12px solid #e0e0e6;
|
||||
border-radius: 3 * 5.12px;
|
||||
background: #ffffff;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.428em;
|
||||
color: #455363;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: #ff7bac;
|
||||
color: #ff7bac;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #ff7bac;
|
||||
color: #ff7bac;
|
||||
background: #fff0f5;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.page-size-selector {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18 * 5.12px;
|
||||
padding: 4 * 5.12px 12 * 5.12px;
|
||||
height: 28 * 5.12px;
|
||||
border: 1 * 5.12px solid #e0e0e6;
|
||||
border-radius: 3 * 5.12px;
|
||||
background: #ffffff;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.428em;
|
||||
color: #455363;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: #ff7bac;
|
||||
}
|
||||
}
|
||||
|
||||
.page-size-menu {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ffffff;
|
||||
border: 1 * 5.12px solid #e0e0e6;
|
||||
border-radius: 3 * 5.12px;
|
||||
box-shadow: 0 2 * 5.12px 8 * 5.12px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
margin-bottom: 2 * 5.12px;
|
||||
}
|
||||
|
||||
.page-size-option {
|
||||
padding: 8 * 5.12px 12 * 5.12px;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.428em;
|
||||
color: #455363;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: #fff0f5;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #fff0f5;
|
||||
color: #ff7bac;
|
||||
}
|
||||
}
|
||||
|
||||
.goto-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8 * 5.12px;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.428em;
|
||||
color: #455363;
|
||||
}
|
||||
|
||||
.goto-input {
|
||||
width: 60 * 5.12px;
|
||||
height: 28 * 5.12px;
|
||||
padding: 4 * 5.12px 12 * 5.12px;
|
||||
border: 1 * 5.12px solid #e0e0e6;
|
||||
border-radius: 3 * 5.12px;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 5.12px;
|
||||
line-height: 1.428em;
|
||||
color: #455363;
|
||||
text-align: center;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
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>
|
||||
|
@ -1,35 +1,45 @@
|
||||
<template>
|
||||
<div class="press-releases-page">
|
||||
<n-infinite-scroll :distance="0" @load="doLoadMore">
|
||||
<main class="p-[35px] mx-auto" style="max-width: calc(100% - 100px)">
|
||||
<div class="title mb-[20px]">
|
||||
<main class="mx-auto">
|
||||
<div class="title-section">
|
||||
<div class="title-decoration"></div>
|
||||
<div class="title">
|
||||
{{ t("press_releases.title") }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-container">
|
||||
<n-select
|
||||
:options="state.selectOptions"
|
||||
v-model:value="state.selectedValue"
|
||||
class="search-select"
|
||||
/>
|
||||
<n-input
|
||||
v-model:value="state.inputValue"
|
||||
<input
|
||||
v-model="state.inputValue"
|
||||
type="text"
|
||||
:placeholder="t('press_releases.search.placeholder')"
|
||||
class="search-input"
|
||||
/>
|
||||
<n-button @click="handleSearch" class="search-button w-[120px]">
|
||||
<button @click="handleSearch" class="search-button">
|
||||
{{ t("press_releases.search.button") }}
|
||||
</n-button>
|
||||
</button>
|
||||
</div>
|
||||
<div v-for="(item, idx) in state.filterNewsData" :key="idx">
|
||||
<div class="news-item mt-[10px]">
|
||||
<div class="news-item-date">{{ item.date }}</div>
|
||||
<div class="reports-list">
|
||||
<div
|
||||
class="news-item-title text-[#0078d7] cursor-pointer"
|
||||
v-for="(item, idx) in state.filterNewsData"
|
||||
:key="idx"
|
||||
class="news-item table-row"
|
||||
>
|
||||
<div class="content">
|
||||
<div class="file-content">
|
||||
<div class="file-info">
|
||||
<div class="vertical-line"></div>
|
||||
<div
|
||||
class="news-item-title text-[#000] cursor-pointer"
|
||||
style="
|
||||
word-break: break-word;
|
||||
word-break: break-all;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-line-clamp: 1;
|
||||
line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -38,19 +48,34 @@
|
||||
>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<n-tooltip
|
||||
trigger="click"
|
||||
:disabled="!item.showTooltip"
|
||||
width="trigger"
|
||||
<svg
|
||||
class="arrow-icon"
|
||||
width="7"
|
||||
height="14"
|
||||
viewBox="0 0 7 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@click="handleNewClick(item)"
|
||||
>
|
||||
<path
|
||||
d="M1 1L6 7L1 13"
|
||||
stroke="#FF7BAC"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<n-tooltip trigger="hover" :disabled="true" width="trigger">
|
||||
<template #trigger>
|
||||
<div
|
||||
:ref="(el) => setTitleRef(el, idx)"
|
||||
class="news-item-content"
|
||||
class="news-item-content file-description"
|
||||
style="
|
||||
word-break: break-word;
|
||||
word-break: break-all;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -63,17 +88,119 @@
|
||||
{{ item.summary }}
|
||||
</div>
|
||||
</n-tooltip>
|
||||
<div class="download-section">
|
||||
<div class="news-item-date">{{ item.date }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator-line"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页器 -->
|
||||
<div class="pagination-container" v-if="state.total > 0">
|
||||
<div class="pagination-controls">
|
||||
<div class="pagination-buttons">
|
||||
<button
|
||||
class="page-btn prev-btn"
|
||||
:disabled="state.currentPage === 1"
|
||||
@click="goToPrevPage"
|
||||
>
|
||||
<svg width="5" height="9" viewBox="0 0 5 9" fill="none">
|
||||
<path
|
||||
d="M4 1L1 4.5L4 8"
|
||||
stroke="#455363"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<template v-for="page in getVisiblePages()" :key="page">
|
||||
<button
|
||||
v-if="page !== '...'"
|
||||
class="page-btn"
|
||||
:class="{ active: page === state.currentPage }"
|
||||
@click="goToPage(page)"
|
||||
>
|
||||
{{ page }}
|
||||
</button>
|
||||
<button v-else class="page-btn disabled" disabled>...</button>
|
||||
</template>
|
||||
|
||||
<button
|
||||
class="page-btn next-btn"
|
||||
:disabled="state.currentPage === totalPages"
|
||||
@click="goToNextPage"
|
||||
>
|
||||
<svg width="5" height="9" viewBox="0 0 5 9" fill="none">
|
||||
<path
|
||||
d="M1 1L4 5L1 8"
|
||||
stroke="#455363"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="page-size-selector" @click="togglePageSizeMenu">
|
||||
<span>{{ state.pageSize }}/page</span>
|
||||
<svg width="10" height="5" viewBox="0 0 10 5" fill="none">
|
||||
<path
|
||||
d="M1 1L5 4L9 1"
|
||||
stroke="#455363"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<div v-if="showPageSizeMenu" class="page-size-menu">
|
||||
<div
|
||||
v-for="size in [10, 20, 50]"
|
||||
:key="size"
|
||||
class="page-size-option"
|
||||
:class="{ active: state.pageSize === size }"
|
||||
@click="changePageSize(size)"
|
||||
>
|
||||
{{ size }}/page
|
||||
</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 class="pagination-info" v-if="state.total > 0">
|
||||
Displaying {{ displayRange.start }} - {{ displayRange.end }} of
|
||||
{{ state.total }} results
|
||||
</div>
|
||||
</main>
|
||||
</n-infinite-scroll>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import customDefaultPage from "@/components/customDefaultPage/index.vue";
|
||||
import { reactive, onMounted, watch, nextTick, ref } from "vue";
|
||||
import { NSelect, NInput, NButton, NInfiniteScroll, NTooltip } from "naive-ui";
|
||||
import {
|
||||
reactive,
|
||||
onMounted,
|
||||
watch,
|
||||
nextTick,
|
||||
ref,
|
||||
computed,
|
||||
onUnmounted,
|
||||
} from "vue";
|
||||
import { NSelect, NInput, NButton, NTooltip } from "naive-ui";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import axios from "axios";
|
||||
|
||||
@ -94,32 +221,16 @@ const state = reactive({
|
||||
}),
|
||||
], //下拉选项
|
||||
inputValue: "", //输入值
|
||||
newsData: [
|
||||
{
|
||||
date: "June 3, 2025",
|
||||
title: "FiEE, Inc. seized market opportunities through 2025 Osaka Expo",
|
||||
content:
|
||||
"Hong Kong, 3 June 2025 — FiEE, Inc. (NASDAQ:FIEE) (“FiEE, Inc.” or the “Company”), a technology company integrating IoT, connectivity and AI to redefine brand management solutions in the digital era, is pleased to announce significant business updates....",
|
||||
},
|
||||
{
|
||||
date: "June 2, 2025",
|
||||
title: "FiEE, Inc. Closes Its First Day of Trading on NASDAQ",
|
||||
content:
|
||||
"Hong Kong, 2 June 2025 — FiEE, Inc. (NASDAQ:FIEE) (“FiEE, Inc.” or the “Company”), a technology company integrating IoT, connectivity and AI to redefine brand management solutions in the digital era, commenced...",
|
||||
},
|
||||
{
|
||||
date: "May 30, 2025",
|
||||
title: "FiEE, Inc. Announces Reinitiation of Trading on Nasdaq",
|
||||
content:
|
||||
"Hong Kong, May 30, 2025 — FiEE, Inc. (“FiEE, Inc.” or the “Company”), a technology company integrating IoT, connectivity and AI to redefine brand management solutions...",
|
||||
},
|
||||
],
|
||||
filterNewsData: [],
|
||||
loading: false, //是否正在加载数据
|
||||
hasMore: true, //是否还有更多数据
|
||||
currentPage: 1, //当前页码
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
gotoPage: 1,
|
||||
});
|
||||
|
||||
const showPageSizeMenu = ref(false);
|
||||
|
||||
const titleRefs = ref([]);
|
||||
|
||||
const setTitleRef = (el, idx) => {
|
||||
@ -139,14 +250,18 @@ const checkAllTitleOverflow = () => {
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// state.filterNewsData = state.newsData;
|
||||
getPressReleasesDisplay();
|
||||
document.addEventListener("click", handleClickOutside);
|
||||
|
||||
nextTick(() => {
|
||||
checkAllTitleOverflow();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener("click", handleClickOutside);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => state.filterNewsData,
|
||||
() => {
|
||||
@ -159,20 +274,21 @@ watch(
|
||||
|
||||
// 获取新闻列表
|
||||
const getPressReleasesDisplay = () => {
|
||||
state.loading = true;
|
||||
let url = "https://erpapi.fiee.com/api/fiee/pressreleases/display";
|
||||
let params = {
|
||||
query: state.inputValue,
|
||||
page: state.currentPage,
|
||||
pageSize: 10,
|
||||
pageSize: state.pageSize,
|
||||
timeStart: state.selectedValue
|
||||
? state.selectedValue === "all_years"
|
||||
? null
|
||||
: new Date(state.selectedValue).getTime()
|
||||
: null,
|
||||
};
|
||||
// console.log(params)
|
||||
axios.post(url, params).then((res) => {
|
||||
// console.log(res)
|
||||
axios
|
||||
.post(url, params)
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
if (res.data.status === 0) {
|
||||
res.data.data?.data?.forEach((item) => {
|
||||
@ -182,70 +298,44 @@ const getPressReleasesDisplay = () => {
|
||||
year: "numeric",
|
||||
});
|
||||
});
|
||||
if (state.currentPage === 1) {
|
||||
state.filterNewsData = res.data.data?.data || [];
|
||||
} else {
|
||||
state.filterNewsData = [
|
||||
...state.filterNewsData,
|
||||
...(res.data.data?.data || []),
|
||||
];
|
||||
}
|
||||
if (state.filterNewsData.length < (res.data.data?.total || 0)) {
|
||||
state.hasMore = true;
|
||||
} else {
|
||||
state.hasMore = false;
|
||||
}
|
||||
state.total = res.data.data?.total || 0;
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.loading = false;
|
||||
});
|
||||
};
|
||||
|
||||
const handleFilter = () => {
|
||||
// 筛选逻辑
|
||||
let filteredData = [...state.newsData];
|
||||
|
||||
// 按年份筛选
|
||||
if (state.selectedValue !== "all_years") {
|
||||
filteredData = filteredData.filter((item) => {
|
||||
// 从日期字符串中提取年份,假设日期格式为 "May 30, 2025"
|
||||
const dateMatch = item.date.match(/\b\d{4}\b/);
|
||||
if (dateMatch) {
|
||||
const year = dateMatch[0];
|
||||
return year === state.selectedValue;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// 按输入内容进行模糊查询(title 和 content)
|
||||
if (state.inputValue && state.inputValue.trim() !== "") {
|
||||
const searchText = state.inputValue.toLowerCase().trim();
|
||||
filteredData = filteredData.filter((item) => {
|
||||
const titleMatch = item.title.toLowerCase().includes(searchText);
|
||||
const contentMatch = item.content.toLowerCase().includes(searchText);
|
||||
return titleMatch || contentMatch;
|
||||
});
|
||||
}
|
||||
|
||||
state.filterNewsData = filteredData;
|
||||
};
|
||||
|
||||
// 添加 watcher 来实现自动筛选
|
||||
watch(
|
||||
() => [state.selectedValue, state.inputValue],
|
||||
() => {
|
||||
// handleFilter();
|
||||
state.currentPage = 1;
|
||||
getPressReleasesDisplay();
|
||||
}
|
||||
);
|
||||
|
||||
const handleSearch = () => {
|
||||
// 手动触发筛选(保留这个函数以保持兼容性)
|
||||
// handleFilter();
|
||||
watch(
|
||||
() => state.pageSize,
|
||||
() => {
|
||||
state.currentPage = 1;
|
||||
getPressReleasesDisplay();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => state.currentPage,
|
||||
(newPage) => {
|
||||
state.gotoPage = newPage;
|
||||
getPressReleasesDisplay();
|
||||
}
|
||||
);
|
||||
|
||||
const handleSearch = () => {
|
||||
state.currentPage = 1;
|
||||
getPressReleasesDisplay();
|
||||
// console.log("筛选结果:", state.filterNewsData);
|
||||
};
|
||||
|
||||
const handleNewClick = (item) => {
|
||||
@ -257,72 +347,461 @@ const handleNewClick = (item) => {
|
||||
});
|
||||
};
|
||||
|
||||
//加载更多数据
|
||||
const doLoadMore = () => {
|
||||
if (!state.hasMore || state.loading) {
|
||||
return;
|
||||
const totalPages = computed(() => {
|
||||
return Math.ceil(state.total / state.pageSize) || 1;
|
||||
});
|
||||
|
||||
const displayRange = computed(() => {
|
||||
if (state.total === 0) return { start: 0, end: 0 };
|
||||
const start = (state.currentPage - 1) * state.pageSize + 1;
|
||||
const end = Math.min(state.currentPage * state.pageSize, state.total);
|
||||
return { start, end };
|
||||
});
|
||||
|
||||
const goToPage = (page) => {
|
||||
if (page >= 1 && page <= totalPages.value) {
|
||||
state.currentPage = page;
|
||||
}
|
||||
// console.log('触底了')
|
||||
state.loading = true;
|
||||
};
|
||||
|
||||
const goToPrevPage = () => {
|
||||
if (state.currentPage > 1) {
|
||||
state.currentPage--;
|
||||
}
|
||||
};
|
||||
|
||||
const goToNextPage = () => {
|
||||
if (state.currentPage < totalPages.value) {
|
||||
state.currentPage++;
|
||||
getPressReleasesDisplay().finally(() => {
|
||||
state.loading = false;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const changePageSize = (size) => {
|
||||
state.pageSize = size;
|
||||
};
|
||||
|
||||
const handleGoto = () => {
|
||||
const page = parseInt(state.gotoPage);
|
||||
if (page >= 1 && page <= totalPages.value) {
|
||||
state.currentPage = page;
|
||||
}
|
||||
};
|
||||
|
||||
const togglePageSizeMenu = () => {
|
||||
showPageSizeMenu.value = !showPageSizeMenu.value;
|
||||
};
|
||||
|
||||
const getVisiblePages = () => {
|
||||
const current = state.currentPage;
|
||||
const total = totalPages.value;
|
||||
const pages = [];
|
||||
|
||||
if (total <= 7) {
|
||||
for (let i = 1; i <= total; 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;
|
||||
};
|
||||
|
||||
// 点击外部关闭页面大小选择菜单
|
||||
const handleClickOutside = (event) => {
|
||||
if (!event.target.closest || !event.target.closest(".page-size-selector")) {
|
||||
showPageSizeMenu.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.press-releases-page {
|
||||
width: 650 * 2.5px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.title-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16 * 2.5px;
|
||||
padding: 0 16 * 2.5px;
|
||||
}
|
||||
.title-decoration {
|
||||
width: 58 * 2.5px;
|
||||
height: 7 * 2.5px;
|
||||
background: #ff7bac;
|
||||
margin: auto 0;
|
||||
margin-top: 43 * 2.5px;
|
||||
}
|
||||
.title {
|
||||
font-size: 63px;
|
||||
color: #333;
|
||||
font-size: 32 * 2.5px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 20 * 2.5px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 25px;
|
||||
gap: 16 * 2.5px;
|
||||
padding: 0 16 * 2.5px;
|
||||
}
|
||||
|
||||
.search-select {
|
||||
width: 360px;
|
||||
:deep(.n-base-selection) {
|
||||
padding: 4px 0;
|
||||
}
|
||||
width: 134 * 2.5px;
|
||||
height: 34 * 2.5px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 360px;
|
||||
width: 292 * 2.5px;
|
||||
height: 34 * 2.5px;
|
||||
padding: 7 * 2.5px 12 * 2.5px;
|
||||
border: 1 * 2.5px solid #e0e0e6;
|
||||
border-radius: 3 * 2.5px;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.375em;
|
||||
letter-spacing: 0.48 * 2.5px;
|
||||
color: #455363;
|
||||
|
||||
&::placeholder {
|
||||
color: #b6b6b6;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.n-input) {
|
||||
.n-input__input {
|
||||
padding: 4px 0;
|
||||
// border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
padding: 4 * 2.5px 0;
|
||||
// border: 1*2.5px solid #ccc;
|
||||
border-radius: 4 * 2.5px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.n-select) {
|
||||
.n-select__input {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
padding: 8 * 2.5px 12 * 2.5px;
|
||||
border: 1 * 2.5px solid #ccc;
|
||||
border-radius: 4 * 2.5px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.n-button) {
|
||||
padding: 20px 16px;
|
||||
border-radius: 4px;
|
||||
padding: 20 * 2.5px 16 * 2.5px;
|
||||
border-radius: 4 * 2.5px;
|
||||
}
|
||||
.search-button {
|
||||
height: 34 * 2.5px;
|
||||
padding: 7 * 2.5px 12 * 2.5px;
|
||||
min-width: 160 * 2.5px;
|
||||
background: #ff7bac;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 3 * 2.5px;
|
||||
cursor: pointer;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.375em;
|
||||
letter-spacing: 0.48 * 2.5px;
|
||||
&:hover {
|
||||
background: #ff7bac;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.reports-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4 * 2.5px;
|
||||
background: #fff;
|
||||
width: 650 * 2.5px;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
border-radius: 8 * 2.5px;
|
||||
|
||||
// &:last-child {
|
||||
// .separator-line {
|
||||
// display: none;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16 * 2.5px;
|
||||
}
|
||||
|
||||
.table-row .content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0;
|
||||
&:hover {
|
||||
background: #fff8fb;
|
||||
}
|
||||
}
|
||||
|
||||
.file-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding-right: 16 * 2.5px;
|
||||
}
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16 * 2.5px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.news-item-title {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.375em;
|
||||
letter-spacing: 0.48 * 2.5px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vertical-line {
|
||||
width: 1 * 2.5px;
|
||||
height: 20 * 2.5px;
|
||||
background: #ff7bac;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.file-description {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.375em;
|
||||
letter-spacing: 0.48 * 2.5px;
|
||||
color: #455363;
|
||||
margin: 0;
|
||||
padding: 0 16 * 2.5px;
|
||||
margin-top: 8 * 2.5px;
|
||||
}
|
||||
|
||||
.news-item-date {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.375em;
|
||||
letter-spacing: 0.48 * 2.5px;
|
||||
color: #455363;
|
||||
}
|
||||
|
||||
.download-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
padding: 4 * 2.5px 16 * 2.5px;
|
||||
}
|
||||
|
||||
.separator-line {
|
||||
width: 100%;
|
||||
height: 1 * 2.5px;
|
||||
background: repeating-linear-gradient(
|
||||
to right,
|
||||
#e6eaee 0 * 2.5px,
|
||||
#e6eaee 2 * 2.5px,
|
||||
transparent 2 * 2.5px,
|
||||
transparent 4 * 2.5px
|
||||
);
|
||||
margin-top: 16 * 2.5px;
|
||||
}
|
||||
|
||||
// 分页器样式
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 20 * 2.5px 0;
|
||||
justify-content: flex-end;
|
||||
padding: 0 16 * 2.5px;
|
||||
}
|
||||
|
||||
.pagination-info {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.4375em;
|
||||
color: #455363;
|
||||
text-align: right;
|
||||
margin-bottom: 30 * 2.5px;
|
||||
padding: 0 16 * 2.5px;
|
||||
}
|
||||
|
||||
.pagination-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8 * 2.5px;
|
||||
}
|
||||
|
||||
.pagination-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8 * 2.5px;
|
||||
}
|
||||
.page-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28 * 2.5px;
|
||||
height: 28 * 2.5px;
|
||||
border: 1 * 2.5px solid #e0e0e6;
|
||||
border-radius: 3 * 2.5px;
|
||||
background: #ffffff;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.428em;
|
||||
color: #455363;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: #ff7bac;
|
||||
color: #ff7bac;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #ff7bac;
|
||||
color: #ff7bac;
|
||||
background: #fff0f5;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.page-size-selector {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18 * 2.5px;
|
||||
padding: 4 * 2.5px 12 * 2.5px;
|
||||
height: 28 * 2.5px;
|
||||
border: 1 * 2.5px solid #e0e0e6;
|
||||
border-radius: 3 * 2.5px;
|
||||
background: #ffffff;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.428em;
|
||||
color: #455363;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: #ff7bac;
|
||||
}
|
||||
}
|
||||
|
||||
.page-size-menu {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ffffff;
|
||||
border: 1 * 2.5px solid #e0e0e6;
|
||||
border-radius: 3 * 2.5px;
|
||||
box-shadow: 0 2 * 2.5px 8 * 2.5px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
margin-bottom: 2 * 2.5px;
|
||||
}
|
||||
|
||||
.page-size-option {
|
||||
padding: 8 * 2.5px 12 * 2.5px;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.428em;
|
||||
color: #455363;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: #fff0f5;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #fff0f5;
|
||||
color: #ff7bac;
|
||||
}
|
||||
}
|
||||
|
||||
.goto-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8 * 2.5px;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.428em;
|
||||
color: #455363;
|
||||
}
|
||||
|
||||
.goto-input {
|
||||
width: 60 * 2.5px;
|
||||
height: 28 * 2.5px;
|
||||
padding: 4 * 2.5px 12 * 2.5px;
|
||||
border: 1 * 2.5px solid #e0e0e6;
|
||||
border-radius: 3 * 2.5px;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14 * 2.5px;
|
||||
line-height: 1.428em;
|
||||
color: #455363;
|
||||
text-align: center;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: #ff7bac;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { computed } from "vue";
|
||||
import { useWindowSize } from "@vueuse/core";
|
||||
|
||||
// import size375 from "./size375/index.vue";
|
||||
import size375 from "./size375/index.vue";
|
||||
import size768 from "./size768/index.vue";
|
||||
import size1440 from "./size1440/index.vue";
|
||||
import size1920 from "./size1920/index.vue";
|
||||
|
@ -262,11 +262,10 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
background-color: #fff;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
/* width:932px */
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
@ -0,0 +1,589 @@
|
||||
<script setup></script>
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="grid-lines">
|
||||
<div class="line solid line-1"></div>
|
||||
<div class="line solid line-5"></div>
|
||||
</div>
|
||||
<section class="hero-section relative">
|
||||
<div class="hero-content">
|
||||
<div class="hero-title">
|
||||
More than just a tool——<br />
|
||||
Comprehensive growth <br />solutions, providing a one- <br />stop
|
||||
solution for content <br />creation, publishing, analysis,<br />
|
||||
and monetization
|
||||
</div>
|
||||
</div>
|
||||
<div class="core-value-card">
|
||||
<div class="card-content">
|
||||
<div class="card-title">Core Value</div>
|
||||
<div class="card-text">
|
||||
The FIEE-SAAS platform is a one-stop content operation solution
|
||||
tailored for creators in the digital era. The platform utilizes
|
||||
intelligent distribution technology, A1 empowerment tools, and
|
||||
full-chain services,Assist you in efficiently reaching audiences on
|
||||
global mainstream platforms such as TikTok, YouTube, and Instagram,
|
||||
creating a KOL brand effect, unlocking content value, and achieving
|
||||
sustainable growth.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src="@/assets/image/375/product-introduction-img2.png"
|
||||
alt="background"
|
||||
class="hero-bg-img"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="features-section">
|
||||
<div class="section-header">
|
||||
<div class="decorator-bar"></div>
|
||||
<div class="section-title">Product Features</div>
|
||||
</div>
|
||||
<div class="features-list">
|
||||
<div class="feature-item">
|
||||
<div class="feature-title">
|
||||
<div class="vertical-line"></div>
|
||||
One-click Synchronous Publishing
|
||||
</div>
|
||||
<div class="feature-description">
|
||||
Synchronize graphic and video content to TikTok, YouTube, and
|
||||
Instagram platforms at once, saving time on repetitive operations.
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="feature-title">
|
||||
<div class="vertical-line"></div>
|
||||
Intelligent Scheduled Publishing
|
||||
</div>
|
||||
<div class="feature-description">
|
||||
Plan the content release time in advance, support batch scheduling,
|
||||
and accurately grasp the optimal release time of each platform.
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="feature-title">
|
||||
<div class="vertical-line"></div>
|
||||
Unified Management of Multiple Accounts
|
||||
</div>
|
||||
<div class="feature-description">
|
||||
Easily manage multiple accounts on one platform without the need for
|
||||
repeated login and switching, improving team collaboration
|
||||
efficiency.
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="feature-title">
|
||||
<div class="vertical-line"></div>
|
||||
Cloud Content Library
|
||||
</div>
|
||||
<div class="feature-description">
|
||||
Safely store and manage all creative materials, access and use them
|
||||
anytime, anywhere, and support quick retrieval and reuse.
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="feature-title">
|
||||
<div class="vertical-line"></div>
|
||||
Basic Data Tracking
|
||||
</div>
|
||||
<div class="feature-description">
|
||||
Visually view the content performance of various platforms,
|
||||
understand core data indicators, and provide a basis for optimizing
|
||||
strategies.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="solutions-section">
|
||||
<div class="section-header">
|
||||
<div class="decorator-bar"></div>
|
||||
<div class="section-title">Value Added Solutions</div>
|
||||
</div>
|
||||
<div class="solutions-content">
|
||||
<div class="solution-image-container">
|
||||
<img
|
||||
src="@/assets/image/375/product-introduction-img1.png"
|
||||
alt="Value Added Solutions"
|
||||
class="solution-image"
|
||||
/>
|
||||
</div>
|
||||
<div class="solutions-list">
|
||||
<div class="solution-item">
|
||||
<img
|
||||
src="@/assets/image/375/product-introduction-icon1.png"
|
||||
alt="KOL Brand Promotion"
|
||||
class="solution-icon"
|
||||
/>
|
||||
<div class="solution-title">
|
||||
<div class="vertical-line"></div>
|
||||
KOL Brand Promotion Services
|
||||
</div>
|
||||
<div class="solution-description">
|
||||
Efficiently connect high-quality business cooperation
|
||||
opportunities and complete the entire process management from
|
||||
order acceptance to publication within the platform.
|
||||
</div>
|
||||
</div>
|
||||
<div class="solution-item">
|
||||
<img
|
||||
src="@/assets/image/375/product-introduction-icon2.png"
|
||||
alt="Content Creation Support"
|
||||
class="solution-icon"
|
||||
/>
|
||||
<div class="solution-title">
|
||||
<div class="vertical-line"></div>
|
||||
Professional Content Creation Support
|
||||
</div>
|
||||
<div class="solution-description">
|
||||
Connect professional shooting and post production teams for you,
|
||||
create high-quality "art+story" content, and strengthen IP
|
||||
influence.
|
||||
</div>
|
||||
</div>
|
||||
<div class="solution-item">
|
||||
<img
|
||||
src="@/assets/image/375/product-introduction-icon3.png"
|
||||
alt="Account Operation"
|
||||
class="solution-icon"
|
||||
/>
|
||||
<div class="solution-title">
|
||||
<div class="vertical-line"></div>
|
||||
Account Operation and Hosting Services
|
||||
</div>
|
||||
<div class="solution-description">
|
||||
From 0 to 1 account positioning, follower growth strategy to
|
||||
monetization cycle, operation experts provide full cycle running
|
||||
and hosting services.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="advantages-section">
|
||||
<div class="advantages-content">
|
||||
<div class="advantages-header">
|
||||
<div class="decorator-bar"></div>
|
||||
<div class="section-title text-white">Our Advantages</div>
|
||||
</div>
|
||||
<div class="advantages-list">
|
||||
<div class="advantage-item">
|
||||
<div class="advantage-title">
|
||||
<div class="vertical-line"></div>
|
||||
Time Saving
|
||||
</div>
|
||||
<div class="advantage-description">
|
||||
Multi platform publishing efficiency improvement, allowing you to
|
||||
focus on content creation.
|
||||
</div>
|
||||
</div>
|
||||
<div class="advantage-item">
|
||||
<div class="advantage-title">
|
||||
<div class="vertical-line"></div>
|
||||
Safe and Reliable
|
||||
</div>
|
||||
<div class="advantage-description">
|
||||
Enterprise level data encryption and permission control ensure
|
||||
account and content security.
|
||||
</div>
|
||||
</div>
|
||||
<div class="advantage-item">
|
||||
<div class="advantage-title">
|
||||
<div class="vertical-line"></div>
|
||||
Maintain Consistency
|
||||
</div>
|
||||
<div class="advantage-description">
|
||||
Ensure that brand information is presented uniformly on all
|
||||
platforms.
|
||||
</div>
|
||||
</div>
|
||||
<div class="advantage-item">
|
||||
<div class="advantage-title">
|
||||
<div class="vertical-line"></div>
|
||||
Data Driven
|
||||
</div>
|
||||
<div class="advantage-description">
|
||||
Optimizing Content Strategies Based on Actual Performance.
|
||||
</div>
|
||||
</div>
|
||||
<div class="advantage-item">
|
||||
<div class="advantage-title">
|
||||
<div class="vertical-line"></div>
|
||||
Easy to Use
|
||||
</div>
|
||||
<div class="advantage-description">
|
||||
Intuitive interface design, no need for professional technical
|
||||
background.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cta-section">
|
||||
<div class="cta-content">
|
||||
<div class="cta-title">Get customized<br />solutions for free</div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="32"
|
||||
height="60"
|
||||
viewBox="0 0 32 60"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M31.3636 42.4968C32.2121 43.3437 32.2121 44.7165 31.3636 45.5635L17.5362 59.3648C16.6877 60.2117 15.3123 60.2117 14.4638 59.3648L0.636387 45.5635C-0.212133 44.7165 -0.212133 43.3437 0.636387 42.4968C1.48491 41.6499 2.8603 41.6499 3.70883 42.4968L13.8272 52.5962L13.8272 2.16868C13.8272 0.970951 14.8 -7.51835e-07 16 -6.99382e-07C17.2 -6.46929e-07 18.1728 0.970951 18.1728 2.16868L18.1728 52.5962L28.2912 42.4968C29.1397 41.6499 30.5151 41.6499 31.3636 42.4968Z"
|
||||
fill="#FF7BAC"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="cta-qr-code">
|
||||
<img
|
||||
src="@/assets/image/375/product-introduction-img6.png"
|
||||
alt="QR Code"
|
||||
/>
|
||||
</div>
|
||||
<img
|
||||
src="@/assets/image/375/product-introduction-img5.png"
|
||||
alt="background"
|
||||
class="cta-bg-img"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
background-color: #fff;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hero-section {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
background-image: url("@/assets/image/375/product-introduction-img3.png");
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% auto;
|
||||
background-position: top;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 24 * 5.12px;
|
||||
font-weight: 500;
|
||||
line-height: 34 * 5.12px;
|
||||
letter-spacing: 0.2 * 5.12px;
|
||||
padding: 153 * 5.12px 0 163 * 5.12px 0;
|
||||
color: #000;
|
||||
z-index: 2;
|
||||
}
|
||||
.hero-bg-img {
|
||||
position: absolute;
|
||||
bottom: -25 * 5.12px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
/* height: 100%; */
|
||||
z-index: 1;
|
||||
}
|
||||
.core-value-card {
|
||||
width: 346 * 5.12px;
|
||||
padding: 40 * 5.12px 32 * 5.12px;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
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);
|
||||
text-align: left;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24 * 5.12px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 32 * 5.12px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.8 * 5.12px;
|
||||
}
|
||||
|
||||
.card-text {
|
||||
font-size: 14 * 5.12px;
|
||||
color: #455363;
|
||||
letter-spacing: 0.48 * 5.12px;
|
||||
text-align: justify;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: 32 * 5.12px;
|
||||
padding: 0 16 * 5.12px;
|
||||
}
|
||||
|
||||
.decorator-bar {
|
||||
width: 58 * 5.12px;
|
||||
height: 7 * 5.12px;
|
||||
background-color: #ff7bac;
|
||||
margin-bottom: 16 * 5.12px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-family: "PingFang SC";
|
||||
font-size: 24 * 5.12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: normal;
|
||||
}
|
||||
.features-section {
|
||||
padding-top: 64 * 5.12px;
|
||||
width: 346 * 5.12px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.features-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24 * 5.12px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16 * 5.12px;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-size: 24 * 5.12px;
|
||||
font-weight: 500;
|
||||
line-height: 32 * 5.12px;
|
||||
letter-spacing: 1.2 * 5.12px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16 * 5.12px;
|
||||
}
|
||||
.feature-description {
|
||||
font-size: 16 * 5.12px;
|
||||
line-height: 22 * 5.12px;
|
||||
color: #455363;
|
||||
letter-spacing: 0.48 * 5.12px;
|
||||
padding: 0 16 * 5.12px;
|
||||
text-align: justify;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
}
|
||||
|
||||
.solutions-section {
|
||||
padding-top: 64 * 5.12px;
|
||||
width: 346 * 5.12px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.solutions-content {
|
||||
display: flex;
|
||||
gap: 32 * 5.12px;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.solutions-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24 * 5.12px;
|
||||
width: 100%;
|
||||
}
|
||||
.solution-item {
|
||||
text-align: left;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.solution-icon {
|
||||
width: 92 * 5.12px;
|
||||
height: 76 * 5.12px;
|
||||
padding-left: 16 * 5.12px;
|
||||
}
|
||||
.solution-title {
|
||||
font-family: "PingFang SC";
|
||||
font-size: 24 * 5.12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 32 * 5.12px; /* 133.333% */
|
||||
letter-spacing: 1.2 * 5.12px;
|
||||
display: flex;
|
||||
gap: 16 * 5.12px;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16 * 5.12px;
|
||||
}
|
||||
.solution-description {
|
||||
font-size: 14 * 5.12px;
|
||||
letter-spacing: 0.48 * 5.12px;
|
||||
padding: 0 16 * 5.12px;
|
||||
text-align: justify;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
font-family: "PingFang SC";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
color: #455363;
|
||||
}
|
||||
.solution-image-container {
|
||||
width: 100%;
|
||||
border-radius: 16 * 5.12px;
|
||||
}
|
||||
.solution-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: cover;
|
||||
border-radius: 16 * 5.12px;
|
||||
}
|
||||
|
||||
.advantages-section {
|
||||
margin-top: 64 * 5.12px;
|
||||
padding: 64 * 5.12px 0;
|
||||
background-image: url("@/assets/image/375/product-introduction-img4.png");
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
color: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.advantages-content {
|
||||
width: 346 * 5.12px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
gap: 16 * 5.12px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.advantages-header {
|
||||
width: 100%;
|
||||
padding: 0 16 * 5.12px;
|
||||
}
|
||||
.advantages-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24 * 5.12px;
|
||||
}
|
||||
.advantage-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16 * 5.12px;
|
||||
}
|
||||
.advantage-title {
|
||||
font-size: 24 * 5.12px;
|
||||
font-weight: 500;
|
||||
line-height: 32 * 5.12px;
|
||||
letter-spacing: 1.2 * 5.12px;
|
||||
display: flex;
|
||||
gap: 16 * 5.12px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.advantage-description {
|
||||
font-size: 16 * 5.12px;
|
||||
line-height: 22 * 5.12px;
|
||||
letter-spacing: 0.48 * 5.12px;
|
||||
opacity: 0.7;
|
||||
padding: 0 16 * 5.12px;
|
||||
}
|
||||
.text-white {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.cta-section {
|
||||
padding: 80 * 5.12px 0 21 * 5.12px 0;
|
||||
width: 346 * 5.12px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.cta-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16 * 5.12px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cta-title {
|
||||
font-family: "PingFang SC";
|
||||
font-size: 24 * 5.12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: normal;
|
||||
}
|
||||
.cta-qr-code {
|
||||
width: 188 * 5.12px;
|
||||
height: 188 * 5.12px;
|
||||
background-color: #90ffff;
|
||||
border-radius: 16 * 5.12px;
|
||||
padding: 14 * 5.12px;
|
||||
margin: 20 * 5.12px 0;
|
||||
}
|
||||
.cta-qr-code img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
.cta-bg-img {
|
||||
width: 480 * 5.12px;
|
||||
height: auto;
|
||||
opacity: 0.8;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.vertical-line {
|
||||
width: 1 * 5.12px;
|
||||
height: 20 * 5.12px;
|
||||
background: #ff7bac;
|
||||
flex-shrink: 0;
|
||||
margin-top: 6 * 5.12px;
|
||||
}
|
||||
|
||||
.grid-lines {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 346 * 5.12px;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.grid-lines .line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.grid-lines .line.solid {
|
||||
width: 1 * 5.12px;
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.grid-lines .line.dashed {
|
||||
width: 0;
|
||||
border-left: 1 * 5.12px dotted rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.grid-lines .line-1 {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.grid-lines .line-5 {
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
@ -2,23 +2,21 @@
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<div class="grid-lines px-fixed">
|
||||
<div class="grid-lines">
|
||||
<div class="line solid line-1"></div>
|
||||
<div class="line dashed line-2"></div>
|
||||
<div class="line dashed line-3"></div>
|
||||
<div class="line dashed line-4"></div>
|
||||
<div class="line solid line-5"></div>
|
||||
</div>
|
||||
<section class="hero-section px-fixed relative">
|
||||
<section class="hero-section relative">
|
||||
<div class="hero-content">
|
||||
<div class="hero-title">
|
||||
More than just a tool——<br />
|
||||
Comprehensive growth solutions, <br />
|
||||
providing a one-stop solution for content creation,<br />
|
||||
publishing, analysis, and monetization
|
||||
Comprehensive growth solutions, <br />providing a one-stop solution
|
||||
for <br />content creation, publishing, analysis, <br />and
|
||||
monetization
|
||||
</div>
|
||||
</div>
|
||||
<div class="core-value-card px-fixed">
|
||||
<div class="core-value-card">
|
||||
<div class="card-content">
|
||||
<div class="card-title">Core Value</div>
|
||||
<div class="card-text">
|
||||
@ -33,13 +31,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src="@/assets/image/1440/product-introduction-img2.png"
|
||||
src="@/assets/image/768/product-introduction-img2.png"
|
||||
alt="background"
|
||||
class="hero-bg-img"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section class="features-section px-fixed">
|
||||
<section class="features-section">
|
||||
<div class="section-header">
|
||||
<div class="decorator-bar"></div>
|
||||
<div class="section-title">Product Features</div>
|
||||
@ -47,7 +45,7 @@
|
||||
<div class="features-list">
|
||||
<div class="feature-item">
|
||||
<div class="feature-title">
|
||||
<div class="vertical-line px-fixed"></div>
|
||||
<div class="vertical-line"></div>
|
||||
One-click Synchronous Publishing
|
||||
</div>
|
||||
<div class="feature-description">
|
||||
@ -57,7 +55,7 @@
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="feature-title">
|
||||
<div class="vertical-line px-fixed"></div>
|
||||
<div class="vertical-line"></div>
|
||||
Intelligent Scheduled Publishing
|
||||
</div>
|
||||
<div class="feature-description">
|
||||
@ -67,7 +65,7 @@
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="feature-title">
|
||||
<div class="vertical-line px-fixed"></div>
|
||||
<div class="vertical-line"></div>
|
||||
Unified Management of Multiple Accounts
|
||||
</div>
|
||||
<div class="feature-description">
|
||||
@ -78,7 +76,7 @@
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="feature-title">
|
||||
<div class="vertical-line px-fixed"></div>
|
||||
<div class="vertical-line"></div>
|
||||
Cloud Content Library
|
||||
</div>
|
||||
<div class="feature-description">
|
||||
@ -88,7 +86,7 @@
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<div class="feature-title">
|
||||
<div class="vertical-line px-fixed"></div>
|
||||
<div class="vertical-line"></div>
|
||||
Basic Data Tracking
|
||||
</div>
|
||||
<div class="feature-description">
|
||||
@ -100,21 +98,28 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="solutions-section px-fixed">
|
||||
<section class="solutions-section">
|
||||
<div class="section-header">
|
||||
<div class="decorator-bar"></div>
|
||||
<div class="section-title">Value Added Solutions</div>
|
||||
</div>
|
||||
<div class="solutions-content">
|
||||
<div class="solution-image-container">
|
||||
<img
|
||||
src="@/assets/image/768/product-introduction-img1.png"
|
||||
alt="Value Added Solutions"
|
||||
class="solution-image"
|
||||
/>
|
||||
</div>
|
||||
<div class="solutions-list">
|
||||
<div class="solution-item">
|
||||
<img
|
||||
src="@/assets/image/1440/product-introduction-icon1.png"
|
||||
src="@/assets/image/768/product-introduction-icon1.png"
|
||||
alt="KOL Brand Promotion"
|
||||
class="solution-icon"
|
||||
/>
|
||||
<div class="solution-title">
|
||||
<div class="vertical-line px-fixed"></div>
|
||||
<div class="vertical-line"></div>
|
||||
KOL Brand Promotion Services
|
||||
</div>
|
||||
<div class="solution-description">
|
||||
@ -125,12 +130,12 @@
|
||||
</div>
|
||||
<div class="solution-item">
|
||||
<img
|
||||
src="@/assets/image/1440/product-introduction-icon2.png"
|
||||
src="@/assets/image/768/product-introduction-icon2.png"
|
||||
alt="Content Creation Support"
|
||||
class="solution-icon"
|
||||
/>
|
||||
<div class="solution-title">
|
||||
<div class="vertical-line px-fixed"></div>
|
||||
<div class="vertical-line"></div>
|
||||
Professional Content Creation Support
|
||||
</div>
|
||||
<div class="solution-description">
|
||||
@ -141,12 +146,12 @@
|
||||
</div>
|
||||
<div class="solution-item">
|
||||
<img
|
||||
src="@/assets/image/1440/product-introduction-icon3.png"
|
||||
src="@/assets/image/768/product-introduction-icon3.png"
|
||||
alt="Account Operation"
|
||||
class="solution-icon"
|
||||
/>
|
||||
<div class="solution-title">
|
||||
<div class="vertical-line px-fixed"></div>
|
||||
<div class="vertical-line"></div>
|
||||
Account Operation and Hosting Services
|
||||
</div>
|
||||
<div class="solution-description">
|
||||
@ -156,40 +161,29 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="solution-image-container">
|
||||
<img
|
||||
src="@/assets/image/1440/product-introduction-img1.png"
|
||||
alt="Value Added Solutions"
|
||||
class="solution-image"
|
||||
style="width: 434px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="advantages-section px-fixed">
|
||||
<div class="advantages-content px-fixed">
|
||||
<section class="advantages-section">
|
||||
<div class="advantages-content">
|
||||
<div class="advantages-header">
|
||||
<div class="decorator-bar"></div>
|
||||
<div class="section-title text-white">Our Advantages</div>
|
||||
</div>
|
||||
|
||||
<div style="width: 50%">
|
||||
<div class="advantages-list">
|
||||
<div class="advantage-item">
|
||||
<div class="advantage-title">
|
||||
<div class="vertical-line px-fixed"></div>
|
||||
<div class="vertical-line"></div>
|
||||
Time Saving
|
||||
</div>
|
||||
<div class="advantage-description">
|
||||
Multi platform publishing efficiency improvement, allowing you
|
||||
to focus on content creation.
|
||||
Multi platform publishing efficiency improvement, allowing you to
|
||||
focus on content creation.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="advantage-item">
|
||||
<div class="advantage-title">
|
||||
<div class="vertical-line px-fixed"></div>
|
||||
<div class="vertical-line"></div>
|
||||
Safe and Reliable
|
||||
</div>
|
||||
<div class="advantage-description">
|
||||
@ -199,7 +193,7 @@
|
||||
</div>
|
||||
<div class="advantage-item">
|
||||
<div class="advantage-title">
|
||||
<div class="vertical-line px-fixed"></div>
|
||||
<div class="vertical-line"></div>
|
||||
Maintain Consistency
|
||||
</div>
|
||||
<div class="advantage-description">
|
||||
@ -209,7 +203,7 @@
|
||||
</div>
|
||||
<div class="advantage-item">
|
||||
<div class="advantage-title">
|
||||
<div class="vertical-line px-fixed"></div>
|
||||
<div class="vertical-line"></div>
|
||||
Data Driven
|
||||
</div>
|
||||
<div class="advantage-description">
|
||||
@ -218,7 +212,7 @@
|
||||
</div>
|
||||
<div class="advantage-item">
|
||||
<div class="advantage-title">
|
||||
<div class="vertical-line px-fixed"></div>
|
||||
<div class="vertical-line"></div>
|
||||
Easy to Use
|
||||
</div>
|
||||
<div class="advantage-description">
|
||||
@ -228,12 +222,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cta-section px-fixed">
|
||||
<section class="cta-section">
|
||||
<img
|
||||
src="@/assets/image/1440/product-introduction-img5.png"
|
||||
src="@/assets/image/768/product-introduction-img5.png"
|
||||
alt="background"
|
||||
class="cta-bg-img"
|
||||
/>
|
||||
@ -258,7 +251,7 @@
|
||||
</div>
|
||||
<div class="cta-qr-code">
|
||||
<img
|
||||
src="@/assets/image/1440/product-introduction-img6.png"
|
||||
src="@/assets/image/768/product-introduction-img6.png"
|
||||
alt="QR Code"
|
||||
/>
|
||||
</div>
|
||||
@ -267,7 +260,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.page-container {
|
||||
background-color: #fff;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
@ -275,10 +268,10 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hero-section.px-fixed {
|
||||
.hero-section {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
background-image: url("@/assets/image/1440/product-introduction-img3.png");
|
||||
background-image: url("@/assets/image/768/product-introduction-img3.png");
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% auto;
|
||||
background-position: top;
|
||||
@ -290,28 +283,29 @@
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 40px;
|
||||
font-size: 32 * 2.5px;
|
||||
font-weight: 500;
|
||||
line-height: 56px;
|
||||
letter-spacing: 1.2px;
|
||||
padding: 153px 0;
|
||||
line-height: 56 * 2.5px;
|
||||
letter-spacing: 1.2 * 2.5px;
|
||||
padding: 153 * 2.5px 0 133 * 2.5px 0;
|
||||
color: #000;
|
||||
z-index: 2;
|
||||
}
|
||||
.hero-bg-img {
|
||||
position: absolute;
|
||||
bottom: -204px;
|
||||
bottom: -24 * 2.5px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
/* height: 100%; */
|
||||
z-index: 1;
|
||||
}
|
||||
.core-value-card.px-fixed {
|
||||
width: 932px;
|
||||
padding: 40px 32px;
|
||||
.core-value-card {
|
||||
width: 662 * 2.5px;
|
||||
padding: 40 * 2.5px 32 * 2.5px;
|
||||
margin: 0 auto;
|
||||
background-color: #fff;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0px 3px 14px 0px rgba(0, 0, 0, 0.16);
|
||||
border-radius: 16 * 2.5px;
|
||||
box-shadow: 0 * 2.5px 3 * 2.5px 14 * 2.5px 0 * 2.5px rgba(0, 0, 0, 0.16);
|
||||
text-align: left;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
@ -320,204 +314,191 @@
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
gap: 24 * 2.5px;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 40px;
|
||||
font-size: 40 * 2.5px;
|
||||
font-weight: 500;
|
||||
line-height: 56px;
|
||||
letter-spacing: 1.2px;
|
||||
line-height: 56 * 2.5px;
|
||||
letter-spacing: 1.2 * 2.5px;
|
||||
}
|
||||
|
||||
.card-text {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
font-size: 16 * 2.5px;
|
||||
line-height: 22 * 2.5px;
|
||||
color: #455363;
|
||||
letter-spacing: 0.48px;
|
||||
letter-spacing: 0.48 * 2.5px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: 32px;
|
||||
padding: 0 16px;
|
||||
margin-bottom: 32 * 2.5px;
|
||||
padding: 0 16 * 2.5px;
|
||||
}
|
||||
|
||||
.decorator-bar {
|
||||
width: 58px;
|
||||
height: 7px;
|
||||
width: 58 * 2.5px;
|
||||
height: 7 * 2.5px;
|
||||
background-color: #ff7bac;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 16 * 2.5px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 40px;
|
||||
font-size: 40 * 2.5px;
|
||||
font-weight: 500;
|
||||
line-height: 56px;
|
||||
letter-spacing: 1.2px;
|
||||
line-height: 56 * 2.5px;
|
||||
letter-spacing: 1.2 * 2.5px;
|
||||
color: #000;
|
||||
}
|
||||
.features-section.px-fixed {
|
||||
padding-top: 200px;
|
||||
width: 932px;
|
||||
.features-section {
|
||||
padding-top: 100 * 2.5px;
|
||||
width: 662 * 2.5px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.features-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
gap: 24 * 2.5px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: 16 * 2.5px;
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-size: 24px;
|
||||
font-size: 24 * 2.5px;
|
||||
font-weight: 500;
|
||||
line-height: 32px;
|
||||
letter-spacing: 1.2px;
|
||||
line-height: 32 * 2.5px;
|
||||
letter-spacing: 1.2 * 2.5px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
gap: 16 * 2.5px;
|
||||
}
|
||||
.feature-description {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
font-size: 16 * 2.5px;
|
||||
line-height: 22 * 2.5px;
|
||||
color: #455363;
|
||||
letter-spacing: 0.48px;
|
||||
padding: 0 16px;
|
||||
text-align: justify;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
letter-spacing: 0.48 * 2.5px;
|
||||
padding: 0 16 * 2.5px;
|
||||
}
|
||||
|
||||
.solutions-section.px-fixed {
|
||||
padding-top: 80px;
|
||||
width: 932px;
|
||||
.solutions-section {
|
||||
padding-top: 80 * 2.5px;
|
||||
width: 662 * 2.5px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.solutions-content {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
gap: 32 * 2.5px;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.solutions-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
width: 466px;
|
||||
gap: 24 * 2.5px;
|
||||
width: 100%;
|
||||
}
|
||||
.solution-item {
|
||||
text-align: left;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
.solution-icon {
|
||||
width: 92px;
|
||||
height: 76px;
|
||||
padding-left: 16px;
|
||||
width: 92 * 2.5px;
|
||||
height: 76 * 2.5px;
|
||||
padding-left: 16 * 2.5px;
|
||||
}
|
||||
.solution-title {
|
||||
font-family: "PingFang SC";
|
||||
font-size: 24px;
|
||||
font-size: 24 * 2.5px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 32px; /* 133.333% */
|
||||
letter-spacing: 1.2px;
|
||||
line-height: 32 * 2.5px; /* 133.333% */
|
||||
letter-spacing: 1.2 * 2.5px;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
gap: 16 * 2.5px;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 16 * 2.5px;
|
||||
}
|
||||
.solution-description {
|
||||
align-self: stretch;
|
||||
font-size: 16 * 2.5px;
|
||||
line-height: 22 * 2.5px;
|
||||
color: #455363;
|
||||
padding: 0 16px;
|
||||
|
||||
text-align: justify;
|
||||
font-feature-settings: "liga" off, "clig" off;
|
||||
/* 正文 */
|
||||
font-family: "PingFang SC";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 22px; /* 137.5% */
|
||||
letter-spacing: 0.48px;
|
||||
letter-spacing: 0.48 * 2.5px;
|
||||
padding: 0 16 * 2.5px;
|
||||
}
|
||||
.solution-image-container {
|
||||
width: 434px;
|
||||
border-radius: 16px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border-radius: 16 * 2.5px;
|
||||
}
|
||||
.solution-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: auto;
|
||||
object-fit: cover;
|
||||
border-radius: 16px;
|
||||
border-radius: 16 * 2.5px;
|
||||
}
|
||||
|
||||
.advantages-section.px-fixed {
|
||||
margin-top: 80px;
|
||||
padding: 80px 0;
|
||||
background-image: url("@/assets/image/1440/product-introduction-img4.png");
|
||||
.advantages-section {
|
||||
margin-top: 80 * 2.5px;
|
||||
padding: 80 * 2.5px 0;
|
||||
background-image: url("@/assets/image/768/product-introduction-img4.png");
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
color: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.advantages-content.px-fixed {
|
||||
width: 932px;
|
||||
.advantages-content {
|
||||
width: 662 * 2.5px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16 * 2.5px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.advantages-header {
|
||||
width: 466px;
|
||||
padding: 0 16px;
|
||||
width: 100%;
|
||||
padding: 0 16 * 2.5px;
|
||||
}
|
||||
.advantages-list {
|
||||
width: 466px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
gap: 24 * 2.5px;
|
||||
}
|
||||
.advantage-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: 16 * 2.5px;
|
||||
}
|
||||
.advantage-title {
|
||||
font-size: 24px;
|
||||
font-size: 24 * 2.5px;
|
||||
font-weight: 500;
|
||||
line-height: 32px;
|
||||
letter-spacing: 1.2px;
|
||||
line-height: 32 * 2.5px;
|
||||
letter-spacing: 1.2 * 2.5px;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
gap: 16 * 2.5px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.advantage-description {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
letter-spacing: 0.48px;
|
||||
font-size: 16 * 2.5px;
|
||||
line-height: 22 * 2.5px;
|
||||
letter-spacing: 0.48 * 2.5px;
|
||||
opacity: 0.7;
|
||||
padding: 0 16px;
|
||||
}
|
||||
.text-white {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.cta-section.px-fixed {
|
||||
padding: 60px 0;
|
||||
.cta-section {
|
||||
padding: 80 * 2.5px 0;
|
||||
position: relative;
|
||||
width: 932px;
|
||||
width: 662 * 2.5px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -525,7 +506,7 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
padding: 0 16 * 2.5px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
@ -533,24 +514,24 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 188px;
|
||||
height: 188 * 2.5px;
|
||||
}
|
||||
.cta-arrow {
|
||||
width: 60px;
|
||||
height: 32px;
|
||||
width: 60 * 2.5px;
|
||||
height: 32 * 2.5px;
|
||||
}
|
||||
.cta-title {
|
||||
font-size: 40px;
|
||||
font-size: 40 * 2.5px;
|
||||
font-weight: 500;
|
||||
line-height: 56px;
|
||||
letter-spacing: 1.2px;
|
||||
line-height: 56 * 2.5px;
|
||||
letter-spacing: 1.2 * 2.5px;
|
||||
}
|
||||
.cta-qr-code {
|
||||
width: 188px;
|
||||
height: 188px;
|
||||
width: 188 * 2.5px;
|
||||
height: 188 * 2.5px;
|
||||
background-color: #90ffff;
|
||||
border-radius: 16px;
|
||||
padding: 14px;
|
||||
border-radius: 16 * 2.5px;
|
||||
padding: 14 * 2.5px;
|
||||
}
|
||||
.cta-qr-code img {
|
||||
width: 100%;
|
||||
@ -559,27 +540,27 @@
|
||||
}
|
||||
.cta-bg-img {
|
||||
position: absolute;
|
||||
top: 80px;
|
||||
left: 355px;
|
||||
width: 530px;
|
||||
height: 268px;
|
||||
top: 80 * 2.5px;
|
||||
left: 60 * 2.5px;
|
||||
width: 530 * 2.5px;
|
||||
height: 268 * 2.5px;
|
||||
opacity: 0.8;
|
||||
z-index: 0;
|
||||
}
|
||||
.vertical-line.px-fixed {
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
.vertical-line {
|
||||
width: 1 * 2.5px;
|
||||
height: 20 * 2.5px;
|
||||
background: #ff7bac;
|
||||
flex-shrink: 0;
|
||||
margin-top: 6px;
|
||||
margin-top: 6 * 2.5px;
|
||||
}
|
||||
|
||||
.grid-lines.px-fixed {
|
||||
.grid-lines {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 932px;
|
||||
width: 662 * 2.5px;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
@ -592,26 +573,26 @@
|
||||
}
|
||||
|
||||
.grid-lines .line.solid {
|
||||
width: 1px;
|
||||
width: 1 * 2.5px;
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.grid-lines .line.dashed {
|
||||
width: 0;
|
||||
border-left: 1px dotted rgba(0, 0, 0, 0.12);
|
||||
border-left: 1 * 2.5px dotted rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.grid-lines .line-1 {
|
||||
left: 0;
|
||||
}
|
||||
.grid-lines .line-2 {
|
||||
left: 310px;
|
||||
left: 162.5 * 2.5px;
|
||||
}
|
||||
.grid-lines .line-3 {
|
||||
left: 620px;
|
||||
left: 325 * 2.5px;
|
||||
}
|
||||
.grid-lines .line-4 {
|
||||
left: 930px;
|
||||
left: 487.5 * 2.5px;
|
||||
}
|
||||
.grid-lines .line-5 {
|
||||
right: 0;
|
||||
|
@ -1,7 +1,6 @@
|
||||
<script setup>
|
||||
import { useStockQuote } from "@/store/stock-quote/index.js";
|
||||
const { getStockQuate, stockQuote, formatted } = useStockQuote();
|
||||
console.log(stockQuote);
|
||||
getStockQuate();
|
||||
</script>
|
||||
|
||||
|
@ -1,82 +1,211 @@
|
||||
<script setup>
|
||||
import { onMounted } from "vue";
|
||||
import { useStockQuote } from "@/store/stock-quote/index.js";
|
||||
const { getStockQuate, stockQuote, formatted } = useStockQuote();
|
||||
getStockQuate();
|
||||
onMounted(() => {
|
||||
getStockQuate();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main
|
||||
class="min-h-60vh flex flex-col items-center justify-start px-2 py-5 pt-500px"
|
||||
>
|
||||
<!-- 价格卡片 -->
|
||||
<section
|
||||
class="w-full max-w-90vw flex flex-col items-center justify-center glass-card p-4 rounded-2xl shadow mb-5"
|
||||
>
|
||||
<div
|
||||
class="text-4xl font-extrabold text-#ff7bac animate-bg-move select-none drop-shadow-lg"
|
||||
>
|
||||
${{ stockQuote.price }}
|
||||
<main class="stock-quote-container-375">
|
||||
<div class="content-wrapper">
|
||||
<div class="title-section">
|
||||
<div class="title-decoration"></div>
|
||||
<div class="title-text">Stock Quote</div>
|
||||
</div>
|
||||
<div
|
||||
class="mt-2 text-sm text-gray-500 font-semibold tracking-widest mb-0px"
|
||||
>
|
||||
NASDAQ: <span class="text-black">FIEE</span>
|
||||
</div>
|
||||
<div class="text-gray-500 text-60px">{{ formatted }}</div>
|
||||
</section>
|
||||
<!-- 信息卡片列表 -->
|
||||
<section class="w-full max-w-90vw grid grid-cols-2 gap-2">
|
||||
<div class="info-card">
|
||||
<div class="text-xs text-gray-400">Open</div>
|
||||
<div class="text-lg font-bold">{{ stockQuote.open }}</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="text-xs text-gray-400">% Change</div>
|
||||
|
||||
<div
|
||||
class="text-lg font-bold"
|
||||
:class="
|
||||
stockQuote.change?.includes('-') ? 'text-green-500' : 'text-red-500'
|
||||
"
|
||||
<div class="data-section">
|
||||
<div class="price-card">
|
||||
<div class="price-value">${{ stockQuote.price }}</div>
|
||||
<div class="price-nasdaq">NASDAQ: FIEE</div>
|
||||
<div class="price-date">{{ formatted }}</div>
|
||||
</div>
|
||||
<div class="details-grid">
|
||||
<div class="details-column">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Open</span>
|
||||
<span class="detail-value">{{ stockQuote.open }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Day's Range</span>
|
||||
<span class="detail-value">{{ stockQuote.daysRange }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Volume</span>
|
||||
<span class="detail-value">{{ stockQuote.volume }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="details-column">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">% Change</span>
|
||||
<span
|
||||
class="detail-value"
|
||||
:class="{
|
||||
'text-red': String(stockQuote.change).includes('-'),
|
||||
'text-green': String(stockQuote.change).includes('+'),
|
||||
}"
|
||||
>
|
||||
{{ stockQuote.change }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">52-Week Range</span>
|
||||
<span class="detail-value">{{ stockQuote.week52Range }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Market Cap</span>
|
||||
<span class="detail-value">{{ stockQuote.marketCap }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="text-xs text-gray-400">Day's Range</div>
|
||||
<div class="text-lg font-bold">{{ stockQuote.daysRange }}</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="text-xs text-gray-400">52-Week Range</div>
|
||||
<div class="text-lg font-bold">{{ stockQuote.week52Range }}</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="text-xs text-gray-400">Volume</div>
|
||||
<div class="text-lg font-bold">{{ stockQuote.volume }}</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="text-xs text-gray-400">Market Cap</div>
|
||||
<div class="text-lg font-bold">{{ stockQuote.marketCap }}</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.glass-card {
|
||||
backdrop-filter: blur(10px);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid rgba(200, 200, 255, 0.18);
|
||||
box-shadow: 0 2px 8px 0 rgba(255, 123, 172, 0.08);
|
||||
<style scoped lang="scss">
|
||||
.stock-quote-container-375 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
background-image: url("@/assets/image/375/bg-stock-quote.png");
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
height: 100vh;
|
||||
}
|
||||
.info-card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 1px 4px 0 rgba(255, 123, 172, 0.06);
|
||||
padding: 12px 10px;
|
||||
|
||||
.content-wrapper {
|
||||
width: 343 * 5.12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8 * 5.12px;
|
||||
margin-bottom: 32 * 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;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 24 * 5.12px;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.03em;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.data-section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 16 * 5.12px;
|
||||
}
|
||||
|
||||
.price-card {
|
||||
width: 100%;
|
||||
height: 280 * 5.12px;
|
||||
background-color: white;
|
||||
border-radius: 16 * 5.12px;
|
||||
box-shadow: 0 3 * 5.12px 14 * 5.12px 0 rgba(0, 0, 0, 0.16);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16 * 5.12px;
|
||||
gap: 4 * 5.12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
width: 100%;
|
||||
font-size: 88 * 5.12px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.48 * 5.12px;
|
||||
background: linear-gradient(to right, #ff7bac, #00ffff);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.price-nasdaq {
|
||||
width: 100%;
|
||||
font-size: 20 * 5.12px;
|
||||
color: black;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1.2 * 5.12px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.price-date {
|
||||
width: 100%;
|
||||
font-size: 16 * 5.12px;
|
||||
color: #455363;
|
||||
font-weight: 500;
|
||||
letter-spacing: 1.2 * 5.12px;
|
||||
}
|
||||
|
||||
.details-grid {
|
||||
width: 343 * 5.12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.details-column {
|
||||
width: 172 * 5.12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
width: 100%;
|
||||
height: 155 * 5.12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
// padding: 16 * 5.12px 32 * 5.12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
width: 100%;
|
||||
font-size: 16 * 5.12px;
|
||||
color: #455363;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
width: 100%;
|
||||
font-size: 20 * 5.12px;
|
||||
color: black;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1.2 * 5.12px;
|
||||
}
|
||||
|
||||
.text-red {
|
||||
color: #cf3050;
|
||||
}
|
||||
|
||||
.text-green {
|
||||
color: #00c48c;
|
||||
}
|
||||
</style>
|
||||
|
@ -7,81 +7,201 @@ getStockQuate();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main ref="main">
|
||||
<main
|
||||
class="min-h-60vh flex flex-col items-center justify-start px-4 py-6 pt-500px"
|
||||
>
|
||||
<!-- 价格卡片 -->
|
||||
<section
|
||||
class="w-full max-w-80vw flex flex-col items-center justify-center glass-card p-6 rounded-2xl shadow mb-6"
|
||||
>
|
||||
<div
|
||||
class="text-5xl font-extrabold text-#ff7bac animate-bg-move select-none drop-shadow-lg"
|
||||
>
|
||||
${{ stockQuote.price }}
|
||||
<main class="stock-quote-container-768">
|
||||
<div class="content-wrapper">
|
||||
<div class="title-section">
|
||||
<div class="title-decoration"></div>
|
||||
<div class="title-text">Stock Quote</div>
|
||||
</div>
|
||||
<div
|
||||
class="mt-3 text-base text-gray-500 font-semibold tracking-widest mb-0px"
|
||||
>
|
||||
NASDAQ: <span class="text-black">FIEE</span>
|
||||
|
||||
<div class="data-section">
|
||||
<div class="price-card">
|
||||
<div class="price-value">${{ stockQuote.price }}</div>
|
||||
<div class="price-nasdaq">NASDAQ: FIEE</div>
|
||||
<div class="price-date">{{ formatted }}</div>
|
||||
</div>
|
||||
<div class="text-gray-500 text-70px">{{ formatted }}</div>
|
||||
</section>
|
||||
<!-- 信息卡片列表 -->
|
||||
<section class="w-full max-w-80vw grid grid-cols-3 gap-4">
|
||||
<div class="info-card">
|
||||
<div class="text-sm text-gray-400">Open</div>
|
||||
<div class="text-xl font-bold">{{ stockQuote.open }}</div>
|
||||
<div class="details-grid">
|
||||
<div class="details-column">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Open</span>
|
||||
<span class="detail-value">{{ stockQuote.open }}</span>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="text-sm text-gray-400">% Change</div>
|
||||
<div
|
||||
class="text-xl font-bold"
|
||||
:class="
|
||||
stockQuote.change?.includes('-')
|
||||
? 'text-green-500'
|
||||
: 'text-red-500'
|
||||
"
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Day's Range</span>
|
||||
<span class="detail-value">{{ stockQuote.daysRange }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Volume</span>
|
||||
<span class="detail-value">{{ stockQuote.volume }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="details-column">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">% Change</span>
|
||||
<span
|
||||
class="detail-value"
|
||||
:class="{
|
||||
'text-red': String(stockQuote.change).includes('-'),
|
||||
'text-green': String(stockQuote.change).includes('+'),
|
||||
}"
|
||||
>
|
||||
{{ stockQuote.change }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">52-Week Range</span>
|
||||
<span class="detail-value">{{ stockQuote.week52Range }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Market Cap</span>
|
||||
<span class="detail-value">{{ stockQuote.marketCap }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="text-sm text-gray-400">Day's Range</div>
|
||||
<div class="text-xl font-bold">{{ stockQuote.daysRange }}</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="text-sm text-gray-400">52-Week Range</div>
|
||||
<div class="text-xl font-bold">{{ stockQuote.week52Range }}</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="text-sm text-gray-400">Volume</div>
|
||||
<div class="text-xl font-bold">{{ stockQuote.volume }}</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="text-sm text-gray-400">Market Cap</div>
|
||||
<div class="text-xl font-bold">{{ stockQuote.marketCap }}</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.glass-card {
|
||||
backdrop-filter: blur(10px);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
border: 1px solid rgba(200, 200, 255, 0.18);
|
||||
box-shadow: 0 3px 12px 0 rgba(255, 123, 172, 0.08);
|
||||
.stock-quote-container-768 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
background-image: url("@/assets/image/768/bg-stock-quote.png");
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
.info-card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 2px 6px 0 rgba(255, 123, 172, 0.06);
|
||||
padding: 16px 14px;
|
||||
|
||||
.content-wrapper {
|
||||
width: 650 * 2.5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.title-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16 * 2.5px;
|
||||
margin-bottom: 32 * 2.5px;
|
||||
margin-top: 43 * 2.5px;
|
||||
padding: 0 16 * 2.5px;
|
||||
}
|
||||
|
||||
.title-decoration {
|
||||
width: 58 * 2.5px;
|
||||
height: 7 * 2.5px;
|
||||
background: #ff7bac;
|
||||
margin: auto 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-family: "PingFang SC", sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 32 * 2.5px;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.03em;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.data-section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.price-card {
|
||||
width: 100%;
|
||||
height: 355 * 2.5px;
|
||||
background-color: white;
|
||||
border-radius: 16 * 2.5px;
|
||||
box-shadow: 0 * 2.5px 3 * 2.5px 14 * 2.5px 0 * 2.5px rgba(0, 0, 0, 0.16);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16 * 2.5px;
|
||||
gap: 30 * 2.5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
width: 100%;
|
||||
font-size: 110 * 2.5px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.48 * 2.5px;
|
||||
background: linear-gradient(to right, #ff7bac, #00ffff);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.price-nasdaq {
|
||||
width: 100%;
|
||||
font-size: 24 * 2.5px;
|
||||
color: black;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1.2 * 2.5px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.price-date {
|
||||
width: 100%;
|
||||
font-size: 18 * 2.5px;
|
||||
color: #455363;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.details-grid {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.details-column {
|
||||
width: 325 * 2.5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
width: 100%;
|
||||
height: 155 * 2.5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16 * 2.5px 32 * 2.5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
width: 100%;
|
||||
font-size: 18 * 2.5px;
|
||||
color: #455363;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
width: 100%;
|
||||
font-size: 24 * 2.5px;
|
||||
color: black;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1.2 * 2.5px;
|
||||
}
|
||||
|
||||
.text-red {
|
||||
color: #cf3050;
|
||||
}
|
||||
|
||||
.text-green {
|
||||
color: #00c48c;
|
||||
}
|
||||
</style>
|
||||
|