chore:补传文件

This commit is contained in:
qian committed 2026-06-13 20:23:24 +08:00
1 parent 6712ec88df
commit 54e776f487
3 files changed
+232

No files matched your search

@@ -0,0 +1,94 @@
import { ref, nextTick } from 'vue'
/**
* 顶部加载进度条(模块级单例)
*
* 参考 NProgress 的精髓:start() 后用指数收敛缓慢爬升、永远到不了 100%,
* 营造「加载中」的视觉反馈;done() 跳到 100% 再淡出。
*
* 状态放在模块作用域(非函数内),保证多处调用共享同一份进度。
*/
const TICK_MS = 100
/** 指数收敛时间常数:越大爬升越慢 */
const TIME_CONSTANT = 1500
/** 运行中进度上限,永远到不了 100% */
const MAX_PROGRESS = 90
/** done() 后让宽度先滑到 100% 再淡出的过渡时间 */
const HIDE_DELAY_MS = 300
/** 绝对超时兜底:防止 done() 因故未被调用导致进度条卡死 */
const TIMEOUT_MS = 15_000
const progress = ref(0)
const visible = ref(false)
/** 是否启用宽度过渡;重置帧临时关闭,避免归零时从右向左缩回 */
const smooth = ref(true)
let tickTimer: ReturnType<typeof setInterval> | null = null
let hideTimer: ReturnType<typeof setTimeout> | null = null
let startTime = 0
function clearTickTimer(): void {
if (tickTimer !== null) {
clearInterval(tickTimer)
tickTimer = null
}
}
function clearHideTimer(): void {
if (hideTimer !== null) {
clearTimeout(hideTimer)
hideTimer = null
}
}
function start(): void {
// 清掉上一次 done() 排程的隐藏,避免「刚 start 就被旧定时器隐藏」
clearHideTimer()
// 幂等:已在运行则直接返回,不重置进度(支持重定向链中途多次 start)
if (visible.value) return
// 关闭宽度过渡,让重置(上次残留的 100% → 0)瞬间完成、不可见
smooth.value = false
progress.value = 0
visible.value = true
// 下一帧恢复过渡并起步爬升
nextTick(() => {
smooth.value = true
progress.value = 8 // 即时起步反馈,避免前 100ms 空白
startTime = Date.now()
clearTickTimer()
tickTimer = setInterval(() => {
const elapsed = Date.now() - startTime
// 绝对超时兜底
if (elapsed > TIMEOUT_MS) {
done()
return
}
// 用 elapsed 而非 tick 累加,避免后台 tab 定时器被 throttle 后进度漂移
const next = MAX_PROGRESS * (1 - Math.exp(-elapsed / TIME_CONSTANT))
progress.value = Math.min(next, 99)
}, TICK_MS)
})
}
function done(): void {
clearTickTimer()
// 幂等:已隐藏则直接返回(会被 afterEach / onError / 超时三处调用)
if (!visible.value) return
progress.value = 100
clearHideTimer()
hideTimer = setTimeout(() => {
visible.value = false
// 不归零 progress,保持 100% 满宽度淡出;下次 start 时再无过渡重置
hideTimer = null
}, HIDE_DELAY_MS)
}
export function useTopProgress() {
return { progress, visible, smooth, start, done }
}
@@ -0,0 +1,94 @@
import { ref, nextTick } from 'vue'
/**
* 顶部加载进度条(模块级单例)
*
* 参考 NProgress 的精髓:start() 后用指数收敛缓慢爬升、永远到不了 100%,
* 营造「加载中」的视觉反馈;done() 跳到 100% 再淡出。
*
* 状态放在模块作用域(非函数内),保证多处调用共享同一份进度。
*/
const TICK_MS = 100
/** 指数收敛时间常数:越大爬升越慢 */
const TIME_CONSTANT = 1500
/** 运行中进度上限,永远到不了 100% */
const MAX_PROGRESS = 90
/** done() 后让宽度先滑到 100% 再淡出的过渡时间 */
const HIDE_DELAY_MS = 300
/** 绝对超时兜底:防止 done() 因故未被调用导致进度条卡死 */
const TIMEOUT_MS = 15_000
const progress = ref(0)
const visible = ref(false)
/** 是否启用宽度过渡;重置帧临时关闭,避免归零时从右向左缩回 */
const smooth = ref(true)
let tickTimer: ReturnType<typeof setInterval> | null = null
let hideTimer: ReturnType<typeof setTimeout> | null = null
let startTime = 0
function clearTickTimer(): void {
if (tickTimer !== null) {
clearInterval(tickTimer)
tickTimer = null
}
}
function clearHideTimer(): void {
if (hideTimer !== null) {
clearTimeout(hideTimer)
hideTimer = null
}
}
function start(): void {
// 清掉上一次 done() 排程的隐藏,避免「刚 start 就被旧定时器隐藏」
clearHideTimer()
// 幂等:已在运行则直接返回,不重置进度(支持重定向链中途多次 start)
if (visible.value) return
// 关闭宽度过渡,让重置(上次残留的 100% → 0)瞬间完成、不可见
smooth.value = false
progress.value = 0
visible.value = true
// 下一帧恢复过渡并起步爬升
nextTick(() => {
smooth.value = true
progress.value = 8 // 即时起步反馈,避免前 100ms 空白
startTime = Date.now()
clearTickTimer()
tickTimer = setInterval(() => {
const elapsed = Date.now() - startTime
// 绝对超时兜底
if (elapsed > TIMEOUT_MS) {
done()
return
}
// 用 elapsed 而非 tick 累加,避免后台 tab 定时器被 throttle 后进度漂移
const next = MAX_PROGRESS * (1 - Math.exp(-elapsed / TIME_CONSTANT))
progress.value = Math.min(next, 99)
}, TICK_MS)
})
}
function done(): void {
clearTickTimer()
// 幂等:已隐藏则直接返回(会被 afterEach / onError / 超时三处调用)
if (!visible.value) return
progress.value = 100
clearHideTimer()
hideTimer = setTimeout(() => {
visible.value = false
// 不归零 progress,保持 100% 满宽度淡出;下次 start 时再无过渡重置
hideTimer = null
}, HIDE_DELAY_MS)
}
export function useTopProgress() {
return { progress, visible, smooth, start, done }
}
+44
View File
@@ -0,0 +1,44 @@
/**
* 以 POST 表单方式把支付参数提交到支付网关。
*
* 易支付 submit.php 的「页面跳转支付」标准做法:后端只返回 submit.php 的 action
* 地址 + 签名后的参数,前端构造一个隐藏表单 POST 提交。
* 相比把 sign/订单参数拼进 GET URL,更安全(不落浏览器历史与服务器日志),
* 也规避 URL 长度限制和中文/特殊字符的编码边界问题。
*
* @param url 表单 action 地址(submit.php)
* @param params 表单隐藏域参数(含 sign / sign_type)
*/
export function submitPaymentForm(url: string, params: Record<string, string>): void {
if (!url) return
const form = document.createElement('form')
form.action = url
form.method = 'POST'
Object.entries(params || {}).forEach(([key, value]) => {
const input = document.createElement('input')
input.type = 'hidden'
input.name = key
input.value = value
form.appendChild(input)
})
document.body.appendChild(form)
form.submit()
document.body.removeChild(form)
}
/**
* 处理后端返回的支付结果:有 form 参数时走 POST 表单提交,否则普通跳转。
* 兼容未来返回纯跳转 URL 的支付渠道(如 Stripe Checkout)。
*/
export function dispatchPayment(result: {
payment_url?: string
params?: Record<string, string>
}): void {
const { payment_url, params } = result || {}
if (!payment_url) return
if (params && Object.keys(params).length > 0) {
submitPaymentForm(payment_url, params)
} else {
window.location.href = payment_url
}
}