This commit is contained in:
van
2026-09-06 20:40:58 +08:00
parent 0c2cbc1b3e
commit 629d0e8e07
2 changed files with 251 additions and 166 deletions
@@ -55,17 +55,13 @@ export default {
backdrop-filter: blur(10px);
::v-deep .el-button {
flex: 1;
flex: 1 1 0;
width: 0;
height: 44px;
margin: 0;
border-radius: 12px;
font-size: 15px;
}
::v-deep .el-button--primary {
flex: 1.6;
font-weight: 600;
}
}
.mfa-slide-enter-active,
@@ -1,6 +1,9 @@
<template>
<div class="app-container fadan-quick-record" :class="{ 'fadan-quick-record--mobile': isMobile }">
<el-card class="box-card">
<div class="page-toolbar">
<el-button icon="el-icon-time" @click="openHistory">历史记录</el-button>
</div>
<el-form :model="form" label-position="top" class="fadan-form">
<el-form-item label="分销标记">
<div class="mark-row">
@@ -103,6 +106,7 @@
<div v-if="!isMobile" class="btn-row">
<el-button type="primary" size="medium" :loading="loading" @click="generate">录单</el-button>
<el-button size="medium" @click="resetForm">重置表单</el-button>
<el-button size="medium" icon="el-icon-time" @click="openHistory">历史记录</el-button>
<el-button v-if="resultText" size="medium" @click="copyResult">
{{ resultPieces.length > 1 ? '复制全部(连在一起)' : '复制结果' }}
</el-button>
@@ -111,30 +115,12 @@
<template v-if="resultText">
<el-divider content-position="left">录单结果</el-divider>
<p class="result-pieces-hint">
点选「录单表单」或点「回填表单」,会把内容填回上方,方便改完再录单。
<template v-if="resultPieces.length > 1">按条复制,每条对应一条聊天消息。</template>
</p>
<p v-if="resultPieces.length > 1" class="result-pieces-hint">按条复制,每条对应一条聊天消息。</p>
<div class="result-pieces">
<div
v-for="(p, i) in resultPieces"
:key="i"
class="result-piece"
:class="{ 'is-form': isFormPiece(p), 'is-selected': selectedPieceIndex === i }"
@click="onResultPieceClick(p, i)"
>
<div v-for="(p, i) in resultPieces" :key="i" class="result-piece">
<div class="result-piece-head">
<span class="result-piece-title">{{ p.title }}</span>
<div class="result-piece-actions">
<el-button
v-if="isFormPiece(p)"
type="success"
plain
size="mini"
@click.native.stop="echoFormPiece(p, i)"
>回填表单</el-button>
<el-button type="primary" plain size="mini" @click.native.stop="copyPieceText(p.text)">复制本条</el-button>
</div>
<el-button type="primary" plain size="mini" @click="copyPieceText(p.text)">复制本条</el-button>
</div>
<el-input
:value="p.text"
@@ -142,7 +128,6 @@
:autosize="{ minRows: resultPieceMinRows(p.text), maxRows: isMobile ? 22 : 28 }"
readonly
class="result-area"
@click.native.stop
/>
</div>
</div>
@@ -170,19 +155,71 @@
<quick-record-model-shop-config v-model="showShopConfig" @saved="loadShopOptions" />
<el-dialog
title="历史发单记录"
:visible.sync="historyVisible"
:width="isMobile ? '96%' : '720px'"
:top="isMobile ? '6vh' : '8vh'"
append-to-body
custom-class="fadan-history-dialog"
@open="loadHistory"
>
<div class="history-search">
<el-input
v-model="historyKeyword"
clearable
placeholder="搜型号、地址、下单人、订单号、分销标记"
prefix-icon="el-icon-search"
@keyup.enter.native="searchHistory"
@clear="searchHistory"
/>
<el-button type="primary" :loading="historyLoading" @click="searchHistory">搜索</el-button>
</div>
<div class="history-list" v-loading="historyLoading">
<div v-if="!historyList.length && !historyLoading" class="history-empty">暂无历史记录</div>
<div
v-for="row in historyList"
:key="row.id"
class="history-item"
@click="applyHistoryOrder(row)"
>
<div class="history-item-top">
<span class="history-model">{{ historyFullModel(row) || '无型号' }}</span>
<span class="history-time">{{ formatHistoryTime(row) }}</span>
</div>
<div class="history-item-meta">
<span v-if="row.distributionMark">{{ row.distributionMark }}</span>
<span v-if="row.buyer">{{ row.buyer }}</span>
<span v-if="row.paymentAmount != null">付款 {{ formatMoneyText(row.paymentAmount) }}</span>
<span v-if="row.orderId">{{ row.orderId }}</span>
</div>
<div v-if="row.address" class="history-item-addr">{{ row.address }}</div>
</div>
</div>
<div class="history-pager">
<el-pagination
small
layout="prev, pager, next"
:current-page.sync="historyPage"
:page-size="historyPageSize"
:total="historyTotal"
:pager-count="isMobile ? 5 : 7"
@current-change="loadHistory"
/>
</div>
</el-dialog>
<mobile-floating-actions v-if="isMobile">
<el-button type="primary" :loading="loading" @click="generate">录单</el-button>
<el-button @click="resetForm">重置</el-button>
<el-button v-if="resultText" @click="copyResult">
{{ resultPieces.length > 1 ? '复制全部' : '复制' }}
</el-button>
<el-button @click="openHistory">历史</el-button>
</mobile-floating-actions>
</div>
</template>
<script>
import { executeInstruction, executeInstructionWithForce } from '@/api/system/instruction'
import { listQuickRecordModelOptions, listQuickRecordShopOptions } from '@/api/system/jdorder'
import { listJDOrders, listQuickRecordModelOptions, listQuickRecordShopOptions } from '@/api/system/jdorder'
import QuickRecordModelShopConfig from './components/QuickRecordModelShopConfig.vue'
export default {
@@ -221,7 +258,13 @@ export default {
modelOptions: [],
shopOptions: [],
showShopConfig: false,
selectedPieceIndex: null
historyVisible: false,
historyLoading: false,
historyKeyword: '',
historyList: [],
historyPage: 1,
historyPageSize: 15,
historyTotal: 0
}
},
mounted() {
@@ -270,119 +313,6 @@ export default {
}
return out
},
isFormPiece(p) {
if (!p) return false
if (p.title && String(p.title).includes('录单表单')) return true
return this.formBodyLooksPersistable(p.text)
},
extractFormBody(full) {
const raw = String(full || '').replace(/\r\n/g, '\n').trim()
if (!raw) return ''
const sep = /\n-{3,}\s*落库回执\s*-{3,}\s*\n/
const m = sep.exec(raw)
return m ? raw.slice(0, m.index).trim() : raw
},
extractFormField(text, startLabel, endLabels) {
const src = String(text || '')
const idx = src.indexOf(startLabel)
if (idx < 0) return ''
let rest = src.slice(idx + startLabel.length)
let end = rest.length
;(endLabels || []).forEach(lab => {
if (!lab) return
const i = rest.indexOf(lab)
if (i >= 0 && i < end) end = i
})
return rest
.slice(0, end)
.replace(/[━—\-]{3,}/g, '')
.replace(/\n{2,}/g, '\n')
.trim()
},
parseOrderFormText(text) {
const raw = String(text || '').replace(/\r\n/g, '\n')
const isNew = raw.includes('下单人(需填):')
|| raw.includes('下单链接(必须用这个):')
|| raw.includes('—————————')
const field = (start, ends) => this.extractFormField(raw, start, ends)
if (isNew) {
return {
mark: field('分销标记:', ['第三方单号:', '—————————', '下单链接(必须用这个):']),
thirdParty: field('第三方单号:', ['—————————', '下单链接(必须用这个):']),
link: field('下单链接(必须用这个):', ['下单地址(注意带分机):', '—————————']),
address: field('下单地址(注意带分机):', ['—————————', '型号:']),
model: field('型号:', ['下单人(需填):', '下单付款(注意核对):']),
buyer: field('下单人(需填):', ['下单付款(注意核对):']),
payment: field('下单付款(注意核对):', ['后返金额(注意核对):']),
rebate: field('后返金额(注意核对):', ['订单号(需填):']),
orderId: field('订单号(需填):', ['物流链接(需填):']),
logistics: field('物流链接(需填):', ['备注(下单号码有变动', '—————————', '京粉实际价格'])
}
}
return {
mark: field('分销标记:', ['第三方单号:', '型号:']),
thirdParty: field('第三方单号:', ['型号:']),
model: field('型号:', ['链接:']),
link: field('链接:', ['下单付款:']),
payment: field('下单付款:', ['后返金额:']),
rebate: field('后返金额:', ['地址:']),
address: field('地址:', ['物流链接:']),
logistics: field('物流链接:', ['订单号:']),
orderId: field('订单号:', ['下单人:']),
buyer: field('下单人:', ['京粉实际价格:'])
}
},
applyMarkSuffixFromText(mark) {
const v = String(mark || '').trim()
if (!v || v === 'F') {
this.form.markSuffix = ''
return
}
if (v.startsWith('F-')) {
this.form.markSuffix = v.slice(2).trim()
return
}
if (v.startsWith('F') && v.length > 1) {
this.form.markSuffix = v.slice(1).replace(/^-/, '').trim()
return
}
this.form.markSuffix = v
},
applyGeneratedFormToFields(text, silent) {
const body = this.extractFormBody(text)
if (!this.formBodyLooksPersistable(body)) {
if (!silent) this.$modal.msgWarning('当前内容不是可回填的录单表单')
return false
}
const parsed = this.parseOrderFormText(body)
this.applyMarkSuffixFromText(parsed.mark)
this.applyModelWithShop(String(parsed.model || '').split('\n')[0].trim())
this.form.address = this.compressAddressOneLine(parsed.address)
this.form.link = String(parsed.link || '').trim()
this.form.thirdPartyOrderNoText = String(parsed.thirdParty || '').trim()
this.form.buyer = String(parsed.buyer || '').trim()
this.form.paymentText = String(parsed.payment || '').trim()
this.form.rebateText = String(parsed.rebate || '').trim()
this.form.orderIdText = String(parsed.orderId || '').trim()
this.form.logisticsLink = String(parsed.logistics || '').trim()
this.selectedPieceIndex = 0
if (!silent) this.$modal.msgSuccess('已回填到表单,可继续编辑后录单')
return true
},
echoFormPiece(p, i) {
if (!this.applyGeneratedFormToFields(p && p.text)) return
this.selectedPieceIndex = i
this.$nextTick(() => {
const formEl = this.$el && this.$el.querySelector('.fadan-form')
if (formEl && formEl.scrollIntoView) {
formEl.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
})
},
onResultPieceClick(p, i) {
if (!this.isFormPiece(p)) return
this.echoFormPiece(p, i)
},
resultPieceMinRows(text) {
const n = String(text || '').split('\n').length
const cap = this.isMobile ? 8 : 6
@@ -634,7 +564,6 @@ export default {
applyPersistResult(mergedBody, persistMsg) {
const msg = persistMsg || ''
this.resultText = (mergedBody || '') + '\n\n--- 落库回执 ---\n' + msg
this.applyGeneratedFormToFields(mergedBody, true)
if (this.isPersistFailureText(msg)) {
this.$modal.msgError('落库未成功,请查看回执')
} else {
@@ -710,8 +639,7 @@ export default {
}
if (!this.canPersistOrderFields()) {
this.resultText = merged
this.applyGeneratedFormToFields(merged, true)
this.$modal.msgWarning('已生成录单文案并回填。请补全下单人、下单付款、订单号、物流链接后再次录单')
this.$modal.msgWarning('已生成录单文案。落库需填写:下单人、下单付款、订单号、物流链接(后返可不填,将按 0.00)')
return
}
const res2 = await executeInstruction({ command: merged })
@@ -752,9 +680,8 @@ export default {
}
if (!this.canPersistOrderFields()) {
this.resultText = merged
this.applyGeneratedFormToFields(merged, true)
this.verifyDialogVisible = false
this.$modal.msgWarning('已强制生成并回填。请补全下单人、下单付款、订单号、物流链接后再次录单')
this.$modal.msgWarning('已强制生成表单。落库仍需填写:下单人、下单付款、订单号、物流链接')
return
}
const res2 = await executeInstruction({ command: merged })
@@ -785,7 +712,92 @@ export default {
logisticsLink: ''
}
this.resultText = ''
this.selectedPieceIndex = null
},
openHistory() {
this.historyVisible = true
},
searchHistory() {
this.historyPage = 1
this.loadHistory()
},
async loadHistory() {
this.historyLoading = true
try {
const kw = (this.historyKeyword || '').trim()
const res = await listJDOrders({
pageNum: this.historyPage,
pageSize: this.historyPageSize,
orderBy: 'id',
isAsc: 'desc',
quickRecordSearch: kw || undefined
})
this.historyList = (res && res.rows) || []
this.historyTotal = (res && res.total) || 0
} catch (e) {
this.historyList = []
this.historyTotal = 0
this.$modal.msgError('加载历史记录失败')
} finally {
this.historyLoading = false
}
},
historyFullModel(row) {
if (!row) return ''
const base = String(row.modelNumber || '').trim()
const shop = String(row.modelShop || '').trim()
if (!base) return shop
if (!shop) return base
if (base.endsWith(shop)) return base
return base + shop
},
formatHistoryTime(row) {
const raw = (row && (row.orderTime || row.createTime)) || ''
if (!raw) return ''
const s = String(raw).replace('T', ' ')
return s.length >= 16 ? s.slice(0, 16) : s
},
applyMarkSuffixFromText(mark) {
const v = String(mark || '').trim()
if (!v || v === 'F') {
this.form.markSuffix = ''
return
}
if (v.startsWith('F-')) {
this.form.markSuffix = v.slice(2).trim()
return
}
if (v.startsWith('F') && v.length > 1) {
this.form.markSuffix = v.slice(1).replace(/^-/, '').trim()
return
}
this.form.markSuffix = v
},
applyHistoryOrder(row) {
if (!row) return
this.applyMarkSuffixFromText(row.distributionMark)
if (row.modelShop) {
this.form.model = String(row.modelNumber || '').trim()
this.form.shopPrefix = String(row.modelShop || '').trim()
} else {
this.applyModelWithShop(this.historyFullModel(row))
}
this.form.address = this.compressAddressOneLine(row.address)
this.form.link = String(row.link || '').trim()
this.form.thirdPartyOrderNoText = String(row.thirdPartyOrderNo || '').trim()
this.form.buyer = String(row.buyer || '').trim()
this.form.paymentText = this.formatMoneyText(row.paymentAmount)
this.form.rebateText = this.formatMoneyText(row.rebateAmount)
this.form.orderIdText = String(row.orderId || '').trim()
this.form.logisticsLink = String(row.logisticsLink || '').trim()
this.resultText = ''
this.historyVisible = false
this.$modal.msgSuccess('已回填历史记录,可修改后录单')
this.$nextTick(() => {
const formEl = this.$el && this.$el.querySelector('.fadan-form')
if (formEl && formEl.scrollIntoView) {
formEl.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
})
},
copyResult() {
const t = this.resultText || ''
@@ -833,6 +845,12 @@ export default {
max-width: 960px;
}
.page-toolbar {
display: flex;
justify-content: flex-end;
margin-bottom: 12px;
}
.fadan-form ::v-deep .el-form-item {
margin-bottom: 14px;
}
@@ -914,8 +932,6 @@ export default {
.result-area ::v-deep .el-textarea__inner {
font-family: Consolas, 'Courier New', monospace;
font-size: 13px;
user-select: text;
-webkit-user-select: text;
}
.result-pieces-hint {
@@ -944,25 +960,81 @@ export default {
font-size: 14px;
}
.result-piece-actions {
.history-search {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 6px;
flex-shrink: 0;
gap: 8px;
margin-bottom: 12px;
}
.result-piece.is-form {
padding: 10px;
border: 1px solid #d9ecff;
.history-search .el-input {
flex: 1;
}
.history-list {
max-height: 52vh;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.history-empty {
padding: 32px 0;
text-align: center;
color: #909399;
}
.history-item {
padding: 12px;
margin-bottom: 8px;
border: 1px solid #ebeef5;
border-radius: 10px;
background: #f7fbff;
background: #fff;
cursor: pointer;
}
.result-piece.is-form.is-selected {
border-color: #67c23a;
background: #f0f9eb;
.history-item:active,
.history-item:hover {
border-color: #409eff;
background: #f5faff;
}
.history-item-top {
display: flex;
justify-content: space-between;
gap: 8px;
margin-bottom: 6px;
}
.history-model {
font-weight: 600;
color: #303133;
word-break: break-all;
}
.history-time {
flex-shrink: 0;
font-size: 12px;
color: #909399;
}
.history-item-meta {
display: flex;
flex-wrap: wrap;
gap: 8px 12px;
font-size: 12px;
color: #606266;
margin-bottom: 4px;
}
.history-item-addr {
font-size: 12px;
color: #909399;
line-height: 1.4;
word-break: break-all;
}
.history-pager {
margin-top: 10px;
text-align: right;
}
.verify-body .verify-code {
@@ -977,5 +1049,22 @@ export default {
.fadan-quick-record--mobile .box-card {
margin: 0 8px 16px;
}
.page-toolbar .el-button {
width: 100%;
height: 40px;
}
.history-search {
flex-direction: column;
}
.history-list {
max-height: 48vh;
}
.history-pager {
text-align: center;
}
}
</style>