1931 lines
67 KiB
Vue
1931 lines
67 KiB
Vue
<template>
|
||
<view>
|
||
<view class="record">
|
||
<view>
|
||
<!-- <view class="record-title">{{ item.name }}</view>-->
|
||
<view class="record-meta">门店:{{ currentItem.merchantName }}</view>
|
||
<!-- <view class="record-meta">小票金额:¥{{ item.amount }}</view>-->
|
||
<!-- <view class="record-meta" style="color: red">-->
|
||
<!-- 红包:¥{{ currentItem.income ? currentItem.income : 0 }}-->
|
||
<!-- <text v-if="hasExtraIncome(currentItem)">(含额外加提:¥{{ formatExtraIncome(currentItem) }})</text>-->
|
||
<!-- </view>-->
|
||
<view class="record-meta">提交人:{{ currentItem.owner || item.owner }} · {{ currentItem.date || item.date }}</view>
|
||
<view class="record-meta">留言:{{ currentItem.remark || '-' }}</view>
|
||
<view class="record-toggle" @tap="toggleDetailExpanded">
|
||
<text>{{ detailExpanded ? '收起详情' : '展开详情' }}</text>
|
||
<u-icon :name="detailExpanded ? 'arrow-up' : 'arrow-down'" size="20" color="#2fc6a7"/>
|
||
</view>
|
||
<view v-if="detailExpanded">
|
||
<view v-if="currentItem.image" class="record-section">
|
||
<view class="record-section-title">小票查看</view>
|
||
<view class="media-images">
|
||
<image
|
||
class="media-image"
|
||
:src="currentItem.image"
|
||
mode="aspectFill"
|
||
@click="emitPreview(currentItem.image, [currentItem.image])"
|
||
/>
|
||
</view>
|
||
</view>
|
||
<view class="record-detail">
|
||
<view v-if="currentItem.detailList && currentItem.detailList.length" class="detail-list">
|
||
<view class="detail-item" v-for="(detail, idx) in currentItem.detailList" :key="idx">
|
||
<view class="detail-head">
|
||
<view class="detail-name">{{ detail.goodsName || detail.name || detail.title || '商品' }}</view>
|
||
</view>
|
||
<view class="detail-grid">
|
||
<view class="detail-kv">
|
||
<text class="detail-label">数量</text>
|
||
<text class="detail-value">x {{ detail.num }}</text>
|
||
</view>
|
||
<view class="detail-kv">
|
||
<text class="detail-label">厂家</text>
|
||
<text class="detail-value">{{ resolveManufacturer(detail) }}</text>
|
||
</view>
|
||
<view class="detail-kv">
|
||
<text class="detail-label">编号</text>
|
||
<text class="detail-value">{{ resolveCode(detail) }}</text>
|
||
</view>
|
||
<view class="detail-kv">
|
||
<text class="detail-label">规格</text>
|
||
<text class="detail-value">{{ resolveSpec(detail) }}</text>
|
||
</view>
|
||
<view class="detail-kv">
|
||
<text class="detail-label">金额</text>
|
||
<text class="detail-value" style="color: red">{{ formatMoney(resolveAmount(detail)) }}</text>
|
||
</view>
|
||
<!-- <view class="detail-kv">-->
|
||
<!-- <text class="detail-label">额外加提</text>-->
|
||
<!-- <text class="detail-value" style="color: red">{{ formatMoney(resolveExtraAmount(detail)) }}</text>-->
|
||
<!-- </view>-->
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="detail-empty">暂无明细</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="record-actions" v-if="item.status === 0">
|
||
<view class="flex-1 mx-15">
|
||
<view
|
||
:class="['chip w-90p', approveDisabled ? 'disabled-chip' : '']"
|
||
@tap="handleApprove"
|
||
>通过</view>
|
||
</view>
|
||
<view class="flex-1 mx-15">
|
||
<view class="chip w-90p danger-chip" @tap="$emit('reject', item)">驳回</view>
|
||
</view>
|
||
<view class="flex-1 mx-15">
|
||
<view class="chip w-90p" @tap="openEditPopup">修改</view>
|
||
</view>
|
||
<view class="flex-1 mx-15">
|
||
<view class="chip w-90p" @tap="openCopyPopup">抄送</view>
|
||
</view>
|
||
</view>
|
||
<view class="record-actions" v-if="detailExpanded && item.status === 2">
|
||
<view class="flex-1 mx-15">
|
||
<view class="chip w-90p" @tap="openCopyPopup">抄送</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="showEditPopup" class="share-popup-mask" @tap="closeEditPopup" @touchmove.stop.prevent></view>
|
||
<view v-if="showEditPopup" class="share-popup card" @tap.stop>
|
||
<view class="popup-head">
|
||
<view class="section-title"> 修改晒单</view>
|
||
<view class="close-btn" @tap="closeEditPopup">关闭</view>
|
||
</view>
|
||
<view class="form-row">
|
||
<view class="label">门店</view>
|
||
<view class="picker-input" @tap="openEditMerchantPicker">
|
||
<view class="picker-input-inner">
|
||
<text :class="editForm.merchantId ? 'picker-text' : 'picker-placeholder'">
|
||
{{ editForm.merchantId ? `${editForm.chainName || '-'}/${editForm.merchantName || '-'}` : '请选择连锁和门店' }}
|
||
</text>
|
||
<u-icon name="arrow-right" size="20" color="#9ca3af"/>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="form-row">
|
||
<view class="label">订单号</view>
|
||
<input v-model="editForm.orderNo" class="input" placeholder="请输入订单号"/>
|
||
</view>
|
||
<view class="form-row">
|
||
<view class="label">日期</view>
|
||
<input v-model="editForm.date" class="input" placeholder="YYYY-MM-DD"/>
|
||
</view>
|
||
<view class="form-row">
|
||
<view class="label">小票金额</view>
|
||
<input v-model="editForm.amount" class="input" type="digit" placeholder="请输入小票金额"/>
|
||
</view>
|
||
<!-- <view class="form-row total-bonus-row">-->
|
||
<!-- <view class="label">总红包金额</view>-->
|
||
<!-- <view class="total-bonus-wrap">-->
|
||
<!-- <view class="form-value total-bonus-value">¥{{ editTotalBonusDisplay }}</view>-->
|
||
<!-- <view class="total-bonus-breakdown">-->
|
||
<!-- 红包金额:¥{{ editTotalIncomeBonusDisplay }},额外红包金额:¥{{ editTotalExtraBonusDisplay }}-->
|
||
<!-- </view>-->
|
||
<!-- </view>-->
|
||
<!-- </view>-->
|
||
<view class="detail-title">商品明细</view>
|
||
<view class="detail-actions">
|
||
<view class="ghost-btn" @tap="addShareItem">新增商品</view>
|
||
</view>
|
||
<view v-if="editForm.list.length === 0" class="detail-empty">暂无明细</view>
|
||
<view v-else class="edit-detail-list">
|
||
<view class="edit-detail-item" v-for="(detail, idx) in editForm.list" :key="idx">
|
||
<view class="edit-detail-head">
|
||
<view class="edit-detail-index">第 {{ idx + 1 }} 项</view>
|
||
<view class="edit-detail-remove" @tap="removeShareItem(idx)">删除</view>
|
||
</view>
|
||
<view class="form-row">
|
||
<view class="label">药品</view>
|
||
<view class="picker-input" @tap="openEditProductPicker(idx)">
|
||
<view class="picker-input-inner">
|
||
<text :class="detail.goodsName ? 'picker-text' : 'picker-placeholder'">
|
||
{{ detail.goodsName || '点击选择药品' }}
|
||
</text>
|
||
<u-icon name="arrow-right" size="20" color="#9ca3af"/>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="form-row">
|
||
<view class="label">编码</view>
|
||
<input
|
||
v-model="detail.goodsCode"
|
||
class="input"
|
||
placeholder="编码"
|
||
@input="onGoodsCodeInput($event, idx)"
|
||
@blur="onGoodsCodeBlur(idx)"
|
||
/>
|
||
</view>
|
||
<view class="form-row">
|
||
<view class="label">溯源码</view>
|
||
<input v-model="detail.sourceCode" class="input" placeholder="溯源码"/>
|
||
</view>
|
||
<!-- <view class="form-row">-->
|
||
<!-- <view class="label">红包金额</view>-->
|
||
<!-- <view class="form-value bonus-text">¥{{ resolveEditIncomeValue(detail).toFixed(2) }}</view>-->
|
||
<!-- </view>-->
|
||
<!-- <view class="form-row">-->
|
||
<!-- <view class="label">额外红包金额</view>-->
|
||
<!-- <view class="form-value bonus-text">¥{{ resolveEditExtraIncomeValue(detail).toFixed(2) }}</view>-->
|
||
<!-- </view>-->
|
||
<view class="form-row">
|
||
<view class="label">数量</view>
|
||
<input v-model="detail.num" class="input" type="number" placeholder="数量"/>
|
||
</view>
|
||
<view class="form-row">
|
||
<view class="label">规格</view>
|
||
<input v-model="detail.spec" class="input" placeholder="规格"/>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="popup-actions">
|
||
<view
|
||
v-if="showRecognizeBtn"
|
||
class="ghost-action"
|
||
@tap="recognizeReceipt"
|
||
>{{ recognizing ? '识别中...' : '识别' }}
|
||
</view>
|
||
<view class="primary-btn submit" @tap="saveEdit">保存</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="showEditMerchantPicker" class="share-popup-mask" @tap="closeEditMerchantPicker" @touchmove.stop.prevent></view>
|
||
<u-popup :show="showEditMerchantPicker" mode="bottom" round="24rpx" @close="closeEditMerchantPicker">
|
||
<view class="merchant-picker">
|
||
<view class="merchant-picker-head">
|
||
<view class="picker-action" @tap="closeEditMerchantPicker">取消</view>
|
||
<text class="picker-title">选择连锁和门店</text>
|
||
<view class="picker-action primary" @tap="confirmEditMerchantPicker">确定</view>
|
||
</view>
|
||
<view class="merchant-picker-search">
|
||
<input
|
||
v-model="editMerchantSearchKeyword"
|
||
class="merchant-search-input"
|
||
placeholder="搜索连锁/门店"
|
||
placeholder-class="merchant-search-placeholder"
|
||
/>
|
||
</view>
|
||
<view v-if="merchantLoading" class="merchant-picker-loading">
|
||
<u-loading-icon mode="circle"></u-loading-icon>
|
||
</view>
|
||
<picker-view
|
||
v-else
|
||
class="merchant-picker-view"
|
||
:value="editMerchantPickerValue"
|
||
:indicator-style="editMerchantIndicatorStyle"
|
||
@change="onEditMerchantPickerChange"
|
||
>
|
||
<picker-view-column>
|
||
<view
|
||
v-for="(item, idx) in editMerchantChainOptions"
|
||
:key="idx"
|
||
class="merchant-picker-item"
|
||
>{{ item.text }}</view>
|
||
</picker-view-column>
|
||
<picker-view-column>
|
||
<view
|
||
v-for="(item, idx) in editMerchantStoreOptions"
|
||
:key="idx"
|
||
class="merchant-picker-item"
|
||
>{{ item.text }}</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
</u-popup>
|
||
|
||
<u-popup :show="showEditProductPicker" mode="bottom" round="24rpx" @close="closeEditProductPicker">
|
||
<view class="product-picker">
|
||
<view class="merchant-picker-head">
|
||
<view class="picker-action" @tap="closeEditProductPicker">取消</view>
|
||
<text class="picker-title">选择药品</text>
|
||
<view class="picker-action primary" @tap="closeEditProductPicker">完成</view>
|
||
</view>
|
||
<view class="merchant-picker-search">
|
||
<input
|
||
v-model="editProductKeyword"
|
||
class="merchant-search-input"
|
||
placeholder="搜索药品名称/编码/厂家/规格"
|
||
placeholder-class="merchant-search-placeholder"
|
||
/>
|
||
</view>
|
||
<view v-if="editProductLoading" class="merchant-picker-loading">
|
||
<u-loading-icon mode="circle"></u-loading-icon>
|
||
</view>
|
||
<scroll-view v-else class="product-scroll" scroll-y>
|
||
<view v-if="filteredEditProductList.length === 0" class="detail-empty">暂无药品</view>
|
||
<view
|
||
v-for="product in filteredEditProductList"
|
||
:key="product._key"
|
||
class="product-option"
|
||
@tap="selectEditProduct(product)"
|
||
>
|
||
<view class="product-option-name">{{ product.goodsName || '-' }}</view>
|
||
<view class="product-option-meta">厂家:{{ product.makerAbb || '-' }}</view>
|
||
<view class="product-option-meta">规格:{{ product.spec || '-' }}</view>
|
||
<view class="product-option-meta">编码:{{ product.goodsCode || '-' }}</view>
|
||
<!-- <view class="product-option-meta">红包金额:¥{{ formatMoneyAmount(getEffectiveIncome(product)) }}</view>-->
|
||
<!-- <view class="product-option-meta">额外红包金额:¥{{ formatMoneyAmount(getEffectiveExtraIncome(product)) }}</view>-->
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</u-popup>
|
||
|
||
<view v-if="showCopyPopup" class="share-popup-mask" @tap="closeCopyPopup" @touchmove.stop.prevent></view>
|
||
<view v-if="showCopyPopup" class="share-popup card" @tap.stop>
|
||
<view class="popup-head">
|
||
<view class="section-title">抄送</view>
|
||
<view class="close-btn" @tap="closeCopyPopup">关闭</view>
|
||
</view>
|
||
<view class="copy-search-row">
|
||
<input
|
||
v-model="copyKeyword"
|
||
class="copy-search-input"
|
||
type="text"
|
||
placeholder="输入姓名/手机号搜索用户"
|
||
@confirm="searchCopyUsers"
|
||
/>
|
||
<view class="copy-search-btn" @tap="searchCopyUsers">搜索</view>
|
||
</view>
|
||
<view v-if="copySearching" class="copy-loading">搜索中...</view>
|
||
<scroll-view v-else class="copy-user-list" scroll-y>
|
||
<view
|
||
v-for="user in copyUsers"
|
||
:key="`copy-share-user-${user.id}`"
|
||
:class="['copy-user-item', selectedCopyUserId === user.id ? 'copy-user-item-active' : '']"
|
||
@tap="selectedCopyUserId = user.id"
|
||
>
|
||
<text class="copy-user-name">{{ user.label }}</text>
|
||
<text class="copy-user-phone">{{ user.phone || '' }}</text>
|
||
</view>
|
||
<view v-if="copyUsers.length === 0" class="copy-empty">暂无用户</view>
|
||
</scroll-view>
|
||
<view class="primary-btn submit" @tap="submitCopy">确认抄送</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import dayjs from 'dayjs'
|
||
import {productByCodeReq, shareAuditDetailReq, shareCopyReq} from "@/api/share";
|
||
import {merchantGroupByChainReq} from "@/api/shop";
|
||
import {aiReq} from "@/api/common";
|
||
import {userSearchReq} from "@/api/user";
|
||
import {productListReq} from "@/api/product";
|
||
|
||
export default {
|
||
name: 'AuditShareItem',
|
||
props: {
|
||
item: {
|
||
type: Object,
|
||
required: true
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
showEditPopup: false,
|
||
showEditMerchantPicker: false,
|
||
showEditProductPicker: false,
|
||
merchantLoading: false,
|
||
editProductLoading: false,
|
||
merchantChainList: [],
|
||
editProductList: [],
|
||
editProductKeyword: '',
|
||
activeEditProductRowIndex: -1,
|
||
editForm: {
|
||
chainName: '',
|
||
chainId: '',
|
||
merchantName: '',
|
||
merchantId: '',
|
||
orderNo: '',
|
||
date: '',
|
||
amount: '',
|
||
list: []
|
||
},
|
||
recognizing: false,
|
||
goodsCodeTimers: {},
|
||
editMerchantSearchKeyword: '',
|
||
editMerchantPickerValue: [0, 0],
|
||
detailExpanded: false,
|
||
detailLoading: false,
|
||
detailLoaded: false,
|
||
detailData: null,
|
||
showCopyPopup: false,
|
||
copyKeyword: '',
|
||
copyUsers: [],
|
||
copySearching: false,
|
||
selectedCopyUserId: ''
|
||
}
|
||
},
|
||
computed: {
|
||
approveDisabled() {
|
||
const total = this.calcTotalIncome(this.currentItem.detailList)
|
||
return !total || total <= 0
|
||
},
|
||
currentItem() {
|
||
return this.detailData || this.item
|
||
},
|
||
editMerchantChainOptions() {
|
||
const list = Array.isArray(this.merchantChainList) ? this.merchantChainList : []
|
||
const keyword = (this.editMerchantSearchKeyword || '').trim()
|
||
if (!keyword) return list.length ? list : [{id: '', text: '暂无连锁', merchants: []}]
|
||
const filtered = list
|
||
.map(chain => {
|
||
const merchants = Array.isArray(chain.merchants) ? chain.merchants : []
|
||
const chainMatch = (chain.text || '').includes(keyword)
|
||
if (chainMatch) return {...chain}
|
||
const matchedMerchants = merchants.filter(merchant => (merchant.text || '').includes(keyword))
|
||
if (!matchedMerchants.length) return null
|
||
return {
|
||
...chain,
|
||
merchants: matchedMerchants
|
||
}
|
||
})
|
||
.filter(Boolean)
|
||
return filtered.length ? filtered : [{id: '', text: '无匹配连锁', merchants: []}]
|
||
},
|
||
editMerchantStoreOptions() {
|
||
const chainIndex = Number(this.editMerchantPickerValue[0] || 0)
|
||
const currentChain = this.editMerchantChainOptions[chainIndex] || this.editMerchantChainOptions[0] || {}
|
||
const merchants = Array.isArray(currentChain.merchants) ? currentChain.merchants : []
|
||
return merchants.length ? merchants : [{id: '', text: '无门店'}]
|
||
},
|
||
editMerchantPickerIndexes() {
|
||
const chainList = Array.isArray(this.merchantChainList) ? this.merchantChainList : []
|
||
if (!chainList.length) return [0, 0]
|
||
let chainIndex = chainList.findIndex(item => item.id === this.editForm.chainId)
|
||
if (chainIndex < 0 && this.editForm.merchantId) {
|
||
chainIndex = chainList.findIndex(item => {
|
||
const merchants = Array.isArray(item.merchants) ? item.merchants : []
|
||
return merchants.some(merchant => merchant.id === this.editForm.merchantId)
|
||
})
|
||
}
|
||
if (chainIndex < 0) chainIndex = 0
|
||
const merchants = Array.isArray(chainList[chainIndex].merchants) ? chainList[chainIndex].merchants : []
|
||
let merchantIndex = merchants.findIndex(item => item.id === this.editForm.merchantId)
|
||
if (merchantIndex < 0) merchantIndex = 0
|
||
return [chainIndex, merchantIndex]
|
||
},
|
||
editMerchantIndicatorStyle() {
|
||
return 'height: 64px;'
|
||
},
|
||
filteredEditProductList() {
|
||
const list = Array.isArray(this.editProductList) ? this.editProductList : []
|
||
const keyword = (this.editProductKeyword || '').trim().toLowerCase()
|
||
if (!keyword) return list
|
||
return list.filter(item => {
|
||
const name = (item.goodsName || '').toLowerCase()
|
||
const code = (item.goodsCode || '').toLowerCase()
|
||
const maker = (item.makerAbb || '').toLowerCase()
|
||
const spec = (item.spec || '').toLowerCase()
|
||
return name.includes(keyword) || code.includes(keyword) || maker.includes(keyword) || spec.includes(keyword)
|
||
})
|
||
},
|
||
editSelectedProductRows() {
|
||
const list = Array.isArray(this.editForm.list) ? this.editForm.list : []
|
||
return list
|
||
.map(item => {
|
||
const num = this.parseQuantity(item.num)
|
||
const validNum = Number.isFinite(num) && num > 0 ? num : 0
|
||
const income = this.resolveEditIncomeValue(item)
|
||
const extraIncome = this.resolveEditExtraIncomeValue(item)
|
||
const singleBonus = income + extraIncome
|
||
return {
|
||
...item,
|
||
_num: validNum || NaN,
|
||
_baseBonusAmount: income * validNum,
|
||
_extraBonusAmount: extraIncome * validNum,
|
||
_bonusAmount: singleBonus * validNum
|
||
}
|
||
})
|
||
.filter(item => Number.isFinite(item._num) && item._num > 0)
|
||
},
|
||
editTotalIncomeBonus() {
|
||
return this.editSelectedProductRows.reduce((sum, item) => sum + (Number(item._baseBonusAmount) || 0), 0)
|
||
},
|
||
editTotalExtraBonus() {
|
||
return this.editSelectedProductRows.reduce((sum, item) => sum + (Number(item._extraBonusAmount) || 0), 0)
|
||
},
|
||
editTotalBonus() {
|
||
return this.editSelectedProductRows.reduce((sum, item) => sum + (Number(item._bonusAmount) || 0), 0)
|
||
},
|
||
editTotalIncomeBonusDisplay() {
|
||
return this.editTotalIncomeBonus.toFixed(2)
|
||
},
|
||
editTotalExtraBonusDisplay() {
|
||
return this.editTotalExtraBonus.toFixed(2)
|
||
},
|
||
editTotalBonusDisplay() {
|
||
return this.editTotalBonus.toFixed(2)
|
||
},
|
||
showRecognizeBtn() {
|
||
const amount = this.editForm.amount
|
||
return amount === null || amount === undefined || String(amount).trim() === ''
|
||
}
|
||
},
|
||
watch: {
|
||
editMerchantSearchKeyword() {
|
||
this.editMerchantPickerValue = [0, 0]
|
||
}
|
||
},
|
||
beforeDestroy() {
|
||
this.clearGoodsCodeTimers()
|
||
},
|
||
methods: {
|
||
async toggleDetailExpanded() {
|
||
const nextExpanded = !this.detailExpanded
|
||
this.detailExpanded = nextExpanded
|
||
if (!nextExpanded || this.detailLoaded || this.detailLoading || !this.item.id) return
|
||
this.detailLoading = true
|
||
try {
|
||
const {data} = await shareAuditDetailReq(this.item.id)
|
||
this.detailData = this.normalizeDetailResponse(data)
|
||
this.detailLoaded = true
|
||
} catch (error) {
|
||
uni.showToast({
|
||
title: '详情加载失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.detailLoading = false
|
||
}
|
||
},
|
||
emitPreview(url, list) {
|
||
this.$emit('preview', url, list)
|
||
},
|
||
clearGoodsCodeTimers() {
|
||
const keys = Object.keys(this.goodsCodeTimers || {})
|
||
keys.forEach(key => {
|
||
clearTimeout(this.goodsCodeTimers[key])
|
||
})
|
||
this.goodsCodeTimers = {}
|
||
},
|
||
normalizeNumericText(value) {
|
||
return String(value || '')
|
||
.trim()
|
||
.replace(/[0-9]/g, char => String.fromCharCode(char.charCodeAt(0) - 65248))
|
||
.replace(/,/g, ',')
|
||
.replace(/。/g, '.')
|
||
.replace(/,/g, '')
|
||
},
|
||
parseQuantity(value) {
|
||
const text = this.normalizeNumericText(value)
|
||
if (!text) return NaN
|
||
const num = Number(text)
|
||
return Number.isFinite(num) ? num : NaN
|
||
},
|
||
normalizeMatchValue(value) {
|
||
if (value === undefined || value === null) return ''
|
||
return String(value).trim().toLowerCase()
|
||
},
|
||
findMatchedDetailFromItemDetailList(row = {}, product = {}) {
|
||
const sourceItem = this.currentItem
|
||
const list = Array.isArray(sourceItem && sourceItem.detailList) ? sourceItem.detailList : []
|
||
if (!list.length) return null
|
||
const targetId = this.normalizeMatchValue(product.productId || row.productId)
|
||
const targetCode = this.normalizeMatchValue(product.goodsCode || row.goodsCode)
|
||
const targetName = this.normalizeMatchValue(product.goodsName || row.goodsName)
|
||
for (let i = 0; i < list.length; i++) {
|
||
const detail = list[i] || {}
|
||
const detailProduct = detail.product || {}
|
||
const detailId = this.normalizeMatchValue(
|
||
detail.productId
|
||
|| detail.goodsId
|
||
|| detail.id
|
||
|| detail.skuId
|
||
|| detailProduct.productId
|
||
|| detailProduct.goodsId
|
||
|| detailProduct.id
|
||
|| detailProduct.skuId
|
||
)
|
||
if (targetId && detailId && targetId === detailId) return detail
|
||
const detailCode = this.normalizeMatchValue(
|
||
detail.goodsCode
|
||
|| detail.code
|
||
|| detail.productCode
|
||
|| detail.skuCode
|
||
|| detail.batchCode
|
||
|| detailProduct.goodsCode
|
||
|| detailProduct.code
|
||
|| detailProduct.productCode
|
||
|| detailProduct.skuCode
|
||
|| detailProduct.batchCode
|
||
)
|
||
if (targetCode && detailCode && targetCode === detailCode) return detail
|
||
const detailName = this.normalizeMatchValue(
|
||
detail.goodsName
|
||
|| detail.name
|
||
|| detail.title
|
||
|| detailProduct.goodsName
|
||
|| detailProduct.name
|
||
|| detailProduct.title
|
||
)
|
||
if (targetName && detailName && targetName === detailName) return detail
|
||
}
|
||
return null
|
||
},
|
||
pickFirstText(source, keys = []) {
|
||
if (!source || typeof source !== 'object') return ''
|
||
for (let i = 0; i < keys.length; i++) {
|
||
const value = source[keys[i]]
|
||
if (value !== undefined && value !== null && String(value).trim()) {
|
||
return String(value).trim()
|
||
}
|
||
}
|
||
return ''
|
||
},
|
||
pickFirstNumber(values = [], fallback = 0) {
|
||
for (let i = 0; i < values.length; i++) {
|
||
const raw = values[i]
|
||
if (raw === '' || raw === null || raw === undefined) continue
|
||
const num = Number(raw)
|
||
if (Number.isFinite(num)) return num
|
||
}
|
||
return fallback
|
||
},
|
||
normalizeExtraIncomeList(rawValue) {
|
||
const parseText = text => {
|
||
try {
|
||
return JSON.parse(text)
|
||
} catch (err) {
|
||
try {
|
||
const normalized = text
|
||
.replace(/^'/, '"')
|
||
.replace(/'$/, '"')
|
||
.replace(/([{,]\s*)'([^']+?)'\s*:/g, '$1"$2":')
|
||
.replace(/:\s*'([^']*?)'(\s*[},])/g, ':"$1"$2')
|
||
return JSON.parse(normalized)
|
||
} catch (error) {
|
||
return null
|
||
}
|
||
}
|
||
}
|
||
const objectToList = obj => {
|
||
if (!obj || typeof obj !== 'object') return []
|
||
const wrapped = this.unwrapNestedList(obj)
|
||
if (Array.isArray(wrapped) && wrapped.length && wrapped[0] !== obj) return wrapped
|
||
if (Array.isArray(obj.list)) return obj.list
|
||
const values = Object.keys(obj)
|
||
.sort((a, b) => Number(a) - Number(b))
|
||
.map(key => obj[key])
|
||
const objectValues = values.filter(val => val && typeof val === 'object')
|
||
if (objectValues.length) return objectValues
|
||
if (obj.startTime || obj.start_time || obj.extraStartTime || obj.extra_start_time || obj.income !== undefined) {
|
||
return [obj]
|
||
}
|
||
return []
|
||
}
|
||
let current = rawValue
|
||
for (let i = 0; i < 3; i++) {
|
||
if (Array.isArray(current)) return current
|
||
if (current === undefined || current === null) return []
|
||
if (typeof current === 'object') return objectToList(current)
|
||
const text = String(current).trim()
|
||
if (!text) return []
|
||
const parsed = parseText(text)
|
||
if (parsed === null) return []
|
||
current = parsed
|
||
}
|
||
return []
|
||
},
|
||
normalizeExtraIncomeItem(item) {
|
||
if (!item || typeof item !== 'object') {
|
||
return {
|
||
income: 0,
|
||
startTime: '',
|
||
endTime: ''
|
||
}
|
||
}
|
||
const incomeRaw = item.income ?? item.extraIncome ?? item.amount ?? item.reward ?? item.bonus ?? item.value ?? 0
|
||
const startTime = item.startTime ?? item.start_time ?? item.extraStartTime ?? item.extra_start_time ?? item.beginTime ?? item.begin_time ?? ''
|
||
const endTime = item.endTime ?? item.end_time ?? item.extraEndTime ?? item.extra_end_time ?? item.finishTime ?? item.finish_time ?? ''
|
||
const income = Number(incomeRaw)
|
||
return {
|
||
income: Number.isFinite(income) ? income : 0,
|
||
startTime,
|
||
endTime
|
||
}
|
||
},
|
||
hasPeriod(startTime, endTime) {
|
||
const hasStart = startTime !== undefined && startTime !== null && String(startTime).trim() !== ''
|
||
const hasEnd = endTime !== undefined && endTime !== null && String(endTime).trim() !== ''
|
||
return hasStart && hasEnd
|
||
},
|
||
parsePeriodToDayjs(value) {
|
||
if (value === undefined || value === null) return null
|
||
const raw = String(value).trim()
|
||
if (!raw) return null
|
||
if (/^-?\d+(\.\d+)?$/.test(raw)) {
|
||
const num = Number(raw)
|
||
if (!Number.isFinite(num)) return null
|
||
const millis = Math.abs(num) < 1e12 ? num * 1000 : num
|
||
const parsedNum = dayjs(millis)
|
||
return parsedNum.isValid() ? parsedNum : null
|
||
}
|
||
const parsedText = dayjs(raw)
|
||
return parsedText.isValid() ? parsedText : null
|
||
},
|
||
isCurrentInPeriod(startTime, endTime) {
|
||
if (!this.hasPeriod(startTime, endTime)) return true
|
||
const start = this.parsePeriodToDayjs(startTime)
|
||
const end = this.parsePeriodToDayjs(endTime)
|
||
if (!start || !end) return true
|
||
const now = dayjs()
|
||
return (now.isAfter(start) || now.isSame(start)) && (now.isBefore(end) || now.isSame(end))
|
||
},
|
||
isCurrentInPeriodStrict(startTime, endTime) {
|
||
if (!this.hasPeriod(startTime, endTime)) return false
|
||
const start = this.parsePeriodToDayjs(startTime)
|
||
const end = this.parsePeriodToDayjs(endTime)
|
||
if (!start || !end) return false
|
||
const now = dayjs()
|
||
return (now.isAfter(start) || now.isSame(start)) && (now.isBefore(end) || now.isSame(end))
|
||
},
|
||
getExtraIncomeDisplayPeriods(detail = {}) {
|
||
const list = this.normalizeExtraIncomeList(detail.extraIncomeList)
|
||
.map(item => this.normalizeExtraIncomeItem(item))
|
||
.filter(item => this.hasPeriod(item.startTime, item.endTime))
|
||
if (list.length) return list
|
||
if (!this.hasPeriod(detail.extraStartTime, detail.extraEndTime)) return []
|
||
return [{
|
||
startTime: detail.extraStartTime,
|
||
endTime: detail.extraEndTime,
|
||
income: Number(detail.extraIncome) || 0
|
||
}]
|
||
},
|
||
getExtraIncomePeriods(detail = {}) {
|
||
const list = this.getExtraIncomeDisplayPeriods(detail)
|
||
.filter(item => (Number(item.income) || 0) > 0)
|
||
if (list.length) return list
|
||
return []
|
||
},
|
||
getEffectiveIncome(detail = {}) {
|
||
const amount = Number(detail.income)
|
||
const base = Number.isFinite(amount) && amount > 0 ? amount : 0
|
||
return this.isCurrentInPeriod(detail.startTime, detail.endTime) ? base : 0
|
||
},
|
||
getEffectiveExtraIncome(detail = {}) {
|
||
const periods = this.getExtraIncomePeriods(detail)
|
||
if (!periods.length) return 0
|
||
return periods.reduce((sum, item) => {
|
||
if (!this.isCurrentInPeriodStrict(item.startTime, item.endTime)) return sum
|
||
return sum + (Number(item.income) || 0)
|
||
}, 0)
|
||
},
|
||
formatMoneyAmount(value) {
|
||
const num = Number(value)
|
||
return Number.isFinite(num) ? num.toFixed(2) : '0.00'
|
||
},
|
||
unwrapNestedList(data) {
|
||
if (Array.isArray(data)) return data
|
||
if (!data || typeof data !== 'object') return []
|
||
const keys = ['list', 'records', 'rows', 'items', 'data', 'result', 'chainList', 'children', 'productList', 'products']
|
||
for (let i = 0; i < keys.length; i++) {
|
||
const value = data[keys[i]]
|
||
if (Array.isArray(value)) return value
|
||
if (value && typeof value === 'object') {
|
||
const nested = this.unwrapNestedList(value)
|
||
if (nested.length) return nested
|
||
}
|
||
}
|
||
return [data]
|
||
},
|
||
isProductNode(node) {
|
||
if (!node || typeof node !== 'object') return false
|
||
const product = node.product && typeof node.product === 'object' ? node.product : {}
|
||
const hasChildrenOnly = Array.isArray(node.children)
|
||
&& !this.pickFirstText(node, ['goodsName', 'goods_name', 'goodsCode', 'code', 'productCode', 'batchCode'])
|
||
&& !node.product
|
||
if (hasChildrenOnly) return false
|
||
return !!(
|
||
this.pickFirstText(node, ['goodsName', 'goods_name', 'goodsCode', 'code', 'productCode', 'batchCode']) ||
|
||
this.pickFirstText(product, ['goodsName', 'goods_name', 'goodsCode', 'code', 'productCode', 'batchCode'])
|
||
)
|
||
},
|
||
collectProductNodes(node, depth = 0) {
|
||
if (!node || depth > 4) return []
|
||
if (Array.isArray(node)) {
|
||
return node.reduce((acc, item) => acc.concat(this.collectProductNodes(item, depth + 1)), [])
|
||
}
|
||
if (typeof node !== 'object') return []
|
||
if (this.isProductNode(node)) return [node]
|
||
const keys = ['productList', 'products', 'list', 'records', 'rows', 'items', 'children', 'data']
|
||
return keys.reduce((acc, key) => {
|
||
const value = node[key]
|
||
if (Array.isArray(value) || (value && typeof value === 'object')) {
|
||
return acc.concat(this.collectProductNodes(value, depth + 1))
|
||
}
|
||
return acc
|
||
}, [])
|
||
},
|
||
normalizePickerProduct(item = {}) {
|
||
const product = item && item.product ? item.product : {}
|
||
const pickValue = (...values) => {
|
||
for (let i = 0; i < values.length; i++) {
|
||
const value = values[i]
|
||
if (value !== undefined && value !== null && String(value).trim() !== '') {
|
||
return value
|
||
}
|
||
}
|
||
return ''
|
||
}
|
||
const goodsCode = pickValue(item.goodsCode, item.code, item.productCode, item.batchCode, product.code, product.goodsCode, product.batchCode)
|
||
const productId = pickValue(item.productId, item.id, item.goodsId, item.skuId, product.productId, product.id, product.goodsId, product.skuId)
|
||
const goodsName = pickValue(item.goodsName, item.goods_name, item.title, item.name, product.title, product.goodsName, product.name)
|
||
const makerAbb = pickValue(item.makerAbb, item.maker, item.manufacturer, product.makerAbb, product.maker, product.manufacturer)
|
||
const spec = pickValue(product.spec, item.spec, item.specification)
|
||
const merchantId = pickValue(item.merchantId, item.merchant_id, product.merchantId, product.merchant_id)
|
||
const merchantName = pickValue(item.merchantName, item.merchant_name, item.shopName, item.shop_name, product.merchantName, product.merchant_name)
|
||
const chainId = pickValue(item.chainId, item.chain_id, product.chainId, product.chain_id)
|
||
const chainName = pickValue(item.chainName, item.chain_name, item.chain, product.chainName, product.chain_name, product.chain)
|
||
const incomeRaw = product.income !== undefined && product.income !== null
|
||
? product.income
|
||
: (item.income !== undefined && item.income !== null ? item.income : (item.shareIncome || item.reward || 0))
|
||
const extraIncomeRaw = product.extraIncome !== undefined && product.extraIncome !== null
|
||
? product.extraIncome
|
||
: (item.extraIncome !== undefined && item.extraIncome !== null ? item.extraIncome : (item.extraReward || item.extraBonus || item.otherIncome || 0))
|
||
const extraIncomeListRaw = pickValue(
|
||
item.extraIncomeList,
|
||
item.extra_income_list,
|
||
product.extraIncomeList,
|
||
product.extra_income_list
|
||
)
|
||
const startTime = pickValue(item.startTime, item.start_time, product.startTime, product.start_time)
|
||
const endTime = pickValue(item.endTime, item.end_time, product.endTime, product.end_time)
|
||
const extraStartTime = pickValue(item.extraStartTime, item.extra_start_time, product.extraStartTime, product.extra_start_time)
|
||
const extraEndTime = pickValue(item.extraEndTime, item.extra_end_time, product.extraEndTime, product.extra_end_time)
|
||
return {
|
||
productId,
|
||
goodsCode,
|
||
goodsName,
|
||
makerAbb,
|
||
spec,
|
||
merchantId,
|
||
merchantName,
|
||
chainId,
|
||
chainName,
|
||
income: Number(incomeRaw) || 0,
|
||
extraIncome: Number(extraIncomeRaw) || 0,
|
||
extraIncomeList: this.normalizeExtraIncomeList(extraIncomeListRaw),
|
||
startTime,
|
||
endTime,
|
||
extraStartTime,
|
||
extraEndTime
|
||
}
|
||
},
|
||
normalizeEditDetail(detail = {}) {
|
||
const product = this.normalizePickerProduct(detail.product || detail)
|
||
return {
|
||
productId: detail.productId || detail.goodsId || product.productId || '',
|
||
goodsCode: detail.goodsCode || detail.code || product.goodsCode || '',
|
||
sourceCode: detail.sourceCode || detail.source_code || '',
|
||
goodsName: detail.goodsName || detail.name || detail.title || product.goodsName || '',
|
||
num: detail.num || detail.qty || '',
|
||
spec: detail.spec || detail.specification || product.spec || '',
|
||
makerAbb: detail.makerAbb || detail.manufacturer || product.makerAbb || '',
|
||
amount: detail.amount || '',
|
||
income: this.pickFirstNumber([detail.income, detail.productIncome, detail.bonus, product.income], 0),
|
||
extraIncome: this.pickFirstNumber([detail.extraIncome, detail.extra_bonus, product.extraIncome], 0),
|
||
incomeSingle: detail.incomeSingle ?? detail.income_single ?? '',
|
||
extraIncomeSingle: detail.extraIncomeSingle ?? detail.extra_income_single ?? '',
|
||
extraIncomeList: detail.extraIncomeList || detail.extra_income_list || product.extraIncomeList || [],
|
||
startTime: detail.startTime || detail.start_time || product.startTime || '',
|
||
endTime: detail.endTime || detail.end_time || product.endTime || '',
|
||
extraStartTime: detail.extraStartTime || detail.extra_start_time || product.extraStartTime || '',
|
||
extraEndTime: detail.extraEndTime || detail.extra_end_time || product.extraEndTime || '',
|
||
product,
|
||
_preferSingleIncomeDisplay: true
|
||
}
|
||
},
|
||
buildEditForm(item) {
|
||
const sourceItem = this.currentItem
|
||
const list = Array.isArray(sourceItem.detailList) ? sourceItem.detailList : []
|
||
return {
|
||
chainName: sourceItem.chainName || sourceItem.chain_name || '',
|
||
chainId: sourceItem.chainId || sourceItem.chain_id || sourceItem.chainIdNormalized || '',
|
||
merchantName: sourceItem.merchantName || '',
|
||
merchantId: sourceItem.merchantId || sourceItem.merchant_id || '',
|
||
orderNo: sourceItem.orderNo || '',
|
||
date: sourceItem.date || '',
|
||
amount: sourceItem.amount || '',
|
||
list: list.map(detail => this.normalizeEditDetail(detail))
|
||
}
|
||
},
|
||
openEditPopup() {
|
||
if (this.item.status !== 0) {
|
||
return
|
||
}
|
||
this.editForm = this.buildEditForm(this.item)
|
||
this.editMerchantSearchKeyword = ''
|
||
this.editMerchantPickerValue = [...this.editMerchantPickerIndexes]
|
||
this.editProductKeyword = ''
|
||
this.editProductList = []
|
||
this.activeEditProductRowIndex = -1
|
||
this.showEditPopup = true
|
||
},
|
||
closeEditPopup() {
|
||
this.showEditPopup = false
|
||
this.showEditProductPicker = false
|
||
this.editProductList = []
|
||
this.activeEditProductRowIndex = -1
|
||
this.clearGoodsCodeTimers()
|
||
},
|
||
unwrapList(data) {
|
||
if (Array.isArray(data)) return data
|
||
if (!data || typeof data !== 'object') return []
|
||
const keys = ['list', 'records', 'rows', 'items', 'data', 'result']
|
||
for (let i = 0; i < keys.length; i++) {
|
||
if (Array.isArray(data[keys[i]])) return data[keys[i]]
|
||
}
|
||
return []
|
||
},
|
||
normalizeUser(item) {
|
||
const id = item.id ?? item.userId ?? item.uid
|
||
const nickname = item.nickname || item.nickName || ''
|
||
const realName = item.realName || item.real_name || item.name || ''
|
||
const phone = item.phone || item.mobile || item.tel || ''
|
||
const label = realName || nickname || phone || `用户${id || ''}`
|
||
return {
|
||
id,
|
||
label,
|
||
phone
|
||
}
|
||
},
|
||
openCopyPopup() {
|
||
this.copyKeyword = ''
|
||
this.copyUsers = []
|
||
this.selectedCopyUserId = ''
|
||
this.showCopyPopup = true
|
||
},
|
||
closeCopyPopup() {
|
||
this.showCopyPopup = false
|
||
},
|
||
async searchCopyUsers() {
|
||
const keyword = (this.copyKeyword || '').trim()
|
||
if (!keyword) {
|
||
uni.showToast({
|
||
title: '请输入keyword',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
this.copySearching = true
|
||
try {
|
||
const {data} = await userSearchReq({
|
||
keywords: keyword
|
||
})
|
||
const list = this.unwrapList(data)
|
||
this.copyUsers = list
|
||
.map(item => this.normalizeUser(item))
|
||
.filter(item => item.id !== undefined && item.id !== null && String(item.id).trim() !== '')
|
||
this.selectedCopyUserId = this.copyUsers[0] ? this.copyUsers[0].id : ''
|
||
} catch (error) {
|
||
this.copyUsers = []
|
||
uni.showToast({
|
||
title: '搜索失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.copySearching = false
|
||
}
|
||
},
|
||
async submitCopy() {
|
||
const targetUserId = this.selectedCopyUserId
|
||
if (targetUserId === '' || targetUserId === null || targetUserId === undefined) {
|
||
uni.showToast({
|
||
title: '请选择用户',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
try {
|
||
await shareCopyReq({
|
||
id: this.item.id,
|
||
sourceUserId: this.item.userId,
|
||
userId: targetUserId,
|
||
isCopy: 1
|
||
})
|
||
uni.showToast({
|
||
title: '抄送成功',
|
||
icon: 'success'
|
||
})
|
||
this.closeCopyPopup()
|
||
this.$emit('copied')
|
||
} catch (error) {
|
||
uni.showToast({
|
||
title: '抄送失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
},
|
||
normalizeMerchant(item) {
|
||
const id = item.id ?? item.merchantId ?? item.merchant_id
|
||
const text = item.name || item.merchantName || item.merchant_name || item.title || '未命名门店'
|
||
return {id, text, raw: item}
|
||
},
|
||
normalizeMerchantChain(item, index) {
|
||
const chainName = this.pickFirstText(item, ['chainName', 'chain', 'chainAbb', 'name', 'title']) || `连锁${index + 1}`
|
||
const rawChainId = item.chainId ?? item.chain_id ?? item.id ?? item.chainCode ?? item.chain_code ?? chainName
|
||
const chainId = rawChainId === undefined || rawChainId === null || String(rawChainId).trim() === ''
|
||
? `chain_${index}`
|
||
: rawChainId
|
||
const childCandidates = ['children', 'merchantList', 'merchants', 'shopList', 'list', 'records']
|
||
let rawChildren = []
|
||
for (let i = 0; i < childCandidates.length; i++) {
|
||
const list = item[childCandidates[i]]
|
||
if (Array.isArray(list) && list.length) {
|
||
rawChildren = list
|
||
break
|
||
}
|
||
}
|
||
let merchants = rawChildren
|
||
.map(child => this.normalizeMerchant(child))
|
||
.filter(child => child.id !== undefined && child.id !== null && String(child.id).trim() !== '')
|
||
if (!merchants.length) {
|
||
const selfMerchant = this.normalizeMerchant(item)
|
||
if (selfMerchant.id !== undefined && selfMerchant.id !== null && String(selfMerchant.id).trim() !== '') {
|
||
merchants = [selfMerchant]
|
||
}
|
||
}
|
||
return {
|
||
id: chainId,
|
||
text: chainName,
|
||
merchants
|
||
}
|
||
},
|
||
normalizeChainIdForApi(rawChainId) {
|
||
const text = String(rawChainId || '').trim()
|
||
if (!text || text.startsWith('chain_')) return ''
|
||
return text
|
||
},
|
||
getArrayFromResponse(data) {
|
||
if (Array.isArray(data)) return data
|
||
if (!data || typeof data !== 'object') return []
|
||
const keys = ['list', 'records', 'rows', 'items', 'data', 'result']
|
||
for (let i = 0; i < keys.length; i++) {
|
||
const value = data[keys[i]]
|
||
if (Array.isArray(value)) return value
|
||
if (value && typeof value === 'object') {
|
||
const nested = this.getArrayFromResponse(value)
|
||
if (nested.length) return nested
|
||
}
|
||
}
|
||
const values = Object.keys(data).map(key => data[key])
|
||
for (let i = 0; i < values.length; i++) {
|
||
if (Array.isArray(values[i])) return values[i]
|
||
}
|
||
return []
|
||
},
|
||
uniquePickerProducts(list = []) {
|
||
const map = {}
|
||
const rows = []
|
||
list.forEach(item => {
|
||
const key = item.productId || item.goodsCode || `${item.goodsName || ''}_${item.spec || ''}_${item.makerAbb || ''}`
|
||
const id = String(key || '').trim()
|
||
if (!id || map[id]) return
|
||
map[id] = true
|
||
rows.push(item)
|
||
})
|
||
return rows
|
||
},
|
||
matchesCurrentMerchantChain(product = {}) {
|
||
const selectedMerchantId = String(this.editForm.merchantId || '').trim()
|
||
const selectedChainId = this.normalizeChainIdForApi(this.editForm.chainId)
|
||
if (selectedMerchantId) {
|
||
const productMerchantId = String(product.merchantId || '').trim()
|
||
if (productMerchantId && productMerchantId !== selectedMerchantId) return false
|
||
}
|
||
if (selectedChainId) {
|
||
const productChainId = String(product.chainId || '').trim()
|
||
if (productChainId && productChainId !== selectedChainId) return false
|
||
}
|
||
return true
|
||
},
|
||
async getMerchantList() {
|
||
this.merchantLoading = true
|
||
try {
|
||
const {data} = await merchantGroupByChainReq({})
|
||
const list = this.unwrapNestedList(data)
|
||
this.merchantChainList = list
|
||
.map((item, index) => this.normalizeMerchantChain(item, index))
|
||
.filter(item => Array.isArray(item.merchants) && item.merchants.length > 0)
|
||
} catch (error) {
|
||
this.merchantChainList = []
|
||
uni.showToast({
|
||
title: '门店列表获取失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.merchantLoading = false
|
||
}
|
||
},
|
||
async openEditMerchantPicker() {
|
||
if (!this.merchantChainList || this.merchantChainList.length === 0) {
|
||
await this.getMerchantList()
|
||
}
|
||
this.editMerchantPickerValue = [...this.editMerchantPickerIndexes]
|
||
this.showEditMerchantPicker = true
|
||
},
|
||
closeEditMerchantPicker() {
|
||
this.showEditMerchantPicker = false
|
||
},
|
||
confirmEditMerchantPicker() {
|
||
const chainIndex = Number(this.editMerchantPickerValue[0] || 0)
|
||
const merchantIndex = Number(this.editMerchantPickerValue[1] || 0)
|
||
const chain = this.editMerchantChainOptions[chainIndex] || null
|
||
const merchants = chain && Array.isArray(chain.merchants) ? chain.merchants : []
|
||
const selected = merchants[merchantIndex]
|
||
if (!selected || selected.id === '' || selected.id === null || selected.id === undefined) {
|
||
return
|
||
}
|
||
this.editForm.chainName = (chain && chain.text) || ''
|
||
this.editForm.chainId = (chain && chain.id) || ''
|
||
this.editForm.merchantName = selected.text || ''
|
||
this.editForm.merchantId = selected.id ?? ''
|
||
this.editProductList = []
|
||
this.editProductKeyword = ''
|
||
this.showEditMerchantPicker = false
|
||
},
|
||
onEditMerchantPickerChange(event) {
|
||
const value = event && event.detail ? event.detail.value : null
|
||
const currentChainIndex = Number(this.editMerchantPickerValue[0] || 0)
|
||
let chainIndex = Array.isArray(value) ? Number(value[0] || 0) : Number(value || 0)
|
||
let merchantIndex = Array.isArray(value) ? Number(value[1] || 0) : 0
|
||
if (!Number.isFinite(chainIndex) || chainIndex < 0) chainIndex = 0
|
||
if (!Number.isFinite(merchantIndex) || merchantIndex < 0) merchantIndex = 0
|
||
const chain = this.editMerchantChainOptions[chainIndex] || this.editMerchantChainOptions[0] || {}
|
||
const merchants = Array.isArray(chain.merchants) ? chain.merchants : []
|
||
if (!merchants.length) {
|
||
merchantIndex = 0
|
||
} else if (chainIndex !== currentChainIndex || merchantIndex > merchants.length - 1) {
|
||
merchantIndex = 0
|
||
}
|
||
this.editMerchantPickerValue = [chainIndex, merchantIndex]
|
||
},
|
||
async openEditProductPicker(index) {
|
||
if (index < 0 || index >= this.editForm.list.length) return
|
||
if (!this.editForm.merchantId && !this.normalizeChainIdForApi(this.editForm.chainId)) {
|
||
uni.showToast({
|
||
title: '请先选择连锁和门店',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
this.activeEditProductRowIndex = index
|
||
this.editProductKeyword = ''
|
||
await this.getEditProductList()
|
||
this.showEditProductPicker = true
|
||
},
|
||
closeEditProductPicker() {
|
||
this.showEditProductPicker = false
|
||
},
|
||
async getEditProductList() {
|
||
this.editProductLoading = true
|
||
try {
|
||
const {data} = await productListReq()
|
||
const rawList = this.getArrayFromResponse(data)
|
||
const normalizedAll = rawList
|
||
.map(item => this.normalizePickerProduct(item))
|
||
.filter(item => !!item.goodsName)
|
||
const filtered = normalizedAll.filter(item => this.matchesCurrentMerchantChain(item))
|
||
const resolvedRows = filtered.length ? filtered : normalizedAll
|
||
const uniqueRows = this.uniquePickerProducts(resolvedRows)
|
||
this.editProductList = uniqueRows.map((item, index) => ({
|
||
...item,
|
||
_key: `edit-product-${index}`
|
||
}))
|
||
} catch (error) {
|
||
this.editProductList = []
|
||
uni.showToast({
|
||
title: '药品数据获取失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.editProductLoading = false
|
||
}
|
||
},
|
||
applyProductToEditDetail(index, rawProduct, fallbackCode = '') {
|
||
const row = this.editForm.list[index]
|
||
if (!row) return
|
||
const product = this.normalizePickerProduct(rawProduct || {})
|
||
const matchedDetail = this.findMatchedDetailFromItemDetailList(row, product)
|
||
const matchedIncome = matchedDetail
|
||
? this.pickFirstNumber([matchedDetail.extraIncome, matchedDetail.productIncome, matchedDetail.bonus], 0)
|
||
: null
|
||
this.$set(row, 'product', product)
|
||
this.$set(row, 'productId', product.productId || row.productId || '')
|
||
this.$set(row, 'goodsCode', product.goodsCode || fallbackCode || row.goodsCode || '')
|
||
this.$set(row, 'goodsName', product.goodsName || row.goodsName || '')
|
||
this.$set(row, 'makerAbb', product.makerAbb || row.makerAbb || '')
|
||
this.$set(row, 'spec', product.spec || row.spec || '')
|
||
this.$set(row, 'income', Number(product.income) || 0)
|
||
this.$set(row, 'extraIncome', rawProduct.extraIncome || 0)
|
||
this.$set(row, 'incomeSingle', '')
|
||
this.$set(row, 'extraIncomeSingle', '')
|
||
this.$set(row, 'extraIncomeList', Array.isArray(product.extraIncomeList) ? product.extraIncomeList : [])
|
||
this.$set(row, 'startTime', product.startTime || '')
|
||
this.$set(row, 'endTime', product.endTime || '')
|
||
this.$set(row, 'extraStartTime', product.extraStartTime || '')
|
||
this.$set(row, 'extraEndTime', product.extraEndTime || '')
|
||
this.$set(row, '_preferSingleIncomeDisplay', false)
|
||
},
|
||
clearProductInfo(index) {
|
||
const row = this.editForm.list[index]
|
||
if (!row) return
|
||
this.$set(row, 'product', null)
|
||
this.$set(row, 'productId', '')
|
||
this.$set(row, 'goodsName', '')
|
||
this.$set(row, 'makerAbb', '')
|
||
this.$set(row, 'spec', '')
|
||
this.$set(row, 'income', 0)
|
||
this.$set(row, 'extraIncome', 0)
|
||
this.$set(row, 'incomeSingle', '')
|
||
this.$set(row, 'extraIncomeSingle', '')
|
||
this.$set(row, 'extraIncomeList', [])
|
||
this.$set(row, 'startTime', '')
|
||
this.$set(row, 'endTime', '')
|
||
this.$set(row, 'extraStartTime', '')
|
||
this.$set(row, 'extraEndTime', '')
|
||
this.$set(row, '_preferSingleIncomeDisplay', false)
|
||
},
|
||
selectEditProduct(product) {
|
||
const index = this.activeEditProductRowIndex
|
||
if (index < 0 || index >= this.editForm.list.length) return
|
||
this.applyProductToEditDetail(index, product)
|
||
this.showEditProductPicker = false
|
||
},
|
||
addShareItem() {
|
||
this.editForm.list.push({
|
||
productId: '',
|
||
goodsCode: '',
|
||
goodsName: '',
|
||
num: '',
|
||
makerAbb: '',
|
||
spec: '',
|
||
amount: '',
|
||
sourceCode: '',
|
||
income: 0,
|
||
extraIncome: 0,
|
||
incomeSingle: '',
|
||
extraIncomeSingle: '',
|
||
extraIncomeList: [],
|
||
startTime: '',
|
||
endTime: '',
|
||
extraStartTime: '',
|
||
extraEndTime: '',
|
||
product: null,
|
||
_preferSingleIncomeDisplay: false
|
||
})
|
||
},
|
||
removeShareItem(idx) {
|
||
this.editForm.list.splice(idx, 1)
|
||
},
|
||
onGoodsCodeInput(event, index) {
|
||
const fallback = this.editForm.list[index] ? this.editForm.list[index].goodsCode : ''
|
||
const value = event && event.detail && event.detail.value !== undefined ? event.detail.value : fallback
|
||
this.queueGoodsCodeLookup(index, value)
|
||
},
|
||
onGoodsCodeBlur(index) {
|
||
const item = this.editForm.list[index]
|
||
const code = item ? item.goodsCode : ''
|
||
this.fetchProductByCode(index, code)
|
||
},
|
||
async queueGoodsCodeLookup(index, code) {
|
||
if (this.goodsCodeTimers[index]) {
|
||
clearTimeout(this.goodsCodeTimers[index])
|
||
}
|
||
const trimmed = (code || '').trim()
|
||
if (!trimmed) {
|
||
if (this.editForm.list[index]) {
|
||
this.clearProductInfo(index)
|
||
}
|
||
return
|
||
}
|
||
this.goodsCodeTimers[index] = setTimeout(async () => {
|
||
await this.fetchProductByCode(index, trimmed)
|
||
}, 400)
|
||
},
|
||
async fetchProductByCode(index, code) {
|
||
if (!this.editForm.merchantName) {
|
||
return uni.showToast({
|
||
title: '请填写门店名称',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
const trimmed = (code || '').trim()
|
||
if (!trimmed) {
|
||
if (this.editForm.list[index]) {
|
||
this.clearProductInfo(index)
|
||
}
|
||
return
|
||
}
|
||
try {
|
||
const {data} = await productByCodeReq(trimmed, this.editForm.merchantName)
|
||
if (this.editForm.list[index]) {
|
||
this.applyProductToEditDetail(index, data || {}, trimmed)
|
||
}
|
||
} catch (err) {
|
||
if (this.editForm.list[index]) {
|
||
this.clearProductInfo(index)
|
||
this.$set(this.editForm.list[index], 'goodsCode', trimmed)
|
||
}
|
||
}
|
||
},
|
||
saveEdit() {
|
||
const detailList = this.editForm.list.map(item => ({
|
||
goodsId: item.productId || '',
|
||
goodsCode: item.goodsCode || '',
|
||
goodsName: item.goodsName || '',
|
||
num: item.num || '',
|
||
spec: item.spec || '',
|
||
amount: item.amount || '',
|
||
sourceCode: item.sourceCode || '',
|
||
income: this.resolveEditIncomeValue(item),
|
||
extraIncome: this.resolveEditExtraIncomeValue(item)
|
||
}))
|
||
const payload = {
|
||
id: this.item.id,
|
||
chainId: this.editForm.chainId || this.item.chainId || '',
|
||
chainName: this.editForm.chainName || this.item.chainName || '',
|
||
merchantName: this.editForm.merchantName,
|
||
orderNo: this.editForm.orderNo,
|
||
date: this.editForm.date,
|
||
detailList,
|
||
amount: this.editForm.amount,
|
||
income: Number(this.editTotalBonusDisplay) || 0,
|
||
extraIncome: Number(this.editTotalExtraBonusDisplay) || 0,
|
||
merchantId: this.editForm.merchantId || this.item.merchantId || '',
|
||
userId: this.item.userId,
|
||
status: 0,
|
||
}
|
||
this.$emit('update', {
|
||
item: this.item,
|
||
payload,
|
||
localDetailList: this.editForm.list
|
||
})
|
||
this.showEditPopup = false
|
||
},
|
||
parseAiJson(value) {
|
||
if (value === null || value === undefined) return null
|
||
if (typeof value === 'object') return value
|
||
let text = String(value).trim()
|
||
if (!text) return null
|
||
text = text.replace(/^```[a-zA-Z]*\n?/, '').replace(/```$/g, '').trim()
|
||
if ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) {
|
||
text = text.slice(1, -1).trim()
|
||
}
|
||
const objStart = text.indexOf('{')
|
||
const objEnd = text.lastIndexOf('}')
|
||
const arrStart = text.indexOf('[')
|
||
const arrEnd = text.lastIndexOf(']')
|
||
if (objStart !== -1 && objEnd > objStart) {
|
||
text = text.slice(objStart, objEnd + 1)
|
||
} else if (arrStart !== -1 && arrEnd > arrStart) {
|
||
text = text.slice(arrStart, arrEnd + 1)
|
||
}
|
||
try {
|
||
return JSON.parse(text)
|
||
} catch (err) {
|
||
const normalized = text.replace(/'/g, '"')
|
||
try {
|
||
return JSON.parse(normalized)
|
||
} catch (error) {
|
||
return null
|
||
}
|
||
}
|
||
},
|
||
normalizeAiField(value) {
|
||
if (value === null || value === undefined) return ''
|
||
return String(value).trim()
|
||
},
|
||
async recognizeReceipt() {
|
||
if (this.recognizing) return
|
||
const sourceItem = this.currentItem
|
||
if (!sourceItem.image) {
|
||
uni.showToast({
|
||
title: '无小票图片可识别',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
try {
|
||
this.recognizing = true
|
||
uni.showLoading({
|
||
title: '识别中...'
|
||
})
|
||
const res = await aiReq({
|
||
imgUrl: sourceItem.image
|
||
})
|
||
const content = res && res.data && res.data.output && res.data.output.choices && res.data.output.choices[0]
|
||
&& res.data.output.choices[0].message ? res.data.output.choices[0].message.content : null
|
||
const rawText = Array.isArray(content)
|
||
? (content[0] && typeof content[0] === 'object' ? (content[0].text || content[0].content || '') : content[0])
|
||
: content
|
||
const parsed = this.parseAiJson(rawText)
|
||
if (!parsed) {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '识别失败',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
const orderNo = this.normalizeAiField(parsed.orderNo || parsed.order_no || parsed.orderId || parsed.order_id || '')
|
||
const amount = this.normalizeAiField(parsed.amount || parsed.totalAmount || parsed.total_amount || parsed.total || parsed.money || '')
|
||
const date = this.normalizeAiField(parsed.date || parsed.printDate || parsed.print_date || parsed.tradeDate || parsed.trade_date || '')
|
||
if (!orderNo && !amount && !date) {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '未识别到可填充字段',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
if (orderNo) this.editForm.orderNo = orderNo
|
||
if (amount) this.editForm.amount = amount
|
||
if (date) this.editForm.date = date
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '识别成功',
|
||
icon: 'success'
|
||
})
|
||
} catch (error) {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '识别失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.recognizing = false
|
||
}
|
||
},
|
||
getDetailIncome(detail) {
|
||
const source = this.resolveDetailRewardSource(detail)
|
||
const num = this.parseQuantity(detail && (detail.num || detail.qty || 0))
|
||
if (!Number.isFinite(num) || num <= 0) return 0
|
||
const income = this.getEffectiveIncome(source)
|
||
const extraIncome = this.getEffectiveExtraIncome(source)
|
||
return (income + extraIncome) * num
|
||
},
|
||
resolveDetailRewardSource(detail = {}) {
|
||
const product = detail && detail.product ? detail.product : {}
|
||
return {
|
||
income: this.pickFirstNumber([detail.income, product.income, detail.productIncome, detail.bonus], 0),
|
||
extraIncome: this.pickFirstNumber([detail.extraIncome, product.extraIncome, detail.extra_bonus, detail.extraBonus], 0),
|
||
extraIncomeList: detail.extraIncomeList || detail.extra_income_list || product.extraIncomeList || product.extra_income_list || [],
|
||
startTime: detail.startTime || detail.start_time || product.startTime || product.start_time || '',
|
||
endTime: detail.endTime || detail.end_time || product.endTime || product.end_time || '',
|
||
extraStartTime: detail.extraStartTime || detail.extra_start_time || product.extraStartTime || product.extra_start_time || '',
|
||
extraEndTime: detail.extraEndTime || detail.extra_end_time || product.extraEndTime || product.extra_end_time || ''
|
||
}
|
||
},
|
||
resolveEditIncomeValue(detail = {}) {
|
||
if (detail && detail._preferSingleIncomeDisplay) {
|
||
const singleRaw = detail.incomeSingle ?? detail.income_single
|
||
if (singleRaw !== undefined && singleRaw !== null && String(singleRaw).trim() !== '') {
|
||
const single = Number(singleRaw)
|
||
if (Number.isFinite(single)) return single
|
||
}
|
||
}
|
||
const source = this.resolveDetailRewardSource(detail)
|
||
return this.getEffectiveIncome(source)
|
||
},
|
||
resolveEditExtraIncomeValue(detail = {}) {
|
||
if (detail && detail._preferSingleIncomeDisplay) {
|
||
const singleRaw = detail.extraIncomeSingle ?? detail.extra_income_single
|
||
if (singleRaw !== undefined && singleRaw !== null && String(singleRaw).trim() !== '') {
|
||
const single = Number(singleRaw)
|
||
if (Number.isFinite(single)) return single
|
||
}
|
||
}
|
||
const raw = detail && detail.extraIncome
|
||
if (raw !== undefined && raw !== null && String(raw).trim() !== '') {
|
||
const direct = Number(raw)
|
||
return Number.isFinite(direct) ? direct : 0
|
||
}
|
||
const source = this.resolveDetailRewardSource(detail)
|
||
return this.getEffectiveExtraIncome(source)
|
||
},
|
||
calcTotalIncome(list) {
|
||
const items = Array.isArray(list) ? list : []
|
||
return items.reduce((sum, detail) => sum + this.getDetailIncome(detail), 0)
|
||
},
|
||
handleApprove() {
|
||
const rawIncome = this.calcTotalIncome(this.currentItem.detailList)
|
||
const income = Number(rawIncome.toFixed(2))
|
||
if (!income || income <= 0) {
|
||
uni.showToast({
|
||
title: '请确认后再通过',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
this.$emit('approve', {item: this.item, income})
|
||
},
|
||
normalizeDetailResponse(data) {
|
||
const detail = data && typeof data === 'object' ? {...data} : {}
|
||
const list = Array.isArray(detail.detailList) ? detail.detailList : []
|
||
detail.detailList = list
|
||
return detail
|
||
},
|
||
resolveManufacturer(detail) {
|
||
if (!detail) return '-'
|
||
const product = detail.product || {}
|
||
const value = detail.makerAbb
|
||
|| detail.manufacturer
|
||
|| detail.vendor
|
||
|| detail.factory
|
||
|| detail.brand
|
||
|| product.makerAbb
|
||
|| product.manufacturer
|
||
|| product.vendor
|
||
|| product.factory
|
||
|| product.brand
|
||
return this.normalizeText(value)
|
||
},
|
||
resolveCode(detail) {
|
||
if (!detail) return '-'
|
||
const value = detail.goodsCode
|
||
|| detail.code
|
||
|| detail.productCode
|
||
|| detail.skuCode
|
||
|| detail.barcode
|
||
return this.normalizeText(value)
|
||
},
|
||
resolveSpec(detail) {
|
||
if (!detail) return '-'
|
||
const product = detail.product || {}
|
||
const value = detail.spec
|
||
|| detail.specification
|
||
|| detail.standard
|
||
|| product.spec
|
||
|| product.specification
|
||
|| product.standard
|
||
return this.normalizeText(value)
|
||
},
|
||
resolveAmount(detail) {
|
||
if (!detail) return ''
|
||
return detail.income
|
||
},
|
||
resolveExtraAmount(detail) {
|
||
if (!detail) return ''
|
||
return detail.extraIncome
|
||
},
|
||
resolveExtraIncomeValue(source = {}) {
|
||
const candidates = [
|
||
source.extraIncome,
|
||
source.extra_income,
|
||
source.extraBonus,
|
||
source.extra_bonus,
|
||
source.otherIncome,
|
||
source.other_income
|
||
]
|
||
for (let i = 0; i < candidates.length; i++) {
|
||
const raw = candidates[i]
|
||
if (raw === '' || raw === null || raw === undefined) continue
|
||
const num = Number(raw)
|
||
if (Number.isFinite(num)) return num
|
||
}
|
||
return 0
|
||
},
|
||
hasExtraIncome(source = {}) {
|
||
return this.resolveExtraIncomeValue(source) !== 0
|
||
},
|
||
formatExtraIncome(source = {}) {
|
||
return this.resolveExtraIncomeValue(source).toFixed(2)
|
||
},
|
||
normalizeText(value) {
|
||
if (value === 0) return '0'
|
||
if (value === null || value === undefined || value === '') return '-'
|
||
return String(value)
|
||
},
|
||
formatMoney(value) {
|
||
if (value === null || value === undefined || value === '') return '-'
|
||
const num = Number(value)
|
||
if (Number.isFinite(num)) {
|
||
return `¥${num.toFixed(2)}`
|
||
}
|
||
return String(value)
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@import './record.scss';
|
||
|
||
.share-popup-mask {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.45);
|
||
z-index: 30;
|
||
}
|
||
|
||
.share-popup {
|
||
position: fixed;
|
||
left: 50%;
|
||
bottom: 40rpx;
|
||
transform: translateX(-50%);
|
||
width: 88%;
|
||
max-height: 70vh;
|
||
overflow: auto;
|
||
z-index: 40;
|
||
}
|
||
|
||
.popup-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.close-btn {
|
||
color: var(--text-sub);
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.record-toggle {
|
||
margin-top: 8rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: 4rpx;
|
||
font-size: 24rpx;
|
||
color: #2fc6a7;
|
||
}
|
||
|
||
.form-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12rpx 0;
|
||
border-bottom: 1rpx solid #edf1f7;
|
||
}
|
||
|
||
.label {
|
||
color: var(--text-main);
|
||
font-size: 26rpx;
|
||
min-width: 140rpx;
|
||
}
|
||
|
||
.input {
|
||
flex: 1;
|
||
text-align: right;
|
||
font-size: 26rpx;
|
||
color: var(--text-main);
|
||
}
|
||
|
||
.picker-input {
|
||
flex: 1;
|
||
}
|
||
|
||
.picker-input-inner {
|
||
width: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: 8rpx;
|
||
min-height: 40rpx;
|
||
}
|
||
|
||
.picker-text {
|
||
color: var(--text-main);
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.picker-placeholder {
|
||
color: #9ca3af;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.merchant-picker {
|
||
padding: 20rpx 24rpx 30rpx;
|
||
}
|
||
|
||
.merchant-picker-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.picker-action {
|
||
font-size: 26rpx;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.picker-action.primary {
|
||
color: #2fc6a7;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.picker-title {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.merchant-picker-search {
|
||
margin: 6rpx 0 12rpx;
|
||
}
|
||
|
||
.merchant-search-input {
|
||
width: 100%;
|
||
background: #f3f4f6;
|
||
border-radius: 12rpx;
|
||
padding: 12rpx 16rpx;
|
||
font-size: 26rpx;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.merchant-search-placeholder {
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.merchant-picker-loading {
|
||
padding: 20rpx 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.merchant-picker-view {
|
||
height: 50vh;
|
||
}
|
||
|
||
.merchant-picker-item {
|
||
height: 64px;
|
||
padding: 0 12rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
font-size: 26rpx;
|
||
color: #1f2937;
|
||
line-height: 1.3;
|
||
white-space: normal;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.form-value {
|
||
flex: 1;
|
||
text-align: right;
|
||
font-size: 26rpx;
|
||
color: var(--text-main);
|
||
}
|
||
|
||
.bonus-text {
|
||
color: #ff3b30;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.disabled-chip {
|
||
color: #9ca3af;
|
||
border-color: #e5e7eb;
|
||
background: #f3f4f6;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.total-bonus-row {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.total-bonus-wrap {
|
||
flex: 1;
|
||
}
|
||
|
||
.total-bonus-value {
|
||
color: #ff2121;
|
||
font-weight: 700;
|
||
text-align: right;
|
||
}
|
||
|
||
.total-bonus-breakdown {
|
||
margin-top: 4rpx;
|
||
font-size: 22rpx;
|
||
color: #6b7280;
|
||
text-align: right;
|
||
}
|
||
|
||
.detail-title {
|
||
margin-top: 12rpx;
|
||
font-weight: 700;
|
||
color: var(--text-main);
|
||
}
|
||
|
||
.detail-actions {
|
||
margin-top: 8rpx;
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.ghost-btn {
|
||
color: var(--brand-primary);
|
||
border: 1rpx solid var(--brand-primary);
|
||
padding: 6rpx 16rpx;
|
||
border-radius: 999rpx;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.edit-detail-list {
|
||
margin-top: 8rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.edit-detail-item {
|
||
border: 1rpx dashed #dfe8f5;
|
||
border-radius: 12rpx;
|
||
padding: 12rpx;
|
||
}
|
||
|
||
.edit-detail-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 6rpx;
|
||
}
|
||
|
||
.edit-detail-index {
|
||
color: var(--text-sub);
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.edit-detail-remove {
|
||
color: #ff3b30;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.submit {
|
||
margin-top: 16rpx;
|
||
padding: 14rpx;
|
||
height: 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.popup-actions {
|
||
margin-top: 16rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.popup-actions .submit {
|
||
margin-top: 0;
|
||
flex: 1;
|
||
}
|
||
|
||
.ghost-action {
|
||
flex: 1;
|
||
height: 60rpx;
|
||
border-radius: 999rpx;
|
||
border: 1rpx solid #d1d5db;
|
||
color: #374151;
|
||
background: #f9fafb;
|
||
font-size: 26rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.product-picker {
|
||
padding: 20rpx 24rpx 30rpx;
|
||
}
|
||
|
||
.product-scroll {
|
||
max-height: 60vh;
|
||
}
|
||
|
||
.product-option {
|
||
padding: 18rpx 12rpx;
|
||
border-bottom: 1rpx solid #edf1f7;
|
||
}
|
||
|
||
.product-option-name {
|
||
font-size: 26rpx;
|
||
color: #111827;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.product-option-meta {
|
||
margin-top: 4rpx;
|
||
font-size: 24rpx;
|
||
color: #4b5563;
|
||
}
|
||
|
||
.copy-search-row {
|
||
display: flex;
|
||
gap: 10rpx;
|
||
align-items: center;
|
||
}
|
||
|
||
.copy-search-input {
|
||
flex: 1;
|
||
height: 70rpx;
|
||
line-height: 70rpx;
|
||
border-radius: 12rpx;
|
||
background: #f3f4f6;
|
||
padding: 0 16rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.copy-search-btn {
|
||
min-width: 110rpx;
|
||
height: 70rpx;
|
||
border-radius: 12rpx;
|
||
background: var(--brand-primary);
|
||
color: #ffffff;
|
||
font-size: 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.copy-loading,
|
||
.copy-empty {
|
||
margin-top: 14rpx;
|
||
font-size: 24rpx;
|
||
color: var(--text-sub);
|
||
}
|
||
|
||
.copy-user-list {
|
||
margin-top: 14rpx;
|
||
max-height: 420rpx;
|
||
}
|
||
|
||
.copy-user-item {
|
||
padding: 12rpx;
|
||
border-radius: 12rpx;
|
||
background: #f9fafb;
|
||
margin-bottom: 10rpx;
|
||
border: 1rpx solid #e5e7eb;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.copy-user-item-active {
|
||
border-color: var(--brand-primary);
|
||
background: #ecfdf5;
|
||
}
|
||
|
||
.copy-user-name {
|
||
font-size: 26rpx;
|
||
color: var(--text-main);
|
||
}
|
||
|
||
.copy-user-phone {
|
||
font-size: 22rpx;
|
||
color: var(--text-sub);
|
||
}
|
||
</style>
|