This commit is contained in:
1 parent
4b6937ecd2
commit
0714b565da
2 files changed
+275
-19
No files matched your search
@@ -0,0 +1,272 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let {
|
||||
value = $bindable(''),
|
||||
name = '',
|
||||
id = ''
|
||||
} = $props<{ value?: string; name?: string; id?: string }>();
|
||||
|
||||
let isOpen = $state(false);
|
||||
|
||||
let displayDate = $state(new Date());
|
||||
|
||||
$effect(() => {
|
||||
if (value) {
|
||||
const parsed = new Date(value);
|
||||
if (!isNaN(parsed.getTime())) {
|
||||
displayDate = parsed;
|
||||
}
|
||||
} else {
|
||||
displayDate = new Date();
|
||||
}
|
||||
});
|
||||
|
||||
let currentMonth = $state(displayDate.getMonth());
|
||||
let currentYear = $state(displayDate.getFullYear());
|
||||
|
||||
let container: HTMLDivElement;
|
||||
|
||||
function handleOutsideClick(e: MouseEvent) {
|
||||
if (isOpen && container && !container.contains(e.target as Node)) {
|
||||
isOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
document.addEventListener('click', handleOutsideClick);
|
||||
return () => document.removeEventListener('click', handleOutsideClick);
|
||||
});
|
||||
|
||||
function prevMonth() {
|
||||
if (currentMonth === 0) {
|
||||
currentMonth = 11;
|
||||
currentYear--;
|
||||
} else {
|
||||
currentMonth--;
|
||||
}
|
||||
}
|
||||
|
||||
function nextMonth() {
|
||||
if (currentMonth === 11) {
|
||||
currentMonth = 0;
|
||||
currentYear++;
|
||||
} else {
|
||||
currentMonth++;
|
||||
}
|
||||
}
|
||||
|
||||
function selectDate(day: number) {
|
||||
const monthStr = String(currentMonth + 1).padStart(2, '0');
|
||||
const dayStr = String(day).padStart(2, '0');
|
||||
value = `${currentYear}-${monthStr}-${dayStr}`;
|
||||
isOpen = false;
|
||||
}
|
||||
|
||||
let daysInMonth = $derived(new Date(currentYear, currentMonth + 1, 0).getDate());
|
||||
let firstDayOfWeek = $derived(new Date(currentYear, currentMonth, 1).getDay()); // 0 is Sunday
|
||||
|
||||
const weekDays = ['日', '一', '二', '三', '四', '五', '六'];
|
||||
</script>
|
||||
|
||||
<div class="date-picker-wrapper" bind:this={container}>
|
||||
<div class="input-wrapper" aria-hidden="true" onclick={() => (isOpen = !isOpen)}>
|
||||
<input type="text" {id} {name} {value} readonly placeholder="选择建站日期..." />
|
||||
<span class="icon">
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect x="3" y="4" width="18" height="18" rx="0" ry="0"></rect>
|
||||
<line x1="16" y1="2" x2="16" y2="6"></line>
|
||||
<line x1="8" y1="2" x2="8" y2="6"></line>
|
||||
<line x1="3" y1="10" x2="21" y2="10"></line>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{#if isOpen}
|
||||
<div class="calendar-popup">
|
||||
<div class="calendar-header">
|
||||
<button type="button" class="nav-btn" onclick={prevMonth}><</button>
|
||||
<span class="current-month">{currentYear}年 {currentMonth + 1}月</span>
|
||||
<button type="button" class="nav-btn" onclick={nextMonth}>></button>
|
||||
</div>
|
||||
|
||||
<div class="calendar-grid">
|
||||
{#each weekDays as wd}
|
||||
<div class="weekday">{wd}</div>
|
||||
{/each}
|
||||
|
||||
{#each Array(firstDayOfWeek) as _}
|
||||
<div class="empty-day"></div>
|
||||
{/each}
|
||||
|
||||
{#each Array(daysInMonth) as _, i}
|
||||
{@const day = i + 1}
|
||||
{@const isSelected =
|
||||
value ===
|
||||
`${currentYear}-${String(currentMonth + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`}
|
||||
{@const isToday =
|
||||
new Date().getDate() === day &&
|
||||
new Date().getMonth() === currentMonth &&
|
||||
new Date().getFullYear() === currentYear}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="day-btn"
|
||||
class:selected={isSelected}
|
||||
class:today={isToday && !isSelected}
|
||||
onclick={() => selectDate(day)}
|
||||
>
|
||||
{day}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.date-picker-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.input-wrapper input {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
padding-right: 35px; /* for icon */
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
font-size: 1rem;
|
||||
color: var(--text-color);
|
||||
border-radius: 0;
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-wrapper:hover input {
|
||||
border-color: var(--text-color);
|
||||
}
|
||||
|
||||
.input-wrapper input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color, #419b38);
|
||||
box-shadow: 0 0 0 2px rgba(65, 155, 56, 0.1);
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.calendar-popup {
|
||||
position: absolute;
|
||||
top: calc(100% + 5px);
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
width: 280px;
|
||||
background: var(--bg-color, #fff);
|
||||
border: 2px solid var(--text-color);
|
||||
box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.1);
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.calendar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.current-month {
|
||||
font-weight: 600;
|
||||
font-size: 1.05rem;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
background: transparent;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
color: var(--text-color);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.nav-btn:hover {
|
||||
border-color: var(--text-color);
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.calendar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.weekday {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-muted, rgba(0, 0, 0, 0.5));
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.day-btn {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
color: var(--text-color);
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.day-btn:hover:not(.selected) {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-color: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.day-btn.today {
|
||||
border-color: var(--text-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.day-btn.selected {
|
||||
background: var(--text-color);
|
||||
color: var(--bg-color, #fff);
|
||||
border-color: var(--text-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
@@ -4,6 +4,7 @@
|
||||
import { addToast } from '$lib/stores/toast.svelte';
|
||||
import Tabs from '$lib/components/admin/Tabs.svelte';
|
||||
import ImagePicker from '$lib/components/admin/FeaturedImagePicker.svelte';
|
||||
import DatePicker from '$lib/components/admin/DatePicker.svelte';
|
||||
import { untrack } from 'svelte';
|
||||
|
||||
let { data, form }: { data: PageData; form: ActionData } = $props();
|
||||
@@ -87,11 +88,10 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="site_launch_date">建站日期</label>
|
||||
<input
|
||||
type="date"
|
||||
<DatePicker
|
||||
id="site_launch_date"
|
||||
name="site_launch_date"
|
||||
value={data.settings['site.launch_date'] || ''}
|
||||
bind:value={data.settings['site.launch_date']}
|
||||
/>
|
||||
<small class="form-hint">用于在底栏显示"感谢陪伴 X 天 X 小时"</small>
|
||||
</div>
|
||||
@@ -389,22 +389,6 @@
|
||||
box-shadow: 0 0 0 2px rgba(65, 155, 56, 0.1);
|
||||
}
|
||||
|
||||
/* 抹除浏览器原生日期选择器的特殊样式,保持与项目其他输入框一致 */
|
||||
.form-group input[type='date'] {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
.form-group input[type='date']::-webkit-calendar-picker-indicator {
|
||||
filter: invert(0.4);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
|
||||
Reference in new issue
Block a user