diff --git a/assets/css/routes/tickets-compose.css b/assets/css/routes/tickets-compose.css new file mode 100644 index 00000000..36b15e44 --- /dev/null +++ b/assets/css/routes/tickets-compose.css @@ -0,0 +1,398 @@ +/* User center ticket (工单) — 回复框 / 新建表单 / 图片待发送区 / 提交成功 + * + * 依赖 tickets.css 定义的 --tickets-* token(同面板同条件入队,先于本文件)。 + * 回复框布局自上而下:工具栏(图片 | 催单 · 关闭)→ 引用条 → 待发送缩略图 → + * 输入框 → 发送行;作为详情弹窗 flex 列的最后一段固定在底部,不参与滚动。 + */ + +/* ------------------------------------------------------------------ * + * 已关闭提示(占据回复框位置) + * ------------------------------------------------------------------ */ + +.aksrt-ticket-detail__closed { + display: flex; + flex: 0 0 auto; + gap: 8px; + align-items: center; + justify-content: center; + margin: 0; + padding: 14px 18px 16px; + border-top: 1px solid var(--tickets-line); + color: var(--tickets-muted); + font-size: 13px; +} + +/* ------------------------------------------------------------------ * + * 回复框 + * ------------------------------------------------------------------ */ + +.aksrt-ticket-composer { + display: grid; + flex: 0 0 auto; + gap: 8px; + padding: 10px 18px 14px; + border-top: 1px solid var(--tickets-line); + background: var(--surface-1, #fff); +} + +.aksrt-ticket-composer__toolbar { + display: flex; + flex-wrap: wrap; + gap: 6px; + align-items: center; + justify-content: space-between; +} + +.aksrt-ticket-composer__tools, +.aksrt-ticket-composer__actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; } + +.aksrt-ticket-composer__quote { + display: flex; + gap: 8px; + align-items: center; + padding: 7px 10px; + border-radius: 8px; + background: color-mix(in srgb, var(--tickets-primary) 8%, transparent); + color: var(--tickets-ink-2); + font-size: 12px; +} + +.aksrt-ticket-composer__quote[hidden] { display: none; } +.aksrt-ticket-composer__quote > .fa { color: var(--tickets-primary); } +.aksrt-ticket-composer__quote > span { flex: 1 1 auto; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } + +.aksrt-ticket-composer__quote button { + flex: 0 0 auto; + padding: 2px 4px; + border: 0; + background: transparent; + color: var(--tickets-muted); + cursor: pointer; +} + +.aksrt-ticket-composer__input textarea { + display: block; + width: 100%; + min-height: 64px; + max-height: 160px; + padding: 9px 12px; + border: 1px solid var(--tickets-line); + border-radius: 10px; + background: var(--surface-1, #fff); + color: inherit; + font-family: inherit; + font-size: 13px; + line-height: 1.6; + resize: none; + scrollbar-width: thin; + transition: border-color .15s ease, box-shadow .15s ease; +} + +.aksrt-ticket-composer__input textarea:focus { + border-color: var(--tickets-primary); + outline: none; + box-shadow: 0 0 0 3px color-mix(in srgb, var(--tickets-primary) 16%, transparent); +} + +.aksrt-ticket-composer__input textarea[aria-invalid="true"] { border-color: var(--tickets-danger); } +.aksrt-ticket-composer__input .aksrt-ticket-field__error { margin-top: 4px; } + +.aksrt-ticket-composer__bar { display: flex; gap: 10px; align-items: center; justify-content: flex-end; } +.aksrt-ticket-composer__hint { margin-right: auto; color: var(--tickets-muted); font-size: 11px; } +.aksrt-ticket-composer__bar .btn-primary { min-width: 88px; } + +/* 工具按钮:图片为幽灵按钮;催单 / 关闭为描边胶囊,与输入区视觉分层。 */ +.aksrt-ticket-tool { + display: inline-flex; + align-items: center; + gap: 5px; + height: 30px; + padding: 0 10px; + border: 1px solid transparent; + border-radius: 8px; + background: transparent; + color: var(--tickets-muted); + font-size: 12px; + white-space: nowrap; + cursor: pointer; + transition: background-color .15s ease, border-color .15s ease, color .15s ease; +} + +.aksrt-ticket-tool:hover:not(:disabled) { background: var(--tickets-soft); color: var(--tickets-ink); } +.aksrt-ticket-tool:focus-visible { outline: 2px solid var(--tickets-primary); outline-offset: 1px; } +.aksrt-ticket-tool:disabled { opacity: .5; cursor: not-allowed; } + +.aksrt-ticket-tool__count { + color: var(--tickets-muted); + font-size: 11px; + font-variant-numeric: tabular-nums; +} + +.aksrt-ticket-tool__count.is-zero { display: none; } + +.aksrt-ticket-tool.is-chip { + height: 28px; + border-color: var(--tickets-line); + border-radius: 999px; +} + +.aksrt-ticket-tool.is-chip:hover:not(:disabled) { + border-color: color-mix(in srgb, var(--tickets-primary) 40%, transparent); + background: color-mix(in srgb, var(--tickets-primary) 7%, transparent); + color: var(--tickets-primary); +} + +.aksrt-ticket-tool.is-danger:hover:not(:disabled) { + border-color: color-mix(in srgb, var(--tickets-danger) 40%, transparent); + background: color-mix(in srgb, var(--tickets-danger) 7%, transparent); + color: var(--tickets-danger); +} + +.aksrt-ticket-tool.is-confirming, +.aksrt-ticket-tool.is-confirming:hover:not(:disabled) { + border-color: var(--tickets-danger); + background: color-mix(in srgb, var(--tickets-danger) 12%, transparent); + color: var(--tickets-danger); +} + +/* 催单冷却:保持可读的倒计时,而不是灰掉不解释。 */ +.aksrt-ticket-tool.is-cooling:disabled { + opacity: 1; + border-style: dashed; + color: var(--tickets-muted); + font-variant-numeric: tabular-nums; + cursor: default; +} + +/* ------------------------------------------------------------------ * + * 新建表单 + * ------------------------------------------------------------------ */ + +.aksrt-ticket-form { display: grid; gap: 16px; text-align: left; } + +.aksrt-ticket-field { + position: relative; + display: grid; + gap: 6px; + min-width: 0; + margin: 0; + padding: 0; + border: 0; +} + +.aksrt-ticket-field > label, +.aksrt-ticket-field > legend, +.aksrt-ticket-field__label { + display: flex; + gap: 6px; + align-items: center; + padding: 0; + color: var(--tickets-ink-2); + font-size: 13px; + font-weight: 600; +} + +.aksrt-ticket-field em { color: var(--tickets-danger); font-style: normal; } +.aksrt-ticket-field__optional { color: var(--tickets-muted); font-size: 12px; font-weight: 400; } + +.aksrt-ticket-field input[type="text"], +.aksrt-ticket-field select, +.aksrt-ticket-field textarea { + width: 100%; + padding: 9px 12px; + border: 1px solid var(--tickets-line); + border-radius: 9px; + background: var(--surface-1, #fff); + color: inherit; + font-family: inherit; + font-size: 13px; + line-height: 1.6; + transition: border-color .15s ease, box-shadow .15s ease; +} + +.aksrt-ticket-field textarea { min-height: 120px; padding-bottom: 24px; resize: vertical; } + +.aksrt-ticket-field input[type="text"]:focus, +.aksrt-ticket-field select:focus, +.aksrt-ticket-field textarea:focus { + border-color: var(--tickets-primary); + outline: none; + box-shadow: 0 0 0 3px color-mix(in srgb, var(--tickets-primary) 16%, transparent); +} + +.aksrt-ticket-field [aria-invalid="true"], +.aksrt-ticket-field[aria-invalid="true"] .aksrt-ticket-type span { + border-color: var(--tickets-danger) !important; +} + +.aksrt-ticket-field__error { margin: 0; color: var(--tickets-danger); font-size: 12px; } +.aksrt-ticket-field__hint { margin: 0; color: var(--tickets-muted); font-size: 12px; } +.aksrt-ticket-field__row { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: center; } + +/* 新建表单的「添加图片」用描边按钮,和其他表单控件同一视觉重量。 */ +.aksrt-ticket-field__row .aksrt-ticket-tool { margin-left: -1px; border-color: var(--tickets-line); } + +.aksrt-ticket-counter { color: var(--tickets-muted); font-size: 11px; font-variant-numeric: tabular-nums; } +.aksrt-ticket-counter.is-near { color: var(--color-warning, #f59e0b); } + +/* 新建表单内计数器贴在输入框右下角;回复框的计数器在发送行里。 */ +.aksrt-ticket-form .aksrt-ticket-field > .aksrt-ticket-counter { + position: absolute; + right: 10px; + bottom: 7px; + pointer-events: none; +} + +.aksrt-ticket-form .aksrt-ticket-field:has(.aksrt-ticket-field__error) > .aksrt-ticket-counter { bottom: 27px; } +.aksrt-ticket-form .aksrt-ticket-field input[type="text"] { padding-right: 72px; } + +/* 部门:单选卡片(替代下拉,选项一目了然)。 */ +.aksrt-ticket-types__list { display: flex; flex-wrap: wrap; gap: 8px; } +.aksrt-ticket-type { position: relative; margin: 0; cursor: pointer; } +.aksrt-ticket-type input { position: absolute; opacity: 0; pointer-events: none; } + +.aksrt-ticket-type span { + display: inline-flex; + align-items: center; + height: 34px; + padding: 0 14px; + border: 1px solid var(--tickets-line); + border-radius: 999px; + background: var(--surface-1, #fff); + font-size: 13px; + transition: border-color .15s ease, background-color .15s ease, color .15s ease; +} + +.aksrt-ticket-type:hover span { border-color: color-mix(in srgb, var(--tickets-primary) 45%, transparent); } + +.aksrt-ticket-type input:checked + span { + border-color: var(--tickets-primary); + background: color-mix(in srgb, var(--tickets-primary) 10%, transparent); + color: var(--tickets-primary); + font-weight: 600; +} + +.aksrt-ticket-type input:focus-visible + span { outline: 2px solid var(--tickets-primary); outline-offset: 2px; } + +.aksrt-ticket-form__actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 4px; } +.aksrt-ticket-form__actions > button { min-width: 96px; } + +/* ------------------------------------------------------------------ * + * 待发送图片条(回复框与新建表单共用) + * ------------------------------------------------------------------ */ + +.aksrt-ticket-tray { + display: flex; + gap: 8px; + padding: 2px 0; + overflow-x: auto; + scrollbar-width: thin; +} + +.aksrt-ticket-tray:empty { display: none; } + +.aksrt-ticket-image { + position: relative; + flex: 0 0 auto; + width: 64px; + height: 64px; + border: 1px solid var(--tickets-line); + border-radius: 9px; + overflow: hidden; + background: var(--tickets-soft); + animation: aksrt-ticket-pop .18s ease-out; +} + +.aksrt-ticket-image img { width: 100%; height: 100%; object-fit: cover; } +.aksrt-ticket-image.is-uploading { display: grid; place-items: center; } + +.aksrt-ticket-image__spinner { + width: 20px; + height: 20px; + border: 2px solid var(--tickets-line); + border-top-color: var(--tickets-primary); + border-radius: 50%; + animation: aksrt-ticket-spin .8s linear infinite; +} + +.aksrt-ticket-image__remove { + position: absolute; + top: 3px; + right: 3px; + display: grid; + place-items: center; + width: 20px; + height: 20px; + padding: 0; + border: 0; + border-radius: 50%; + background: rgb(0 0 0 / 55%); + color: #fff; + font-size: 10px; + cursor: pointer; + transition: background-color .15s ease; +} + +.aksrt-ticket-image__remove:hover { background: rgb(0 0 0 / 75%); } +.aksrt-ticket-image__remove:focus-visible { outline: 2px solid #fff; outline-offset: 1px; } + +@keyframes aksrt-ticket-spin { to { transform: rotate(360deg); } } +@keyframes aksrt-ticket-pop { from { opacity: 0; transform: scale(.9); } } + +/* ------------------------------------------------------------------ * + * 提交成功 + * ------------------------------------------------------------------ */ + +.aksrt-ticket-done { display: grid; gap: 8px; justify-items: center; padding: 12px 4px 4px; text-align: center; } + +.aksrt-ticket-done__icon { + display: inline-grid; + place-items: center; + width: 56px; + height: 56px; + margin-bottom: 4px; + border-radius: 50%; + background: color-mix(in srgb, var(--color-success, #10b981) 14%, transparent); + color: var(--color-success, #10b981); + font-size: 24px; +} + +.aksrt-ticket-done h4 { margin: 0; font-size: 17px; } +.aksrt-ticket-done > p { margin: 0; color: var(--tickets-muted); font-size: 13px; } + +.aksrt-ticket-done__notice { + width: 100%; + margin-top: 6px; + padding: 12px 14px; + border-radius: 10px; + background: var(--tickets-soft); + color: var(--tickets-ink-2); + font-size: 13px; + line-height: 1.7; + text-align: left; +} + +.aksrt-ticket-done__notice > :last-child { margin-bottom: 0; } +.aksrt-ticket-done .aksrt-ticket-form__actions { justify-content: center; width: 100%; margin-top: 10px; } + +/* 按钮忙碌态(发送 / 提交 / 催单中)。 */ +.aksrt-ticket-modal .is-busy { opacity: .7; cursor: progress; } + +/* ------------------------------------------------------------------ * + * 窄屏 + * ------------------------------------------------------------------ */ + +@media (max-width: 640px) { + .aksrt-ticket-composer { padding: 8px 12px calc(12px + env(safe-area-inset-bottom, 0px)); } + .aksrt-ticket-composer__hint { display: none; } + .aksrt-ticket-composer__bar { justify-content: space-between; } + .aksrt-ticket-form__actions > button { flex: 1 1 0; } +} + +@media (hover: none) { + .aksrt-ticket-composer__hint { display: none; } +} + +@media (prefers-reduced-motion: reduce) { + .aksrt-ticket-image { animation: none; } +} diff --git a/assets/css/routes/tickets.css b/assets/css/routes/tickets.css index 48f66989..82e8cf83 100644 --- a/assets/css/routes/tickets.css +++ b/assets/css/routes/tickets.css @@ -395,22 +395,113 @@ .aksrt-ticket-modal .aksrt-modal-runtime__title { font-size: 16px; font-weight: 700; } -/* ------------------------------------------------------------------ * - * 详情:概要 + 对话时间线 - * ------------------------------------------------------------------ */ - -.aksrt-ticket-detail { display: grid; gap: 16px; text-align: left; } - -.aksrt-ticket-detail__summary { - display: grid; - gap: 8px; - padding: 14px 16px; - border-radius: 10px; - background: var(--tickets-soft); +/* 详情:对话框定高且自身不滚动 —— 概要吸顶、时间线是唯一滚动区、回复框吸底。 */ +.aksrt-ticket-detail-modal .aksrt-modal-runtime__dialog { + display: flex; + flex-direction: column; + height: min(88vh, 720px); + overflow: hidden; } -.aksrt-ticket-detail__head { display: flex; flex-wrap: wrap; gap: 9px; align-items: center; } -.aksrt-ticket-detail__title { margin: 0; font-size: 16px; font-weight: 650; line-height: 1.5; word-break: break-word; } +.aksrt-ticket-detail-modal.aksrt-modal-runtime--bottom .aksrt-modal-runtime__dialog { height: 90vh; } +.aksrt-ticket-detail-modal .aksrt-modal-runtime__header { flex: 0 0 auto; padding-bottom: 12px; } + +.aksrt-ticket-detail-modal .aksrt-modal-runtime__content { + display: flex; + flex: 1 1 auto; + flex-direction: column; + min-height: 0; + padding: 0; + overflow: hidden; +} + +/* 加载骨架 / 错误态不走三段布局,补回内边距。 */ +.aksrt-ticket-detail-modal .aksrt-modal-runtime__content > :not(.aksrt-ticket-detail) { margin: 18px; } + +/* ------------------------------------------------------------------ * + * 详情:吸顶概要条 + 对话时间线 + * ------------------------------------------------------------------ */ + +.aksrt-ticket-detail { + display: flex; + flex: 1 1 auto; + flex-direction: column; + min-height: 0; + text-align: left; +} + +/* 概要条默认收起;展开的要素面板浮在时间线之上,不推动对话。 */ +.aksrt-ticket-detail__summary { + position: relative; + z-index: 2; + flex: 0 0 auto; + margin: 0 18px; + border: 1px solid var(--tickets-line); + border-radius: 10px; + background: var(--surface-1, #fff); + box-shadow: inset 3px 0 0 var(--aksrt-ticket-color, var(--tickets-primary)); +} + +.aksrt-ticket-detail__toggle { + display: flex; + gap: 10px; + align-items: center; + width: 100%; + padding: 9px 12px 9px 14px; + border: 0; + border-radius: 10px; + background: transparent; + color: inherit; + font: inherit; + text-align: left; + cursor: pointer; + transition: background-color .15s ease; +} + +.aksrt-ticket-detail__toggle:hover { background: var(--tickets-soft); } +.aksrt-ticket-detail__toggle:focus-visible { outline: 2px solid var(--tickets-primary); outline-offset: -2px; } + +.aksrt-ticket-detail__title { + flex: 1 1 auto; + min-width: 0; + overflow: hidden; + font-size: 14px; + font-weight: 600; + white-space: nowrap; + text-overflow: ellipsis; +} + +.aksrt-ticket-detail__toggle-text { + display: inline-flex; + flex: 0 0 auto; + gap: 4px; + align-items: center; + color: var(--tickets-muted); + font-size: 12px; +} + +.aksrt-ticket-detail__toggle-text .fa { font-size: 10px; transition: transform .2s ease; } +.aksrt-ticket-detail__summary.is-open .aksrt-ticket-detail__toggle-text .fa { transform: rotate(180deg); } + +.aksrt-ticket-detail__more { + position: absolute; + top: calc(100% + 6px); + right: 0; + left: 0; + display: grid; + gap: 12px; + max-height: min(50vh, 360px); + padding: 14px 16px; + overflow-y: auto; + border: 1px solid var(--tickets-line); + border-radius: 10px; + background: var(--surface-1, #fff); + box-shadow: 0 12px 32px rgb(15 18 28 / 14%); + scrollbar-width: thin; +} + +.aksrt-ticket-detail__more[hidden] { display: none; } +.aksrt-ticket-detail__full-title { margin: 0; font-size: 15px; font-weight: 650; line-height: 1.5; word-break: break-word; } .aksrt-ticket-detail__facts { display: grid; @@ -423,7 +514,32 @@ .aksrt-ticket-detail__facts dt { margin: 0 0 2px; color: var(--tickets-muted); font-size: 11px; } .aksrt-ticket-detail__facts dd { margin: 0; overflow: hidden; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; } -.aksrt-ticket-thread { display: grid; gap: 16px; margin: 0; padding: 0; list-style: none; } +/* 唯一滚动区:细滚动条,仅悬停时加深。 */ +.aksrt-ticket-thread { + display: flex; + flex: 1 1 auto; + flex-direction: column; + gap: 16px; + min-height: 0; + margin: 0; + padding: 16px 18px; + overflow-y: auto; + overscroll-behavior: contain; + list-style: none; + scrollbar-width: thin; + scrollbar-color: color-mix(in srgb, var(--tickets-ink) 16%, transparent) transparent; +} + +.aksrt-ticket-thread:focus-visible { outline: 2px solid var(--tickets-primary); outline-offset: -2px; } +.aksrt-ticket-thread::-webkit-scrollbar { width: 6px; } +.aksrt-ticket-thread::-webkit-scrollbar-track { background: transparent; } + +.aksrt-ticket-thread::-webkit-scrollbar-thumb { + border-radius: 3px; + background: color-mix(in srgb, var(--tickets-ink) 16%, transparent); +} + +.aksrt-ticket-thread:hover::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--tickets-ink) 28%, transparent); } /* 用户消息靠右(主色气泡),客服消息靠左(中性气泡)。 */ .aksrt-ticket-msg { display: flex; gap: 10px; align-items: flex-start; } @@ -546,331 +662,6 @@ .aksrt-ticket-attachment img { width: 100%; height: 100%; object-fit: cover; transition: transform .2s ease; } .aksrt-ticket-attachment:hover img { transform: scale(1.05); } -.aksrt-ticket-detail__closed { - display: flex; - gap: 8px; - align-items: center; - justify-content: center; - margin: 0; - padding: 12px; - border-radius: 10px; - background: var(--tickets-soft); - color: var(--tickets-muted); - font-size: 13px; -} - -/* ------------------------------------------------------------------ * - * 回复框(吸底) - * ------------------------------------------------------------------ */ - -.aksrt-ticket-composer { - position: sticky; - bottom: -16px; - display: grid; - gap: 10px; - margin: 0 -18px -16px; - padding: 14px 18px 16px; - border-top: 1px solid var(--tickets-line); - background: var(--surface-1, #fff); -} - -.aksrt-ticket-composer__quote { - display: flex; - gap: 8px; - align-items: center; - padding: 7px 10px; - border-radius: 8px; - background: color-mix(in srgb, var(--tickets-primary) 8%, transparent); - color: var(--tickets-ink-2); - font-size: 12px; -} - -.aksrt-ticket-composer__quote[hidden] { display: none; } -.aksrt-ticket-composer__quote > .fa { color: var(--tickets-primary); } -.aksrt-ticket-composer__quote > span { flex: 1 1 auto; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } - -.aksrt-ticket-composer__quote button { - flex: 0 0 auto; - padding: 2px 4px; - border: 0; - background: transparent; - color: var(--tickets-muted); - cursor: pointer; -} - -.aksrt-ticket-composer__bar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; } -.aksrt-ticket-composer__tools { display: flex; flex: 1 1 auto; gap: 4px; } - -.aksrt-ticket-tool { - display: inline-flex; - align-items: center; - gap: 5px; - height: 32px; - padding: 0 10px; - border: 0; - border-radius: 8px; - background: transparent; - color: var(--tickets-muted); - font-size: 12px; - cursor: pointer; - transition: background-color .15s ease, color .15s ease; -} - -.aksrt-ticket-tool:hover:not(:disabled) { background: var(--tickets-soft); color: var(--tickets-ink); } -.aksrt-ticket-tool.is-danger:hover:not(:disabled) { color: var(--tickets-danger); } - -.aksrt-ticket-tool.is-confirming { - background: color-mix(in srgb, var(--tickets-danger) 12%, transparent); - color: var(--tickets-danger); -} - -.aksrt-ticket-tool:disabled { opacity: .6; cursor: default; } - -/* ------------------------------------------------------------------ * - * 表单(新建 / 回复共用) - * ------------------------------------------------------------------ */ - -.aksrt-ticket-form { display: grid; gap: 16px; text-align: left; } - -.aksrt-ticket-field { - position: relative; - display: grid; - gap: 6px; - min-width: 0; - margin: 0; - padding: 0; - border: 0; -} - -.aksrt-ticket-field > label, -.aksrt-ticket-field > legend, -.aksrt-ticket-field__label { - display: flex; - gap: 6px; - align-items: center; - padding: 0; - color: var(--tickets-ink-2); - font-size: 13px; - font-weight: 600; -} - -.aksrt-ticket-field em { color: var(--tickets-danger); font-style: normal; } - -.aksrt-ticket-field__optional { - color: var(--tickets-muted); - font-size: 12px; - font-weight: 400; -} - -.aksrt-ticket-field input[type="text"], -.aksrt-ticket-field select, -.aksrt-ticket-field textarea { - width: 100%; - padding: 9px 12px; - border: 1px solid var(--tickets-line); - border-radius: 9px; - background: var(--surface-1, #fff); - color: inherit; - font-family: inherit; - font-size: 13px; - line-height: 1.6; - transition: border-color .15s ease, box-shadow .15s ease; -} - -.aksrt-ticket-field textarea { min-height: 84px; resize: vertical; } -.aksrt-ticket-form .aksrt-ticket-field textarea { min-height: 120px; padding-bottom: 24px; } - -.aksrt-ticket-field input[type="text"]:focus, -.aksrt-ticket-field select:focus, -.aksrt-ticket-field textarea:focus { - border-color: var(--tickets-primary); - outline: none; - box-shadow: 0 0 0 3px color-mix(in srgb, var(--tickets-primary) 16%, transparent); -} - -.aksrt-ticket-field [aria-invalid="true"], -.aksrt-ticket-field[aria-invalid="true"] .aksrt-ticket-type span { - border-color: var(--tickets-danger) !important; -} - -.aksrt-ticket-field__error { - margin: 0; - color: var(--tickets-danger); - font-size: 12px; -} - -.aksrt-ticket-field__hint { margin: 0; color: var(--tickets-muted); font-size: 12px; } - -.aksrt-ticket-counter { - color: var(--tickets-muted); - font-size: 11px; - font-variant-numeric: tabular-nums; -} - -.aksrt-ticket-counter.is-near { color: var(--color-warning, #f59e0b); } - -/* 新建表单内计数器贴在输入框右下角;回复框的计数器在工具栏里。 */ -.aksrt-ticket-form .aksrt-ticket-field > .aksrt-ticket-counter { - position: absolute; - right: 10px; - bottom: 7px; - pointer-events: none; -} - -.aksrt-ticket-form .aksrt-ticket-field:has(.aksrt-ticket-field__error) > .aksrt-ticket-counter { bottom: 27px; } -.aksrt-ticket-form .aksrt-ticket-field input[type="text"] { padding-right: 72px; } - -/* 部门:单选卡片(替代下拉,选项一目了然)。 */ -.aksrt-ticket-types__list { display: flex; flex-wrap: wrap; gap: 8px; } -.aksrt-ticket-type { position: relative; margin: 0; cursor: pointer; } - -.aksrt-ticket-type input { - position: absolute; - opacity: 0; - pointer-events: none; -} - -.aksrt-ticket-type span { - display: inline-flex; - align-items: center; - height: 34px; - padding: 0 14px; - border: 1px solid var(--tickets-line); - border-radius: 999px; - background: var(--surface-1, #fff); - font-size: 13px; - transition: border-color .15s ease, background-color .15s ease, color .15s ease; -} - -.aksrt-ticket-type:hover span { border-color: color-mix(in srgb, var(--tickets-primary) 45%, transparent); } - -.aksrt-ticket-type input:checked + span { - border-color: var(--tickets-primary); - background: color-mix(in srgb, var(--tickets-primary) 10%, transparent); - color: var(--tickets-primary); - font-weight: 600; -} - -.aksrt-ticket-type input:focus-visible + span { - outline: 2px solid var(--tickets-primary); - outline-offset: 2px; -} - -.aksrt-ticket-form__actions { - display: flex; - gap: 10px; - justify-content: flex-end; - margin-top: 4px; -} - -.aksrt-ticket-form__actions > button { min-width: 96px; } - -/* ------------------------------------------------------------------ * - * 图片上传 - * ------------------------------------------------------------------ */ - -.aksrt-ticket-uploader { display: flex; flex-wrap: wrap; gap: 8px; } -.aksrt-ticket-images { display: contents; } - -.aksrt-ticket-image { - position: relative; - width: 68px; - height: 68px; - border: 1px solid var(--tickets-line); - border-radius: 9px; - overflow: hidden; - background: var(--tickets-soft); -} - -.aksrt-ticket-image img { width: 100%; height: 100%; object-fit: cover; } - -.aksrt-ticket-image.is-add { - display: inline-grid; - place-content: center; - gap: 2px; - padding: 0; - border-style: dashed; - background: transparent; - color: var(--tickets-muted); - font-size: 11px; - text-align: center; - cursor: pointer; - transition: border-color .15s ease, color .15s ease; -} - -.aksrt-ticket-image.is-add .fa { font-size: 15px; } -.aksrt-ticket-image.is-add:hover:not(:disabled) { border-color: var(--tickets-primary); color: var(--tickets-primary); } -.aksrt-ticket-image.is-add:disabled { opacity: .45; cursor: not-allowed; } - -.aksrt-ticket-image.is-uploading { display: grid; place-items: center; } - -.aksrt-ticket-image__spinner { - width: 20px; - height: 20px; - border: 2px solid var(--tickets-line); - border-top-color: var(--tickets-primary); - border-radius: 50%; - animation: aksrt-ticket-spin .8s linear infinite; -} - -.aksrt-ticket-image__remove { - position: absolute; - top: 3px; - right: 3px; - display: grid; - place-items: center; - width: 20px; - height: 20px; - padding: 0; - border: 0; - border-radius: 50%; - background: rgb(0 0 0 / 55%); - color: #fff; - font-size: 10px; - cursor: pointer; -} - -@keyframes aksrt-ticket-spin { to { transform: rotate(360deg); } } - -/* ------------------------------------------------------------------ * - * 提交成功 - * ------------------------------------------------------------------ */ - -.aksrt-ticket-done { display: grid; gap: 8px; justify-items: center; padding: 12px 4px 4px; text-align: center; } - -.aksrt-ticket-done__icon { - display: inline-grid; - place-items: center; - width: 56px; - height: 56px; - margin-bottom: 4px; - border-radius: 50%; - background: color-mix(in srgb, var(--color-success, #10b981) 14%, transparent); - color: var(--color-success, #10b981); - font-size: 24px; -} - -.aksrt-ticket-done h4 { margin: 0; font-size: 17px; } -.aksrt-ticket-done > p { margin: 0; color: var(--tickets-muted); font-size: 13px; } - -.aksrt-ticket-done__notice { - width: 100%; - margin-top: 6px; - padding: 12px 14px; - border-radius: 10px; - background: var(--tickets-soft); - color: var(--tickets-ink-2); - font-size: 13px; - line-height: 1.7; - text-align: left; -} - -.aksrt-ticket-done__notice > :last-child { margin-bottom: 0; } -.aksrt-ticket-done .aksrt-ticket-form__actions { justify-content: center; width: 100%; margin-top: 10px; } - -/* 按钮忙碌态(发送 / 提交 / 催单中)。 */ -.aksrt-ticket-modal .is-busy { opacity: .7; cursor: progress; } - /* ------------------------------------------------------------------ * * 窄屏 * ------------------------------------------------------------------ */ @@ -885,9 +676,6 @@ .aksrt-tickets-page__card { padding-right: 36px; } .aksrt-tickets-page__card-arrow { right: 14px; } .aksrt-ticket-msg__main { max-width: calc(100% - 42px); } - .aksrt-ticket-composer__tools .aksrt-ticket-tool span { display: none; } - .aksrt-ticket-tool.is-confirming span { display: inline; } - .aksrt-ticket-form__actions > button { flex: 1 1 0; } } @media (prefers-reduced-motion: reduce) { diff --git a/assets/js/ticket-center-core.js b/assets/js/ticket-center-core.js index 46dba94a..16558452 100644 --- a/assets/js/ticket-center-core.js +++ b/assets/js/ticket-center-core.js @@ -227,7 +227,10 @@ button.disabled = full; }); var count = root.querySelector('[data-aksrt-ticket-image-count]'); - if (count) count.textContent = holder.children.length + ' / ' + attachmentMax(); + if (count) { + count.textContent = holder.children.length + ' / ' + attachmentMax(); + count.classList.toggle('is-zero', holder.children.length === 0); + } } function uploadFiles(root, files) { @@ -311,16 +314,31 @@ shell.addEventListener('change', function (event) { clearInvalid(event.target.closest('fieldset') || event.target); }); + // 粘贴截图直接进入待上传区(未开启附件时表单内没有图片容器,自然跳过)。 + shell.addEventListener('paste', function (event) { + var root = event.target.closest && event.target.closest('form'); + if (!root || !imageHolder(root) || !event.clipboardData) return; + var files = Array.prototype.filter.call(event.clipboardData.files || [], function (file) { + return /^image\//.test(file.type); + }); + if (!files.length) return; + event.preventDefault(); + uploadFiles(root, files); + }); return ctx; } - function uploaderHtml() { + // 回复框:工具栏里的「图片」按钮 + 输入框上方的待发送缩略图条(空时由 CSS :empty 收起)。 + function attachButtonHtml() { if (!attachmentEnabled()) return ''; - return '
支持 JPG / PNG / GIF / WebP,最多 ' + attachmentMax() + ' 张
支持 JPG / PNG / GIF / WebP,可直接粘贴截图,最多 ' + attachmentMax() + ' 张