feat(V0.2): 标题30字+智能拆分 / 时间标记 MM-dd / 时间词前缀 (T-TK-14~16)
Docker Build and Push / build-image (push) Successful in 1m25s
Docker Build and Push / build-image (push) Successful in 1m25s
- 标题上限收紧至 30 字 (服务端校验+详情编辑框); 快速添加输入框 放宽到 200 字: 超长时在 30 字内分隔符处截断为标题, 完整原文进描述 - Task.markDate (DateTime, 迁移 20261025120000): 详情页 DatePicker 选择/清除, 列表行与看板卡显示 MM-dd 小标签 - 快速添加时间词前缀: 今天/明天/后天/本周/下周/本月/下月 开头 (后跟分隔符) 自动设为时间标记并移出标题; 本周=本周日, 本月=月末 - 需求/功能/数据库设计文档同步更新 T-TK-14~16 与 Task 表字段
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
// 快速添加的标题解析: 时间词前缀 + 30 字分隔符截断 (T-TK-14/16)
|
||||
export const MAX_TITLE = 30
|
||||
|
||||
// 分隔符: 空格 / 中英文标点 / 斜杠等 —— 截断优先落在分隔符上, 不切词
|
||||
const SEPARATOR = /[\s,,。;;、!!??::“”"'’‘()()\[\]【】\-_/\\|]/
|
||||
|
||||
/**
|
||||
* 长文本拆分: ≤30 字原样; 超长时在 30 字内(且不少于 8 字)找最后一个分隔符截断,
|
||||
* 前段为标题, **完整原文**作为描述 (整体进详情, 不丢内容)。
|
||||
*/
|
||||
export function splitTitle(raw) {
|
||||
const text = String(raw || '').trim()
|
||||
if (text.length <= MAX_TITLE) return { title: text, desc: '' }
|
||||
let cut = -1
|
||||
for (let i = MAX_TITLE; i >= 8; i--) {
|
||||
if (SEPARATOR.test(text[i - 1])) { cut = i; break }
|
||||
}
|
||||
if (cut < 0) cut = MAX_TITLE
|
||||
return { title: text.slice(0, cut).trim(), desc: text }
|
||||
}
|
||||
|
||||
// 时间词 → 日期 (返回 YYYY-MM-DD):
|
||||
// 今天/明天/后天 = 当日 +0/+1/+2
|
||||
// 本周 = 本周日, 下周 = 下周日 (周一定义为一周开始)
|
||||
// 本月 = 本月最后一天, 下月 = 下月最后一天
|
||||
const toISO = (d) => {
|
||||
const p = (n) => String(n).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`
|
||||
}
|
||||
|
||||
export function dateFromWord(word, now = new Date()) {
|
||||
const day = (n) => { const d = new Date(now); d.setDate(d.getDate() + n); return toISO(d) }
|
||||
const endOfWeek = (offsetWeeks) => {
|
||||
const d = new Date(now)
|
||||
const dow = (d.getDay() + 6) % 7 // 周一=0
|
||||
d.setDate(d.getDate() + offsetWeeks * 7 + (6 - dow))
|
||||
return toISO(d)
|
||||
}
|
||||
const endOfMonth = (offsetMonths) => {
|
||||
const d = new Date(now.getFullYear(), now.getMonth() + offsetMonths + 1, 0)
|
||||
return toISO(d)
|
||||
}
|
||||
switch (word) {
|
||||
case '今天': return day(0)
|
||||
case '明天': return day(1)
|
||||
case '后天': return day(2)
|
||||
case '本周': return endOfWeek(0)
|
||||
case '下周': return endOfWeek(1)
|
||||
case '本月': return endOfMonth(0)
|
||||
case '下月': return endOfMonth(1)
|
||||
default: return null
|
||||
}
|
||||
}
|
||||
|
||||
// 匹配最长的时间词前缀 (后面跟分隔符或直接结束)
|
||||
const TIME_WORDS = ['本周', '下周', '本月', '下月', '今天', '明天', '后天']
|
||||
|
||||
/**
|
||||
* 解析时间词前缀: "明天 交周报" → { markDate: '2026-10-26', rest: '交周报' }
|
||||
* 无匹配时 rest 为原文, markDate 为 null。
|
||||
*/
|
||||
export function parseTimePrefix(raw) {
|
||||
const text = String(raw || '').trim()
|
||||
for (const w of TIME_WORDS) {
|
||||
if (text.startsWith(w)) {
|
||||
const after = text.slice(w.length)
|
||||
if (after === '' || SEPARATOR.test(after[0])) {
|
||||
return { markDate: dateFromWord(w), rest: after.trim() }
|
||||
}
|
||||
}
|
||||
}
|
||||
return { markDate: null, rest: text }
|
||||
}
|
||||
Reference in New Issue
Block a user