chore:补传文件
This commit is contained in:
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 }
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user