Merge branch 'master' of https://git.mmlizi.com/lizi/shop-toy
This commit is contained in:
89
interaction-flow.md
Normal file
89
interaction-flow.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# 应结账款页面交互流程图
|
||||||
|
|
||||||
|
## 用户操作流程
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[用户进入应结账款页面] --> B[查看商户列表]
|
||||||
|
B --> C[点击商户头部的"消账"按钮]
|
||||||
|
C --> D[进入选择模式]
|
||||||
|
|
||||||
|
D --> E{用户操作}
|
||||||
|
E -->|点击订单| F[选择/取消选择订单]
|
||||||
|
E -->|点击全选| G[全选/取消全选]
|
||||||
|
E -->|点击取消| H[退出选择模式]
|
||||||
|
E -->|选择订单后点击进行消账| I[打开消账弹窗]
|
||||||
|
|
||||||
|
F --> J[更新已选数量和金额]
|
||||||
|
G --> J
|
||||||
|
J --> K{是否有选中订单}
|
||||||
|
K -->|有| L[显示"进行消账(X)"按钮]
|
||||||
|
K -->|无| M[显示"取消"按钮]
|
||||||
|
|
||||||
|
L --> N[点击"进行消账"]
|
||||||
|
N --> I
|
||||||
|
I --> O[填写消账信息]
|
||||||
|
O --> P[提交消账申请]
|
||||||
|
P --> Q[消账成功]
|
||||||
|
Q --> R[退出选择模式]
|
||||||
|
R --> S[刷新列表]
|
||||||
|
|
||||||
|
H --> T[返回正常模式]
|
||||||
|
M --> T
|
||||||
|
T --> B
|
||||||
|
S --> B
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据流图
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[用户点击消账] --> B[selectionMode[merchantId] = true]
|
||||||
|
B --> C[显示选择界面]
|
||||||
|
|
||||||
|
C --> D[用户选择订单]
|
||||||
|
D --> E[selectedOrders[merchantId] 更新]
|
||||||
|
E --> F[计算选中数量和金额]
|
||||||
|
F --> G[更新按钮状态]
|
||||||
|
|
||||||
|
G --> H[点击进行消账]
|
||||||
|
H --> I[获取选中订单数据]
|
||||||
|
I --> J[调用消账API]
|
||||||
|
J --> K[消账完成]
|
||||||
|
K --> L[selectionMode[merchantId] = false]
|
||||||
|
L --> M[刷新页面数据]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 组件状态管理
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
stateDiagram-v2
|
||||||
|
[*] --> NormalMode
|
||||||
|
NormalMode --> SelectionMode: 点击消账按钮
|
||||||
|
SelectionMode --> NormalMode: 点击取消
|
||||||
|
SelectionMode --> NormalMode: 消账完成
|
||||||
|
SelectionMode --> SelectionMode: 选择/取消选择订单
|
||||||
|
SelectionMode --> SelectionMode: 全选/取消全选
|
||||||
|
|
||||||
|
state SelectionMode {
|
||||||
|
[*] --> NoSelection
|
||||||
|
NoSelection --> HasSelection: 选择订单
|
||||||
|
HasSelection --> NoSelection: 取消选择所有订单
|
||||||
|
HasSelection --> HasSelection: 继续选择订单
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 条件判断逻辑
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A[订单点击事件] --> B{是否在选择模式?}
|
||||||
|
B -->|否| C[忽略点击]
|
||||||
|
B -->|是| D{订单状态是否为未结或逾期?}
|
||||||
|
D -->|否| E[忽略点击]
|
||||||
|
D -->|是| F{订单是否已选中?}
|
||||||
|
F -->|是| G[取消选中订单]
|
||||||
|
F -->|否| H[选中订单]
|
||||||
|
G --> I[更新选中列表]
|
||||||
|
H --> I
|
||||||
|
I --> J[更新UI显示]
|
||||||
342
settlement-redesign-plan.md
Normal file
342
settlement-redesign-plan.md
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
# 应结账款页面重构方案
|
||||||
|
|
||||||
|
## 需求概述
|
||||||
|
|
||||||
|
1. 将商户卡片中的"批量消账"按钮改为"消账"
|
||||||
|
2. 移除每个订单的"申请消账"按钮
|
||||||
|
3. 点击"消账"按钮后,进入选择模式,可以选择该商户下的多个订单
|
||||||
|
4. 选择订单后,按钮文本变为"进行消账(X)",X为选择的订单数量
|
||||||
|
5. 添加全选/取消全选功能
|
||||||
|
6. 显示已选订单的汇总金额
|
||||||
|
7. 限制只能选择未结和逾期状态的订单
|
||||||
|
8. 点击"进行消账"后,执行批量消账逻辑
|
||||||
|
|
||||||
|
## 数据结构设计
|
||||||
|
|
||||||
|
### 新增状态变量
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 选择模式状态
|
||||||
|
const selectionMode = ref<Record<string, boolean>>({}) // 记录每个商户是否处于选择模式
|
||||||
|
const selectedOrders = ref<Record<string, string[]>>({}) // 记录每个商户选中的订单ID列表
|
||||||
|
```
|
||||||
|
|
||||||
|
### 计算属性
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 计算每个商户选中的订单数量
|
||||||
|
const selectedCount = computed(() => {
|
||||||
|
return (merchantId: string) => selectedOrders.value[merchantId]?.length || 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算每个商户选中的订单总金额
|
||||||
|
const selectedAmount = computed(() => {
|
||||||
|
return (merchantId: string) => {
|
||||||
|
const group = groupedByMerchant.value.find(g => g.merchantId === merchantId)
|
||||||
|
if (!group) return 0
|
||||||
|
|
||||||
|
const selectedIds = selectedOrders.value[merchantId] || []
|
||||||
|
return group.settlements
|
||||||
|
.filter(item => selectedIds.includes(item.id))
|
||||||
|
.reduce((sum, item) => sum + item.amount, 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算每个商户是否全选
|
||||||
|
const isAllSelected = computed(() => {
|
||||||
|
return (merchantId: string) => {
|
||||||
|
const group = groupedByMerchant.value.find(g => g.merchantId === merchantId)
|
||||||
|
if (!group) return false
|
||||||
|
|
||||||
|
const selectableOrders = group.settlements.filter(
|
||||||
|
item => item.status === SettlementStatus.UNSETTLED || item.status === SettlementStatus.OVERDUE
|
||||||
|
)
|
||||||
|
const selectedIds = selectedOrders.value[merchantId] || []
|
||||||
|
|
||||||
|
return selectableOrders.length > 0 && selectableOrders.every(item => selectedIds.includes(item.id))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## UI 设计
|
||||||
|
|
||||||
|
### 1. 商户头部修改
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<!-- 商户头部 -->
|
||||||
|
<view class="merchant-header">
|
||||||
|
<!-- 原有商户信息保持不变 -->
|
||||||
|
|
||||||
|
<!-- 头部操作区域 -->
|
||||||
|
<view v-if="currentTab === 0" class="header-action">
|
||||||
|
<!-- 非选择模式 -->
|
||||||
|
<view v-if="!selectionMode[group.merchantId]" class="batch-btn-small" @click.stop="enterSelectionMode(group.merchantId)">
|
||||||
|
<text class="i-carbon-checkmark-outline" />
|
||||||
|
<text>消账</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 选择模式 -->
|
||||||
|
<template v-else>
|
||||||
|
<view class="selection-controls">
|
||||||
|
<view class="select-all-btn" @click.stop="toggleSelectAll(group.merchantId)">
|
||||||
|
<text class="i-carbon-checkmark-filled" v-if="isAllSelected(group.merchantId)" />
|
||||||
|
<text class="i-carbon-checkmark-outline" v-else />
|
||||||
|
<text>全选</text>
|
||||||
|
</view>
|
||||||
|
<view class="selected-info">
|
||||||
|
<text>已选 {{ selectedCount(group.merchantId) }} 单</text>
|
||||||
|
<text class="amount">¥{{ selectedAmount(group.merchantId).toFixed(2) }}</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="selectedCount(group.merchantId) > 0"
|
||||||
|
class="batch-btn-small active"
|
||||||
|
@click.stop="handleBatchWriteOff(group.merchantId)"
|
||||||
|
>
|
||||||
|
<text class="i-carbon-checkmark-outline" />
|
||||||
|
<text>进行消账({{ selectedCount(group.merchantId) }})</text>
|
||||||
|
</view>
|
||||||
|
<view v-else class="cancel-btn" @click.stop="exitSelectionMode(group.merchantId)">
|
||||||
|
<text>取消</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 订单列表修改
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<!-- 订单列表 -->
|
||||||
|
<view class="order-list">
|
||||||
|
<view
|
||||||
|
v-for="item in group.settlements"
|
||||||
|
:key="item.id"
|
||||||
|
class="order-item"
|
||||||
|
:class="{
|
||||||
|
'selection-mode': selectionMode[group.merchantId],
|
||||||
|
'selected': selectedOrders[group.merchantId]?.includes(item.id),
|
||||||
|
'disabled': item.status !== SettlementStatus.UNSETTLED && item.status !== SettlementStatus.OVERDUE
|
||||||
|
}"
|
||||||
|
@click="handleOrderClick(group.merchantId, item.id, item.status)"
|
||||||
|
>
|
||||||
|
<!-- 选择框 -->
|
||||||
|
<view v-if="selectionMode[group.merchantId]" class="checkbox">
|
||||||
|
<text class="i-carbon-checkmark-filled" v-if="selectedOrders[group.merchantId]?.includes(item.id)" />
|
||||||
|
<text class="i-carbon-checkmark-outline" v-else />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 订单内容 -->
|
||||||
|
<view class="order-content">
|
||||||
|
<!-- 原有订单头部和内容保持不变 -->
|
||||||
|
<!-- 移除订单操作区域 -->
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 功能实现
|
||||||
|
|
||||||
|
### 1. 进入选择模式
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function enterSelectionMode(merchantId: string) {
|
||||||
|
selectionMode.value[merchantId] = true
|
||||||
|
selectedOrders.value[merchantId] = []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 退出选择模式
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function exitSelectionMode(merchantId: string) {
|
||||||
|
selectionMode.value[merchantId] = false
|
||||||
|
selectedOrders.value[merchantId] = []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 处理订单点击
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function handleOrderClick(merchantId: string, orderId: string, status: SettlementStatus) {
|
||||||
|
// 只有在选择模式下且订单状态为未结或逾期时才能选择
|
||||||
|
if (!selectionMode.value[merchantId]) return
|
||||||
|
if (status !== SettlementStatus.UNSETTLED && status !== SettlementStatus.OVERDUE) return
|
||||||
|
|
||||||
|
const selected = selectedOrders.value[merchantId] || []
|
||||||
|
const index = selected.indexOf(orderId)
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
// 取消选择
|
||||||
|
selected.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
// 添加选择
|
||||||
|
selected.push(orderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedOrders.value[merchantId] = selected
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 全选/取消全选
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function toggleSelectAll(merchantId: string) {
|
||||||
|
const group = groupedByMerchant.value.find(g => g.merchantId === merchantId)
|
||||||
|
if (!group) return
|
||||||
|
|
||||||
|
const selectableOrders = group.settlements.filter(
|
||||||
|
item => item.status === SettlementStatus.UNSETTLED || item.status === SettlementStatus.OVERDUE
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isAllSelected.value(merchantId)) {
|
||||||
|
// 取消全选
|
||||||
|
selectedOrders.value[merchantId] = []
|
||||||
|
} else {
|
||||||
|
// 全选
|
||||||
|
selectedOrders.value[merchantId] = selectableOrders.map(item => item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 批量消账
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function handleBatchWriteOff(merchantId: string) {
|
||||||
|
const selectedIds = selectedOrders.value[merchantId] || []
|
||||||
|
if (selectedIds.length === 0) return
|
||||||
|
|
||||||
|
const group = groupedByMerchant.value.find(g => g.merchantId === merchantId)
|
||||||
|
if (!group) return
|
||||||
|
|
||||||
|
// 获取选中的订单
|
||||||
|
const selectedSettlements = group.settlements.filter(item => selectedIds.includes(item.id))
|
||||||
|
|
||||||
|
currentSettlement.value = null
|
||||||
|
currentMerchantSettlements.value = selectedSettlements
|
||||||
|
isBatchMode.value = true
|
||||||
|
writeOffVisible.value = true
|
||||||
|
|
||||||
|
// 退出选择模式
|
||||||
|
exitSelectionMode(merchantId)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 样式设计
|
||||||
|
|
||||||
|
### 1. 选择模式样式
|
||||||
|
|
||||||
|
```scss
|
||||||
|
.order-item {
|
||||||
|
&.selection-mode {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 20rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2rpx solid #ddd;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.i-carbon-checkmark-outline {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.i-carbon-checkmark-filled {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected .checkbox {
|
||||||
|
background: rgba($primary, 0.1);
|
||||||
|
border-color: $primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 选择控制区域样式
|
||||||
|
|
||||||
|
```scss
|
||||||
|
.selection-controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12rpx;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
.select-all-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6rpx;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
background: rgba($primary, 0.05);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
color: $primary;
|
||||||
|
font-size: 22rpx;
|
||||||
|
|
||||||
|
.i-carbon-checkmark-outline,
|
||||||
|
.i-carbon-checkmark-filled {
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-info {
|
||||||
|
text-align: right;
|
||||||
|
font-size: 22rpx;
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
display: block;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $danger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
padding: 8rpx 20rpx;
|
||||||
|
background: rgba($text-2, 0.1);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
color: $text-2;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-btn-small.active {
|
||||||
|
background: $primary;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 交互流程
|
||||||
|
|
||||||
|
1. 用户点击商户头部的"消账"按钮
|
||||||
|
2. 进入选择模式,显示选择框和全选按钮
|
||||||
|
3. 用户可以选择/取消选择符合条件的订单
|
||||||
|
4. 选择订单后,显示已选数量和汇总金额
|
||||||
|
5. 点击"进行消账"按钮,打开消账弹窗
|
||||||
|
6. 消账完成后退出选择模式,刷新列表
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 只有未结和逾期状态的订单才能被选择
|
||||||
|
2. 选择模式下,其他商户的卡片保持正常状态
|
||||||
|
3. 消账弹窗需要支持批量处理多个订单
|
||||||
|
4. 需要处理选择状态的响应式更新
|
||||||
390
src/components/finance/WriteOffRecord.vue
Normal file
390
src/components/finance/WriteOffRecord.vue
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
<template>
|
||||||
|
<view class="write-off-record-popup" :class="{ show: visible }">
|
||||||
|
<view class="mask" @click="handleClose"></view>
|
||||||
|
<view class="content">
|
||||||
|
<view class="header">
|
||||||
|
<text class="title">消账记录</text>
|
||||||
|
<text class="close-btn i-carbon-close" @click="handleClose"></text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<scroll-view scroll-y class="body">
|
||||||
|
<view v-if="writeOffRecords.length === 0" class="empty-state">
|
||||||
|
<text class="empty-text">暂无消账记录</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-else class="record-list">
|
||||||
|
<view
|
||||||
|
v-for="(record, index) in writeOffRecords"
|
||||||
|
:key="record.id"
|
||||||
|
class="record-item"
|
||||||
|
>
|
||||||
|
<!-- 记录头部 -->
|
||||||
|
<view class="record-header">
|
||||||
|
<view class="record-info">
|
||||||
|
<text class="record-id">记录编号: {{ record.id }}</text>
|
||||||
|
<view class="status-badge" :class="getStatusClass(record.status)">
|
||||||
|
{{ getStatusText(record.status) }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="record-amount">
|
||||||
|
<text class="amount-label">消账金额</text>
|
||||||
|
<text class="amount-value">¥{{ record.amount.toFixed(2) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 记录内容 -->
|
||||||
|
<view class="record-content">
|
||||||
|
<view class="info-row">
|
||||||
|
<text class="info-label">提交时间</text>
|
||||||
|
<text class="info-value">{{ record.submitTime }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="record.remark" class="info-row">
|
||||||
|
<text class="info-label">备注说明</text>
|
||||||
|
<text class="info-value">{{ record.remark }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 凭证图片 -->
|
||||||
|
<view v-if="record.proof && record.proof.length > 0" class="proof-section">
|
||||||
|
<view class="proof-label">凭证图片</view>
|
||||||
|
<view class="proof-list">
|
||||||
|
<view
|
||||||
|
v-for="(img, imgIndex) in record.proof"
|
||||||
|
:key="imgIndex"
|
||||||
|
class="proof-item"
|
||||||
|
@click="previewImage(img, record.proof)"
|
||||||
|
>
|
||||||
|
<image :src="img" mode="aspectFill" class="proof-image" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<view class="footer">
|
||||||
|
<view class="btn close-btn" @click="handleClose">关闭</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { WriteOff, WriteOffStatus } from '@/typings/mall'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
visible: boolean
|
||||||
|
writeOffRecords: WriteOff[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
writeOffRecords: () => [],
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:visible': [visible: boolean]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// 状态文本
|
||||||
|
function getStatusText(status: WriteOffStatus) {
|
||||||
|
switch (status) {
|
||||||
|
case 'pending':
|
||||||
|
return '待审核'
|
||||||
|
case 'approved':
|
||||||
|
return '已通过'
|
||||||
|
case 'rejected':
|
||||||
|
return '已拒绝'
|
||||||
|
default:
|
||||||
|
return '未知'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 状态样式类
|
||||||
|
function getStatusClass(status: WriteOffStatus) {
|
||||||
|
switch (status) {
|
||||||
|
case 'pending':
|
||||||
|
return 'warning'
|
||||||
|
case 'approved':
|
||||||
|
return 'success'
|
||||||
|
case 'rejected':
|
||||||
|
return 'danger'
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
function handleClose() {
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预览图片
|
||||||
|
function previewImage(current: string, urls: string[]) {
|
||||||
|
uni.previewImage({
|
||||||
|
current,
|
||||||
|
urls,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
// 设计令牌
|
||||||
|
$primary: #4d80f0;
|
||||||
|
$danger: #fa4350;
|
||||||
|
$warning: #ff8f0d;
|
||||||
|
$success: #00c05a;
|
||||||
|
|
||||||
|
$text-1: #262626;
|
||||||
|
$text-2: #909399;
|
||||||
|
$text-3: #c0c4cc;
|
||||||
|
|
||||||
|
$bg-page: #f4f4f4;
|
||||||
|
$bg-card: #ffffff;
|
||||||
|
|
||||||
|
.write-off-record-popup {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 999;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: visibility 0.3s;
|
||||||
|
|
||||||
|
&.show {
|
||||||
|
visibility: visible;
|
||||||
|
|
||||||
|
.mask {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx 24rpx 0 0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition: transform 0.3s;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 80vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: 30rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
font-size: 40rpx;
|
||||||
|
color: $text-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
padding: 30rpx;
|
||||||
|
max-height: 600rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 100rpx 0;
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: $text-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-item {
|
||||||
|
background: $bg-card;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.record-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
|
||||||
|
.record-id {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: $text-2;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 22rpx;
|
||||||
|
padding: 4rpx 12rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&.success {
|
||||||
|
color: $success;
|
||||||
|
background: rgba($success, 0.1);
|
||||||
|
}
|
||||||
|
&.warning {
|
||||||
|
color: $warning;
|
||||||
|
background: rgba($warning, 0.1);
|
||||||
|
}
|
||||||
|
&.danger {
|
||||||
|
color: $danger;
|
||||||
|
background: rgba($danger, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-amount {
|
||||||
|
text-align: right;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.amount-label {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: $text-2;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-value {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $danger;
|
||||||
|
font-family: 'DIN Alternate', sans-serif;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.record-content {
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: $text-2;
|
||||||
|
min-width: 120rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: $text-1;
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-all;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.proof-section {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
|
||||||
|
.proof-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: $text-2;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proof-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16rpx;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
|
||||||
|
.proof-item {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.proof-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1rpx solid #f5f5f5;
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
height: 88rpx;
|
||||||
|
background: $bg-page;
|
||||||
|
color: $text-1;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Settlement } from '@/typings/mall'
|
import type { Settlement } from '@/typings/mall'
|
||||||
|
import type { WriteOff } from '@/typings/mall'
|
||||||
import SettlementItem from '@/components/finance/SettlementItem.vue'
|
import SettlementItem from '@/components/finance/SettlementItem.vue'
|
||||||
import WriteOffForm from '@/components/finance/WriteOffForm.vue'
|
import WriteOffForm from '@/components/finance/WriteOffForm.vue'
|
||||||
|
import WriteOffRecord from '@/components/finance/WriteOffRecord.vue'
|
||||||
import { useFinanceStore } from '@/store/finance'
|
import { useFinanceStore } from '@/store/finance'
|
||||||
import { SettlementStatus } from '@/typings/mall'
|
import { SettlementStatus } from '@/typings/mall'
|
||||||
|
|
||||||
@@ -36,6 +38,14 @@ const currentSettlement = ref<Settlement | null>(null)
|
|||||||
const currentMerchantSettlements = ref<Settlement[]>([]) // 批量消账时的商户所有账款
|
const currentMerchantSettlements = ref<Settlement[]>([]) // 批量消账时的商户所有账款
|
||||||
const isBatchMode = ref(false) // 是否批量消账模式
|
const isBatchMode = ref(false) // 是否批量消账模式
|
||||||
|
|
||||||
|
// 消账记录弹窗相关
|
||||||
|
const writeOffRecordVisible = ref(false)
|
||||||
|
const currentWriteOffRecords = ref<WriteOff[]>([]) // 当前订单的消账记录
|
||||||
|
|
||||||
|
// 选择模式状态
|
||||||
|
const selectionMode = ref<Record<string, boolean>>({}) // 记录每个商户是否处于选择模式
|
||||||
|
const selectedOrders = ref<Record<string, string[]>>({}) // 记录每个商户选中的订单ID列表
|
||||||
|
|
||||||
// 按商户分组
|
// 按商户分组
|
||||||
const groupedByMerchant = computed(() => {
|
const groupedByMerchant = computed(() => {
|
||||||
const groups: Record<string, {
|
const groups: Record<string, {
|
||||||
@@ -72,6 +82,39 @@ const groupedByMerchant = computed(() => {
|
|||||||
return Object.values(groups)
|
return Object.values(groups)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 计算每个商户选中的订单数量
|
||||||
|
const selectedCount = computed(() => {
|
||||||
|
return (merchantId: string) => selectedOrders.value[merchantId]?.length || 0
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算每个商户选中的订单总金额
|
||||||
|
const selectedAmount = computed(() => {
|
||||||
|
return (merchantId: string) => {
|
||||||
|
const group = groupedByMerchant.value.find(g => g.merchantId === merchantId)
|
||||||
|
if (!group) return 0
|
||||||
|
|
||||||
|
const selectedIds = selectedOrders.value[merchantId] || []
|
||||||
|
return group.settlements
|
||||||
|
.filter(item => selectedIds.includes(item.id))
|
||||||
|
.reduce((sum, item) => sum + item.amount, 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算每个商户是否全选
|
||||||
|
const isAllSelected = computed(() => {
|
||||||
|
return (merchantId: string) => {
|
||||||
|
const group = groupedByMerchant.value.find(g => g.merchantId === merchantId)
|
||||||
|
if (!group) return false
|
||||||
|
|
||||||
|
const selectableOrders = group.settlements.filter(
|
||||||
|
item => item.status === SettlementStatus.UNSETTLED || item.status === SettlementStatus.OVERDUE
|
||||||
|
)
|
||||||
|
const selectedIds = selectedOrders.value[merchantId] || []
|
||||||
|
|
||||||
|
return selectableOrders.length > 0 && selectableOrders.every(item => selectedIds.includes(item.id))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 页面显示
|
// 页面显示
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
loadData()
|
loadData()
|
||||||
@@ -120,7 +163,97 @@ function handleOpenWriteOff(item: Settlement) {
|
|||||||
writeOffVisible.value = true
|
writeOffVisible.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打开商户批量消账弹窗
|
// 进入选择模式
|
||||||
|
function enterSelectionMode(merchantId: string) {
|
||||||
|
selectionMode.value[merchantId] = true
|
||||||
|
selectedOrders.value[merchantId] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退出选择模式
|
||||||
|
function exitSelectionMode(merchantId: string) {
|
||||||
|
selectionMode.value[merchantId] = false
|
||||||
|
selectedOrders.value[merchantId] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理订单点击
|
||||||
|
async function handleOrderClick(merchantId: string, orderId: string, status: SettlementStatus) {
|
||||||
|
// 已结订单点击显示消账记录
|
||||||
|
if (status === SettlementStatus.SETTLED) {
|
||||||
|
await showWriteOffRecords(orderId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有在选择模式下且订单状态为未结或逾期时才能选择
|
||||||
|
if (!selectionMode.value[merchantId]) return
|
||||||
|
if (status !== SettlementStatus.UNSETTLED && status !== SettlementStatus.OVERDUE) return
|
||||||
|
|
||||||
|
const selected = selectedOrders.value[merchantId] || []
|
||||||
|
const index = selected.indexOf(orderId)
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
// 取消选择
|
||||||
|
selected.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
// 添加选择
|
||||||
|
selected.push(orderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedOrders.value[merchantId] = selected
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示消账记录
|
||||||
|
async function showWriteOffRecords(settlementId: string) {
|
||||||
|
try {
|
||||||
|
uni.showLoading({ title: '加载中...' })
|
||||||
|
await financeStore.fetchWriteOffList(settlementId)
|
||||||
|
currentWriteOffRecords.value = financeStore.writeOffList
|
||||||
|
writeOffRecordVisible.value = true
|
||||||
|
} catch (error) {
|
||||||
|
uni.showToast({ title: '获取消账记录失败', icon: 'none' })
|
||||||
|
} finally {
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全选/取消全选
|
||||||
|
function toggleSelectAll(merchantId: string) {
|
||||||
|
const group = groupedByMerchant.value.find(g => g.merchantId === merchantId)
|
||||||
|
if (!group) return
|
||||||
|
|
||||||
|
const selectableOrders = group.settlements.filter(
|
||||||
|
item => item.status === SettlementStatus.UNSETTLED || item.status === SettlementStatus.OVERDUE
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isAllSelected.value(merchantId)) {
|
||||||
|
// 取消全选
|
||||||
|
selectedOrders.value[merchantId] = []
|
||||||
|
} else {
|
||||||
|
// 全选
|
||||||
|
selectedOrders.value[merchantId] = selectableOrders.map(item => item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量消账
|
||||||
|
function handleBatchWriteOff(merchantId: string) {
|
||||||
|
const selectedIds = selectedOrders.value[merchantId] || []
|
||||||
|
if (selectedIds.length === 0) return
|
||||||
|
|
||||||
|
const group = groupedByMerchant.value.find(g => g.merchantId === merchantId)
|
||||||
|
if (!group) return
|
||||||
|
|
||||||
|
// 获取选中的订单
|
||||||
|
const selectedSettlements = group.settlements.filter(item => selectedIds.includes(item.id))
|
||||||
|
|
||||||
|
currentSettlement.value = null
|
||||||
|
currentMerchantSettlements.value = selectedSettlements
|
||||||
|
isBatchMode.value = true
|
||||||
|
writeOffVisible.value = true
|
||||||
|
|
||||||
|
// 退出选择模式
|
||||||
|
exitSelectionMode(merchantId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开商户批量消账弹窗(保留原有功能作为备用)
|
||||||
function handleOpenBatchWriteOff(merchantId: string) {
|
function handleOpenBatchWriteOff(merchantId: string) {
|
||||||
const group = groupedByMerchant.value.find(g => g.merchantId === merchantId)
|
const group = groupedByMerchant.value.find(g => g.merchantId === merchantId)
|
||||||
if (!group)
|
if (!group)
|
||||||
@@ -249,7 +382,7 @@ function getDaysUntilDue(dateStr: string) {
|
|||||||
<text class="i-carbon-warning-filled alert-icon" />
|
<text class="i-carbon-warning-filled alert-icon" />
|
||||||
<text class="alert-title">近期到期提醒</text>
|
<text class="alert-title">近期到期提醒</text>
|
||||||
</view>
|
</view>
|
||||||
<scroll-view scroll-x class="due-list" show-scrollbar="false">
|
<scroll-view scroll-x class="due-list" :show-scrollbar="false">
|
||||||
<view
|
<view
|
||||||
v-for="item in financeStore.dueOrders"
|
v-for="item in financeStore.dueOrders"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -305,51 +438,86 @@ function getDaysUntilDue(dateStr: string) {
|
|||||||
|
|
||||||
<!-- 头部批量操作 -->
|
<!-- 头部批量操作 -->
|
||||||
<view v-if="currentTab === 0" class="header-action">
|
<view v-if="currentTab === 0" class="header-action">
|
||||||
<view class="batch-btn-small" @click.stop="handleOpenBatchWriteOff(group.merchantId)">
|
<!-- 非选择模式 -->
|
||||||
|
<view v-if="!selectionMode[group.merchantId]" class="batch-btn-small" @click.stop="enterSelectionMode(group.merchantId)">
|
||||||
<text class="i-carbon-checkmark-outline" />
|
<text class="i-carbon-checkmark-outline" />
|
||||||
<text>批量消账</text>
|
<text>消账</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 选择模式 -->
|
||||||
|
<template v-else>
|
||||||
|
<view class="selection-controls">
|
||||||
|
<view class="select-all-btn" @click.stop="toggleSelectAll(group.merchantId)">
|
||||||
|
<text class="i-carbon-checkmark-filled" v-if="isAllSelected(group.merchantId)" />
|
||||||
|
<text class="i-carbon-checkmark-outline" v-else />
|
||||||
|
<text>全选</text>
|
||||||
|
</view>
|
||||||
|
<view class="selected-info">
|
||||||
|
<text>已选 {{ selectedCount(group.merchantId) }} 单</text>
|
||||||
|
<text class="amount">¥{{ selectedAmount(group.merchantId).toFixed(2) }}</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="selectedCount(group.merchantId) > 0"
|
||||||
|
class="batch-btn-small active"
|
||||||
|
@click.stop="handleBatchWriteOff(group.merchantId)"
|
||||||
|
>
|
||||||
|
<text class="i-carbon-checkmark-outline" />
|
||||||
|
<text>进行消账({{ selectedCount(group.merchantId) }})</text>
|
||||||
|
</view>
|
||||||
|
<view v-else class="cancel-btn" @click.stop="exitSelectionMode(group.merchantId)">
|
||||||
|
<text>取消</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 订单列表 -->
|
<!-- 订单列表 -->
|
||||||
<view class="order-list">
|
<view class="order-list">
|
||||||
<view v-for="item in group.settlements" :key="item.id" class="order-item">
|
<view
|
||||||
<!-- 订单头部 -->
|
v-for="item in group.settlements"
|
||||||
<view class="order-header">
|
:key="item.id"
|
||||||
<view class="order-no-wrapper">
|
class="order-item"
|
||||||
<text class="order-label">订单号</text>
|
:class="{
|
||||||
<text class="order-no">{{ item.orderNo }}</text>
|
'selection-mode': selectionMode[group.merchantId],
|
||||||
</view>
|
'selected': selectedOrders[group.merchantId]?.includes(item.id),
|
||||||
<view class="status-badge" :class="getStatusClass(item.status)">
|
'disabled': item.status !== SettlementStatus.UNSETTLED && item.status !== SettlementStatus.OVERDUE
|
||||||
{{ getStatusText(item.status) }}
|
}"
|
||||||
</view>
|
@click="handleOrderClick(group.merchantId, item.id, item.status)"
|
||||||
|
>
|
||||||
|
<!-- 选择框 -->
|
||||||
|
<view v-if="selectionMode[group.merchantId]" class="checkbox">
|
||||||
|
<text class="i-carbon-checkmark-filled" v-if="selectedOrders[group.merchantId]?.includes(item.id)" />
|
||||||
|
<text class="i-carbon-checkmark-outline" v-else />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 订单内容 -->
|
<!-- 订单内容 -->
|
||||||
<view class="order-body">
|
<view class="order-content">
|
||||||
<view class="info-row">
|
<!-- 订单头部 -->
|
||||||
<text class="info-label">到期日期</text>
|
<view class="order-header">
|
||||||
<text class="info-value">{{ item.dueDate }}</text>
|
<view class="order-no-wrapper">
|
||||||
|
<text class="order-label">订单号</text>
|
||||||
|
<text class="order-no">{{ item.orderNo }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="status-badge" :class="getStatusClass(item.status)">
|
||||||
|
{{ getStatusText(item.status) }}
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view v-if="item.settlementDate" class="info-row">
|
|
||||||
<text class="info-label">结算日期</text>
|
|
||||||
<text class="info-value">{{ item.settlementDate }}</text>
|
|
||||||
</view>
|
|
||||||
<view class="info-row">
|
|
||||||
<text class="info-label">应结金额</text>
|
|
||||||
<text class="info-value amount">¥{{ item.amount.toFixed(2) }}</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 订单操作 -->
|
<!-- 订单内容 -->
|
||||||
<view
|
<view class="order-body">
|
||||||
v-if="item.status === SettlementStatus.UNSETTLED || item.status === SettlementStatus.OVERDUE"
|
<view class="info-row">
|
||||||
class="order-footer"
|
<text class="info-label">到期日期</text>
|
||||||
>
|
<text class="info-value">{{ item.dueDate }}</text>
|
||||||
<view class="action-btn" @click="handleOpenWriteOff(item)">
|
</view>
|
||||||
<text class="i-carbon-document-tasks" />
|
<view v-if="item.settlementDate" class="info-row">
|
||||||
<text>申请消账</text>
|
<text class="info-label">结算日期</text>
|
||||||
|
<text class="info-value">{{ item.settlementDate }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="info-row">
|
||||||
|
<text class="info-label">应结金额</text>
|
||||||
|
<text class="info-value amount">¥{{ item.amount.toFixed(2) }}</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -367,6 +535,12 @@ function getDaysUntilDue(dateStr: string) {
|
|||||||
:default-amount="currentWriteOffAmount"
|
:default-amount="currentWriteOffAmount"
|
||||||
@submit="handleSubmitWriteOff"
|
@submit="handleSubmitWriteOff"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 消账记录弹窗 -->
|
||||||
|
<WriteOffRecord
|
||||||
|
v-model:visible="writeOffRecordVisible"
|
||||||
|
:write-off-records="currentWriteOffRecords"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -610,6 +784,54 @@ $bg-card: #ffffff;
|
|||||||
.i-carbon-checkmark-outline {
|
.i-carbon-checkmark-outline {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: $primary;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12rpx;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
.select-all-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6rpx;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
background: rgba($primary, 0.05);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
color: $primary;
|
||||||
|
font-size: 22rpx;
|
||||||
|
|
||||||
|
.i-carbon-checkmark-outline,
|
||||||
|
.i-carbon-checkmark-filled {
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-info {
|
||||||
|
text-align: right;
|
||||||
|
font-size: 22rpx;
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
display: block;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $danger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cancel-btn {
|
||||||
|
padding: 8rpx 20rpx;
|
||||||
|
background: rgba($text-2, 0.1);
|
||||||
|
border-radius: 20rpx;
|
||||||
|
color: $text-2;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -628,6 +850,49 @@ $bg-card: #ffffff;
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.selection-mode {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 20rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
|
||||||
|
.checkbox {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2rpx solid #ddd;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.i-carbon-checkmark-outline {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.i-carbon-checkmark-filled {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected .checkbox {
|
||||||
|
background: rgba($primary, 0.1);
|
||||||
|
border-color: $primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 订单头部
|
// 订单头部
|
||||||
.order-header {
|
.order-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user