feat(ui): 查标第 6 步前端补齐
- 读标页:每条清单项写最近一次检查的结论,点投标侧出处看投标原文;
「已响应」也能反馈判断有误(漏报);证书要求写资质库里的情况
(新接口 GET /projects/{id}/qualification-matches,资质库空时不写)
- 概览:检查记录卡(历次检查、各级问题数、报告下载)、文件卡
- 工作台:「致命」列、统计可点筛选、待处理的致命问题卡(?f= 定位到问题)
- 日期输入加日历弹层(DateInput,Reka UI Calendar)
- 查标页:? 键显示快捷键说明;反馈对话框抽成 FeedbackDialog 共用
- 扫描件提示去掉多余空格
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
112
apps/web/src/components/DateInput.vue
Normal file
112
apps/web/src/components/DateInput.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* 日期输入(设计规范 §7.22、§9.3):可以直接键入(接受 2026/11/20、2026年11月20日 等,离开时整理成 2026-11-20),
|
||||
* 右侧按钮打开日期选择器。选择器周一开始;今天 1px 边框,选中墨底白字;非本月的格子留空;不可选的日期只变灰。
|
||||
* 交互基础用 Reka UI 的 Popover + Calendar(键盘方向键选日期、Esc 关闭、焦点回到按钮),外观来自 app.css 的 .dt / .cal。
|
||||
*/
|
||||
import { getLocalTimeZone, parseDate, today, type DateValue } from "@internationalized/date";
|
||||
import { Calendar, ChevronLeft, ChevronRight } from "@lucide/vue";
|
||||
import {
|
||||
CalendarCell,
|
||||
CalendarCellTrigger,
|
||||
CalendarGrid,
|
||||
CalendarGridBody,
|
||||
CalendarGridHead,
|
||||
CalendarGridRow,
|
||||
CalendarHeadCell,
|
||||
CalendarHeader,
|
||||
CalendarHeading,
|
||||
CalendarNext,
|
||||
CalendarPrev,
|
||||
CalendarRoot,
|
||||
PopoverAnchor,
|
||||
PopoverContent,
|
||||
PopoverPortal,
|
||||
PopoverRoot,
|
||||
PopoverTrigger,
|
||||
} from "reka-ui";
|
||||
import { computed, ref } from "vue";
|
||||
import { parseDay } from "@/lib/labels";
|
||||
|
||||
const props = defineProps<{ id?: string; placeholder?: string; disabled?: boolean; label?: string }>();
|
||||
const text = defineModel<string>({ required: true });
|
||||
const emit = defineEmits<{ blur: [] }>();
|
||||
|
||||
const open = ref(false);
|
||||
const input = ref<HTMLInputElement | null>(null);
|
||||
defineExpose({ select: () => input.value?.select(), focus: () => input.value?.focus() });
|
||||
|
||||
/** 输入框里能认出的日期,选择器打开时停在这个月 */
|
||||
const value = computed<DateValue | undefined>(() => {
|
||||
const d = parseDay(text.value);
|
||||
return d ? parseDate(d) : undefined;
|
||||
});
|
||||
function pick(v: DateValue | undefined) {
|
||||
if (!v) return;
|
||||
text.value = v.toString();
|
||||
open.value = false;
|
||||
}
|
||||
function onBlur() {
|
||||
const d = parseDay(text.value);
|
||||
if (d) text.value = d;
|
||||
emit("blur");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopoverRoot v-model:open="open">
|
||||
<PopoverAnchor as-child>
|
||||
<div class="dt">
|
||||
<input
|
||||
:id="id"
|
||||
ref="input"
|
||||
v-model="text"
|
||||
class="input"
|
||||
:placeholder="placeholder ?? '例如:2026-11-20'"
|
||||
:disabled="disabled"
|
||||
autocomplete="off"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
<PopoverTrigger as-child>
|
||||
<button type="button" class="icon-btn sm" :aria-label="`打开日期选择器${label ? `:${label}` : ''}`" :disabled="disabled">
|
||||
<Calendar class="ic" :stroke-width="1.5" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
</div>
|
||||
</PopoverAnchor>
|
||||
<PopoverPortal>
|
||||
<PopoverContent class="cal" align="start" :side-offset="4" :aria-label="label ?? '选择日期'">
|
||||
<CalendarRoot
|
||||
v-slot="{ grid, weekDays }"
|
||||
:model-value="value"
|
||||
:default-placeholder="value ?? today(getLocalTimeZone())"
|
||||
locale="zh-CN"
|
||||
:week-starts-on="1"
|
||||
weekday-format="narrow"
|
||||
initial-focus
|
||||
@update:model-value="pick"
|
||||
>
|
||||
<CalendarHeader class="cal-h">
|
||||
<CalendarPrev class="icon-btn sm" aria-label="上个月"><ChevronLeft class="ic" :stroke-width="1.5" /></CalendarPrev>
|
||||
<CalendarHeading v-slot="{ headingValue }"><b>{{ headingValue }}</b></CalendarHeading>
|
||||
<CalendarNext class="icon-btn sm" aria-label="下个月"><ChevronRight class="ic" :stroke-width="1.5" /></CalendarNext>
|
||||
</CalendarHeader>
|
||||
<CalendarGrid v-for="month in grid" :key="month.value.toString()" class="cal-grid">
|
||||
<CalendarGridHead>
|
||||
<CalendarGridRow>
|
||||
<CalendarHeadCell v-for="d in weekDays" :key="d" class="wd">{{ d }}</CalendarHeadCell>
|
||||
</CalendarGridRow>
|
||||
</CalendarGridHead>
|
||||
<CalendarGridBody>
|
||||
<CalendarGridRow v-for="(week, i) in month.rows" :key="i">
|
||||
<CalendarCell v-for="d in week" :key="d.toString()" :date="d">
|
||||
<CalendarCellTrigger :day="d" :month="month.value" class="day" />
|
||||
</CalendarCell>
|
||||
</CalendarGridRow>
|
||||
</CalendarGridBody>
|
||||
</CalendarGrid>
|
||||
</CalendarRoot>
|
||||
</PopoverContent>
|
||||
</PopoverPortal>
|
||||
</PopoverRoot>
|
||||
</template>
|
||||
Reference in New Issue
Block a user