补全其他文件
This commit is contained in:
parent
5298ed1acf
commit
dcb3f8cc41
323
pages/register/register.vue
Normal file
323
pages/register/register.vue
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
<template>
|
||||||
|
<view class="register-container">
|
||||||
|
<!-- 手机号 -->
|
||||||
|
<view class="input-row">
|
||||||
|
<text class="label">手机号:</text>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
v-model="phone"
|
||||||
|
class="input-field"
|
||||||
|
placeholder="请输入手机号"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 密码 -->
|
||||||
|
<view class="input-row">
|
||||||
|
<text class="label">密码:</text>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
v-model="pwd"
|
||||||
|
class="input-field"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 确认密码 -->
|
||||||
|
<view class="input-row">
|
||||||
|
<text class="label">确认密码:</text>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
v-model="confirmPwd"
|
||||||
|
class="input-field"
|
||||||
|
placeholder="请再次输入密码"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 手机验证码 -->
|
||||||
|
<view class="input-row">
|
||||||
|
<text class="label">验证码:</text>
|
||||||
|
<view class="captcha-container">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="userCaptcha"
|
||||||
|
class="input-field captcha-input"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="captcha-btn"
|
||||||
|
:class="{ disabled: countdown > 0 || !isPhoneValidForCaptcha }"
|
||||||
|
@click="sendSmsCode"
|
||||||
|
:disabled="countdown > 0 || !isPhoneValidForCaptcha"
|
||||||
|
>
|
||||||
|
{{ countdown > 0 ? `${countdown}秒后重发` : '获取验证码' }}
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 注册按钮 -->
|
||||||
|
<button
|
||||||
|
class="btn register-btn"
|
||||||
|
:class="{ disabled: !canRegister }"
|
||||||
|
@click="handleRegister"
|
||||||
|
:disabled="!canRegister"
|
||||||
|
>
|
||||||
|
注册
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- 返回登录 -->
|
||||||
|
<view class="back-link">
|
||||||
|
<text class="back-text" @click="goToLogin">已有账号?点击登录</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onUnmounted } from 'vue'
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const account = ref('')
|
||||||
|
const pwd = ref('')
|
||||||
|
const confirmPwd = ref('')
|
||||||
|
const phone = ref('')
|
||||||
|
const userCaptcha = ref('') // 用户输入的验证码
|
||||||
|
const smsCode = ref('') // 服务器返回的验证码(模拟)
|
||||||
|
const countdown = ref(0) // 倒计时
|
||||||
|
let timer = null // 倒计时定时器
|
||||||
|
|
||||||
|
// 计算属性:判断手机号是否有效(用于获取验证码按钮)
|
||||||
|
const isPhoneValidForCaptcha = computed(() => {
|
||||||
|
const phoneRegex = /^1[3-9]\d{9}$/
|
||||||
|
return phone.value.trim() && phoneRegex.test(phone.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算属性:判断是否可以注册
|
||||||
|
const canRegister = computed(() => {
|
||||||
|
// 检查账号、密码、确认密码、手机号和验证码是否都已填写
|
||||||
|
const isAccountValid = account.value.trim().length >= 6
|
||||||
|
const isPasswordValid = pwd.value.trim().length > 0
|
||||||
|
const isConfirmPasswordValid = confirmPwd.value === pwd.value
|
||||||
|
const isPhoneValid = isPhoneValidForCaptcha.value
|
||||||
|
const isCaptchaValid = userCaptcha.value.trim().length > 0
|
||||||
|
|
||||||
|
return isAccountValid &&
|
||||||
|
isPasswordValid &&
|
||||||
|
isConfirmPasswordValid &&
|
||||||
|
isPhoneValid &&
|
||||||
|
isCaptchaValid
|
||||||
|
})
|
||||||
|
|
||||||
|
// 发送短信验证码
|
||||||
|
const sendSmsCode = async () => {
|
||||||
|
// 验证手机号格式
|
||||||
|
const phoneRegex = /^1[3-9]\d{9}$/
|
||||||
|
if (!phone.value.trim()) {
|
||||||
|
uni.showToast({ title: '请输入手机号', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!phoneRegex.test(phone.value)) {
|
||||||
|
uni.showToast({ title: '手机号格式不正确', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟发送短信验证码(实际应用中应调用后端接口)
|
||||||
|
try {
|
||||||
|
uni.showLoading({ title: '发送中...' })
|
||||||
|
|
||||||
|
// 模拟API请求延迟
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
|
||||||
|
// 生成6位数字验证码
|
||||||
|
const code = Math.floor(100000 + Math.random() * 900000).toString()
|
||||||
|
smsCode.value = code
|
||||||
|
|
||||||
|
console.log('发送的验证码:', code) // 仅用于调试
|
||||||
|
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: '验证码已发送', icon: 'success' })
|
||||||
|
|
||||||
|
// 开始倒计时
|
||||||
|
startCountdown()
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: '验证码发送失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始倒计时
|
||||||
|
const startCountdown = () => {
|
||||||
|
countdown.value = 60
|
||||||
|
if (timer) clearInterval(timer)
|
||||||
|
|
||||||
|
timer = setInterval(() => {
|
||||||
|
countdown.value--
|
||||||
|
if (countdown.value <= 0) {
|
||||||
|
clearInterval(timer)
|
||||||
|
timer = null
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册逻辑
|
||||||
|
const handleRegister = async () => {
|
||||||
|
if (!account.value.trim()) {
|
||||||
|
uni.showToast({ title: '请输入账号', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (account.value.length < 6) {
|
||||||
|
uni.showToast({ title: '账号长度不能少于6位', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pwd.value.trim()) {
|
||||||
|
uni.showToast({ title: '请输入密码', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pwd.value !== confirmPwd.value) {
|
||||||
|
uni.showToast({ title: '两次输入的密码不一致', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证手机号
|
||||||
|
const phoneRegex = /^1[3-9]\d{9}$/
|
||||||
|
if (!phone.value.trim()) {
|
||||||
|
uni.showToast({ title: '请输入手机号', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!phoneRegex.test(phone.value)) {
|
||||||
|
uni.showToast({ title: '手机号格式不正确', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userCaptcha.value.trim()) {
|
||||||
|
uni.showToast({ title: '请输入验证码', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证验证码
|
||||||
|
if (userCaptcha.value !== smsCode.value) {
|
||||||
|
uni.showToast({ title: '验证码错误', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 这里写你的实际注册请求逻辑(示例)
|
||||||
|
uni.showLoading({ title: '注册中...' })
|
||||||
|
try {
|
||||||
|
// 模拟API请求
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: '注册成功', icon: 'success' })
|
||||||
|
|
||||||
|
// 注册成功后跳转到登录页
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/login/index'
|
||||||
|
})
|
||||||
|
}, 500)
|
||||||
|
} catch (error) {
|
||||||
|
uni.hideLoading()
|
||||||
|
uni.showToast({ title: '注册失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回登录页
|
||||||
|
const goToLogin = () => {
|
||||||
|
uni.navigateTo({ url: '/pages/login/index' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在页面卸载时清除定时器
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.register-container {
|
||||||
|
padding: 40rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
width: 160rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
flex: 1;
|
||||||
|
height: 80rpx;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-input {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-btn {
|
||||||
|
width: 180rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 80rpx;
|
||||||
|
text-align: center;
|
||||||
|
background-color: #007aff;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-btn.disabled {
|
||||||
|
background-color: #ccc;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #007aff;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.disabled {
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-text {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #999;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-text:active {
|
||||||
|
color: #007aff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
170
pages/shop/detail.vue
Normal file
170
pages/shop/detail.vue
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
<template>
|
||||||
|
<scroll-view class="page" scroll-y>
|
||||||
|
<view class="banner" v-if="product.image">
|
||||||
|
<image class="banner-img" :src="product.image" mode="widthFix" />
|
||||||
|
<view class="banner-badge" v-if="requiredPoints > 0">{{ requiredPoints }}积分</view>
|
||||||
|
</view>
|
||||||
|
<view class="header">
|
||||||
|
<view class="title">{{ product.title || product.name || '-' }}</view>
|
||||||
|
<view class="meta" v-if="requiredPoints > 0">所需积分:{{ requiredPoints }}</view>
|
||||||
|
<view class="meta" v-if="userPoints !== null">我的积分:{{ userPoints }}</view>
|
||||||
|
</view>
|
||||||
|
<view class="desc" v-if="product.description">{{ product.description }}</view>
|
||||||
|
<view class="actions">
|
||||||
|
<view class="qty">
|
||||||
|
<text class="label">数量</text>
|
||||||
|
<view class="stepper">
|
||||||
|
<text class="btn" @tap="changeQty(-1)">-</text>
|
||||||
|
<text class="num">{{ quantity }}</text>
|
||||||
|
<text class="btn" @tap="changeQty(1)">+</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<button class="redeem-btn" type="primary" @tap="onRedeem" :disabled="redeeming || !canRedeem">积分兑换</button>
|
||||||
|
<view class="hint" v-if="requiredPoints > 0">共需积分:{{ totalRequired }}</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
|
import { getProductDetail, getPointsBalance, redeemProductByPoints } from '@/api/appUser'
|
||||||
|
|
||||||
|
const productId = ref('')
|
||||||
|
const product = ref({})
|
||||||
|
const quantity = ref(1)
|
||||||
|
const userPoints = ref(null)
|
||||||
|
const redeeming = ref(false)
|
||||||
|
|
||||||
|
const requiredPoints = computed(() => {
|
||||||
|
const p = product.value || {}
|
||||||
|
const n = Number(p.price)
|
||||||
|
return isNaN(n) ? 0 : n
|
||||||
|
})
|
||||||
|
const totalRequired = computed(() => requiredPoints.value * quantity.value)
|
||||||
|
const canRedeem = computed(() => {
|
||||||
|
const up = Number(userPoints.value)
|
||||||
|
const need = Number(totalRequired.value)
|
||||||
|
return requiredPoints.value > 0 && !isNaN(up) && up >= need
|
||||||
|
})
|
||||||
|
|
||||||
|
function changeQty(delta) {
|
||||||
|
const next = quantity.value + delta
|
||||||
|
quantity.value = next < 1 ? 1 : next
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchDetail(id) {
|
||||||
|
try {
|
||||||
|
const res = await getProductDetail(id)
|
||||||
|
const data = res && (res.data || res.item || res.product) ? (res.data || res.item || res.product) : res
|
||||||
|
const img = data && (data.main_image || data.image || data.img || data.pic || '')
|
||||||
|
const title = data && (data.title || data.name || data.product_name || '')
|
||||||
|
const price = data && (data.price ?? data.points_required ?? data.points ?? data.integral ?? null)
|
||||||
|
const description = data && (data.description ?? data.desc ?? data.rules ?? '')
|
||||||
|
const albumRaw = data && data.album
|
||||||
|
const album = normalizeAlbum(albumRaw)
|
||||||
|
const first = album[0] || img
|
||||||
|
product.value = { image: first, album, title, price, description, ...data }
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchMyPoints() {
|
||||||
|
try {
|
||||||
|
const uid = uni.getStorageSync('user_id')
|
||||||
|
const b = await getPointsBalance(uid)
|
||||||
|
const balance = b && b.balance !== undefined ? b.balance : b
|
||||||
|
userPoints.value = Number(balance) || 0
|
||||||
|
} catch (e) { userPoints.value = 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onRedeem() {
|
||||||
|
const token = uni.getStorageSync('token')
|
||||||
|
const phoneBound = !!uni.getStorageSync('phone_bound')
|
||||||
|
if (!token || !phoneBound) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '请先登录并绑定手机号',
|
||||||
|
confirmText: '去登录',
|
||||||
|
success: (res) => { if (res.confirm) uni.navigateTo({ url: '/pages/login/index' }) }
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const need = requiredPoints.value * quantity.value
|
||||||
|
if (!requiredPoints.value || need <= 0) {
|
||||||
|
uni.showToast({ title: '该商品未设置积分', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (userPoints.value === null) {
|
||||||
|
await fetchMyPoints()
|
||||||
|
}
|
||||||
|
if (Number(userPoints.value) < Number(need)) {
|
||||||
|
uni.showToast({ title: '积分不足', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
redeeming.value = true
|
||||||
|
const uid = uni.getStorageSync('user_id')
|
||||||
|
await redeemProductByPoints(uid, productId.value, quantity.value)
|
||||||
|
uni.showToast({ title: '兑换成功', icon: 'success' })
|
||||||
|
try {
|
||||||
|
const b = await getPointsBalance(uid)
|
||||||
|
const balance = b && b.balance !== undefined ? b.balance : b
|
||||||
|
userPoints.value = Number(balance) || 0
|
||||||
|
} catch (_) {}
|
||||||
|
} catch (e) {
|
||||||
|
uni.showToast({ title: e && (e.message || e.errMsg) || '兑换失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
redeeming.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad((opts) => {
|
||||||
|
const id = (opts && (opts.id || opts.product_id)) || ''
|
||||||
|
productId.value = id
|
||||||
|
if (id) fetchDetail(id)
|
||||||
|
fetchMyPoints()
|
||||||
|
})
|
||||||
|
|
||||||
|
function normalizeAlbum(x) {
|
||||||
|
const result = []
|
||||||
|
try {
|
||||||
|
if (!x) return result
|
||||||
|
if (Array.isArray(x)) {
|
||||||
|
return x.map(s => String(s || '').trim()).filter(Boolean)
|
||||||
|
}
|
||||||
|
const s = String(x || '').trim()
|
||||||
|
if (!s) return result
|
||||||
|
if (s.startsWith('[') && s.endsWith(']')) {
|
||||||
|
const arr = JSON.parse(s)
|
||||||
|
if (Array.isArray(arr)) return arr.map(u => String(u || '').trim()).filter(Boolean)
|
||||||
|
}
|
||||||
|
if (s.includes(',')) {
|
||||||
|
return s.split(',').map(u => u.trim()).filter(Boolean)
|
||||||
|
}
|
||||||
|
return [s]
|
||||||
|
} catch (_) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page { min-height: 100vh; padding-bottom: 120rpx; background: #F8F8F8 }
|
||||||
|
.banner { position: relative; padding: 0 }
|
||||||
|
.banner-img { width: 100%; display: block }
|
||||||
|
.banner-badge { position: absolute; right: 16rpx; bottom: 16rpx; background: rgba(0,0,0,0.7); color: #fff; padding: 8rpx 16rpx; border-radius: 24rpx; font-size: 24rpx }
|
||||||
|
.header { padding: 24rpx; background: #fff }
|
||||||
|
.title { font-size: 36rpx; font-weight: 700; color: #333 }
|
||||||
|
.meta { margin-top: 8rpx; font-size: 26rpx; color: #666 }
|
||||||
|
.desc { background: #fff; padding: 0 24rpx 24rpx; font-size: 26rpx; color: #444 }
|
||||||
|
.actions { margin-top: 12rpx; padding: 24rpx; background: #fff }
|
||||||
|
.qty { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16rpx }
|
||||||
|
.label { font-size: 28rpx }
|
||||||
|
.stepper { display: flex; align-items: center; }
|
||||||
|
.btn { width: 56rpx; height: 56rpx; line-height: 56rpx; text-align: center; background: #f4f4f4; border-radius: 12rpx }
|
||||||
|
.num { width: 80rpx; text-align: center; font-size: 28rpx }
|
||||||
|
.redeem-btn { margin-top: 8rpx }
|
||||||
|
.hint { margin-top: 12rpx; font-size: 24rpx; color: #999 }
|
||||||
|
</style>
|
||||||
35
utils/assets.js
Normal file
35
utils/assets.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
export async function ensureLocal(url) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
try {
|
||||||
|
const cache = uni.getStorageSync('asset_cache_v1') || {}
|
||||||
|
const hit = cache[url]
|
||||||
|
if (hit && typeof hit === 'string' && hit) { resolve(hit); return }
|
||||||
|
uni.downloadFile({
|
||||||
|
url,
|
||||||
|
success: (res) => {
|
||||||
|
const tmp = res.tempFilePath || ''
|
||||||
|
if (!tmp) { resolve(url); return }
|
||||||
|
uni.saveFile({
|
||||||
|
tempFilePath: tmp,
|
||||||
|
success: (s) => {
|
||||||
|
const saved = s.savedFilePath || tmp
|
||||||
|
cache[url] = saved
|
||||||
|
uni.setStorageSync('asset_cache_v1', cache)
|
||||||
|
resolve(saved)
|
||||||
|
},
|
||||||
|
fail: () => { resolve(tmp) }
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fail: () => { resolve(url) }
|
||||||
|
})
|
||||||
|
} catch (_) { resolve(url) }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function preloadAssets(urls) {
|
||||||
|
const tasks = urls.map(u => ensureLocal(u))
|
||||||
|
const results = await Promise.allSettled(tasks)
|
||||||
|
const map = {}
|
||||||
|
results.forEach((r, i) => { map[urls[i]] = r.status === 'fulfilled' ? r.value : urls[i] })
|
||||||
|
return map
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user