新增:京东下单页面

This commit is contained in:
Linzm committed 2026-11-03 14:13:25 +08:00
1 parent 8e8cde9ef5
commit 007c675603
25 files changed
+3264 -25

No files matched your search

+103
View File
@@ -0,0 +1,103 @@
export type FlyRect = {
left: number
top: number
width: number
height: number
}
function prefersReducedMotion() {
return typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches
}
/** 二次贝塞尔:起点 → 控制点 → 终点 */
function quad(p0: number, p1: number, p2: number, t: number) {
const u = 1 - t
return u * u * p0 + 2 * u * t * p1 + t * t * p2
}
function easeInOut(t: number) {
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2
}
/** 将图片从源矩形沿抛物线飞到目标矩形,动画结束后移除 */
export function flyImageToRect(url: string, from: FlyRect, to: FlyRect, duration = 860): Promise<void> {
if (typeof document === 'undefined' || !url || from.width <= 0 || from.height <= 0) {
return Promise.resolve()
}
if (prefersReducedMotion()) return Promise.resolve()
const ghost = document.createElement('img')
ghost.src = url
ghost.alt = ''
ghost.className = 'jd-img-fly'
ghost.setAttribute('aria-hidden', 'true')
Object.assign(ghost.style, {
position: 'fixed',
left: `${from.left}px`,
top: `${from.top}px`,
width: `${from.width}px`,
height: `${from.height}px`,
margin: '0',
padding: '0',
border: '0',
borderRadius: '12px',
objectFit: 'contain',
objectPosition: 'center',
background: '#e8eaed',
zIndex: '10000',
pointerEvents: 'none',
boxShadow: '0 12px 28px rgba(15, 23, 42, 0.22)',
transformOrigin: 'top left',
willChange: 'transform, opacity, border-radius',
} as CSSStyleDeclaration)
document.body.appendChild(ghost)
const sxEnd = to.width / Math.max(1, from.width)
const syEnd = to.height / Math.max(1, from.height)
const dx = to.left - from.left
const dy = to.top - from.top
const lift = Math.min(160, Math.max(72, Math.hypot(dx, dy) * 0.28))
const cx = dx * 0.5
const cy = dy * 0.5 - lift
const steps = 18
const keyframes: Keyframe[] = []
for (let i = 0; i <= steps; i += 1) {
const raw = i / steps
const t = easeInOut(raw)
const x = quad(0, cx, dx, t)
const y = quad(0, cy, dy, t)
const scaleX = 1 + (sxEnd - 1) * t
const scaleY = 1 + (syEnd - 1) * t
const radius = 12 + (10 - 12) * t
keyframes.push({
offset: raw,
transform: `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`,
opacity: String(1 - 0.08 * t),
borderRadius: `${radius}px`,
})
}
return new Promise((resolve) => {
const finish = () => {
ghost.remove()
resolve()
}
const anim = ghost.animate(keyframes, {
duration,
easing: 'linear',
fill: 'forwards',
})
anim.onfinish = finish
anim.oncancel = finish
})
}
export function rectFromElement(el: Element | null | undefined): FlyRect | null {
if (!el) return null
const box = el.getBoundingClientRect()
if (box.width <= 0 || box.height <= 0) return null
return { left: box.left, top: box.top, width: box.width, height: box.height }
}
+53 -18
View File
@@ -69,30 +69,65 @@ const requestHandler = (config: InternalAxiosRequestConfig): InternalAxiosReques
// Add a request interceptor
request.interceptors.request.use(requestHandler, errorHandler)
function coerceJsonPayload(payload: any) {
// 后端若用 text/plain 返回 JSON,axios 不会自动 parse,这里补一层
if (typeof payload !== 'string') return payload
const text = payload.replace(/^\uFEFF/, '').trim()
if (!text) return payload
try {
return JSON.parse(text)
} catch {
const start = text.indexOf('{')
const end = text.lastIndexOf('}')
if (start >= 0 && end > start) {
try {
return JSON.parse(text.slice(start, end + 1))
} catch {
return payload
}
}
return payload
}
}
// 响应拦截器
const responseHandler = (response: { data: any }) => {
if (!response?.data)
if (!response?.data && response?.data !== 0 && response?.data !== false)
return response
const { code, data, message } = response.data
if (!code)
return response.data
// 拦截时间
switch (code) {
case 1000:
if (Array.isArray(data)) {
// transfter(data, timezone.info);
} else if (data && data.list !== undefined && data.list?.length > 0) {
// transfter(data.list, timezone.info);
}
return data
default:
return Promise.reject(new Error(message))
// 兼容偶发双层:{ data: { code, data, message } }
let payload = coerceJsonPayload(response.data)
if (
payload &&
typeof payload === 'object' &&
payload.code == null &&
payload.data &&
typeof payload.data === 'object' &&
payload.data.code != null
) {
payload = payload.data
}
return response.data
if (!payload || typeof payload !== 'object')
return payload
const { code, data, message } = payload
if (code == null || code === '')
return payload
// 兼容 code 为字符串 "1000"
const numericCode = Number(code)
if (numericCode === 1000) {
if (Array.isArray(data)) {
// transfter(data, timezone.info);
} else if (data && data.list !== undefined && data.list?.length > 0) {
// transfter(data.list, timezone.info);
}
return data
}
return Promise.reject(new Error(message || '请求失败'))
}
// Add a response interceptor