From 2c983e826d009a91a75d945147c4d5e16cb52f5d Mon Sep 17 00:00:00 2001 From: GiveMePast <1248298477@qq.com> Date: Sat, 8 Aug 2026 18:29:00 +0800 Subject: [PATCH] wip --- src/components/ticket/TicketForm.vue | 352 ++++++++++++++++++++++++++- 1 file changed, 348 insertions(+), 4 deletions(-) diff --git a/src/components/ticket/TicketForm.vue b/src/components/ticket/TicketForm.vue index ff83c60..3391f46 100644 --- a/src/components/ticket/TicketForm.vue +++ b/src/components/ticket/TicketForm.vue @@ -63,8 +63,33 @@ -
- + +
+
+
+ +
+
+ + +
+
+ +
{{ form.progress }}%
+
+
+ {{ form.progress }} + {{ progressStage }} +
@@ -131,6 +156,91 @@ const handleCustomerChange = () => { form.operator_id = null; }; +// 任务进度颜色:按进度值取语义色;填充主色通过 CSS transition 平滑过渡,跨阶段变色无硬切 +const progressColor = computed(() => { + const p = form.progress; + if (p >= 100) return "#10b981"; // 已完成 - 绿 + if (p >= 75) return "#409eff"; // 快完成 - 蓝 + if (p >= 40) return "#e6a23c"; // 进行中 - 橙 + return "#f56c6c"; // 刚开始 - 红 +}); + +// 进度阶段文案 +const progressStage = computed(() => { + const p = form.progress; + if (p >= 100) return "已完成"; + if (p >= 75) return "接近完成"; + if (p >= 40) return "推进中"; + return "刚开始"; +}); + +// --- 可拖动进度条交互 --- +const dragging = ref(false); +const hovering = ref(false); +const bubbleX = ref(50); // 气泡跟随光标的水平位置(轨道内百分比) +const trackRef = ref(null); + +const setProgressFromClientX = (clientX) => { + const el = trackRef.value; + if (!el) return; + const rect = el.getBoundingClientRect(); + const ratio = (clientX - rect.left) / rect.width; + const pct = Math.min(100, Math.max(0, Math.round(ratio * 100))); + form.progress = pct; +}; + +// 气泡水平位置跟随光标,钳制在轨道两端以内避免溢出 +const updateBubbleX = (clientX) => { + const el = trackRef.value; + if (!el) return; + const rect = el.getBoundingClientRect(); + const ratio = (clientX - rect.left) / rect.width; + bubbleX.value = Math.min(94, Math.max(6, ratio * 100)); +}; + +const onTrackMouseMove = (e) => updateBubbleX(e.clientX); + +const onTrackMouseDown = (e) => { + e.preventDefault(); + setProgressFromClientX(e.clientX); + updateBubbleX(e.clientX); + dragging.value = true; + const move = (ev) => { + setProgressFromClientX(ev.clientX); + updateBubbleX(ev.clientX); + }; + const up = () => { + dragging.value = false; + window.removeEventListener("mousemove", move); + window.removeEventListener("mouseup", up); + }; + window.addEventListener("mousemove", move); + window.addEventListener("mouseup", up); +}; + +const onTrackTouchStart = (e) => { + const touch = e.touches[0]; + setProgressFromClientX(touch.clientX); + updateBubbleX(touch.clientX); + dragging.value = true; + const move = (ev) => { + ev.preventDefault(); + setProgressFromClientX(ev.touches[0].clientX); + updateBubbleX(ev.touches[0].clientX); + }; + const up = () => { + dragging.value = false; + window.removeEventListener("touchmove", move); + window.removeEventListener("touchend", up); + }; + window.addEventListener("touchmove", move, { passive: false }); + window.addEventListener("touchend", up); +}; + +const changeStep = (delta) => { + form.progress = Math.min(100, Math.max(0, form.progress + delta)); +}; + // 状态变为已完成(2)或已完结(3)时,自动将进度设为100 watch( () => form.status, @@ -283,8 +393,242 @@ const handleSubmit = async () => { border-radius: var(--radius-sm); } -.progress-input-wrap { - padding: 0 4px; +/* ===== 海浪液位进度条:水位随进度上涨,浪尖从左往右涌动 ===== */ +.gauge-progress { + --progress-color: #4f46e5; /* 兜底色,实际由行内样式注入 */ + display: flex; + align-items: center; + gap: 16px; + padding: 10px 0 6px; + width: 100%; +} + +/* --- 轨道:玻璃胶囊舱 + 四分刻度线(刻度没入水中部分被液面覆盖) --- */ +.gp-track { + position: relative; + flex: 1; + height: 26px; + margin: 0 16px; + border-radius: 999px; + cursor: pointer; + outline: none; + background-image: + linear-gradient(90deg, transparent calc(25% - 0.5px), rgba(15, 23, 42, 0.08) 25%, transparent calc(25% + 0.5px)), + linear-gradient(90deg, transparent calc(50% - 0.5px), rgba(15, 23, 42, 0.08) 50%, transparent calc(50% + 0.5px)), + linear-gradient(90deg, transparent calc(75% - 0.5px), rgba(15, 23, 42, 0.08) 75%, transparent calc(75% + 0.5px)), + linear-gradient(180deg, #e2e7f0 0%, #edf0f7 45%, #f5f7fb 100%); + box-shadow: + inset 0 2px 5px rgba(15, 23, 42, 0.14), + inset 0 -1px 0 rgba(255, 255, 255, 0.9), + 0 1px 2px rgba(255, 255, 255, 0.7); + transition: box-shadow 0.2s ease; +} + +.gp-track:hover { + box-shadow: + inset 0 2px 6px rgba(15, 23, 42, 0.18), + inset 0 -1px 0 rgba(255, 255, 255, 0.9), + 0 1px 2px rgba(255, 255, 255, 0.7); +} + +.gp-track:focus-visible { + box-shadow: + 0 0 0 3px color-mix(in srgb, var(--progress-color) 32%, transparent), + inset 0 2px 5px rgba(15, 23, 42, 0.14); +} + +/* 裁剪层:液体与海浪只允许出现在胶囊舱内(气泡在裁剪层之外,不被裁掉) */ +.gp-clip { + position: absolute; + inset: 0; + border-radius: 999px; + overflow: hidden; +} + +/* --- 液体:铺满全宽,水位高度 = 进度值 --- */ +.gp-liquid { + position: absolute; + left: 0; + right: 0; + bottom: 0; + background-color: var(--progress-color); /* 主色可过渡:跨阶段变色平滑无硬切 */ + background-image: + linear-gradient(180deg, rgba(255, 255, 255, 0.30) 0%, rgba(255, 255, 255, 0) 45%), /* 近水面提亮 */ + linear-gradient(0deg, rgba(0, 0, 0, 0.14) 0%, rgba(0, 0, 0, 0) 40%), /* 水深处压暗 */ + linear-gradient(90deg, rgba(255, 255, 255, 0.18) 0%, rgba(255, 255, 255, 0) 40%); /* 左浅右深 → 颜色渐变 */ + box-shadow: 0 3px 12px -2px color-mix(in srgb, var(--progress-color) 55%, transparent); /* 底部同色系辉光 */ + transition: height 0.22s ease, background-color 0.35s ease, box-shadow 0.35s ease; +} + +/* 进度为 0 时隐藏水体 */ +.gp-liquid.is-empty { + display: none; +} + +/* 流光扫过:一道斜向高光周期性掠过水体 */ +.gp-sheen { + position: absolute; + inset: 0; + overflow: hidden; + pointer-events: none; +} + +.gp-sheen::before { + content: ""; + position: absolute; + top: -25%; + bottom: -25%; + left: -45%; + width: 34%; + background: linear-gradient(105deg, transparent 0%, rgba(255, 255, 255, 0.45) 50%, transparent 100%); + transform: skewX(-12deg); + animation: sheenSweep 3.6s cubic-bezier(0.4, 0, 0.2, 1) infinite; +} + +@keyframes sheenSweep { + 0% { left: -45%; } + 45% { left: 120%; } + 100% { left: 120%; } +} + +/* --- 海浪:前后两层浪尖横跨液面,视差涌动(从左往右) --- */ +.gp-waves { + position: absolute; + left: 0; + width: 200%; /* 两个浪形周期,平移 50% 即可无缝循环 */ + pointer-events: none; + animation: waveDriftX linear infinite; +} + +.gp-waves path { + transition: fill 0.35s ease; +} + +/* 后层涌浪:主色半透明,幅面更高、流动更慢(约 2 个浪峰横跨轨道) */ +.gp-waves-back { + top: -8px; + height: 14px; + opacity: 0.55; + animation-duration: 5s; + animation-delay: -1.6s; /* 错开相位 */ +} + +.gp-waves-back path { + fill: var(--progress-color); +} + +/* 前层浪花:主色调白的泡沫色,贴着液面、流动更快(约 3 个浪峰横跨轨道) */ +.gp-waves-front { + top: -7px; + height: 12px; + animation-duration: 3.2s; +} + +.gp-waves-front path { + fill: color-mix(in srgb, var(--progress-color) 35%, #ffffff); +} + +@keyframes waveDriftX { + from { transform: translateX(-50%); } + to { transform: translateX(0); } +} + +/* 拖动时:水位即时响应(无过渡延迟),海浪加速涌动 */ +.gauge-progress.is-dragging .gp-liquid { + transition: none; +} + +.gauge-progress.is-dragging .gp-waves-front { + animation-duration: 1.6s; +} + +.gauge-progress.is-dragging .gp-waves-back { + animation-duration: 2.6s; +} + +/* --- 气泡:跟随光标的弹性提示 --- */ +.gp-bubble { + position: absolute; + bottom: calc(100% + 8px); + transform: translateX(-50%) translateY(6px) scale(0.9); + transform-origin: bottom center; + background: var(--progress-color); + color: #fff; + font-size: 12px; + font-weight: 700; + line-height: 1; + padding: 6px 10px; + border-radius: 7px; + white-space: nowrap; + opacity: 0; + pointer-events: none; + box-shadow: 0 6px 16px -4px color-mix(in srgb, var(--progress-color) 65%, transparent); + transition: opacity 0.18s ease, transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1), background-color 0.35s ease, left 0.08s linear; +} + +.gp-bubble::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + border: 5px solid transparent; + border-top-color: var(--progress-color); + transition: border-top-color 0.35s ease; +} + +.gauge-progress.is-hovering .gp-bubble, +.gauge-progress.is-dragging .gp-bubble { + opacity: 1; + transform: translateX(-50%) translateY(0) scale(1); +} + +/* --- 读数与阶段徽标 --- */ +.gp-meta { + display: flex; + align-items: center; + gap: 10px; + flex-shrink: 0; + min-width: 118px; +} + +.gp-pct { + font-family: "Bahnschrift", "DIN Alternate", "Segoe UI", sans-serif; + font-size: 27px; + font-weight: 600; + font-variant-numeric: tabular-nums; + letter-spacing: -0.5px; + line-height: 1; + color: var(--text-primary, #1f2937); +} + +.gp-pct::after { + content: "%"; + font-size: 13px; + font-weight: 600; + color: var(--text-muted, #9ca3af); + margin-left: 2px; +} + +.gp-stage { + font-size: 12px; + font-weight: 600; + line-height: 1; + padding: 5px 10px; + border-radius: 999px; + white-space: nowrap; + color: var(--progress-color); + background: color-mix(in srgb, var(--progress-color) 10%, #ffffff); + border: 1px solid color-mix(in srgb, var(--progress-color) 32%, transparent); + transition: color 0.35s ease, background-color 0.35s ease, border-color 0.35s ease; +} + +/* 尊重系统“减弱动态效果”设置 */ +@media (prefers-reduced-motion: reduce) { + .gp-waves, + .gp-sheen::before { + animation: none; + } } :deep(.el-form-item__label) {