This commit is contained in:
2025-11-03 19:44:15 +08:00
parent e5c7af48a2
commit a68eba7b5f
2 changed files with 510 additions and 0 deletions

View File

@@ -130,6 +130,24 @@ export function transferWithGift(data) {
})
}
// 批量创建礼金券
export function batchCreateGiftCoupons(data) {
return request({
url: '/jarvis/jdorder/batchCreateGiftCoupons',
method: 'post',
data
})
}
// 文本URL替换批量创建礼金并替换
export function replaceUrlsWithGiftCoupons(data) {
return request({
url: '/jarvis/jdorder/replaceUrlsWithGiftCoupons',
method: 'post',
data
})
}
// 导出JD订单列表
export function exportJDOrders(query) {
return request({

View File

@@ -0,0 +1,492 @@
<template>
<div class="app-container">
<el-card shadow="never">
<div slot="header">
<span style="font-weight: bold; font-size: 18px;">批量创建礼金并替换URL</span>
<el-divider direction="vertical"></el-divider>
<span style="color: #909399; font-size: 14px;">支持批量创建20张1元面额礼金券并自动将文本中的URL替换为包含礼金的推广链接</span>
</div>
<!-- 商品信息配置区域 -->
<el-form :model="form" :rules="rules" ref="form" label-width="120px" style="margin-bottom: 20px;">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="商品链接/SKU" prop="materialUrl">
<el-input
v-model="form.materialUrl"
placeholder="请输入商品链接或SKU ID必须先点击查询按钮获取商品信息"
@keyup.enter.native="queryProductInfo"
>
<el-button slot="append" icon="el-icon-search" @click="queryProductInfo" :loading="queryLoading">查询</el-button>
</el-input>
<div style="color: #E6A23C; font-size: 12px; margin-top: 5px;">
<i class="el-icon-warning"></i>
<strong>重要</strong>请先点击"查询"按钮获取商品信息特别是SKU ID然后再创建礼金
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="商品名称">
<el-input v-model="form.skuName" placeholder="查询商品信息后自动填充" :disabled="true" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="商品类型">
<el-input :value="form.owner === 'g' ? '自营' : (form.owner === 'pop' ? 'POP' : '未查询')" disabled>
<template slot="prepend">
<i :class="form.owner === 'g' ? 'el-icon-success' : (form.owner === 'pop' ? 'el-icon-warning' : 'el-icon-question')"
:style="{color: form.owner === 'g' ? '#67C23A' : (form.owner === 'pop' ? '#409EFF' : '#909399')}"></i>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="礼金金额(元)" prop="amount">
<el-input-number v-model="form.amount" :min="1" :max="50" :precision="2" :step="0.01" style="width: 100%" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="每张礼金数量" prop="quantity">
<el-input-number v-model="form.quantity" :min="1" :max="100" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="批量创建数量" prop="batchSize">
<el-input-number v-model="form.batchSize" :min="1" :max="100" style="width: 100%" />
<div style="color: #909399; font-size: 12px; margin-top: 5px;">默认20张</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<!-- 文本输入区域 -->
<el-divider content-position="left">文本内容</el-divider>
<el-form-item label="输入文本" style="margin-bottom: 10px;">
<el-input
type="textarea"
:rows="10"
v-model="content"
placeholder="请输入包含京东商品链接的文本内容系统会自动识别URL并替换为包含礼金的推广链接"
style="margin-bottom: 10px;"
/>
<div style="color: #909399; font-size: 12px; margin-bottom: 10px;">
<i class="el-icon-info"></i>
系统会自动识别文本中的京东链接包括 https://item.jd.com、u.jd.com 等格式),并批量创建对应数量的礼金券进行替换
</div>
<div style="text-align: right;">
<span style="color: #909399; margin-right: 10px;">已识别URL数量: <strong style="color: #409EFF;">{{ detectedUrls.length }}</strong></span>
<el-button type="primary" @click="handleReplace" :loading="processing" :disabled="detectedUrls.length === 0 || !form.queryResult">
<i class="el-icon-magic-stick"></i> 批量创建并替换
</el-button>
</div>
</el-form-item>
<!-- 进度显示 -->
<el-card v-if="processing" shadow="never" style="margin-top: 20px;">
<el-progress :percentage="progress" :status="progressStatus" :stroke-width="20">
<template slot="format">
{{ progressText }}
</template>
</el-progress>
<div style="margin-top: 10px; text-align: center; color: #909399;">
{{ progressDetail }}
</div>
</el-card>
<!-- 结果展示区域 -->
<el-card v-if="result && result.replacedContent" shadow="never" style="margin-top: 20px;">
<div slot="header">
<span style="font-weight: bold;">替换结果</span>
<el-button style="float: right; padding: 3px 0;" type="text" @click="copyResult">
<i class="el-icon-document-copy"></i> 复制结果
</el-button>
</div>
<!-- 统计信息 -->
<el-row :gutter="20" style="margin-bottom: 20px;">
<el-col :span="6">
<el-statistic title="总URL数" :value="result.totalUrls || 0" />
</el-col>
<el-col :span="6">
<el-statistic title="成功替换" :value="result.replacedCount || 0">
<template slot="suffix">
<span style="color: #67C23A;"></span>
</template>
</el-statistic>
</el-col>
<el-col :span="6">
<el-statistic title="失败数量" :value="(result.totalUrls || 0) - (result.replacedCount || 0)">
<template slot="suffix">
<span style="color: #F56C6C;"></span>
</template>
</el-statistic>
</el-col>
<el-col :span="6">
<el-statistic title="成功率" :value="result.totalUrls > 0 ? Math.round((result.replacedCount / result.totalUrls) * 100) : 0">
<template slot="suffix">
<span style="color: #409EFF;">%</span>
</template>
</el-statistic>
</el-col>
</el-row>
<!-- 替换后的文本 -->
<el-form-item label="替换后的文本">
<el-input
type="textarea"
:rows="10"
v-model="result.replacedContent"
readonly
/>
</el-form-item>
<!-- 替换详情表格 -->
<el-table :data="result.replacements || []" border style="margin-top: 20px;" max-height="300">
<el-table-column type="index" label="序号" width="60" />
<el-table-column label="原始URL" prop="originalUrl" min-width="250" show-overflow-tooltip />
<el-table-column label="新URL" prop="newUrl" min-width="250" show-overflow-tooltip />
<el-table-column label="状态" width="100">
<template slot-scope="scope">
<el-tag :type="scope.row.success ? 'success' : 'danger'">
{{ scope.row.success ? '成功' : '失败' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="礼金Key" prop="giftCouponKey" width="200" show-overflow-tooltip>
<template slot-scope="scope">
<span v-if="scope.row.giftCouponKey">{{ scope.row.giftCouponKey }}</span>
<span v-else style="color: #909399;">-</span>
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right">
<template slot-scope="scope">
<el-button
type="text"
size="mini"
@click="copyToClipboard(scope.row.newUrl || scope.row.originalUrl)"
:disabled="!scope.row.success"
>
复制新URL
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
</el-card>
</div>
</template>
<script>
import { generatePromotionContent, replaceUrlsWithGiftCoupons } from '@/api/system/jdorder'
export default {
name: 'BatchGiftCoupon',
data() {
return {
content: '',
form: {
materialUrl: '',
skuName: '',
owner: 'g',
amount: 1.0, // 默认1元
quantity: 1, // 默认每个礼金数量为1
batchSize: 20, // 默认批量创建20张
queryResult: null // 保存查询到的商品信息
},
rules: {
materialUrl: [{ required: true, message: '请输入商品链接或SKU', trigger: 'blur' }],
amount: [{ required: true, message: '请输入礼金金额', trigger: 'blur' }],
quantity: [{ required: true, message: '请输入礼金数量', trigger: 'blur' }],
batchSize: [{ required: true, message: '请输入批量创建数量', trigger: 'blur' }]
},
queryLoading: false,
processing: false,
progress: 0,
progressText: '',
progressDetail: '',
progressStatus: '',
result: null,
detectedUrls: []
}
},
watch: {
content(newVal) {
this.detectUrls(newVal)
}
},
methods: {
/** 查询商品信息 */
async queryProductInfo() {
const materialUrl = this.form.materialUrl.trim()
if (!materialUrl) {
this.$modal.msgWarning('请输入商品链接或SKU ID')
return
}
this.queryLoading = true
try {
const res = await generatePromotionContent({ promotionContent: materialUrl })
if (res && res.code === 200) {
let productInfo = null
try {
let resultStr = res.data || res.msg
let parsed = null
if (typeof resultStr === 'string') {
parsed = JSON.parse(resultStr)
} else {
parsed = resultStr
}
if (Array.isArray(parsed) && parsed.length > 0) {
productInfo = parsed[0]
} else if (parsed && typeof parsed === 'object') {
if (parsed.list && Array.isArray(parsed.list) && parsed.list.length > 0) {
productInfo = parsed.list[0]
} else if (parsed.data && Array.isArray(parsed.data) && parsed.data.length > 0) {
productInfo = parsed.data[0]
} else if (parsed.materialUrl || parsed.owner || parsed.skuName) {
productInfo = parsed
}
}
if (productInfo) {
this.form.queryResult = productInfo
this.form.skuName = productInfo.skuName || productInfo.title || productInfo.productName || productInfo.cleanSkuName || ''
const ownerValue = productInfo.owner || (productInfo.popId ? 'pop' : 'g') || 'g'
this.form.owner = ownerValue === 'p' ? 'pop' : (ownerValue === 'pop' ? 'pop' : 'g')
this.$modal.msgSuccess('商品信息查询成功:' + (this.form.owner === 'g' ? '自营' : 'POP'))
} else {
this.$modal.msgWarning('未找到商品信息,请检查链接是否正确')
}
} catch (e) {
this.$modal.msgWarning('返回数据格式异常:' + e.message)
}
} else {
this.$modal.msgError('查询商品信息失败:' + (res.msg || '未知错误'))
}
} catch (e) {
this.$modal.msgError('查询失败:' + (e.message || '未知错误'))
} finally {
this.queryLoading = false
}
},
/** 检测文本中的URL */
detectUrls(text) {
if (!text || text.trim().length === 0) {
this.detectedUrls = []
return
}
const urlPattern = /(https?:\/\/[^\s]+)|(u\.jd\.com\/[^\s]+)/gi
const urls = []
let match
while ((match = urlPattern.exec(text)) !== null) {
const url = match[0]
if (url && !urls.includes(url.trim())) {
urls.push(url.trim())
}
}
this.detectedUrls = urls
},
/** 批量创建并替换 */
async handleReplace() {
if (!this.$refs.form) {
return
}
this.$refs.form.validate(async (valid) => {
if (!valid) return
if (!this.form.queryResult) {
this.$modal.msgWarning('请先查询商品信息')
return
}
if (this.detectedUrls.length === 0) {
this.$modal.msgWarning('文本中未找到URL无需替换')
return
}
if (this.detectedUrls.length > 100) {
this.$modal.msgError('检测到的URL数量超过100个请分批处理')
return
}
// 确认操作
try {
await this.$confirm(
`检测到 ${this.detectedUrls.length} 个URL将批量创建 ${this.detectedUrls.length}${this.form.amount} 元礼金券并替换URL。是否继续`,
'确认操作',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
} catch {
return
}
this.processing = true
this.progress = 0
this.progressText = '准备中...'
this.progressDetail = ''
this.progressStatus = ''
this.result = null
try {
// 构建请求参数
const params = {
content: this.content,
amount: this.form.amount,
quantity: this.form.quantity,
batchSize: this.detectedUrls.length, // 根据检测到的URL数量自动设置
owner: this.form.owner || 'g',
skuName: this.form.skuName || ''
}
// 根据查询结果设置skuId或materialUrl
if (this.form.queryResult) {
const queryResult = this.form.queryResult
const isPop = this.form.owner === 'pop'
if (isPop) {
if (queryResult.materialUrl) {
params.materialUrl = queryResult.materialUrl
} else if ((queryResult.skuId || queryResult.skuid) && /^\d+$/.test(String(queryResult.skuId || queryResult.skuid))) {
params.skuId = String(queryResult.skuId || queryResult.skuid)
}
} else {
const skuIdValue = queryResult.skuId || queryResult.skuid
if (skuIdValue && /^\d+$/.test(String(skuIdValue))) {
params.skuId = String(skuIdValue)
} else if (queryResult.materialUrl) {
params.materialUrl = queryResult.materialUrl
}
}
}
// 如果没有设置skuId或materialUrl使用用户输入的
if (!params.skuId && !params.materialUrl) {
const materialUrl = this.form.materialUrl.trim()
if (materialUrl) {
if (/^\d+$/.test(materialUrl)) {
params.skuId = materialUrl
} else {
params.materialUrl = materialUrl
}
}
}
console.log('批量替换请求参数:', params)
// 调用替换接口
this.progress = 30
this.progressText = '正在批量创建礼金券...'
this.progressDetail = '这可能需要几分钟时间,请耐心等待'
const res = await replaceUrlsWithGiftCoupons(params)
this.progress = 80
this.progressText = '正在处理结果...'
if (res && res.code === 200 && res.data) {
this.result = res.data
this.progress = 100
this.progressStatus = 'success'
this.progressText = '完成!'
this.progressDetail = `成功替换 ${this.result.replacedCount || 0} / ${this.result.totalUrls || 0} 个URL`
this.$modal.msgSuccess(`批量替换完成!成功 ${this.result.replacedCount || 0} / ${this.result.totalUrls || 0}`)
} else {
this.progress = 100
this.progressStatus = 'exception'
this.progressText = '失败'
this.progressDetail = res.msg || '未知错误'
this.$modal.msgError('批量替换失败:' + (res.msg || '未知错误'))
}
} catch (e) {
console.error('批量替换异常', e)
this.progress = 100
this.progressStatus = 'exception'
this.progressText = '失败'
this.progressDetail = e.message || '未知错误'
let errorMsg = '未知错误'
if (e.response && e.response.data) {
errorMsg = e.response.data.msg || e.response.data.message || JSON.stringify(e.response.data)
} else if (e.message) {
errorMsg = e.message
}
this.$modal.msgError('批量替换失败:' + errorMsg)
} finally {
this.processing = false
}
})
},
/** 复制结果 */
copyResult() {
if (this.result && this.result.replacedContent) {
this.copyToClipboard(this.result.replacedContent)
}
},
/** 复制到剪贴板 */
copyToClipboard(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => {
this.$modal.msgSuccess('复制成功')
}).catch(() => {
this.fallbackCopyToClipboard(text)
})
} else {
this.fallbackCopyToClipboard(text)
}
},
/** 降级复制方法 */
fallbackCopyToClipboard(text) {
const textArea = document.createElement('textarea')
textArea.value = text
textArea.style.position = 'fixed'
textArea.style.opacity = '0'
document.body.appendChild(textArea)
textArea.select()
try {
document.execCommand('copy')
this.$modal.msgSuccess('复制成功')
} catch (err) {
this.$modal.msgError('复制失败')
}
document.body.removeChild(textArea)
}
}
}
</script>
<style scoped>
.app-container {
padding: 20px;
}
.el-divider {
margin: 10px 0;
}
.el-statistic {
text-align: center;
}
</style>