feat: 新增后台日期选择器组件并应用于站点设置
continuous-integration/drone/push Build is failing

This commit is contained in:
seahi committed 2026-08-14 18:03:33 +08:00
1 parent 4b6937ecd2
commit 0714b565da
2 files changed
+275 -19

No files matched your search

+272
View File
@@ -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}>&lt;</button>
<span class="current-month">{currentYear}年 {currentMonth + 1}月</span>
<button type="button" class="nav-btn" onclick={nextMonth}>&gt;</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>
+3 -19
View File
@@ -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;