diff --git a/docs/V0.2/任务模块/任务模块功能设计.md b/docs/V0.2/任务模块/任务模块功能设计.md index 79f2cd8..5daf786 100644 --- a/docs/V0.2/任务模块/任务模块功能设计.md +++ b/docs/V0.2/任务模块/任务模块功能设计.md @@ -360,7 +360,7 @@ | --- | --- | | 任务 `⋯` 菜单 | 新增「归档」(`Dialog.confirm`,含子任务时提示连带条数);成功后列表/看板乐观移除 | | 项目管理 | 「归档设置」分区:`Selector` 四档(3 天后/一周后/一月后/永不),保存即生效 | -| 项目视图头部 | 项目栏新增「已归档」入口 → 全屏弹层:过滤条(关键词/类型/列/归档时段)+ 分页「加载更多」+ 行内「恢复 / 删除」 | +| 项目视图头部 | 项目栏新增「已归档」入口 → 全屏弹层:过滤条(**关键词 / 看板列 / 归档时段**;时段含「本周 / 本月 / 本季度」快捷项与自定义区间;条件编辑后点「筛选」统一提交,另有「重置」;**不提供类型过滤**)+ 已应用条件标签(可单独撤销)与命中条数 + 分页「加载更多」+ 行内「恢复 / 删除」 | | 主视图 | 列表/看板无需感知归档(数据源已排除);恢复后经 `load()` 回到原位 | ### 15.5 边界 diff --git a/src/api/task.js b/src/api/task.js index 8ff0789..285ff57 100644 --- a/src/api/task.js +++ b/src/api/task.js @@ -763,13 +763,23 @@ export const taskApi = { where.push('columnId = ?') args.push(String(p.columnId)) } + // 日期边界: 传 'YYYY-MM-DD' 时按**本地时区**的那一天换算成 UTC 瞬时再比较。 + // (原来直接拼 'T00:00:00Z', 本地选的日期会偏移 8 小时, 导致当天凌晨的归档被漏掉) + const dayToIso = (v, endOfDay) => { + const s = String(v) + if (s.includes('T')) return s // 已带时间的 ISO, 原样使用 + const d = new Date(`${s}T00:00:00`) // 本地 00:00 + if (Number.isNaN(d.getTime())) return null + if (endOfDay) d.setHours(23, 59, 59, 999) + return d.toISOString() + } if (p.from) { - where.push('archivedAt >= ?') - args.push(String(p.from) + 'T00:00:00+08:00'.replace('+08:00', 'Z')) + const iso = dayToIso(p.from, false) + if (iso) { where.push('archivedAt >= ?'); args.push(iso) } } if (p.to) { - where.push('archivedAt <= ?') - args.push(String(p.to) + 'T23:59:59.999Z') + const iso = dayToIso(p.to, true) + if (iso) { where.push('archivedAt <= ?'); args.push(iso) } } if (p.cursor) { const [ts, cid] = String(p.cursor).split('|') diff --git a/src/components/checkin/DayRing.jsx b/src/components/checkin/DayRing.jsx index 6a3f2eb..ea57392 100644 --- a/src/components/checkin/DayRing.jsx +++ b/src/components/checkin/DayRing.jsx @@ -2,16 +2,31 @@ // // 为什么是环而不是圆点: // 旧实现是 4px 小圆点, 只按状态上色 —— 3/5 和 5/5 都是同一个绿点, -// 看不出"当天做到几成"。改成环形后 **弧长 = 当天完成比例**, 颜色仍表状态: -// 0 < actual < expected -> 弧长按比例 (部分完成 / 半填充) -// actual >= expected -> 整圈 (全部完成 / 实心) -// actual = 0 且 expected > 0 -> 只有底环 (未开始 / 空心) +// 看不出"当天做到几成"。改成环形后 **弧长 = 当天完成比例**, 颜色表状态: +// 0 < actual < expected -> 弧长按比例 (部分完成) +// actual >= expected -> 整圈 (全部完成) +// actual = 0 且 expected > 0 -> 只有底环 (未开始) // expected = 0 由父组件判断, 不渲染本组件 (当天没有应打卡项) // -// 颜色不写在这里: 由 CSS 按 `.mv-day` 作用域上色 (各视图调色板可能不同)。 +// 颜色分级 (4 档, 由 CSS 按 `.mv-day` 作用域上色, 各视图可各自调色板): +// 小半 (ratio < 0.5) -> 红 +// 大半 (0.5 <= r < 1) -> 橘 +// 完成 (r = 1) -> 蓝 +// 超额 -> 绿 +// 注意: 服务端统计时把 actual 封顶在 expected (`Math.min(cnt, exp)`), +// 所以 ratio 永远 <= 1, **超额只能靠 status === 'over' 判断**, 不能看比例。 const DEFAULT_SIZE = 30 const DEFAULT_STROKE = 2.5 +/** 4 档分级: 小半 / 大半 / 完成 / 超额 */ +function tierOf(status, p) { + if (status === 'over') return 'over' + if (status === 'full') return 'full' + if (p <= 0) return 'empty' + if (p >= 1) return 'full' + return p < 0.5 ? 'low' : 'high' +} + export default function DayRing({ status = 'partial', ratio = 0, @@ -19,6 +34,7 @@ export default function DayRing({ stroke = DEFAULT_STROKE, }) { const p = Math.max(0, Math.min(1, Number(ratio) || 0)) + const tier = tierOf(status, p) const r = (size - stroke) / 2 const c = 2 * Math.PI * r const half = size / 2 @@ -37,7 +53,7 @@ export default function DayRing({ /> {p > 0 && ( 本月完成率 {Math.round((cal.rate || 0) * 100)}% - {/* 图例: 弧长=完成比例, 颜色=状态 */} + {/* 图例: 弧长=完成比例, 颜色=4 档 (小半/大半/完成/超额) */} - 部分 + 小半 + 大半 完成 超额 diff --git a/src/pages/task/ArchivedTasks.jsx b/src/pages/task/ArchivedTasks.jsx index 64fbe4b..f990ea9 100644 --- a/src/pages/task/ArchivedTasks.jsx +++ b/src/pages/task/ArchivedTasks.jsx @@ -1,29 +1,67 @@ -import { useCallback, useEffect, useRef, useState } from 'react' -import { Button, DatePicker, Dialog, DotLoading, Input, Popup, Selector, Toast } from 'antd-mobile' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { Button, DatePicker, Dialog, DotLoading, Input, Popup, Toast } from 'antd-mobile' import { taskApi } from '../../api/task.js' import { kindColor } from './kind.js' // 已归档任务查询 (T-ARC-04/05/06) -// 过滤: 关键词(标题) / 任务类型 / 看板列 / 归档时间范围 +// 过滤: 关键词(标题) / 看板列 / 归档时间 (含 本周/本月/本季度 快捷项) +// 交互: 条件先编辑 -> 点「筛选」才提交 +// (原实现改任一条件就即时重查, 连点会连发请求, 输关键词时还会闪) // 游标分页: 「加载更多」追加; 行内 恢复 / 彻底删除 +// 不再提供「任务类型」过滤: 类型是弱维度, 归档场景几乎不按类型检索 const LIMIT = 20 +const EMPTY = { keyword: '', columnId: '', time: 'all', from: '', to: '' } + +const TIME_OPTIONS = [ + { key: 'all', label: '全部' }, + { key: 'week', label: '本周' }, + { key: 'month', label: '本月' }, + { key: 'quarter', label: '本季度' }, + { key: 'custom', label: '自定义' }, +] +const TIME_LABEL = { week: '本周', month: '本月', quarter: '本季度' } + +const pad = (n) => String(n).padStart(2, '0') +const fmtDate = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` + +/** + * 快捷时间 -> [from, to]。本地日期,区间截止到今天(不跨到未来)。 + * 纯函数,便于单测。 + */ +export function presetRange(key, now = new Date()) { + const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()) + if (key === 'week') { + const dow = (today.getDay() + 6) % 7 // 周一为 0 + const mon = new Date(today) + mon.setDate(today.getDate() - dow) + return [fmtDate(mon), fmtDate(today)] + } + if (key === 'month') { + return [fmtDate(new Date(today.getFullYear(), today.getMonth(), 1)), fmtDate(today)] + } + if (key === 'quarter') { + const qm = Math.floor(today.getMonth() / 3) * 3 // 当季首月 + return [fmtDate(new Date(today.getFullYear(), qm, 1)), fmtDate(today)] + } + return ['', ''] +} + export default function ArchivedTasks({ project, visible, onClose, onChanged }) { const [items, setItems] = useState(null) const [nextCursor, setNextCursor] = useState(null) const [loading, setLoading] = useState(false) - const [filters, setFilters] = useState({ keyword: '', kind: '', columnId: '', from: '', to: '' }) + const [draft, setDraft] = useState(EMPTY) // 表单里正在编辑的条件 + const [applied, setApplied] = useState(EMPTY) // 已提交、真正参与查询的条件 const [fromVisible, setFromVisible] = useState(false) const [toVisible, setToVisible] = useState(false) const cursorRef = useRef('') const columns = project?.columns || [] - const kinds = project?.taskKinds || [] const buildParams = (f, cursor) => { const p = { limit: LIMIT } - if (f.keyword.trim()) p.keyword = f.keyword.trim() - if (f.kind) p.kind = f.kind + if (f.keyword) p.keyword = f.keyword if (f.columnId) p.columnId = f.columnId if (f.from) p.from = f.from if (f.to) p.to = f.to @@ -35,7 +73,7 @@ export default function ArchivedTasks({ project, visible, onClose, onChanged }) setLoading(true) try { const cursor = reset ? '' : cursorRef.current - const r = await taskApi.archivedList(project.id, buildParams(filters, cursor)) + const r = await taskApi.archivedList(project.id, buildParams(applied, cursor)) setItems((prev) => (reset || !prev ? r.items : [...prev, ...r.items])) cursorRef.current = r.nextCursor || '' setNextCursor(r.nextCursor || null) @@ -45,7 +83,7 @@ export default function ArchivedTasks({ project, visible, onClose, onChanged }) setLoading(false) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [project.id, filters]) + }, [project.id, applied]) useEffect(() => { if (!visible) return @@ -54,7 +92,51 @@ export default function ArchivedTasks({ project, visible, onClose, onChanged }) load(true) }, [visible, load]) - const setF = (patch) => setFilters((p) => ({ ...p, ...patch })) + const setD = (patch) => setDraft((p) => ({ ...p, ...patch })) + const colName = (id) => columns.find((c) => String(c.id) === String(id))?.name || '' + const fmt = (iso) => (iso ? String(iso).slice(0, 10) : '') + + const activeCount = + (applied.keyword ? 1 : 0) + (applied.columnId ? 1 : 0) + (applied.time !== 'all' ? 1 : 0) + + const appliedTags = useMemo(() => { + const tags = [] + if (applied.keyword) tags.push({ key: 'keyword', text: `标题: ${applied.keyword}` }) + if (applied.columnId) tags.push({ key: 'columnId', text: `看板列: ${colName(applied.columnId)}` }) + if (applied.time === 'custom') { + if (applied.from || applied.to) { + tags.push({ key: 'time', text: `${applied.from || '…'} ~ ${applied.to || '…'}` }) + } + } else if (applied.time !== 'all') { + tags.push({ key: 'time', text: TIME_LABEL[applied.time] }) + } + return tags + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [applied, columns]) + + function submit() { + const [f, t] = draft.time === 'custom' ? [draft.from, draft.to] : presetRange(draft.time) + setApplied({ ...draft, keyword: draft.keyword.trim(), from: f, to: t }) + } + + function reset() { + setDraft(EMPTY) + setApplied(EMPTY) + } + + // 去掉某一条生效条件 (即时生效, 不必再点按钮) + function dropTag(key) { + const nextA = { ...applied } + const nextD = { ...draft } + if (key === 'keyword') { nextA.keyword = ''; nextD.keyword = '' } + if (key === 'columnId') { nextA.columnId = ''; nextD.columnId = '' } + if (key === 'time') { + nextA.time = 'all'; nextA.from = ''; nextA.to = '' + nextD.time = 'all'; nextD.from = ''; nextD.to = '' + } + setApplied(nextA) + setDraft(nextD) + } async function doRestore(item) { try { @@ -62,7 +144,6 @@ export default function ArchivedTasks({ project, visible, onClose, onChanged }) Toast.show({ content: '已恢复' }) setItems((prev) => (prev || []).filter((x) => x.id !== item.id)) onChanged?.() - load(true) } catch (e) { Toast.show({ content: e.message || '恢复失败' }) } @@ -71,7 +152,9 @@ export default function ArchivedTasks({ project, visible, onClose, onChanged }) async function doRemove(item) { const subs = item.children?.length || 0 const ok = await Dialog.confirm({ - content: subs ? `彻底删除「${item.title}」?将一并删除 ${subs} 条子任务,不可恢复。` : `彻底删除「${item.title}」?不可恢复。`, + content: subs + ? `彻底删除「${item.title}」?将一并删除 ${subs} 条子任务,不可恢复。` + : `彻底删除「${item.title}」?不可恢复。`, confirmText: '删除', }) if (!ok) return @@ -85,8 +168,7 @@ export default function ArchivedTasks({ project, visible, onClose, onChanged }) } } - const fmt = (iso) => (iso ? iso.slice(0, 10) : '') - const colName = (id) => columns.find((c) => c.id === id)?.name || '' + const draftRange = draft.time === 'custom' ? ['', ''] : presetRange(draft.time) return ( ‹ 已归档任务 + {project?.name ? {project.name} : null} -
- load(true)} - onChange={(v) => setF({ keyword: v })} - onBlur={() => load(true)} - /> -
-
- ({ label: k, value: k }))]} - value={[filters.kind]} - onSelect={(_, ext) => { setF({ kind: ext.items[0].value }); setTimeout(() => load(true), 0) }} - /> -
+ {/* 筛选卡 */} +
+
+ 标题 + + + setD({ keyword: v })} + onEnter={submit} + /> + +
- ({ label: c.name, value: String(c.id) }))]} - value={[filters.columnId]} - onSelect={(_, ext) => { setF({ columnId: ext.items[0].value }); setTimeout(() => load(true), 0) }} - /> -
- 归档 - setFromVisible(true)}> - {filters.from || '开始日期'} + +
+ 看板列 + + + setD({ columnId: '' })} + >全部 + {columns.map((c) => ( + setD({ columnId: String(c.id) })} + >{c.name} + ))} + - ~ - setToVisible(true)}> - {filters.to || '结束日期'} +
+ +
+ 归档时间 + + + {TIME_OPTIONS.map((o) => ( + setD({ time: o.key, ...(o.key === 'custom' ? {} : { from: '', to: '' }) })} + >{o.label} + ))} + + {draft.time === 'custom' && ( + + setFromVisible(true)}> + {draft.from || '开始日期'} + + ~ + setToVisible(true)}> + {draft.to || '结束日期'} + + + )} + {draftRange[0] &&
{draftRange[0]} ~ {draftRange[1]}
}
- {(filters.from || filters.to) && ( - { setF({ from: '', to: '' }); setTimeout(() => load(true), 0) }}>清除 - )} +
+ +
+ +
@@ -143,61 +259,72 @@ export default function ArchivedTasks({ project, visible, onClose, onChanged }) visible={fromVisible} onClose={() => setFromVisible(false)} precision="day" - onConfirm={(d) => { - const p = (n) => String(n).padStart(2, '0') - setF({ from: `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}` }) - setFromVisible(false) - setTimeout(() => load(true), 0) - }} + onConfirm={(d) => { setD({ from: fmtDate(d) }); setFromVisible(false) }} /> setToVisible(false)} precision="day" - onConfirm={(d) => { - const p = (n) => String(n).padStart(2, '0') - setF({ to: `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}` }) - setToVisible(false) - setTimeout(() => load(true), 0) - }} + onConfirm={(d) => { setD({ to: fmtDate(d) }); setToVisible(false) }} /> -
- {items === null && loading &&
加载中...
} - {items !== null && items.length === 0 && !loading && ( -
没有符合条件的归档任务
+ {/* 已应用条件 (可单独撤销) */} + {appliedTags.length > 0 && ( +
+ {appliedTags.map((t) => ( + + {t.text} + dropTag(t.key)}>✕ + + ))} + 共 {items?.length || 0} 条 +
+ )} + +
+ {items === null && loading && ( +
加载中…
)} - {(items || []).map((t) => ( -
-
-
{t.title}
-
- {t.kind && {t.kind}} - {colName(t.columnId)} - 归档于 {fmt(t.archivedAt)} -
+ + {items !== null && items.length === 0 && !loading && ( + appliedTags.length === 0 ? ( +
+
📭
+ 这个项目还没有归档任务 +
任务完成后可在它的 ⋯ 菜单里归档
-
- - + ) : ( +
+
🗂
+ 没有符合条件的归档任务 +
试试放宽时间范围或清空关键词
+
+ ) + )} + + {(items || []).map((t) => ( +
+
+
{t.title}
+ {t.number != null && #{t.number}} +
+
+ {t.kind && ( + {t.kind} + )} + {colName(t.columnId) && {colName(t.columnId)}} + + 归档于 {fmt(t.archivedAt)} +
+
+ doRestore(t)}>恢复 + doRemove(t)}>删除
))} + {nextCursor && ( - )} diff --git a/src/styles/globals.css b/src/styles/globals.css index a7675d3..c8fa587 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -883,9 +883,11 @@ body.me-safe-top .app-safe-top { height: 0; } position: relative; width: 26px; height: 26px; display: flex; align-items: center; justify-content: center; } -.mv-day .day-ring-bar.partial { stroke: #ff8f1f; } -.mv-day .day-ring-bar.full { stroke: #00b578; } -.mv-day .day-ring-bar.over { stroke: #ff6b6b; } +/* 环颜色 4 档: 小半(红) / 大半(橘) / 完成(蓝) / 超额(绿) */ +.mv-day .day-ring-bar.low { stroke: #ff3141; } +.mv-day .day-ring-bar.high { stroke: #ff8f1f; } +.mv-day .day-ring-bar.full { stroke: #1677ff; } +.mv-day .day-ring-bar.over { stroke: #00b578; } /* 选中日: 蓝底之上改用白色环, 弧长仍表达完成度 */ .mv-day.sel .day-ring-bg { stroke: rgba(255, 255, 255, .38); } .mv-day.sel .day-ring-bar { stroke: #fff; } @@ -895,9 +897,10 @@ body.me-safe-top .app-safe-top { height: 0; } .mv-legend { display: inline-flex; align-items: center; gap: 4px; color: #b0b6bf; } .mv-legend i { width: 7px; height: 7px; border-radius: 50%; display: inline-block; margin-left: 5px; } .mv-legend i:first-child { margin-left: 0; } -.mv-legend i.partial { background: #ff8f1f; } -.mv-legend i.full { background: #00b578; } -.mv-legend i.over { background: #ff6b6b; } +.mv-legend i.low { background: #ff3141; } +.mv-legend i.high { background: #ff8f1f; } +.mv-legend i.full { background: #1677ff; } +.mv-legend i.over { background: #00b578; } /* ---- 当日栏 ---- */ .mv-daybar { display: flex; align-items: center; gap: 8px; padding: 10px 14px 2px; font-size: 13px; font-weight: 600; } @@ -1312,3 +1315,88 @@ textarea.inp { resize: vertical; } border-radius: 999px; padding: 2px 9px; flex-shrink: 0; white-space: nowrap; } .drag-pill.cur { background: #c0c7d0; } + +/* ---- 已归档任务 (T-ARC-04/05/06) ---- + 结构: 筛选卡(标题/看板列/归档时间 + 重置/筛选) -> 已应用条件 -> 列表 + 交互: 条件先编辑, 点「筛选」统一提交; 生效条件可在列表上方单独撤销 */ +.arc-proj { + margin-left: auto; font-size: 11px; color: #999; background: #f5f6f8; + border-radius: 999px; padding: 2px 9px; max-width: 130px; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; +} +.arc-fcard { + background: #fff; margin: 10px 12px 0; border-radius: 12px; padding: 4px 14px 12px; + box-shadow: 0 1px 2px rgba(0, 0, 0, .04); +} +.arc-frow { display: flex; align-items: center; gap: 10px; padding: 10px 0; border-bottom: 1px solid #f4f5f7; } +.arc-frow:last-of-type { border-bottom: 0; } +.arc-frow > .lbl { width: 56px; flex-shrink: 0; font-size: 12.5px; font-weight: 600; color: var(--color-text); } +.arc-frow > .ctl { flex: 1; min-width: 0; } + +/* 关键词输入框: 前面有常驻标签「标题」, 框内是占位提示 */ +.arc-kw { + display: flex; align-items: center; gap: 7px; + background: #f7f8fa; border: 1px solid #eceef1; border-radius: 8px; padding: 6px 9px; +} +.arc-kw:focus-within { border-color: var(--color-primary); background: #fff; } +.arc-kw .adm-input { flex: 1; min-width: 0; border: 0; background: transparent; padding: 0; --font-size: 13px; } +.arc-kw .adm-input-element { font-size: 13px; } + +.arc-chips { display: flex; flex-wrap: wrap; gap: 6px; } +.arc-chip { + font-size: 12px; padding: 4px 10px; border-radius: 999px; + border: 1px solid #e6e8eb; background: #fafbfc; color: var(--color-muted); + cursor: pointer; white-space: nowrap; +} +.arc-chip.on { background: #eaf3ff; border-color: var(--color-primary); color: var(--color-primary); font-weight: 600; } + +.arc-range { display: flex; align-items: center; gap: 7px; margin-top: 8px; } +.arc-range .dp { + font-size: 12px; padding: 4px 10px; border: 1px dashed #d9dee5; border-radius: 7px; + color: #999; cursor: pointer; background: #fff; +} +.arc-range .dp.on { color: var(--color-text); border-style: solid; border-color: #d9dee5; } +.arc-range .sep { color: #c2c6cc; font-size: 12px; } +.arc-hint { font-size: 11px; color: #9aa4b2; margin-top: 6px; } + +.arc-actions { display: flex; gap: 9px; padding-top: 12px; } +.arc-btn { height: 36px; border-radius: 9px; border: 0; font-size: 13.5px; font-weight: 600; cursor: pointer; } +.arc-btn.ghost { flex: 0 0 84px; background: #f4f5f7; color: var(--color-muted); } +.arc-btn.primary { flex: 1; background: var(--color-primary); color: #fff; } +.arc-btn .badge { + display: inline-block; min-width: 16px; height: 16px; line-height: 16px; border-radius: 8px; + background: rgba(255, 255, 255, .28); font-size: 10.5px; margin-left: 5px; padding: 0 4px; +} + +.arc-applied { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 11px 14px 3px; } +.arc-applied .tag { + font-size: 11.5px; background: #eaf3ff; color: var(--color-primary); + border-radius: 6px; padding: 2px 7px; display: inline-flex; align-items: center; gap: 5px; +} +.arc-applied .tag .x { cursor: pointer; opacity: .6; } +.arc-applied .cnt { margin-left: auto; font-size: 11.5px; color: #999; } + +.arc-list { padding: 8px 12px 0; display: flex; flex-direction: column; gap: 8px; } +.arc-item { background: #fff; border-radius: 10px; padding: 11px 12px; box-shadow: 0 1px 2px rgba(0, 0, 0, .04); } +.arc-item .top { display: flex; gap: 8px; align-items: flex-start; } +.arc-item .ttl { flex: 1; min-width: 0; font-size: 13.5px; font-weight: 600; line-height: 1.42; word-break: break-word; } +.arc-item .meta { + display: flex; gap: 7px; align-items: center; flex-wrap: wrap; + margin-top: 7px; font-size: 10.5px; color: #999; +} +.arc-item .meta .col { color: var(--color-muted); } +.arc-item .meta .dot { width: 3px; height: 3px; border-radius: 50%; background: #d5d9e0; } +.arc-item .acts { + display: flex; gap: 7px; justify-content: flex-end; + margin-top: 9px; padding-top: 9px; border-top: 1px dashed #f0f1f3; +} +.arc-item .acts .mini { + font-size: 11.5px; padding: 3px 12px; border-radius: 999px; + border: 1px solid var(--color-primary); color: var(--color-primary); + background: #fff; cursor: pointer; white-space: nowrap; +} +.arc-item .acts .mini.danger { border-color: #ffd4d7; color: var(--color-danger); } + +.arc-empty { text-align: center; padding: 52px 20px; color: #b8bcc4; font-size: 13px; } +.arc-empty .ico { font-size: 34px; opacity: .35; margin-bottom: 10px; } +.arc-empty .sub { font-size: 11.5px; color: #c2c6cc; margin-top: 6px; }