Files
knowledge-governance/web/src/qaWeeks.ts
T
姚章浩 87f97a2f9b
build / build (push) Successful in 1m7s
feat(qaops): 完成详情下拉与每周自动保存草稿
详情使用共享主表字典,稀疏修改所选字段并保留原始问答及旧复核审计。
月周统计提供文本和下拉双入口;每租户每周一份持久草稿,按技能六节双语模板真实预填。
加入幂等自动保存、冲突保留、未知响应重试、切周及离开保护和schema58非破坏降级保护。

验证:完整make verify、隔离PostgreSQL全mirror竞态回归、真实恢复库浏览器验收通过。
浏览器实测字段精确读回、月周双输入、模板预填、MD/CSV、自动保存、冲突、失败重试与API重启恢复。
未连接或改写DobotAI生产源库,未运行付费模型;提交时尚未部署。
2026-10-20 05:44:02 +08:00

75 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export interface QAWeek {
year: number
week: number
start: string
end: string
label: string
}
const day = 86_400_000
const dateOnly = (value: number) => new Date(value).toISOString().slice(0, 10)
export function qaWeekValue(value: Pick<QAWeek, 'year' | 'week'>): string {
return `${value.year}-W${String(value.week).padStart(2, '0')}`
}
export function currentQAWeekValue(now = new Date()): string {
return qaWeekValue(currentQAWeek(now))
}
export function currentQAWeek(now = new Date()): QAWeek {
const beijing = new Date(now.getTime() + 8 * 3_600_000)
beijing.setUTCHours(0, 0, 0, 0)
beijing.setUTCDate(beijing.getUTCDate() + 3 - ((beijing.getUTCDay() + 6) % 7))
const year = beijing.getUTCFullYear()
const week = Math.floor((beijing.getTime() - firstMonday(year)) / (7 * day)) + 1
return qaWeek(year, week)
}
export function qaDateTimeToISO(value: string): string {
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/.test(value)) throw new Error('时间格式无效')
const parsed = new Date(`${value}:00+08:00`)
if (
!Number.isFinite(parsed.getTime()) ||
new Date(parsed.getTime() + 8 * 3_600_000).toISOString().slice(0, 16) !== value
) {
throw new Error('时间格式无效')
}
return parsed.toISOString()
}
export function qaWeekQueryRange(year: number, week: number, now = new Date()): QAWeek {
const range = qaWeek(year, week)
if (
Date.parse(qaDateTimeToISO(range.start)) < now.getTime() &&
now.getTime() < Date.parse(qaDateTimeToISO(range.end))
) {
return { ...range, end: new Date(now.getTime() + 8 * 3_600_000).toISOString().slice(0, 16) }
}
return range
}
const firstMonday = (year: number) => {
const jan4 = new Date(Date.UTC(year, 0, 4))
return jan4.getTime() - ((jan4.getUTCDay() + 6) % 7) * day
}
export function qaWeeksInYear(year: number): number {
if (!Number.isInteger(year) || year < 1900 || year > 9998) throw new Error('年份无效')
return (firstMonday(year + 1) - firstMonday(year)) / (7 * day)
}
export function qaWeek(year: number, week: number): QAWeek {
if (!Number.isInteger(week) || week < 1 || week > qaWeeksInYear(year)) throw new Error('周次无效')
const start = firstMonday(year) + (week - 1) * 7 * day
const first = dateOnly(start)
const last = dateOnly(start + 6 * day)
const crossYear = first.slice(0, 4) !== String(year) || last.slice(0, 4) !== String(year)
return {
year,
week,
start: `${first}T00:00`,
end: `${dateOnly(start + 7 * day)}T00:00`,
label: `第${week}周(${crossYear ? first : first.slice(5)} 到 ${crossYear ? last : last.slice(5)})`,
}
}