4556114b4d
normalizeMerchantChain 的兜底链以 `chain_${index}` 结尾,该值是数组下标而非
连锁标识。审核员改门店时 saveMerchantEdit 会把 chain.id 直接写入 chainId,
落库后按连锁筛选(YxeSelfReportMapper.xml: a.chain_id = #{param.chainId}
等值匹配)就再也筛不出这条记录。
存量数据已出现 chain_3/chain_6 等脏值,导致 5 条已通过记录按连锁筛选时
永久漏查。本次修复:
- 兜底链结尾改用连锁名(与提交端 create.vue 的 `?? chainName` 对齐)
- saveMerchantEdit 写入前拦截 `chain_` 前缀,退回连锁名
后端 MerchantServiceImpl.groupByChain 已返回稳定 chainId,正常路径不再回退。
1424 lines
48 KiB
Vue
1424 lines
48 KiB
Vue
<template>
|
||
<view>
|
||
<view class="record">
|
||
<view>
|
||
<view class="record-meta" v-if="currentRecord.chainName && currentRecord.chainName !== '-'">连锁:{{ currentRecord.chainName }}</view>
|
||
<view class="record-meta-row">
|
||
<view class="record-meta">门店:{{ currentRecord.merchantName || '-' }}</view>
|
||
<view v-if="item.status === 0" class="record-meta-action" @tap="openMerchantEditPicker">修改</view>
|
||
</view>
|
||
<view class="record-meta-row">
|
||
<view class="record-meta">活动日期:{{ currentRecord.date || item.date || '-' }}</view>
|
||
<view v-if="item.status === 0" class="record-meta-action" @tap="openDateEditPicker">修改</view>
|
||
</view>
|
||
<!-- <view class="record-meta" style="color: red">奖金:¥{{ formatPrice(currentRecord.reward || 0) }}</view>-->
|
||
<view class="record-meta">评分:{{ currentRecord.rating || '-' }} 星</view>
|
||
<view class="record-meta">提交人:{{ currentRecord.owner || item.owner }}</view>
|
||
<view class="record-meta">小结:{{ currentRecord.summary || '-' }}</view>
|
||
<view class="record-meta text-red" v-if="currentRecord.rejectReason">驳回原因:{{ currentRecord.rejectReason }}</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" class="record-detail">
|
||
<view class="record-section">
|
||
<view class="record-section-title">上下班打卡</view>
|
||
<view v-if="clockImageList.length" class="media-images">
|
||
<image
|
||
v-for="(img, idx) in clockImageList"
|
||
:key="`clock-${idx}`"
|
||
class="media-image"
|
||
:src="img"
|
||
mode="aspectFill"
|
||
@click="emitPreview(img, clockImageList)"
|
||
/>
|
||
</view>
|
||
<view v-else class="detail-empty">暂无打卡图片</view>
|
||
</view>
|
||
|
||
<view class="record-section">
|
||
<view class="record-section-title">门头摆台照</view>
|
||
<view v-if="boothImageList.length" class="media-images">
|
||
<image
|
||
v-for="(img, idx) in boothImageList"
|
||
:key="`booth-${idx}`"
|
||
class="media-image"
|
||
:src="img"
|
||
mode="aspectFill"
|
||
@click="emitPreview(img, boothImageList)"
|
||
/>
|
||
</view>
|
||
<view v-else class="detail-empty">暂无门头摆台照</view>
|
||
</view>
|
||
|
||
<view class="record-section">
|
||
<view class="record-section-title">小票上传</view>
|
||
<view v-if="receiptImageList.length" class="media-images">
|
||
<image
|
||
v-for="(img, idx) in receiptImageList"
|
||
:key="`receipt-${idx}`"
|
||
class="media-image"
|
||
:src="img"
|
||
mode="aspectFill"
|
||
@click="emitPreview(img, receiptImageList)"
|
||
/>
|
||
</view>
|
||
<view v-else class="detail-empty">暂无小票</view>
|
||
</view>
|
||
|
||
<view class="record-section">
|
||
<view class="record-section-title-row">
|
||
<view class="record-section-title">销量填写</view>
|
||
<view v-if="item.status === 0" class="section-action" @tap="openEditPopup">修改销量</view>
|
||
</view>
|
||
<view v-if="salesRows.length" class="detail-list">
|
||
<view class="detail-item" v-for="(detail, idx) in salesRows" :key="`sales-${idx}`">
|
||
<view class="detail-head">
|
||
<view class="detail-name">{{ detail.name }}</view>
|
||
<view class="detail-value text-red">销量:{{ detail.qty }}{{ detail.unit }}</view>
|
||
</view>
|
||
<view class="detail-grid">
|
||
<view class="detail-kv">
|
||
<text class="detail-label">分组</text>
|
||
<text class="detail-value">{{ detail.groupTitle }}</text>
|
||
</view>
|
||
<view class="detail-kv">
|
||
<text class="detail-label">厂家</text>
|
||
<text class="detail-value">{{ detail.makerAbb || '-' }}</text>
|
||
</view>
|
||
<view class="detail-kv">
|
||
<text class="detail-label">规格</text>
|
||
<text class="detail-value">{{ detail.spec || '-' }}</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" @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>
|
||
</view>
|
||
|
||
<view v-if="showEditPopup" class="self-report-popup-mask" @tap="closeEditPopup" @touchmove.stop.prevent></view>
|
||
<view v-if="showEditPopup" class="self-report-popup card" @tap.stop>
|
||
<view class="popup-head">
|
||
<view class="section-title">修改销量填写</view>
|
||
<view class="close-btn" @tap="closeEditPopup">关闭</view>
|
||
</view>
|
||
<!-- <view class="edit-summary">-->
|
||
<!-- <view class="edit-summary-label">奖金合计</view>-->
|
||
<!-- <view class="edit-summary-value">¥{{ editTotalRewardDisplay }}</view>-->
|
||
<!-- </view>-->
|
||
<view class="detail-actions">
|
||
<view class="ghost-btn" @tap="openProductPicker(-1)">新增商品</view>
|
||
</view>
|
||
<view v-if="editSalesItems.length === 0" class="detail-empty">暂无销量商品</view>
|
||
<view v-else class="edit-sales-list">
|
||
<view class="edit-sales-item" v-for="(detail, idx) in editSalesItems" :key="detail._key || idx">
|
||
<view class="edit-sales-head">
|
||
<view>
|
||
<view class="edit-sales-name">{{ detail.name || '商品' }}</view>
|
||
<view class="edit-sales-group">{{ detail.groupTitle || '自定义商品' }}</view>
|
||
</view>
|
||
<view class="edit-sales-remove" @tap="removeEditSalesItem(idx)">删除</view>
|
||
</view>
|
||
<view class="form-row">
|
||
<view class="label">商品</view>
|
||
<view class="picker-input" @tap="openProductPicker(idx)">
|
||
<view class="picker-input-inner">
|
||
<text :class="detail.name ? 'picker-text' : 'picker-placeholder'">{{ detail.name || '点击选择商品' }}</text>
|
||
<u-icon name="arrow-right" size="20" color="#9ca3af"/>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="detail-grid edit-grid">
|
||
<view class="detail-kv">
|
||
<text class="detail-label">厂家</text>
|
||
<text class="detail-value">{{ detail.makerAbb || '-' }}</text>
|
||
</view>
|
||
<view class="detail-kv">
|
||
<text class="detail-label">编码</text>
|
||
<text class="detail-value">{{ detail.goodsCode || '-' }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="form-row" v-if="detail.specOptions && detail.specOptions.length">
|
||
<view class="label">规格</view>
|
||
<picker class="picker-input" mode="selector" :range="detail.specOptions" @change="onSpecChange($event, detail)">
|
||
<view class="picker-input-inner">
|
||
<text class="picker-text">{{ detail.spec || detail.specOptions[0] || '规格' }}</text>
|
||
<u-icon name="arrow-right" size="20" color="#9ca3af"/>
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
<view class="form-row" v-else>
|
||
<view class="label">规格</view>
|
||
<input v-model="detail.spec" class="input" placeholder="规格"/>
|
||
</view>
|
||
<!-- <view class="form-row">-->
|
||
<!-- <view class="label">单品奖金</view>-->
|
||
<!-- <view class="form-value bonus-text">¥{{ formatMoney(getTaskIncomeNumber(detail)) }}</view>-->
|
||
<!-- </view>-->
|
||
<view class="form-row">
|
||
<view class="label">数量</view>
|
||
<view class="qty-input-wrap">
|
||
<input :value="String(detail.qty === undefined || detail.qty === null ? '' : detail.qty)" class="input qty-input" type="digit" placeholder="0" @input="onEditQtyInput($event, idx)"/>
|
||
<text class="qty-unit">{{ detail.unit || '盒' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="popup-actions">
|
||
<view class="primary-btn submit" @tap="saveEdit">{{ editSaving ? '保存中...' : '保存' }}</view>
|
||
</view>
|
||
</view>
|
||
|
||
<u-popup :show="showMerchantEditPicker" mode="bottom" round="24rpx" @close="closeMerchantEditPicker">
|
||
<view class="merchant-edit-picker">
|
||
<view class="merchant-picker-head">
|
||
<view class="picker-action" @tap="closeMerchantEditPicker">取消</view>
|
||
<text class="picker-title">修改门店</text>
|
||
<view class="picker-action primary" @tap="saveMerchantEdit">{{ merchantEditSaving ? '保存中...' : '保存' }}</view>
|
||
</view>
|
||
<view v-if="merchantEditLoading" class="merchant-picker-loading">
|
||
<u-loading-icon mode="circle"></u-loading-icon>
|
||
</view>
|
||
<view v-else>
|
||
<view class="merchant-picker-search">
|
||
<input v-model="merchantEditKeyword" class="merchant-search-input" placeholder="搜索门店" placeholder-class="merchant-search-placeholder" @input="onMerchantEditSearchInput"/>
|
||
</view>
|
||
<picker-view class="merchant-picker-view" :value="merchantEditPickerValue" @change="onMerchantEditPickerChange">
|
||
<picker-view-column>
|
||
<view v-for="(chain, index) in merchantEditChains" :key="`edit-chain-${index}`" class="merchant-picker-item">
|
||
{{ chain.text }}
|
||
</view>
|
||
</picker-view-column>
|
||
<picker-view-column>
|
||
<view v-for="(merchant, index) in merchantEditStores" :key="`edit-merchant-${index}`" class="merchant-picker-item">
|
||
{{ merchant.text }}
|
||
</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
</view>
|
||
</u-popup>
|
||
|
||
<u-datetime-picker
|
||
v-if="showDateEditPicker"
|
||
:key="dateEditPickerKey"
|
||
:show="showDateEditPicker"
|
||
:value="dateEditPickerValue"
|
||
mode="date"
|
||
@confirm="saveDateEdit"
|
||
@cancel="closeDateEditPicker"
|
||
@close="closeDateEditPicker"
|
||
/>
|
||
|
||
<u-popup :show="showProductPicker" mode="bottom" round="24rpx" @close="closeProductPicker">
|
||
<view class="product-picker">
|
||
<view class="merchant-picker-head">
|
||
<view class="picker-action" @tap="closeProductPicker">取消</view>
|
||
<text class="picker-title">选择商品</text>
|
||
<view class="picker-action primary" @tap="closeProductPicker">完成</view>
|
||
</view>
|
||
<view class="merchant-picker-search">
|
||
<input
|
||
v-model="productKeyword"
|
||
class="merchant-search-input"
|
||
placeholder="搜索商品名称/编码/厂家/规格"
|
||
placeholder-class="merchant-search-placeholder"
|
||
/>
|
||
</view>
|
||
<view v-if="productLoading" class="merchant-picker-loading">
|
||
<u-loading-icon mode="circle"></u-loading-icon>
|
||
</view>
|
||
<view v-else class="product-panel">
|
||
<scroll-view class="category-sidebar" scroll-y>
|
||
<view
|
||
v-for="group in filteredProductGroups"
|
||
:key="group.key"
|
||
class="category-item"
|
||
:class="currentProductGroupKey === group.key ? 'category-item-active' : ''"
|
||
@tap="selectProductGroup(group.key)"
|
||
>
|
||
{{ group.title }}
|
||
</view>
|
||
</scroll-view>
|
||
<scroll-view class="product-panel-main" scroll-y>
|
||
<view v-if="filteredProductGroups.length === 0" class="detail-empty">暂无可选商品</view>
|
||
<view v-else-if="displayedProductOptions.length === 0" class="detail-empty">该分类暂无商品</view>
|
||
<view v-else class="product-picker-list">
|
||
<view
|
||
v-for="product in displayedProductOptions"
|
||
:key="product._key"
|
||
class="product-picker-item"
|
||
@tap="selectProduct(product)"
|
||
>
|
||
<view class="product-picker-main">
|
||
<view class="product-picker-name">{{ product.name || '-' }}</view>
|
||
<view class="product-picker-sub">{{ product.makerAbb || '-' }} / {{ product.spec || '规格' }}</view>
|
||
<view class="product-picker-sub">编码:{{ product.goodsCode || '-' }}</view>
|
||
<!-- <view class="product-picker-income">奖金金额:¥{{ formatMoney(getTaskIncomeNumber(product)) }}</view>-->
|
||
</view>
|
||
<view class="product-picker-tag">{{ isProductSelected(product) ? '已添加' : '添加' }}</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</u-popup>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import dayjs from 'dayjs'
|
||
import {productGroupByCateReq} from "@/api/product";
|
||
import {merchantGroupByChainReq} from "@/api/shop";
|
||
import {selfReportAuditDetailReq, selfReportChangeReq} from "@/api/self-report";
|
||
|
||
export default {
|
||
name: 'AuditSelfReportItem',
|
||
props: {
|
||
item: {
|
||
type: Object,
|
||
required: true
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
detailExpanded: false,
|
||
detailLoading: false,
|
||
detailLoaded: false,
|
||
detailData: null,
|
||
showEditPopup: false,
|
||
editSaving: false,
|
||
showProductPicker: false,
|
||
productLoading: false,
|
||
productGroups: [],
|
||
productLoadedChainName: '',
|
||
productKeyword: '',
|
||
activeProductGroupKey: '',
|
||
editSalesItems: [],
|
||
activeEditSalesIndex: -1,
|
||
showMerchantEditPicker: false,
|
||
merchantEditLoading: false,
|
||
merchantEditSaving: false,
|
||
merchantEditChains: [],
|
||
merchantEditPickerValue: [0, 0],
|
||
merchantEditKeyword: '',
|
||
showDateEditPicker: false,
|
||
dateEditPickerValue: Date.now(),
|
||
dateEditPickerKey: 0,
|
||
dateEditSaving: false
|
||
}
|
||
},
|
||
computed: {
|
||
currentRecord() {
|
||
return this.detailData || this.item
|
||
},
|
||
clockImageList() {
|
||
const source = this.currentRecord
|
||
return this.parseClockImageList(source.clockPhotos)
|
||
},
|
||
boothImageList() {
|
||
const source = this.currentRecord
|
||
return this.parseImageList(source.boothPhotos)
|
||
},
|
||
receiptImageList() {
|
||
const source = this.currentRecord
|
||
return this.parseImageList(source.receiptPhotos)
|
||
},
|
||
salesRows() {
|
||
const source = this.currentRecord
|
||
const groups = Array.isArray(source.salesGroups) ? source.salesGroups : []
|
||
const rows = []
|
||
groups.forEach((group, gIdx) => {
|
||
const items = Array.isArray(group && group.items) ? group.items : []
|
||
items.forEach((detail, iIdx) => {
|
||
rows.push({
|
||
key: `${gIdx}-${iIdx}`,
|
||
groupTitle: group && group.title ? group.title : `分组${gIdx + 1}`,
|
||
name: detail && detail.name ? detail.name : '-',
|
||
makerAbb: detail && detail.makerAbb ? detail.makerAbb : '',
|
||
spec: detail && detail.spec ? detail.spec : '',
|
||
qty: detail && detail.qty !== undefined && detail.qty !== null ? detail.qty : 0,
|
||
unit: detail && detail.unit ? detail.unit : ''
|
||
})
|
||
})
|
||
})
|
||
return rows
|
||
},
|
||
filteredProductGroups() {
|
||
const keyword = (this.productKeyword || '').trim().toLowerCase()
|
||
const groups = Array.isArray(this.productGroups) ? this.productGroups : []
|
||
if (!keyword) return groups
|
||
return groups
|
||
.map(group => {
|
||
const items = Array.isArray(group.items) ? group.items : []
|
||
const nextItems = items.filter(item => {
|
||
const name = String(item.name || '').toLowerCase()
|
||
const maker = String(item.makerAbb || '').toLowerCase()
|
||
const spec = String(item.spec || '').toLowerCase()
|
||
const code = String(item.goodsCode || '').toLowerCase()
|
||
return name.includes(keyword) || maker.includes(keyword) || spec.includes(keyword) || code.includes(keyword)
|
||
})
|
||
return Object.assign({}, group, {items: nextItems})
|
||
})
|
||
.filter(group => Array.isArray(group.items) && group.items.length)
|
||
},
|
||
currentProductGroupKey() {
|
||
const groups = Array.isArray(this.filteredProductGroups) ? this.filteredProductGroups : []
|
||
if (!groups.length) return ''
|
||
const exists = groups.some(group => group.key === this.activeProductGroupKey)
|
||
return exists ? this.activeProductGroupKey : groups[0].key
|
||
},
|
||
displayedProductOptions() {
|
||
const groups = Array.isArray(this.filteredProductGroups) ? this.filteredProductGroups : []
|
||
const current = groups.find(group => group.key === this.currentProductGroupKey)
|
||
return current && Array.isArray(current.items) ? current.items : []
|
||
},
|
||
editTotalRewardAmount() {
|
||
return (Array.isArray(this.editSalesItems) ? this.editSalesItems : []).reduce((sum, item) => {
|
||
return sum + (this.parseNumber(item && item.qty) * this.getTaskIncomeNumber(item))
|
||
}, 0)
|
||
},
|
||
editTotalRewardDisplay() {
|
||
return this.formatMoney(this.editTotalRewardAmount)
|
||
},
|
||
merchantEditStores() {
|
||
const chainIndex = Number(this.merchantEditPickerValue[0] || 0)
|
||
const chain = this.merchantEditChains[chainIndex] || this.merchantEditChains[0] || {}
|
||
const stores = Array.isArray(chain.merchants) ? chain.merchants : []
|
||
const keyword = this.merchantEditKeyword.trim()
|
||
const filteredStores = keyword ? stores.filter(store => String(store.text || '').includes(keyword)) : stores
|
||
return filteredStores.length ? filteredStores : [{id: '', text: keyword ? '无匹配门店' : '暂无门店'}]
|
||
}
|
||
},
|
||
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 selfReportAuditDetailReq(this.item.id)
|
||
this.detailData = this.normalizeDetailData(data)
|
||
this.detailLoaded = true
|
||
} catch (error) {
|
||
uni.showToast({
|
||
title: '详情加载失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.detailLoading = false
|
||
}
|
||
},
|
||
emitPreview(url, list) {
|
||
this.$emit('preview', url, list)
|
||
},
|
||
handleApprove() {
|
||
this.$emit('approve', this.currentRecord)
|
||
},
|
||
pickFirstText(source, keys = []) {
|
||
if (!source || typeof source !== 'object') return ''
|
||
for (let i = 0; i < keys.length; i += 1) {
|
||
const value = source[keys[i]]
|
||
if (value !== undefined && value !== null && String(value).trim()) {
|
||
return String(value).trim()
|
||
}
|
||
}
|
||
return ''
|
||
},
|
||
parseNumber(value) {
|
||
const text = String(value === null || value === undefined ? '' : value).trim()
|
||
if (!text) return 0
|
||
const num = Number(text)
|
||
return Number.isFinite(num) ? num : 0
|
||
},
|
||
formatMoney(value) {
|
||
const num = Number(value)
|
||
return Number.isFinite(num) ? num.toFixed(2) : '0.00'
|
||
},
|
||
getTaskIncomeNumber(item) {
|
||
if (!item || typeof item !== 'object') return 0
|
||
const num = Number(item.taskIncome !== undefined && item.taskIncome !== null ? item.taskIncome : item.income)
|
||
return Number.isFinite(num) ? num : 0
|
||
},
|
||
normalizeSalesGroupItem(detail = {}, groupTitle = '') {
|
||
const product = detail.product && typeof detail.product === 'object' ? detail.product : {}
|
||
const specOptions = Array.isArray(detail.specOptions) && detail.specOptions.length
|
||
? detail.specOptions
|
||
: Array.isArray(product.specOptions) && product.specOptions.length
|
||
? product.specOptions
|
||
: []
|
||
const productId = detail.productId || detail.goodsId || product.productId || product.goodsId || product.id || ''
|
||
const goodsCode = detail.goodsCode || detail.code || detail.productCode || product.goodsCode || product.code || product.productCode || ''
|
||
const name = detail.name || detail.goodsName || detail.title || product.goodsName || product.name || product.title || ''
|
||
const makerAbb = detail.makerAbb || detail.manufacturer || product.makerAbb || product.manufacturer || ''
|
||
const spec = detail.spec || detail.specification || product.spec || specOptions[0] || ''
|
||
const taskIncome = detail.taskIncome !== undefined && detail.taskIncome !== null
|
||
? detail.taskIncome
|
||
: product.income !== undefined && product.income !== null
|
||
? product.income
|
||
: detail.income || 0
|
||
return {
|
||
_key: detail._key || `${Date.now()}_${Math.random().toString(16).slice(2)}`,
|
||
groupTitle: groupTitle || detail.groupTitle || '自定义商品',
|
||
productId,
|
||
goodsCode,
|
||
name,
|
||
makerAbb,
|
||
spec,
|
||
specOptions,
|
||
unit: detail.unit || product.unit || '盒',
|
||
qty: detail.qty !== undefined && detail.qty !== null ? detail.qty : detail.num || 0,
|
||
taskIncome
|
||
}
|
||
},
|
||
flattenSalesGroups(groups) {
|
||
const source = Array.isArray(groups) ? groups : []
|
||
const rows = []
|
||
source.forEach((group, gIdx) => {
|
||
const items = Array.isArray(group && group.items) ? group.items : []
|
||
items.forEach((detail, iIdx) => {
|
||
rows.push(this.normalizeSalesGroupItem(Object.assign({}, detail, {
|
||
_key: detail && detail._key ? detail._key : `${gIdx}_${iIdx}_${Date.now()}`
|
||
}), group && group.title ? group.title : `分组${gIdx + 1}`))
|
||
})
|
||
})
|
||
return rows
|
||
},
|
||
buildSalesGroupsFromItems(items) {
|
||
const groupsMap = {}
|
||
;(Array.isArray(items) ? items : []).forEach(item => {
|
||
const groupTitle = item.groupTitle || '自定义商品'
|
||
if (!groupsMap[groupTitle]) {
|
||
groupsMap[groupTitle] = {
|
||
title: groupTitle,
|
||
items: []
|
||
}
|
||
}
|
||
groupsMap[groupTitle].items.push({
|
||
productId: item.productId || '',
|
||
goodsCode: item.goodsCode || '',
|
||
name: item.name || '',
|
||
makerAbb: item.makerAbb || '',
|
||
spec: item.spec || '',
|
||
taskIncome: this.getTaskIncomeNumber(item),
|
||
qty: this.parseNumber(item.qty),
|
||
unit: item.unit || '盒',
|
||
specOptions: Array.isArray(item.specOptions) ? item.specOptions : []
|
||
})
|
||
})
|
||
return Object.keys(groupsMap).map(key => groupsMap[key])
|
||
},
|
||
unwrapProductList(data) {
|
||
if (Array.isArray(data)) return data
|
||
if (!data || typeof data !== 'object') return []
|
||
const keys = ['list', 'records', 'rows', 'items', 'data', 'result', 'children', 'productGroup', 'productList', 'products']
|
||
for (let i = 0; i < keys.length; i += 1) {
|
||
if (Array.isArray(data[keys[i]])) return data[keys[i]]
|
||
}
|
||
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 = ['productGroup', '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
|
||
}, [])
|
||
},
|
||
normalizeSpecOptions(item) {
|
||
const raw = item.specOptions || item.specList || item.specs || []
|
||
if (Array.isArray(raw)) {
|
||
return raw.map(value => `${value}`.trim()).filter(Boolean)
|
||
}
|
||
if (typeof raw === 'string') {
|
||
return raw.split(/[、,,]/).map(value => value.trim()).filter(Boolean)
|
||
}
|
||
const spec = item.spec || item.specification || item.standard || ''
|
||
return spec ? [spec] : []
|
||
},
|
||
normalizeProductNode(item = {}) {
|
||
const product = item && item.product ? item.product : {}
|
||
const goodsCode = item.goodsCode || item.code || item.productCode || item.batchCode || product.code || product.goodsCode || product.batchCode || ''
|
||
const productId = item.productId || item.id || item.goodsId || item.skuId || product.productId || product.id || product.goodsId || product.skuId || ''
|
||
const name = item.goodsName || item.goods_name || item.title || item.name || product.title || product.goodsName || product.name || ''
|
||
const makerAbb = item.makerAbb || item.maker || item.manufacturer || product.makerAbb || product.maker || product.manufacturer || ''
|
||
const spec = product.spec || item.spec || item.specification || ''
|
||
return {
|
||
productId,
|
||
goodsCode,
|
||
name,
|
||
makerAbb,
|
||
spec,
|
||
unit: item.unit || product.unit || '盒',
|
||
taskIncome: item.taskIncome !== undefined && item.taskIncome !== null ? item.taskIncome : (product.income || item.income || 0),
|
||
specOptions: this.normalizeSpecOptions(Object.assign({}, product, item))
|
||
}
|
||
},
|
||
buildProductGroupsFromData(rawData) {
|
||
const rootList = this.unwrapProductList(rawData)
|
||
const tabs = []
|
||
rootList.forEach((categoryNode, categoryIndex) => {
|
||
if (!categoryNode || typeof categoryNode !== 'object') return
|
||
const label = categoryNode.cate || categoryNode.title || categoryNode.name
|
||
const products = Array.isArray(categoryNode.children) ? categoryNode.children : []
|
||
if (!label || !products.length) return
|
||
tabs.push({
|
||
key: `cate_children_${categoryIndex}`,
|
||
label,
|
||
rawProducts: products
|
||
})
|
||
})
|
||
if (!tabs.length) {
|
||
const allProducts = this.collectProductNodes(rootList)
|
||
if (allProducts.length) {
|
||
tabs.push({
|
||
key: 'all',
|
||
label: '全部',
|
||
rawProducts: allProducts
|
||
})
|
||
}
|
||
}
|
||
let rowSeed = 0
|
||
return tabs.map((tab, tabIndex) => {
|
||
const items = (Array.isArray(tab.rawProducts) ? tab.rawProducts : [])
|
||
.map((rawItem, itemIndex) => {
|
||
const product = this.normalizeProductNode(rawItem)
|
||
if (!product.name) return null
|
||
const specOptions = Array.isArray(product.specOptions) && product.specOptions.length
|
||
? product.specOptions
|
||
: [product.spec || '规格']
|
||
return {
|
||
...product,
|
||
groupTitle: tab.label || `分类${tabIndex + 1}`,
|
||
spec: product.spec || specOptions[0] || '规格',
|
||
specOptions,
|
||
_key: `audit_product_${tabIndex}_${itemIndex}_${rowSeed++}`
|
||
}
|
||
})
|
||
.filter(Boolean)
|
||
return {
|
||
key: tab.key || `group_${tabIndex}`,
|
||
title: tab.label || `分类${tabIndex + 1}`,
|
||
items
|
||
}
|
||
}).filter(group => group.items.length)
|
||
},
|
||
async loadProductGroups() {
|
||
const chainName = String(this.currentRecord.chainName || '').trim()
|
||
if (!chainName || chainName === '-') {
|
||
this.productGroups = []
|
||
this.productLoadedChainName = ''
|
||
this.activeProductGroupKey = ''
|
||
return
|
||
}
|
||
if (this.productLoadedChainName === chainName && this.productGroups.length) return
|
||
this.productLoading = true
|
||
try {
|
||
const {data} = await productGroupByCateReq({chain: chainName, type: 0})
|
||
this.productGroups = this.buildProductGroupsFromData(data)
|
||
this.productLoadedChainName = chainName
|
||
this.activeProductGroupKey = this.productGroups[0] ? this.productGroups[0].key : ''
|
||
} catch (error) {
|
||
this.productGroups = []
|
||
this.productLoadedChainName = ''
|
||
this.activeProductGroupKey = ''
|
||
uni.showToast({
|
||
title: '商品数据获取失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.productLoading = false
|
||
}
|
||
},
|
||
async ensureDetailLoaded() {
|
||
if (this.detailLoaded || this.detailLoading || !this.item.id) return
|
||
this.detailLoading = true
|
||
try {
|
||
const {data} = await selfReportAuditDetailReq(this.item.id)
|
||
this.detailData = this.normalizeDetailData(data)
|
||
this.detailLoaded = true
|
||
} catch (error) {
|
||
uni.showToast({
|
||
title: '详情加载失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.detailLoading = false
|
||
}
|
||
},
|
||
unwrapMerchantList(data) {
|
||
if (Array.isArray(data)) return data
|
||
if (!data || typeof data !== 'object') return []
|
||
const keys = ['list', 'records', 'rows', 'items', 'data', 'result', 'chainList']
|
||
for (let i = 0; i < keys.length; i += 1) {
|
||
const value = data[keys[i]]
|
||
if (Array.isArray(value)) return value
|
||
if (value && typeof value === 'object') {
|
||
const nested = this.unwrapMerchantList(value)
|
||
if (nested.length) return nested
|
||
}
|
||
}
|
||
return [data]
|
||
},
|
||
normalizeMerchant(item = {}) {
|
||
return {
|
||
id: item.id ?? item.merchantId ?? item.merchant_id ?? '',
|
||
text: item.name || item.merchantName || item.merchant_name || item.title || '未命名门店'
|
||
}
|
||
},
|
||
normalizeMerchantChain(item = {}, index) {
|
||
const text = item.chainName || item.chain || item.chainAbb || item.name || item.title || `连锁${index + 1}`
|
||
// 兜底必须用连锁名,不能用 `chain_${index}` 这类位置型占位符:
|
||
// 该 id 会被 saveMerchantEdit 写进 yxe_self_report.chain_id,而按连锁筛选走的是
|
||
// 等值匹配(YxeSelfReportMapper.xml: a.chain_id = #{param.chainId}),
|
||
// 占位符一旦落库,这条记录就再也筛不出来(后端已返回稳定 chainId,此处为兜底防线)。
|
||
const id = item.chainId ?? item.chain_id ?? item.id ?? item.chainCode ?? item.chain_code ?? text
|
||
const childKeys = ['children', 'merchantList', 'merchants', 'shopList', 'list', 'records']
|
||
let children = []
|
||
for (let i = 0; i < childKeys.length; i += 1) {
|
||
if (Array.isArray(item[childKeys[i]]) && item[childKeys[i]].length) {
|
||
children = item[childKeys[i]]
|
||
break
|
||
}
|
||
}
|
||
let merchants = children
|
||
.map(child => this.normalizeMerchant(child))
|
||
.filter(merchant => merchant.id !== '' && merchant.id !== null && merchant.id !== undefined)
|
||
if (!merchants.length) {
|
||
const merchant = this.normalizeMerchant(item)
|
||
if (merchant.id !== '' && merchant.id !== null && merchant.id !== undefined) {
|
||
merchants = [merchant]
|
||
}
|
||
}
|
||
return {id, text, merchants}
|
||
},
|
||
async loadMerchantEditChains() {
|
||
if (this.merchantEditChains.length) return
|
||
try {
|
||
const {data} = await merchantGroupByChainReq({})
|
||
this.merchantEditChains = this.unwrapMerchantList(data)
|
||
.map((item, index) => this.normalizeMerchantChain(item, index))
|
||
.filter(chain => chain.merchants.length)
|
||
} catch (error) {
|
||
this.merchantEditChains = []
|
||
uni.showToast({title: '门店列表获取失败', icon: 'none'})
|
||
}
|
||
},
|
||
async openMerchantEditPicker() {
|
||
if (this.item.status !== 0) return
|
||
this.showMerchantEditPicker = true
|
||
this.merchantEditKeyword = ''
|
||
this.merchantEditLoading = true
|
||
try {
|
||
await this.ensureDetailLoaded()
|
||
await this.loadMerchantEditChains()
|
||
if (!this.merchantEditChains.length) {
|
||
this.showMerchantEditPicker = false
|
||
return
|
||
}
|
||
const record = this.currentRecord
|
||
const chainIndex = this.merchantEditChains.findIndex(chain => String(chain.id) === String(record.chainId))
|
||
const safeChainIndex = chainIndex > -1 ? chainIndex : 0
|
||
const stores = this.merchantEditChains[safeChainIndex].merchants
|
||
const merchantIndex = stores.findIndex(merchant => String(merchant.id) === String(record.merchantId))
|
||
this.merchantEditPickerValue = [safeChainIndex, merchantIndex > -1 ? merchantIndex : 0]
|
||
} finally {
|
||
this.merchantEditLoading = false
|
||
}
|
||
},
|
||
closeMerchantEditPicker() {
|
||
if (!this.merchantEditSaving) {
|
||
this.showMerchantEditPicker = false
|
||
this.merchantEditKeyword = ''
|
||
}
|
||
},
|
||
onMerchantEditPickerChange(event) {
|
||
const value = Array.isArray(event.detail.value) ? event.detail.value : [0, 0]
|
||
const chainIndex = Number(value[0] || 0)
|
||
const previousChainIndex = Number(this.merchantEditPickerValue[0] || 0)
|
||
this.merchantEditPickerValue = [chainIndex, chainIndex === previousChainIndex ? Number(value[1] || 0) : 0]
|
||
},
|
||
onMerchantEditSearchInput() {
|
||
this.merchantEditPickerValue = [Number(this.merchantEditPickerValue[0] || 0), 0]
|
||
},
|
||
async saveMerchantEdit() {
|
||
if (this.merchantEditSaving) return
|
||
const chainIndex = Number(this.merchantEditPickerValue[0] || 0)
|
||
const merchantIndex = Number(this.merchantEditPickerValue[1] || 0)
|
||
const chain = this.merchantEditChains[chainIndex]
|
||
const merchant = this.merchantEditStores[merchantIndex]
|
||
if (!chain || !merchant || !merchant.id) {
|
||
uni.showToast({title: '请选择门店', icon: 'none'})
|
||
return
|
||
}
|
||
this.merchantEditSaving = true
|
||
// 写入前兜底:`chain_${index}` 这类位置型占位符不能落库(详见 normalizeMerchantChain 注释),
|
||
// 否则该记录会被按连锁等值筛选永久漏掉。这里退回用连锁名作为标识。
|
||
const chainIdText = String(chain.id ?? '').trim()
|
||
const safeChainId = !chainIdText || chainIdText.startsWith('chain_')
|
||
? String(chain.text || '').trim()
|
||
: chainIdText
|
||
try {
|
||
await selfReportChangeReq({
|
||
id: this.currentRecord.id,
|
||
chainId: safeChainId,
|
||
chainName: chain.text,
|
||
merchantId: merchant.id,
|
||
merchantName: merchant.text
|
||
})
|
||
this.detailData = Object.assign({}, this.currentRecord, {
|
||
chainId: safeChainId,
|
||
chainName: chain.text,
|
||
merchantId: merchant.id,
|
||
merchantName: merchant.text
|
||
})
|
||
this.detailLoaded = true
|
||
this.showMerchantEditPicker = false
|
||
this.$emit('updated', this.detailData)
|
||
uni.showToast({title: '保存成功', icon: 'success'})
|
||
} catch (error) {
|
||
uni.showToast({title: error && error.message ? error.message : '保存失败', icon: 'none'})
|
||
} finally {
|
||
this.merchantEditSaving = false
|
||
}
|
||
},
|
||
async openDateEditPicker() {
|
||
if (this.item.status !== 0) return
|
||
await this.ensureDetailLoaded()
|
||
const value = dayjs(this.currentRecord.date || this.item.date)
|
||
this.dateEditPickerValue = value.isValid() ? value.valueOf() : Date.now()
|
||
this.dateEditPickerKey += 1
|
||
await this.$nextTick()
|
||
this.showDateEditPicker = true
|
||
},
|
||
closeDateEditPicker() {
|
||
if (!this.dateEditSaving) this.showDateEditPicker = false
|
||
},
|
||
async saveDateEdit(event) {
|
||
if (this.dateEditSaving) return
|
||
const date = dayjs(event.value).format('YYYY-MM-DD')
|
||
this.dateEditSaving = true
|
||
try {
|
||
await selfReportChangeReq({id: this.currentRecord.id, date})
|
||
this.detailData = Object.assign({}, this.currentRecord, {date})
|
||
this.detailLoaded = true
|
||
this.showDateEditPicker = false
|
||
this.$emit('updated', this.detailData)
|
||
uni.showToast({title: '保存成功', icon: 'success'})
|
||
} catch (error) {
|
||
uni.showToast({title: error && error.message ? error.message : '保存失败', icon: 'none'})
|
||
} finally {
|
||
this.dateEditSaving = false
|
||
}
|
||
},
|
||
async openEditPopup() {
|
||
if (this.item.status !== 0) return
|
||
await this.ensureDetailLoaded()
|
||
const salesGroups = this.parseMaybeJson(this.currentRecord.salesGroups, [])
|
||
this.editSalesItems = this.flattenSalesGroups(salesGroups)
|
||
this.productKeyword = ''
|
||
this.activeEditSalesIndex = -1
|
||
this.showEditPopup = true
|
||
await this.loadProductGroups()
|
||
},
|
||
closeEditPopup() {
|
||
this.showEditPopup = false
|
||
this.showProductPicker = false
|
||
this.activeEditSalesIndex = -1
|
||
this.productKeyword = ''
|
||
},
|
||
async openProductPicker(index) {
|
||
await this.loadProductGroups()
|
||
if (!this.productGroups.length) {
|
||
uni.showToast({
|
||
title: '当前连锁暂无可选商品',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
this.activeEditSalesIndex = Number.isInteger(index) ? index : -1
|
||
this.productKeyword = ''
|
||
this.showProductPicker = true
|
||
if (!this.activeProductGroupKey && this.filteredProductGroups.length) {
|
||
this.activeProductGroupKey = this.filteredProductGroups[0].key
|
||
}
|
||
},
|
||
closeProductPicker() {
|
||
this.showProductPicker = false
|
||
this.activeEditSalesIndex = -1
|
||
},
|
||
selectProductGroup(key) {
|
||
this.activeProductGroupKey = key
|
||
},
|
||
isProductSelected(product) {
|
||
return (Array.isArray(this.editSalesItems) ? this.editSalesItems : []).some((item, index) => {
|
||
if (index === this.activeEditSalesIndex) return false
|
||
if (product.productId && item.productId) {
|
||
return String(item.productId) === String(product.productId)
|
||
}
|
||
return item.name === product.name
|
||
})
|
||
},
|
||
selectProduct(product) {
|
||
if (this.isProductSelected(product)) {
|
||
uni.showToast({
|
||
title: '该商品已添加',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
const next = this.normalizeSalesGroupItem(product, product.groupTitle || '自定义商品')
|
||
if (this.activeEditSalesIndex >= 0 && this.editSalesItems[this.activeEditSalesIndex]) {
|
||
const current = this.editSalesItems[this.activeEditSalesIndex]
|
||
this.$set(this.editSalesItems, this.activeEditSalesIndex, Object.assign({}, current, next, {
|
||
qty: current.qty,
|
||
groupTitle: next.groupTitle || current.groupTitle
|
||
}))
|
||
} else {
|
||
next.qty = ''
|
||
this.editSalesItems.push(next)
|
||
}
|
||
this.closeProductPicker()
|
||
},
|
||
removeEditSalesItem(index) {
|
||
this.editSalesItems.splice(index, 1)
|
||
},
|
||
onEditQtyInput(event, index) {
|
||
const current = this.editSalesItems[index]
|
||
if (!current) return
|
||
const qty = event && event.detail ? event.detail.value : ''
|
||
this.$set(this.editSalesItems, index, Object.assign({}, current, {qty}))
|
||
},
|
||
onSpecChange(event, item) {
|
||
const value = Number(event.detail.value || 0)
|
||
item.spec = item.specOptions[value] || item.spec
|
||
},
|
||
parseImageList(list) {
|
||
const source = Array.isArray(list) ? list : []
|
||
return source
|
||
.map(item => {
|
||
if (!item) return ''
|
||
if (typeof item === 'string') return item
|
||
return item.url || item.path || item.image || ''
|
||
})
|
||
.filter(Boolean)
|
||
},
|
||
parseClockImageList(list) {
|
||
const source = Array.isArray(list) ? list : []
|
||
return source
|
||
.map(item => {
|
||
if (!item) return ''
|
||
if (typeof item === 'string') return item
|
||
return item.url || item.path || item.image || ''
|
||
})
|
||
.filter(Boolean)
|
||
},
|
||
parseMaybeJson(value, fallback) {
|
||
if (value === null || value === undefined || value === '') return fallback
|
||
if (typeof value === 'string') {
|
||
try {
|
||
return JSON.parse(value)
|
||
} catch (error) {
|
||
return fallback
|
||
}
|
||
}
|
||
return value
|
||
},
|
||
normalizeDetailData(data) {
|
||
const detail = data && typeof data === 'object' ? {...data} : {}
|
||
detail.clockPhotos = this.parseMaybeJson(detail.clockPhotos, [])
|
||
detail.boothPhotos = this.parseMaybeJson(detail.boothPhotos, [])
|
||
detail.receiptPhotos = this.parseImageList(this.parseMaybeJson(detail.receiptPhoto, []))
|
||
detail.salesGroups = this.parseMaybeJson(detail.salesGroups, [])
|
||
return detail
|
||
},
|
||
async saveEdit() {
|
||
if (this.editSaving) return
|
||
const items = Array.isArray(this.editSalesItems) ? this.editSalesItems : []
|
||
if (!items.length) {
|
||
uni.showToast({
|
||
title: '请至少保留一项销量商品',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
for (let i = 0; i < items.length; i += 1) {
|
||
const item = items[i]
|
||
if (!String(item.name || '').trim()) {
|
||
uni.showToast({
|
||
title: '请选择商品',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
if (this.parseNumber(item.qty) < 0) {
|
||
uni.showToast({
|
||
title: '数量不能小于0',
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
}
|
||
this.editSaving = true
|
||
try {
|
||
const salesGroups = this.buildSalesGroupsFromItems(items)
|
||
const reward = this.editTotalRewardAmount
|
||
await selfReportChangeReq({
|
||
id: this.currentRecord.id,
|
||
salesGroups: JSON.stringify(salesGroups),
|
||
reward
|
||
})
|
||
const nextRecord = Object.assign({}, this.currentRecord, {
|
||
salesGroups,
|
||
reward
|
||
})
|
||
this.detailData = nextRecord
|
||
this.detailLoaded = true
|
||
this.showEditPopup = false
|
||
this.showProductPicker = false
|
||
this.activeEditSalesIndex = -1
|
||
this.$emit('updated', nextRecord)
|
||
uni.showToast({
|
||
title: '保存成功',
|
||
icon: 'success'
|
||
})
|
||
} catch (error) {
|
||
uni.showToast({
|
||
title: error && error.message ? error.message : '保存失败',
|
||
icon: 'none'
|
||
})
|
||
} finally {
|
||
this.editSaving = false
|
||
}
|
||
},
|
||
formatPrice(value) {
|
||
const num = Number(value || 0)
|
||
return num.toFixed(2)
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@import './record.scss';
|
||
|
||
.record-section {
|
||
margin-top: 18rpx;
|
||
}
|
||
|
||
.record-meta-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14rpx;
|
||
}
|
||
|
||
.record-meta-action {
|
||
margin-top: 6rpx;
|
||
color: #2fc6a7;
|
||
font-size: 24rpx;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.record-section-title {
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.record-toggle {
|
||
margin-top: 12rpx;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
color: #2fc6a7;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.detail-empty {
|
||
color: #94a3b8;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.record-section-title-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12rpx;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.record-section-title-row .record-section-title {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.section-action {
|
||
color: #2fc6a7;
|
||
font-size: 24rpx;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.self-report-popup-mask {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
z-index: 50;
|
||
background: rgba(15, 23, 42, 0.42);
|
||
}
|
||
|
||
.self-report-popup {
|
||
position: fixed;
|
||
left: 20rpx;
|
||
right: 20rpx;
|
||
bottom: 20rpx;
|
||
z-index: 60;
|
||
max-height: 86vh;
|
||
overflow-y: auto;
|
||
padding: 20rpx;
|
||
border-radius: 18rpx;
|
||
background: #ffffff;
|
||
}
|
||
|
||
.popup-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 30rpx;
|
||
color: #1f2937;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.close-btn {
|
||
color: #64748b;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.edit-summary {
|
||
margin-top: 12rpx;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 12rpx 16rpx;
|
||
border-radius: 12rpx;
|
||
background: #f7f9fc;
|
||
}
|
||
|
||
.edit-summary-label {
|
||
color: #64748b;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.edit-summary-value {
|
||
color: #f5576c;
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.detail-actions {
|
||
margin-top: 12rpx;
|
||
}
|
||
|
||
.ghost-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 10rpx 16rpx;
|
||
border-radius: 999rpx;
|
||
color: #2563eb;
|
||
background: #eff6ff;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.edit-sales-list {
|
||
margin-top: 12rpx;
|
||
}
|
||
|
||
.edit-sales-item {
|
||
margin-bottom: 16rpx;
|
||
padding: 16rpx;
|
||
border-radius: 12rpx;
|
||
background: #f7f9fc;
|
||
}
|
||
|
||
.edit-sales-head {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 12rpx;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.edit-sales-name {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.edit-sales-group {
|
||
margin-top: 4rpx;
|
||
font-size: 22rpx;
|
||
color: #64748b;
|
||
}
|
||
|
||
.edit-sales-remove {
|
||
color: #f5576c;
|
||
font-size: 24rpx;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.form-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
margin-top: 10rpx;
|
||
}
|
||
|
||
.label {
|
||
width: 120rpx;
|
||
color: #64748b;
|
||
font-size: 24rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.picker-input,
|
||
.input {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.input {
|
||
min-height: 72rpx;
|
||
padding: 0 20rpx;
|
||
border-radius: 12rpx;
|
||
background: #ffffff;
|
||
border: 1rpx solid #e5e7eb;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.picker-input-inner {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
min-height: 72rpx;
|
||
padding: 0 20rpx;
|
||
border-radius: 12rpx;
|
||
background: #ffffff;
|
||
border: 1rpx solid #e5e7eb;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.picker-text {
|
||
color: #1f2937;
|
||
}
|
||
|
||
.picker-placeholder {
|
||
color: #94a3b8;
|
||
}
|
||
|
||
.bonus-text {
|
||
color: #f5576c;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.qty-input-wrap {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
min-width: 0;
|
||
}
|
||
|
||
.qty-input {
|
||
min-width: 0;
|
||
}
|
||
|
||
.qty-unit {
|
||
color: #64748b;
|
||
font-size: 24rpx;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.popup-actions {
|
||
margin-top: 16rpx;
|
||
}
|
||
|
||
.product-picker {
|
||
padding: 0;
|
||
}
|
||
|
||
.merchant-picker-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 22rpx 24rpx;
|
||
}
|
||
|
||
.picker-title {
|
||
font-size: 30rpx;
|
||
color: #1f2937;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.picker-action {
|
||
color: #64748b;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.picker-action.primary {
|
||
color: #2563eb;
|
||
}
|
||
|
||
.merchant-picker-search {
|
||
padding: 0 24rpx 18rpx;
|
||
}
|
||
|
||
.merchant-edit-picker {
|
||
background: #ffffff;
|
||
padding-bottom: calc(env(safe-area-inset-bottom) + 12rpx);
|
||
}
|
||
|
||
.merchant-picker-view {
|
||
width: 100%;
|
||
height: 420rpx;
|
||
}
|
||
|
||
.merchant-picker-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 64px;
|
||
padding: 0 24rpx;
|
||
text-align: center;
|
||
font-size: 26rpx;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.merchant-search-input {
|
||
height: 72rpx;
|
||
padding: 0 24rpx;
|
||
border-radius: 999rpx;
|
||
background: #f7f9fc;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.merchant-picker-loading {
|
||
min-height: 360rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.product-panel {
|
||
display: flex;
|
||
min-height: 540rpx;
|
||
}
|
||
|
||
.category-sidebar {
|
||
width: 180rpx;
|
||
max-height: 540rpx;
|
||
background: #f8fafc;
|
||
}
|
||
|
||
.category-item {
|
||
padding: 18rpx 14rpx;
|
||
font-size: 24rpx;
|
||
color: #475569;
|
||
}
|
||
|
||
.category-item-active {
|
||
color: #2563eb;
|
||
font-weight: 700;
|
||
background: #ffffff;
|
||
}
|
||
|
||
.product-panel-main {
|
||
flex: 1;
|
||
max-height: 540rpx;
|
||
padding: 0 18rpx 20rpx 12rpx;
|
||
}
|
||
|
||
.product-picker-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.product-picker-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16rpx;
|
||
padding: 14rpx 12rpx;
|
||
border-radius: 12rpx;
|
||
background: #f8fafc;
|
||
}
|
||
|
||
.product-picker-main {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.product-picker-name {
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.product-picker-sub,
|
||
.product-picker-income {
|
||
margin-top: 4rpx;
|
||
font-size: 22rpx;
|
||
color: #64748b;
|
||
}
|
||
|
||
.product-picker-income {
|
||
color: #f5576c;
|
||
}
|
||
|
||
.product-picker-tag {
|
||
color: #2563eb;
|
||
font-size: 24rpx;
|
||
white-space: nowrap;
|
||
}
|
||
</style>
|