Commit 28dc12db7291ec5d08981eec693d807ee073993f
1 parent
07c554d7
```
feat(reimbursement): 实现报销申请编辑功能并优化详情展示 - 新增 `edit-dialog.vue` 组件,支持修改报销申请信息 - 在详情页中展示购买记录列表及其附件预览 - 优化审批按钮权限判断逻辑,确保仅当前审批人可操作 - 调整接口调用方式,适配后端API变更 - 注释部分旧布局代码,为后续调整做准备 - 样式微调,提升用户体验与界面一致性 ```
Showing
18 changed files
with
6952 additions
and
295 deletions
antis-ncc-admin/src/views/lqReimbursementApplication/approval-history-dialog.vue
antis-ncc-admin/src/views/lqReimbursementApplication/detail-dialog.vue
| ... | ... | @@ -15,7 +15,7 @@ |
| 15 | 15 | <span class="card-title">申请信息</span> |
| 16 | 16 | </div> |
| 17 | 17 | <el-row :gutter="20"> |
| 18 | - <el-col :span="8"> | |
| 18 | + <!-- <el-col :span="8"> | |
| 19 | 19 | <div class="info-item"> |
| 20 | 20 | <label class="info-label">申请编号:</label> |
| 21 | 21 | <span class="info-value">{{ formData.id || '无' }}</span> |
| ... | ... | @@ -26,7 +26,7 @@ |
| 26 | 26 | <label class="info-label">申请人编号:</label> |
| 27 | 27 | <span class="info-value">{{ formData.applicationUserId || '无' }}</span> |
| 28 | 28 | </div> |
| 29 | - </el-col> | |
| 29 | + </el-col> --> | |
| 30 | 30 | <el-col :span="8"> |
| 31 | 31 | <div class="info-item"> |
| 32 | 32 | <label class="info-label">申请人姓名:</label> |
| ... | ... | @@ -35,8 +35,8 @@ |
| 35 | 35 | </el-col> |
| 36 | 36 | <el-col :span="8"> |
| 37 | 37 | <div class="info-item"> |
| 38 | - <label class="info-label">申请门店ID:</label> | |
| 39 | - <span class="info-value">{{ formData.applicationStoreId || '无' }}</span> | |
| 38 | + <label class="info-label">申请门店:</label> | |
| 39 | + <span class="info-value">{{ formData.applicationStoreName || '无' }}</span> | |
| 40 | 40 | </div> |
| 41 | 41 | </el-col> |
| 42 | 42 | <el-col :span="8"> |
| ... | ... | @@ -51,7 +51,7 @@ |
| 51 | 51 | <span class="info-value amount">{{ formatMoney(formData.amount) || '无' }}</span> |
| 52 | 52 | </div> |
| 53 | 53 | </el-col> |
| 54 | - <el-col :span="8"> | |
| 54 | + <!-- <el-col :span="8"> | |
| 55 | 55 | <div class="info-item"> |
| 56 | 56 | <label class="info-label">审批状态:</label> |
| 57 | 57 | <el-tag |
| ... | ... | @@ -67,10 +67,80 @@ |
| 67 | 67 | <label class="info-label">购买记录编号:</label> |
| 68 | 68 | <span class="info-value">{{ formData.purchaseRecordsId || '无' }}</span> |
| 69 | 69 | </div> |
| 70 | - </el-col> | |
| 70 | + </el-col> --> | |
| 71 | 71 | </el-row> |
| 72 | 72 | </el-card> |
| 73 | 73 | |
| 74 | + <!-- 购买记录列表 --> | |
| 75 | + <el-card class="info-card" shadow="hover" v-if="purchaseRecords && purchaseRecords.length > 0"> | |
| 76 | + <div slot="header" class="card-header"> | |
| 77 | + <i class="el-icon-shopping-cart-full"></i> | |
| 78 | + <span class="card-title">购买记录列表</span> | |
| 79 | + </div> | |
| 80 | + <el-table :data="purchaseRecords" border size="small" style="width: 100%"> | |
| 81 | + <el-table-column prop="reimbursementCategoryName" label="分类名称" > | |
| 82 | + <template slot-scope="scope"> | |
| 83 | + <div class="category-item"> | |
| 84 | + <span>{{ scope.row.reimbursementCategoryName || '无' }}</span> | |
| 85 | + </div> | |
| 86 | + </template> | |
| 87 | + </el-table-column> | |
| 88 | + <el-table-column prop="unitPrice" label="单价" > | |
| 89 | + <template slot-scope="scope"> | |
| 90 | + <div class="price-item"> | |
| 91 | + <span>{{ formatMoney(scope.row.unitPrice) }}</span> | |
| 92 | + </div> | |
| 93 | + </template> | |
| 94 | + </el-table-column> | |
| 95 | + <el-table-column prop="quantity" label="数量" > | |
| 96 | + <template slot-scope="scope"> | |
| 97 | + <div class="quantity-item"> | |
| 98 | + <span>{{ scope.row.quantity || '无' }}</span> | |
| 99 | + </div> | |
| 100 | + </template> | |
| 101 | + </el-table-column> | |
| 102 | + <el-table-column prop="amount" label="金额" > | |
| 103 | + <template slot-scope="scope"> | |
| 104 | + <div class="amount-item"> | |
| 105 | + <span class="amount-value">{{ formatMoney(scope.row.amount) }}</span> | |
| 106 | + </div> | |
| 107 | + </template> | |
| 108 | + </el-table-column> | |
| 109 | + <el-table-column prop="purchaseTime" label="购买时间" width="180" > | |
| 110 | + <template slot-scope="scope"> | |
| 111 | + <div class="time-item"> | |
| 112 | + <span>{{ formatDateTime(scope.row.purchaseTime) || '无' }}</span> | |
| 113 | + </div> | |
| 114 | + </template> | |
| 115 | + </el-table-column> | |
| 116 | + <el-table-column prop="memo" label="备注说明" show-overflow-tooltip> | |
| 117 | + <template slot-scope="scope"> | |
| 118 | + {{ scope.row.memo || '无' }} | |
| 119 | + </template> | |
| 120 | + </el-table-column> | |
| 121 | + <el-table-column prop="attachment" label="附件" width="100" > | |
| 122 | + <template slot-scope="scope"> | |
| 123 | + <div v-if="scope.row.attachment && scope.row.attachment.length > 0" class="attachment-list"> | |
| 124 | + <div> | |
| 125 | + <i class="el-icon-paperclip"></i> | |
| 126 | + <span class="attachment-name">{{ scope.row.attachment.length + '个文件' }}</span> | |
| 127 | + </div> | |
| 128 | + <div class="attachment-item"> | |
| 129 | + <el-image :src="baseUrl + scope.row.attachment[0].url" fit="contain" :style="{ | |
| 130 | + width: '100%', | |
| 131 | + height: '100px', | |
| 132 | + display: 'block', | |
| 133 | + margin: '0 auto' | |
| 134 | + }" :preview-src-list="getPreviewSrcList(scope.row.attachment)"> | |
| 135 | + </el-image> | |
| 136 | + </div> | |
| 137 | + </div> | |
| 138 | + <span v-else>无</span> | |
| 139 | + </template> | |
| 140 | + </el-table-column> | |
| 141 | + </el-table> | |
| 142 | + </el-card> | |
| 143 | + | |
| 74 | 144 | <!-- 审批流程卡片 --> |
| 75 | 145 | <el-card class="info-card" shadow="hover" v-if="nodes && nodes.length > 0"> |
| 76 | 146 | <div slot="header" class="card-header"> |
| ... | ... | @@ -223,6 +293,7 @@ export default { |
| 223 | 293 | name: 'DetailDialog', |
| 224 | 294 | data() { |
| 225 | 295 | return { |
| 296 | + baseUrl: process.env.VUE_APP_BASE_API, | |
| 226 | 297 | visible: false, |
| 227 | 298 | loading: false, |
| 228 | 299 | approving: false, |
| ... | ... | @@ -232,6 +303,7 @@ export default { |
| 232 | 303 | currentNodeOrder: 0, |
| 233 | 304 | approvalStatus: '', |
| 234 | 305 | returnedReason: null, |
| 306 | + purchaseRecords: [], // 购买记录列表数据 | |
| 235 | 307 | approvalForm: { |
| 236 | 308 | result: '通过', |
| 237 | 309 | opinion: '' |
| ... | ... | @@ -244,12 +316,31 @@ export default { |
| 244 | 316 | if (this.approvalStatus !== '审批中') { |
| 245 | 317 | return false |
| 246 | 318 | } |
| 247 | - // 这里可以根据实际登录用户信息判断 | |
| 248 | - // 暂时返回true,实际应该判断当前登录用户是否在currentApprovers中 | |
| 249 | - return true | |
| 319 | + | |
| 320 | + // 获取当前登录用户信息 | |
| 321 | + const currentUser = this.$store.getters.userInfo | |
| 322 | + if (!currentUser || !currentUser.userId) { | |
| 323 | + return false | |
| 324 | + } | |
| 325 | + | |
| 326 | + // 判断当前用户是否在currentApprovers中 | |
| 327 | + if (!this.currentApprovers || this.currentApprovers.length === 0) { | |
| 328 | + return false | |
| 329 | + } | |
| 330 | + | |
| 331 | + // 检查当前用户的userId是否在审批人列表中 | |
| 332 | + const isCurrentApprover = this.currentApprovers.some(approver => { | |
| 333 | + return approver.userId === currentUser.userId | |
| 334 | + }) | |
| 335 | + | |
| 336 | + return isCurrentApprover | |
| 250 | 337 | } |
| 251 | 338 | }, |
| 252 | 339 | methods: { |
| 340 | + // 获取预览图片列表 | |
| 341 | + getPreviewSrcList(attachment) { | |
| 342 | + return attachment.map(item => this.baseUrl + item.url) | |
| 343 | + }, | |
| 253 | 344 | init(id) { |
| 254 | 345 | this.visible = true |
| 255 | 346 | this.loading = true |
| ... | ... | @@ -259,6 +350,7 @@ export default { |
| 259 | 350 | this.currentNodeOrder = 0 |
| 260 | 351 | this.approvalStatus = '' |
| 261 | 352 | this.returnedReason = null |
| 353 | + this.purchaseRecords = [] | |
| 262 | 354 | this.approvalForm = { |
| 263 | 355 | result: '通过', |
| 264 | 356 | opinion: '' |
| ... | ... | @@ -280,6 +372,11 @@ export default { |
| 280 | 372 | this.currentNodeOrder = response.data.currentNodeOrder || 0 |
| 281 | 373 | this.approvalStatus = response.data.approvalStatus || '' |
| 282 | 374 | this.returnedReason = response.data.returnedReason || null |
| 375 | + this.purchaseRecords = response.data.purchaseRecords || [] | |
| 376 | + this.purchaseRecords = this.purchaseRecords.map(item => { | |
| 377 | + item.attachment = item.attachment?JSON.parse(item.attachment):[] | |
| 378 | + return item | |
| 379 | + }) | |
| 283 | 380 | } else { |
| 284 | 381 | this.$message.error(response.msg || '获取详情失败') |
| 285 | 382 | } |
| ... | ... | @@ -300,12 +397,8 @@ export default { |
| 300 | 397 | this.approving = true |
| 301 | 398 | try { |
| 302 | 399 | const response = await request({ |
| 303 | - url: `/api/Extend/LqReimbursementApplication/${this.formData.id}/Actions/Approve`, | |
| 304 | - method: 'POST', | |
| 305 | - data: { | |
| 306 | - result: this.approvalForm.result, | |
| 307 | - opinion: this.approvalForm.opinion || '' | |
| 308 | - } | |
| 400 | + url: `/api/Extend/LqReimbursementApplication/${this.formData.id}/Actions/Approve?result=${this.approvalForm.result}&opinion=${this.approvalForm.opinion || ''}`, | |
| 401 | + method: 'POST' | |
| 309 | 402 | }) |
| 310 | 403 | |
| 311 | 404 | if (response.code === 200) { |
| ... | ... | @@ -331,6 +424,7 @@ export default { |
| 331 | 424 | this.currentNodeOrder = 0 |
| 332 | 425 | this.approvalStatus = '' |
| 333 | 426 | this.returnedReason = null |
| 427 | + this.purchaseRecords = [] | |
| 334 | 428 | this.approvalForm = { |
| 335 | 429 | result: '通过', |
| 336 | 430 | opinion: '' |
| ... | ... | @@ -396,7 +490,7 @@ export default { |
| 396 | 490 | |
| 397 | 491 | <style lang="scss" scoped> |
| 398 | 492 | .detail-content { |
| 399 | - max-height: 70vh; | |
| 493 | + //max-height: 70vh; | |
| 400 | 494 | overflow-y: auto; |
| 401 | 495 | } |
| 402 | 496 | |
| ... | ... | @@ -575,5 +669,51 @@ export default { |
| 575 | 669 | color: #F56C6C; |
| 576 | 670 | line-height: 1.6; |
| 577 | 671 | } |
| 672 | + | |
| 673 | +// 购买记录表格项样式 | |
| 674 | +.category-item, | |
| 675 | +.price-item, | |
| 676 | +.quantity-item, | |
| 677 | +.amount-item, | |
| 678 | +.time-item { | |
| 679 | + display: flex; | |
| 680 | + align-items: center; | |
| 681 | + justify-content: center; | |
| 682 | + gap: 6px; | |
| 683 | + | |
| 684 | + .category-icon { | |
| 685 | + color: #67C23A; | |
| 686 | + font-size: 16px; | |
| 687 | + } | |
| 688 | + | |
| 689 | + .price-icon { | |
| 690 | + color: #909399; | |
| 691 | + font-size: 16px; | |
| 692 | + } | |
| 693 | + | |
| 694 | + .quantity-icon { | |
| 695 | + color: #409EFF; | |
| 696 | + font-size: 16px; | |
| 697 | + } | |
| 698 | + | |
| 699 | + .amount-icon { | |
| 700 | + color: #F56C6C; | |
| 701 | + font-size: 16px; | |
| 702 | + } | |
| 703 | + | |
| 704 | + .time-icon { | |
| 705 | + color: #909399; | |
| 706 | + font-size: 16px; | |
| 707 | + } | |
| 708 | + | |
| 709 | + span { | |
| 710 | + color: #606266; | |
| 711 | + } | |
| 712 | +} | |
| 713 | + | |
| 714 | +.amount-value { | |
| 715 | + font-weight: 600; | |
| 716 | + color: #F56C6C; | |
| 717 | +} | |
| 578 | 718 | </style> |
| 579 | 719 | ... | ... |
antis-ncc-admin/src/views/lqReimbursementApplication/edit-dialog.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <el-dialog | |
| 3 | + title="修改报销申请" | |
| 4 | + :visible.sync="visible" | |
| 5 | + :close-on-click-modal="false" | |
| 6 | + class="NCC-dialog NCC-dialog_center" | |
| 7 | + width="1200px" | |
| 8 | + @close="handleClose" | |
| 9 | + > | |
| 10 | + <div class="form-content" v-loading="loading"> | |
| 11 | + <el-form | |
| 12 | + ref="form" | |
| 13 | + :model="dataForm" | |
| 14 | + :rules="rules" | |
| 15 | + label-width="120px" | |
| 16 | + label-position="right" | |
| 17 | + > | |
| 18 | + <!-- 基本信息 --> | |
| 19 | + <el-card class="form-card" shadow="hover"> | |
| 20 | + <div slot="header" class="card-header"> | |
| 21 | + <i class="el-icon-document"></i> | |
| 22 | + <span class="card-title">基本信息</span> | |
| 23 | + </div> | |
| 24 | + <el-row :gutter="20"> | |
| 25 | + <el-col :span="12"> | |
| 26 | + <el-form-item label="申请人" prop="applicationUserId"> | |
| 27 | + <user-select | |
| 28 | + v-model="dataForm.applicationUserId" | |
| 29 | + placeholder="请选择申请人" | |
| 30 | + clearable | |
| 31 | + @change="handleUserChange" | |
| 32 | + /> | |
| 33 | + </el-form-item> | |
| 34 | + </el-col> | |
| 35 | + <el-col :span="12"> | |
| 36 | + <el-form-item label="申请人姓名" prop="applicationUserName"> | |
| 37 | + <el-input | |
| 38 | + v-model="dataForm.applicationUserName" | |
| 39 | + placeholder="自动填充" | |
| 40 | + readonly | |
| 41 | + /> | |
| 42 | + </el-form-item> | |
| 43 | + </el-col> | |
| 44 | + <el-col :span="12"> | |
| 45 | + <el-form-item label="申请门店" prop="applicationStoreId"> | |
| 46 | + <el-select | |
| 47 | + v-model="dataForm.applicationStoreId" | |
| 48 | + placeholder="请选择申请门店" | |
| 49 | + clearable | |
| 50 | + filterable | |
| 51 | + style="width: 100%" | |
| 52 | + > | |
| 53 | + <el-option | |
| 54 | + v-for="store in storeOptions" | |
| 55 | + :key="store.id" | |
| 56 | + :label="store.dm" | |
| 57 | + :value="store.id" | |
| 58 | + /> | |
| 59 | + </el-select> | |
| 60 | + </el-form-item> | |
| 61 | + </el-col> | |
| 62 | + <el-col :span="12"> | |
| 63 | + <el-form-item label="申请时间" prop="applicationTime"> | |
| 64 | + <el-date-picker | |
| 65 | + v-model="dataForm.applicationTime" | |
| 66 | + type="datetime" | |
| 67 | + placeholder="请选择申请时间" | |
| 68 | + format="yyyy-MM-dd HH:mm:ss" | |
| 69 | + value-format="timestamp" | |
| 70 | + style="width: 100%" | |
| 71 | + /> | |
| 72 | + </el-form-item> | |
| 73 | + </el-col> | |
| 74 | + <el-col :span="12"> | |
| 75 | + <el-form-item label="总金额" prop="amount"> | |
| 76 | + <el-input | |
| 77 | + v-model="dataForm.amount" | |
| 78 | + placeholder="自动计算" | |
| 79 | + readonly | |
| 80 | + > | |
| 81 | + <template slot="prepend">¥</template> | |
| 82 | + </el-input> | |
| 83 | + </el-form-item> | |
| 84 | + </el-col> | |
| 85 | + </el-row> | |
| 86 | + </el-card> | |
| 87 | + | |
| 88 | + <!-- 选择购买记录 --> | |
| 89 | + <el-card class="form-card" shadow="hover"> | |
| 90 | + <div slot="header" class="card-header"> | |
| 91 | + <i class="el-icon-shopping-cart-full"></i> | |
| 92 | + <span class="card-title">选择购买记录</span> | |
| 93 | + </div> | |
| 94 | + <div> | |
| 95 | + <el-button | |
| 96 | + type="primary" | |
| 97 | + icon="el-icon-plus" | |
| 98 | + @click="openPurchaseDialog" | |
| 99 | + > | |
| 100 | + 选择购买记录 | |
| 101 | + </el-button> | |
| 102 | + <span v-if="selectedPurchaseRecords.length > 0" class="selected-count"> | |
| 103 | + 已选择 {{ selectedPurchaseRecords.length }} 条记录 | |
| 104 | + </span> | |
| 105 | + </div> | |
| 106 | + | |
| 107 | + <!-- 已选择的购买记录列表 --> | |
| 108 | + <div v-if="selectedPurchaseRecords.length > 0" class="purchase-list"> | |
| 109 | + <el-table :data="selectedPurchaseRecords" border size="small"> | |
| 110 | + <el-table-column prop="reimbursementCategoryName" label="分类名称" /> | |
| 111 | + <el-table-column prop="unitPrice" label="单价" > | |
| 112 | + <template slot-scope="scope"> | |
| 113 | + ¥{{ formatMoney(scope.row.unitPrice) }} | |
| 114 | + </template> | |
| 115 | + </el-table-column> | |
| 116 | + <el-table-column prop="quantity" label="数量" /> | |
| 117 | + <el-table-column prop="amount" label="金额" > | |
| 118 | + <template slot-scope="scope"> | |
| 119 | + ¥{{ formatMoney(scope.row.amount) }} | |
| 120 | + </template> | |
| 121 | + </el-table-column> | |
| 122 | + <el-table-column prop="purchaseTime" label="购买时间" width="180"> | |
| 123 | + <template slot-scope="scope"> | |
| 124 | + {{ formatDateTime(scope.row.purchaseTime) }} | |
| 125 | + </template> | |
| 126 | + </el-table-column> | |
| 127 | + <el-table-column label="操作" width="100" align="center"> | |
| 128 | + <template slot-scope="scope"> | |
| 129 | + <el-button | |
| 130 | + v-if="!scope.row.isInitial" | |
| 131 | + type="text" | |
| 132 | + icon="el-icon-delete" | |
| 133 | + @click="removePurchaseRecord(scope.$index)" | |
| 134 | + style="color: #F56C6C" | |
| 135 | + > | |
| 136 | + 移除 | |
| 137 | + </el-button> | |
| 138 | + <span v-else style="color: #909399; font-size: 12px;">初始记录</span> | |
| 139 | + </template> | |
| 140 | + </el-table-column> | |
| 141 | + </el-table> | |
| 142 | + </div> | |
| 143 | + </el-card> | |
| 144 | + </el-form> | |
| 145 | + </div> | |
| 146 | + <span slot="footer" class="dialog-footer"> | |
| 147 | + <el-button @click="visible = false">取 消</el-button> | |
| 148 | + <el-button | |
| 149 | + type="primary" | |
| 150 | + @click="handleSubmit" | |
| 151 | + :loading="submitting" | |
| 152 | + > | |
| 153 | + 保存修改 | |
| 154 | + </el-button> | |
| 155 | + </span> | |
| 156 | + | |
| 157 | + <!-- 选择购买记录弹窗 --> | |
| 158 | + <el-dialog | |
| 159 | + title="选择购买记录" | |
| 160 | + :visible.sync="showPurchaseDialog" | |
| 161 | + width="900px" | |
| 162 | + append-to-body | |
| 163 | + > | |
| 164 | + <div class="purchase-dialog-content"> | |
| 165 | + <div class="purchase-search"> | |
| 166 | + <el-input | |
| 167 | + v-model="purchaseSearchKeyword" | |
| 168 | + placeholder="搜索分类名称" | |
| 169 | + clearable | |
| 170 | + @input="handlePurchaseSearch" | |
| 171 | + style="width: 300px" | |
| 172 | + > | |
| 173 | + <el-button slot="append" icon="el-icon-search" @click="loadPurchaseRecords" /> | |
| 174 | + </el-input> | |
| 175 | + </div> | |
| 176 | + <el-table | |
| 177 | + :data="purchaseRecordList" | |
| 178 | + v-loading="purchaseLoading" | |
| 179 | + @selection-change="handlePurchaseSelectionChange" | |
| 180 | + border | |
| 181 | + max-height="400" | |
| 182 | + > | |
| 183 | + <el-table-column type="selection" width="55"/> | |
| 184 | + <el-table-column prop="reimbursementCategoryName" label="分类名称" /> | |
| 185 | + <el-table-column prop="unitPrice" label="单价" > | |
| 186 | + <template slot-scope="scope"> | |
| 187 | + ¥{{ formatMoney(scope.row.unitPrice) }} | |
| 188 | + </template> | |
| 189 | + </el-table-column> | |
| 190 | + <el-table-column prop="quantity" label="数量" /> | |
| 191 | + <el-table-column prop="amount" label="金额" > | |
| 192 | + <template slot-scope="scope"> | |
| 193 | + ¥{{ formatMoney(scope.row.amount) }} | |
| 194 | + </template> | |
| 195 | + </el-table-column> | |
| 196 | + <el-table-column prop="purchaseTime" label="购买时间" width="180"> | |
| 197 | + <template slot-scope="scope"> | |
| 198 | + {{ formatDateTime(scope.row.purchaseTime) }} | |
| 199 | + </template> | |
| 200 | + </el-table-column> | |
| 201 | + </el-table> | |
| 202 | + <div class="purchase-pagination"> | |
| 203 | + <el-pagination | |
| 204 | + @size-change="handlePurchaseSizeChange" | |
| 205 | + @current-change="handlePurchaseCurrentChange" | |
| 206 | + :current-page="purchaseQuery.currentPage" | |
| 207 | + :page-sizes="[10, 20, 50]" | |
| 208 | + :page-size="purchaseQuery.pageSize" | |
| 209 | + layout="total, sizes, prev, pager, next" | |
| 210 | + :total="purchaseTotal" | |
| 211 | + /> | |
| 212 | + </div> | |
| 213 | + </div> | |
| 214 | + <span slot="footer" class="dialog-footer"> | |
| 215 | + <el-button @click="showPurchaseDialog = false">取 消</el-button> | |
| 216 | + <el-button type="primary" @click="confirmPurchaseSelection">确 定</el-button> | |
| 217 | + </span> | |
| 218 | + </el-dialog> | |
| 219 | + </el-dialog> | |
| 220 | +</template> | |
| 221 | + | |
| 222 | +<script> | |
| 223 | +import request from '@/utils/request' | |
| 224 | + | |
| 225 | +export default { | |
| 226 | + name: 'EditDialog', | |
| 227 | + data() { | |
| 228 | + return { | |
| 229 | + visible: false, | |
| 230 | + loading: false, | |
| 231 | + submitting: false, | |
| 232 | + applicationId: null, | |
| 233 | + storeOptions: [], | |
| 234 | + dataForm: { | |
| 235 | + applicationUserId: '', | |
| 236 | + applicationUserName: '', | |
| 237 | + applicationStoreId: '', | |
| 238 | + applicationTime: null, | |
| 239 | + amount: '0.00', | |
| 240 | + selectedPurchaseRecordIds: [] | |
| 241 | + }, | |
| 242 | + rules: { | |
| 243 | + applicationStoreId: [ | |
| 244 | + { required: true, message: '请选择申请门店', trigger: 'change' } | |
| 245 | + ], | |
| 246 | + applicationTime: [ | |
| 247 | + { required: true, message: '请选择申请时间', trigger: 'change' } | |
| 248 | + ], | |
| 249 | + amount: [ | |
| 250 | + { required: true, message: '总金额不能为空', trigger: 'blur' } | |
| 251 | + ] | |
| 252 | + }, | |
| 253 | + selectedPurchaseRecords: [], | |
| 254 | + showPurchaseDialog: false, | |
| 255 | + purchaseRecordList: [], | |
| 256 | + purchaseLoading: false, | |
| 257 | + purchaseSearchKeyword: '', | |
| 258 | + purchaseQuery: { | |
| 259 | + currentPage: 1, | |
| 260 | + pageSize: 20, | |
| 261 | + reimbursementCategoryName: '' | |
| 262 | + }, | |
| 263 | + purchaseTotal: 0, | |
| 264 | + purchaseSelection: [], | |
| 265 | + initialPurchaseRecordIds: [] // 存储初始的购买记录ID列表 | |
| 266 | + } | |
| 267 | + }, | |
| 268 | + created() { | |
| 269 | + this.loadStoreOptions() | |
| 270 | + }, | |
| 271 | + methods: { | |
| 272 | + async init(id) { | |
| 273 | + this.visible = true | |
| 274 | + this.loading = true | |
| 275 | + this.applicationId = id | |
| 276 | + this.submitting = false | |
| 277 | + | |
| 278 | + // 重置表单 | |
| 279 | + this.dataForm = { | |
| 280 | + applicationUserId: '', | |
| 281 | + applicationUserName: '', | |
| 282 | + applicationStoreId: '', | |
| 283 | + applicationTime: null, | |
| 284 | + amount: '0.00', | |
| 285 | + selectedPurchaseRecordIds: [] | |
| 286 | + } | |
| 287 | + this.selectedPurchaseRecords = [] | |
| 288 | + this.initialPurchaseRecordIds = [] // 重置初始记录ID列表 | |
| 289 | + | |
| 290 | + // 加载详情数据 | |
| 291 | + await this.loadDetail(id) | |
| 292 | + }, | |
| 293 | + | |
| 294 | + async loadDetail(id) { | |
| 295 | + try { | |
| 296 | + const response = await request({ | |
| 297 | + url: `/api/Extend/LqReimbursementApplication/${id}`, | |
| 298 | + method: 'GET' | |
| 299 | + }) | |
| 300 | + | |
| 301 | + if (response.code === 200 && response.data && response.data.form) { | |
| 302 | + const formData = response.data.form | |
| 303 | + const purchaseRecords = response.data.purchaseRecords || [] | |
| 304 | + | |
| 305 | + // 填充表单数据 | |
| 306 | + this.dataForm = { | |
| 307 | + applicationUserId: formData.applicationUserId || '', | |
| 308 | + applicationUserName: formData.applicationUserName || '', | |
| 309 | + applicationStoreId: formData.applicationStoreId || '', | |
| 310 | + applicationTime: formData.applicationTime || null, | |
| 311 | + amount: formData.amount || '0.00', | |
| 312 | + selectedPurchaseRecordIds: purchaseRecords.map(record => record.id) | |
| 313 | + } | |
| 314 | + | |
| 315 | + // 保存初始的购买记录ID列表 | |
| 316 | + this.initialPurchaseRecordIds = purchaseRecords.map(record => record.id) | |
| 317 | + | |
| 318 | + // 填充购买记录列表,标记初始记录 | |
| 319 | + this.selectedPurchaseRecords = purchaseRecords.map(record => { | |
| 320 | + // 处理附件字段 | |
| 321 | + if (record.attachment) { | |
| 322 | + try { | |
| 323 | + record.attachment = typeof record.attachment === 'string' | |
| 324 | + ? JSON.parse(record.attachment) | |
| 325 | + : record.attachment | |
| 326 | + } catch (e) { | |
| 327 | + record.attachment = [] | |
| 328 | + } | |
| 329 | + } else { | |
| 330 | + record.attachment = [] | |
| 331 | + } | |
| 332 | + // 标记为初始记录 | |
| 333 | + record.isInitial = true | |
| 334 | + return record | |
| 335 | + }) | |
| 336 | + | |
| 337 | + // 计算总金额 | |
| 338 | + this.calculateAmount() | |
| 339 | + } else { | |
| 340 | + this.$message.error(response.msg || '获取详情失败') | |
| 341 | + this.visible = false | |
| 342 | + } | |
| 343 | + } catch (error) { | |
| 344 | + console.error('获取详情失败:', error) | |
| 345 | + this.$message.error('获取详情失败') | |
| 346 | + this.visible = false | |
| 347 | + } finally { | |
| 348 | + this.loading = false | |
| 349 | + } | |
| 350 | + }, | |
| 351 | + | |
| 352 | + async loadStoreOptions() { | |
| 353 | + try { | |
| 354 | + const response = await request({ | |
| 355 | + url: '/api/Extend/LqMdxx', | |
| 356 | + method: 'GET', | |
| 357 | + data: { | |
| 358 | + currentPage: 1, | |
| 359 | + pageSize: 1000 | |
| 360 | + } | |
| 361 | + }) | |
| 362 | + if (response.code === 200 && response.data && response.data.list) { | |
| 363 | + this.storeOptions = response.data.list | |
| 364 | + } | |
| 365 | + } catch (error) { | |
| 366 | + console.error('加载门店列表失败:', error) | |
| 367 | + } | |
| 368 | + }, | |
| 369 | + | |
| 370 | + // 申请人改变时,自动填充申请人姓名 | |
| 371 | + handleUserChange(userId) { | |
| 372 | + if (userId) { | |
| 373 | + // 处理userId可能是字符串(逗号分隔)的情况,取第一个 | |
| 374 | + const userIdArray = typeof userId === 'string' | |
| 375 | + ? userId.split(',').filter(id => id && id.trim()) | |
| 376 | + : [userId] | |
| 377 | + const actualUserId = userIdArray.length > 0 ? userIdArray[0] : userId | |
| 378 | + | |
| 379 | + // 获取用户信息 | |
| 380 | + request({ | |
| 381 | + url: '/api/permission/Users/' + actualUserId, | |
| 382 | + method: 'GET', | |
| 383 | + }).then(res => { | |
| 384 | + if (res.code === 200 && res.data) { | |
| 385 | + this.dataForm.applicationUserName = res.data.realName || res.data.userName || res.data.fullName || '' | |
| 386 | + // 如果门店为空,自动填充用户的门店 | |
| 387 | + if (!this.dataForm.applicationStoreId && res.data.mdid) { | |
| 388 | + this.dataForm.applicationStoreId = res.data.mdid | |
| 389 | + } | |
| 390 | + } | |
| 391 | + }).catch(error => { | |
| 392 | + console.error('获取用户信息失败:', error) | |
| 393 | + }) | |
| 394 | + } else { | |
| 395 | + // 清空申请人时,也清空姓名 | |
| 396 | + this.dataForm.applicationUserName = '' | |
| 397 | + } | |
| 398 | + }, | |
| 399 | + | |
| 400 | + openPurchaseDialog() { | |
| 401 | + this.showPurchaseDialog = true | |
| 402 | + this.loadPurchaseRecords() | |
| 403 | + }, | |
| 404 | + | |
| 405 | + async loadPurchaseRecords() { | |
| 406 | + this.purchaseLoading = true | |
| 407 | + try { | |
| 408 | + const params = { | |
| 409 | + currentPage: this.purchaseQuery.currentPage, | |
| 410 | + pageSize: this.purchaseQuery.pageSize, | |
| 411 | + approveStatus: '未审批', // 只选择未审批的购买记录 | |
| 412 | + createUserStoreId: this.dataForm.applicationStoreId || '暂无', | |
| 413 | + } | |
| 414 | + if (this.purchaseSearchKeyword) { | |
| 415 | + params.reimbursementCategoryName = this.purchaseSearchKeyword | |
| 416 | + } | |
| 417 | + const response = await request({ | |
| 418 | + url: '/api/Extend/LqPurchaseRecords', | |
| 419 | + method: 'GET', | |
| 420 | + data: params | |
| 421 | + }) | |
| 422 | + if (response.code === 200 && response.data) { | |
| 423 | + this.purchaseRecordList = response.data.list || [] | |
| 424 | + this.purchaseTotal = (response.data.pagination && response.data.pagination.Total) || 0 | |
| 425 | + } | |
| 426 | + } catch (error) { | |
| 427 | + console.error('加载购买记录失败:', error) | |
| 428 | + this.$message.error('加载购买记录失败') | |
| 429 | + } finally { | |
| 430 | + this.purchaseLoading = false | |
| 431 | + } | |
| 432 | + }, | |
| 433 | + | |
| 434 | + handlePurchaseSearch() { | |
| 435 | + this.purchaseQuery.currentPage = 1 | |
| 436 | + this.loadPurchaseRecords() | |
| 437 | + }, | |
| 438 | + | |
| 439 | + handlePurchaseSelectionChange(selection) { | |
| 440 | + this.purchaseSelection = selection | |
| 441 | + }, | |
| 442 | + | |
| 443 | + confirmPurchaseSelection() { | |
| 444 | + // 合并新选择的记录,标记为非初始记录 | |
| 445 | + const newRecords = this.purchaseSelection.filter(item => { | |
| 446 | + return !this.selectedPurchaseRecords.find(record => record.id === item.id) | |
| 447 | + }).map(record => { | |
| 448 | + // 标记新增的记录 | |
| 449 | + record.isInitial = false | |
| 450 | + return record | |
| 451 | + }) | |
| 452 | + this.selectedPurchaseRecords = [...this.selectedPurchaseRecords, ...newRecords] | |
| 453 | + this.calculateAmount() | |
| 454 | + this.showPurchaseDialog = false | |
| 455 | + // 清空选择 | |
| 456 | + this.purchaseSelection = [] | |
| 457 | + }, | |
| 458 | + | |
| 459 | + removePurchaseRecord(index) { | |
| 460 | + const record = this.selectedPurchaseRecords[index] | |
| 461 | + // 检查是否是初始记录,初始记录不允许移除 | |
| 462 | + if (record && record.isInitial) { | |
| 463 | + this.$message.warning('初始购买记录不能移除') | |
| 464 | + return | |
| 465 | + } | |
| 466 | + this.selectedPurchaseRecords.splice(index, 1) | |
| 467 | + this.calculateAmount() | |
| 468 | + }, | |
| 469 | + | |
| 470 | + calculateAmount() { | |
| 471 | + // 计算总金额 | |
| 472 | + let total = 0 | |
| 473 | + this.selectedPurchaseRecords.forEach(record => { | |
| 474 | + total += Number(record.amount || 0) | |
| 475 | + }) | |
| 476 | + this.dataForm.amount = total.toFixed(2) | |
| 477 | + this.dataForm.selectedPurchaseRecordIds = this.selectedPurchaseRecords.map(record => record.id) | |
| 478 | + }, | |
| 479 | + | |
| 480 | + handlePurchaseSizeChange(val) { | |
| 481 | + this.purchaseQuery.pageSize = val | |
| 482 | + this.loadPurchaseRecords() | |
| 483 | + }, | |
| 484 | + | |
| 485 | + handlePurchaseCurrentChange(val) { | |
| 486 | + this.purchaseQuery.currentPage = val | |
| 487 | + this.loadPurchaseRecords() | |
| 488 | + }, | |
| 489 | + | |
| 490 | + async handleSubmit() { | |
| 491 | + // 验证表单 | |
| 492 | + this.$refs.form.validate(async (valid) => { | |
| 493 | + if (!valid) { | |
| 494 | + return false | |
| 495 | + } | |
| 496 | + | |
| 497 | + // 验证购买记录 | |
| 498 | + if (!this.selectedPurchaseRecords || this.selectedPurchaseRecords.length === 0) { | |
| 499 | + this.$message.warning('请至少选择一条购买记录') | |
| 500 | + return false | |
| 501 | + } | |
| 502 | + | |
| 503 | + this.submitting = true | |
| 504 | + try { | |
| 505 | + // 准备提交数据(只传递有值的字段) | |
| 506 | + const submitData = { | |
| 507 | + id: this.applicationId | |
| 508 | + } | |
| 509 | + | |
| 510 | + // 只添加有值的字段 | |
| 511 | + if (this.dataForm.applicationUserId) { | |
| 512 | + submitData.applicationUserId = this.dataForm.applicationUserId | |
| 513 | + } | |
| 514 | + if (this.dataForm.applicationUserName) { | |
| 515 | + submitData.applicationUserName = this.dataForm.applicationUserName | |
| 516 | + } | |
| 517 | + if (this.dataForm.applicationStoreId) { | |
| 518 | + submitData.applicationStoreId = this.dataForm.applicationStoreId | |
| 519 | + } | |
| 520 | + if (this.dataForm.applicationTime) { | |
| 521 | + submitData.applicationTime = this.dataForm.applicationTime | |
| 522 | + } | |
| 523 | + if (this.dataForm.amount) { | |
| 524 | + submitData.amount = this.dataForm.amount | |
| 525 | + } | |
| 526 | + if (this.dataForm.selectedPurchaseRecordIds && this.dataForm.selectedPurchaseRecordIds.length > 0) { | |
| 527 | + submitData.selectedPurchaseRecordIds = this.dataForm.selectedPurchaseRecordIds | |
| 528 | + } | |
| 529 | + | |
| 530 | + // 调用 PUT 接口更新数据 | |
| 531 | + const response = await request({ | |
| 532 | + url: `/api/Extend/LqReimbursementApplication/${this.applicationId}`, | |
| 533 | + method: 'PUT', | |
| 534 | + data: submitData | |
| 535 | + }) | |
| 536 | + | |
| 537 | + if (response.code === 200) { | |
| 538 | + this.$message.success('修改成功') | |
| 539 | + | |
| 540 | + // 修改成功后,自动重新提交审批 | |
| 541 | + try { | |
| 542 | + const submitResponse = await request({ | |
| 543 | + url: `/api/Extend/LqReimbursementApplication/${this.applicationId}/Actions/SubmitApproval`, | |
| 544 | + method: 'POST' | |
| 545 | + }) | |
| 546 | + | |
| 547 | + if (submitResponse.code === 200) { | |
| 548 | + this.$message.success('已重新提交审批') | |
| 549 | + } else { | |
| 550 | + this.$message.warning('修改成功,但重新提交审批失败:' + (submitResponse.msg || '未知错误')) | |
| 551 | + } | |
| 552 | + } catch (error) { | |
| 553 | + console.error('重新提交审批失败:', error) | |
| 554 | + this.$message.warning('修改成功,但重新提交审批失败,请手动提交') | |
| 555 | + } | |
| 556 | + | |
| 557 | + this.visible = false | |
| 558 | + this.$emit('refresh') | |
| 559 | + } else { | |
| 560 | + this.$message.error(response.msg || '修改失败') | |
| 561 | + } | |
| 562 | + } catch (error) { | |
| 563 | + console.error('修改失败:', error) | |
| 564 | + this.$message.error('修改失败') | |
| 565 | + } finally { | |
| 566 | + this.submitting = false | |
| 567 | + } | |
| 568 | + }) | |
| 569 | + }, | |
| 570 | + | |
| 571 | + handleClose() { | |
| 572 | + this.visible = false | |
| 573 | + this.$refs.form && this.$refs.form.resetFields() | |
| 574 | + this.dataForm = { | |
| 575 | + applicationUserId: '', | |
| 576 | + applicationUserName: '', | |
| 577 | + applicationStoreId: '', | |
| 578 | + applicationTime: null, | |
| 579 | + amount: '0.00', | |
| 580 | + selectedPurchaseRecordIds: [] | |
| 581 | + } | |
| 582 | + this.selectedPurchaseRecords = [] | |
| 583 | + this.initialPurchaseRecordIds = [] | |
| 584 | + this.applicationId = null | |
| 585 | + }, | |
| 586 | + | |
| 587 | + formatMoney(amount) { | |
| 588 | + if (!amount) return '0.00' | |
| 589 | + return Number(amount).toLocaleString('zh-CN', { | |
| 590 | + minimumFractionDigits: 2, | |
| 591 | + maximumFractionDigits: 2 | |
| 592 | + }) | |
| 593 | + }, | |
| 594 | + | |
| 595 | + formatDateTime(dateTime) { | |
| 596 | + if (!dateTime) return '无' | |
| 597 | + const date = new Date(dateTime) | |
| 598 | + const year = date.getFullYear() | |
| 599 | + const month = String(date.getMonth() + 1).padStart(2, '0') | |
| 600 | + const day = String(date.getDate()).padStart(2, '0') | |
| 601 | + const hours = String(date.getHours()).padStart(2, '0') | |
| 602 | + const minutes = String(date.getMinutes()).padStart(2, '0') | |
| 603 | + return `${year}-${month}-${day} ${hours}:${minutes}` | |
| 604 | + } | |
| 605 | + } | |
| 606 | +} | |
| 607 | +</script> | |
| 608 | + | |
| 609 | +<style lang="scss" scoped> | |
| 610 | +.form-content { | |
| 611 | + overflow-y: auto; | |
| 612 | + padding: 0 10px; | |
| 613 | +} | |
| 614 | + | |
| 615 | +.form-card { | |
| 616 | + margin-bottom: 20px; | |
| 617 | + | |
| 618 | + .card-header { | |
| 619 | + display: flex; | |
| 620 | + align-items: center; | |
| 621 | + gap: 8px; | |
| 622 | + font-weight: 600; | |
| 623 | + color: #303133; | |
| 624 | + | |
| 625 | + i { | |
| 626 | + font-size: 18px; | |
| 627 | + color: #409EFF; | |
| 628 | + } | |
| 629 | + | |
| 630 | + .card-title { | |
| 631 | + flex: 1; | |
| 632 | + } | |
| 633 | + } | |
| 634 | +} | |
| 635 | + | |
| 636 | +.selected-count { | |
| 637 | + margin-left: 12px; | |
| 638 | + color: #67C23A; | |
| 639 | + font-size: 14px; | |
| 640 | +} | |
| 641 | + | |
| 642 | +.purchase-list { | |
| 643 | + margin-top: 16px; | |
| 644 | +} | |
| 645 | + | |
| 646 | +.purchase-dialog-content { | |
| 647 | + .purchase-search { | |
| 648 | + margin-bottom: 16px; | |
| 649 | + } | |
| 650 | + | |
| 651 | + .purchase-pagination { | |
| 652 | + margin-top: 16px; | |
| 653 | + text-align: right; | |
| 654 | + } | |
| 655 | +} | |
| 656 | +</style> | |
| 657 | + | ... | ... |
antis-ncc-admin/src/views/lqReimbursementApplication/form-dialog.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <el-dialog | |
| 3 | + title="创建报销申请" | |
| 4 | + :visible.sync="visible" | |
| 5 | + :close-on-click-modal="false" | |
| 6 | + class="NCC-dialog NCC-dialog_center" | |
| 7 | + width="1200px" | |
| 8 | + @close="handleClose" | |
| 9 | + > | |
| 10 | + <div class="form-content" v-loading="loading"> | |
| 11 | + <el-form | |
| 12 | + ref="form" | |
| 13 | + :model="dataForm" | |
| 14 | + :rules="rules" | |
| 15 | + label-width="120px" | |
| 16 | + label-position="right" | |
| 17 | + > | |
| 18 | + <!-- 基本信息 --> | |
| 19 | + <el-card class="form-card" shadow="hover"> | |
| 20 | + <div slot="header" class="card-header"> | |
| 21 | + <i class="el-icon-document"></i> | |
| 22 | + <span class="card-title">基本信息</span> | |
| 23 | + </div> | |
| 24 | + <el-row :gutter="20"> | |
| 25 | + <el-col :span="12"> | |
| 26 | + <el-form-item label="申请人" prop="applicationUserId"> | |
| 27 | + <user-select | |
| 28 | + v-model="dataForm.applicationUserId" | |
| 29 | + placeholder="请选择申请人" | |
| 30 | + clearable | |
| 31 | + @change="handleUserChange" | |
| 32 | + /> | |
| 33 | + </el-form-item> | |
| 34 | + </el-col> | |
| 35 | + <el-col :span="12"> | |
| 36 | + <el-form-item label="申请人姓名" prop="applicationUserName"> | |
| 37 | + <el-input | |
| 38 | + v-model="dataForm.applicationUserName" | |
| 39 | + placeholder="自动填充" | |
| 40 | + readonly | |
| 41 | + /> | |
| 42 | + </el-form-item> | |
| 43 | + </el-col> | |
| 44 | + <el-col :span="12"> | |
| 45 | + <el-form-item label="申请门店" prop="applicationStoreId"> | |
| 46 | + <el-select | |
| 47 | + v-model="dataForm.applicationStoreId" | |
| 48 | + placeholder="自动填充" | |
| 49 | + disabled | |
| 50 | + style="width: 100%" | |
| 51 | + > | |
| 52 | + <el-option | |
| 53 | + v-for="store in storeOptions" | |
| 54 | + :key="store.id" | |
| 55 | + :label="store.dm" | |
| 56 | + :value="store.id" | |
| 57 | + /> | |
| 58 | + </el-select> | |
| 59 | + </el-form-item> | |
| 60 | + </el-col> | |
| 61 | + <el-col :span="12"> | |
| 62 | + <el-form-item label="申请时间" prop="applicationTime"> | |
| 63 | + <el-date-picker | |
| 64 | + v-model="dataForm.applicationTime" | |
| 65 | + type="datetime" | |
| 66 | + placeholder="请选择申请时间" | |
| 67 | + format="yyyy-MM-dd HH:mm:ss" | |
| 68 | + value-format="timestamp" | |
| 69 | + style="width: 100%" | |
| 70 | + /> | |
| 71 | + </el-form-item> | |
| 72 | + </el-col> | |
| 73 | + <el-col :span="12"> | |
| 74 | + <el-form-item label="总金额" prop="amount"> | |
| 75 | + <el-input | |
| 76 | + v-model="dataForm.amount" | |
| 77 | + placeholder="自动计算" | |
| 78 | + readonly | |
| 79 | + > | |
| 80 | + <template slot="prepend">¥</template> | |
| 81 | + </el-input> | |
| 82 | + </el-form-item> | |
| 83 | + </el-col> | |
| 84 | + </el-row> | |
| 85 | + </el-card> | |
| 86 | + | |
| 87 | + <!-- 选择购买记录 --> | |
| 88 | + <el-card class="form-card" shadow="hover"> | |
| 89 | + <div slot="header" class="card-header"> | |
| 90 | + <i class="el-icon-shopping-cart-full"></i> | |
| 91 | + <span class="card-title">选择购买记录</span> | |
| 92 | + </div> | |
| 93 | + <div> | |
| 94 | + <el-button | |
| 95 | + type="primary" | |
| 96 | + icon="el-icon-plus" | |
| 97 | + @click="openPurchaseDialog" | |
| 98 | + > | |
| 99 | + 选择购买记录 | |
| 100 | + </el-button> | |
| 101 | + <span v-if="selectedPurchaseRecords.length > 0" class="selected-count"> | |
| 102 | + 已选择 {{ selectedPurchaseRecords.length }} 条记录 | |
| 103 | + </span> | |
| 104 | + </div> | |
| 105 | + | |
| 106 | + <!-- 已选择的购买记录列表 --> | |
| 107 | + <div v-if="selectedPurchaseRecords.length > 0" class="purchase-list"> | |
| 108 | + <el-table :data="selectedPurchaseRecords" border size="small"> | |
| 109 | + <el-table-column prop="reimbursementCategoryName" label="分类名称" /> | |
| 110 | + <el-table-column prop="unitPrice" label="单价" > | |
| 111 | + <template slot-scope="scope"> | |
| 112 | + ¥{{ formatMoney(scope.row.unitPrice) }} | |
| 113 | + </template> | |
| 114 | + </el-table-column> | |
| 115 | + <el-table-column prop="quantity" label="数量" /> | |
| 116 | + <el-table-column prop="amount" label="金额" > | |
| 117 | + <template slot-scope="scope"> | |
| 118 | + ¥{{ formatMoney(scope.row.amount) }} | |
| 119 | + </template> | |
| 120 | + </el-table-column> | |
| 121 | + <el-table-column prop="purchaseTime" label="购买时间" width="180"> | |
| 122 | + <template slot-scope="scope"> | |
| 123 | + {{ formatDateTime(scope.row.purchaseTime) }} | |
| 124 | + </template> | |
| 125 | + </el-table-column> | |
| 126 | + <el-table-column label="操作" width="100" align="center"> | |
| 127 | + <template slot-scope="scope"> | |
| 128 | + <el-button | |
| 129 | + type="text" | |
| 130 | + icon="el-icon-delete" | |
| 131 | + @click="removePurchaseRecord(scope.$index)" | |
| 132 | + style="color: #F56C6C" | |
| 133 | + > | |
| 134 | + 移除 | |
| 135 | + </el-button> | |
| 136 | + </template> | |
| 137 | + </el-table-column> | |
| 138 | + </el-table> | |
| 139 | + </div> | |
| 140 | + </el-card> | |
| 141 | + | |
| 142 | + <!-- 审批节点配置 --> | |
| 143 | + <el-card class="form-card" shadow="hover"> | |
| 144 | + <div slot="header" class="card-header"> | |
| 145 | + <i class="el-icon-s-order"></i> | |
| 146 | + <span class="card-title">审批节点配置(1-5个节点)</span> | |
| 147 | + <el-button | |
| 148 | + type="text" | |
| 149 | + icon="el-icon-plus" | |
| 150 | + @click="addNode" | |
| 151 | + style="margin-left: auto;" | |
| 152 | + :disabled="dataForm.nodes.length >= 5" | |
| 153 | + > | |
| 154 | + 添加节点 | |
| 155 | + </el-button> | |
| 156 | + </div> | |
| 157 | + <div class="nodes-list"> | |
| 158 | + <div | |
| 159 | + v-for="(node, index) in dataForm.nodes" | |
| 160 | + :key="index" | |
| 161 | + class="node-item" | |
| 162 | + > | |
| 163 | + <div class="node-header"> | |
| 164 | + <span class="node-order">节点 {{ node.nodeOrder }}</span> | |
| 165 | + <el-button | |
| 166 | + type="text" | |
| 167 | + icon="el-icon-delete" | |
| 168 | + @click="removeNode(index)" | |
| 169 | + style="color: #F56C6C" | |
| 170 | + :disabled="dataForm.nodes.length <= 1" | |
| 171 | + > | |
| 172 | + 删除 | |
| 173 | + </el-button> | |
| 174 | + </div> | |
| 175 | + <el-row :gutter="20"> | |
| 176 | + <el-col :span="12"> | |
| 177 | + <el-form-item | |
| 178 | + :label="`节点${node.nodeOrder}名称`" | |
| 179 | + :prop="`nodes.${index}.nodeName`" | |
| 180 | + :rules="{ required: true, message: '请输入节点名称', trigger: 'blur' }" | |
| 181 | + > | |
| 182 | + <el-input | |
| 183 | + v-model="node.nodeName" | |
| 184 | + placeholder="请输入节点名称" | |
| 185 | + /> | |
| 186 | + </el-form-item> | |
| 187 | + </el-col> | |
| 188 | + <el-col :span="12"> | |
| 189 | + <el-form-item | |
| 190 | + :label="`节点${node.nodeOrder}类型`" | |
| 191 | + :prop="`nodes.${index}.approvalType`" | |
| 192 | + :rules="{ required: true, message: '请选择审批类型', trigger: 'change' }" | |
| 193 | + > | |
| 194 | + <el-select | |
| 195 | + v-model="node.approvalType" | |
| 196 | + placeholder="请选择审批类型" | |
| 197 | + style="width: 100%" | |
| 198 | + > | |
| 199 | + <el-option label="会签" value="会签" /> | |
| 200 | + <el-option label="或签" value="或签" /> | |
| 201 | + </el-select> | |
| 202 | + </el-form-item> | |
| 203 | + </el-col> | |
| 204 | + <el-col :span="24"> | |
| 205 | + <el-form-item | |
| 206 | + :label="`节点${node.nodeOrder}审批人`" | |
| 207 | + :prop="`nodes.${index}.approverIds`" | |
| 208 | + :rules="{ required: true, message: '请选择至少一个审批人', trigger: 'change' }" | |
| 209 | + > | |
| 210 | + <user-select | |
| 211 | + v-model="node.approverIds" | |
| 212 | + placeholder="请选择审批人" | |
| 213 | + :multiple="true" | |
| 214 | + @change="(val) => handleApproverChange(index, val)" | |
| 215 | + style="width: 100%" | |
| 216 | + /> | |
| 217 | + </el-form-item> | |
| 218 | + </el-col> | |
| 219 | + <el-col :span="24" v-if="node.approverNames && node.approverNames.length > 0"> | |
| 220 | + <el-form-item label="已选审批人"> | |
| 221 | + <el-tag | |
| 222 | + v-for="(name, nameIndex) in node.approverNames" | |
| 223 | + :key="nameIndex" | |
| 224 | + style="margin-right: 8px;" | |
| 225 | + > | |
| 226 | + {{ name }} | |
| 227 | + </el-tag> | |
| 228 | + </el-form-item> | |
| 229 | + </el-col> | |
| 230 | + </el-row> | |
| 231 | + </div> | |
| 232 | + <div v-if="dataForm.nodes.length === 0" class="empty-nodes"> | |
| 233 | + <el-empty description="请添加审批节点(至少1个)" :image-size="80" /> | |
| 234 | + </div> | |
| 235 | + </div> | |
| 236 | + </el-card> | |
| 237 | + </el-form> | |
| 238 | + </div> | |
| 239 | + <span slot="footer" class="dialog-footer"> | |
| 240 | + <el-button @click="visible = false">取 消</el-button> | |
| 241 | + <el-button | |
| 242 | + type="primary" | |
| 243 | + @click="handleSubmit" | |
| 244 | + :loading="submitting" | |
| 245 | + > | |
| 246 | + 创建并提交审批 | |
| 247 | + </el-button> | |
| 248 | + </span> | |
| 249 | + | |
| 250 | + <!-- 选择购买记录弹窗 --> | |
| 251 | + <el-dialog | |
| 252 | + title="选择购买记录" | |
| 253 | + :visible.sync="showPurchaseDialog" | |
| 254 | + width="900px" | |
| 255 | + append-to-body | |
| 256 | + > | |
| 257 | + <div class="purchase-dialog-content"> | |
| 258 | + <div class="purchase-search"> | |
| 259 | + <el-input | |
| 260 | + v-model="purchaseSearchKeyword" | |
| 261 | + placeholder="搜索分类名称" | |
| 262 | + clearable | |
| 263 | + @input="handlePurchaseSearch" | |
| 264 | + style="width: 300px" | |
| 265 | + > | |
| 266 | + <el-button slot="append" icon="el-icon-search" @click="loadPurchaseRecords" /> | |
| 267 | + </el-input> | |
| 268 | + </div> | |
| 269 | + <el-table | |
| 270 | + :data="purchaseRecordList" | |
| 271 | + v-loading="purchaseLoading" | |
| 272 | + @selection-change="handlePurchaseSelectionChange" | |
| 273 | + border | |
| 274 | + max-height="400" | |
| 275 | + > | |
| 276 | + <el-table-column type="selection" width="55"/> | |
| 277 | + <el-table-column prop="reimbursementCategoryName" label="分类名称" /> | |
| 278 | + <el-table-column prop="unitPrice" label="单价" > | |
| 279 | + <template slot-scope="scope"> | |
| 280 | + ¥{{ formatMoney(scope.row.unitPrice) }} | |
| 281 | + </template> | |
| 282 | + </el-table-column> | |
| 283 | + <el-table-column prop="quantity" label="数量" /> | |
| 284 | + <el-table-column prop="amount" label="金额" > | |
| 285 | + <template slot-scope="scope"> | |
| 286 | + ¥{{ formatMoney(scope.row.amount) }} | |
| 287 | + </template> | |
| 288 | + </el-table-column> | |
| 289 | + <el-table-column prop="purchaseTime" label="购买时间" width="180"> | |
| 290 | + <template slot-scope="scope"> | |
| 291 | + {{ formatDateTime(scope.row.purchaseTime) }} | |
| 292 | + </template> | |
| 293 | + </el-table-column> | |
| 294 | + </el-table> | |
| 295 | + <div class="purchase-pagination"> | |
| 296 | + <el-pagination | |
| 297 | + @size-change="handlePurchaseSizeChange" | |
| 298 | + @current-change="handlePurchaseCurrentChange" | |
| 299 | + :current-page="purchaseQuery.currentPage" | |
| 300 | + :page-sizes="[10, 20, 50]" | |
| 301 | + :page-size="purchaseQuery.pageSize" | |
| 302 | + layout="total, sizes, prev, pager, next" | |
| 303 | + :total="purchaseTotal" | |
| 304 | + /> | |
| 305 | + </div> | |
| 306 | + </div> | |
| 307 | + <span slot="footer" class="dialog-footer"> | |
| 308 | + <el-button @click="showPurchaseDialog = false">取 消</el-button> | |
| 309 | + <el-button type="primary" @click="confirmPurchaseSelection">确 定</el-button> | |
| 310 | + </span> | |
| 311 | + </el-dialog> | |
| 312 | + </el-dialog> | |
| 313 | +</template> | |
| 314 | + | |
| 315 | +<script> | |
| 316 | +import request from '@/utils/request' | |
| 317 | +import { getUserInfoList } from '@/api/permission/user' | |
| 318 | + | |
| 319 | +export default { | |
| 320 | + name: 'FormDialog', | |
| 321 | + data() { | |
| 322 | + return { | |
| 323 | + visible: false, | |
| 324 | + loading: false, | |
| 325 | + submitting: false, | |
| 326 | + storeOptions: [], | |
| 327 | + dataForm: { | |
| 328 | + applicationUserId: '', | |
| 329 | + applicationUserName: '', | |
| 330 | + applicationStoreId: '', | |
| 331 | + applicationTime: null, | |
| 332 | + amount: '0.00', | |
| 333 | + selectedPurchaseRecordIds: [], | |
| 334 | + nodes: [] | |
| 335 | + }, | |
| 336 | + rules: { | |
| 337 | + applicationStoreId: [ | |
| 338 | + { required: true, message: '请选择申请门店', trigger: 'change' } | |
| 339 | + ], | |
| 340 | + applicationTime: [ | |
| 341 | + { required: true, message: '请选择申请时间', trigger: 'change' } | |
| 342 | + ], | |
| 343 | + amount: [ | |
| 344 | + { required: true, message: '总金额不能为空', trigger: 'blur' } | |
| 345 | + ], | |
| 346 | + selectedPurchaseRecordIds: [ | |
| 347 | + { required: true, message: '请至少选择一条购买记录', trigger: 'change', validator: this.validatePurchaseRecords } | |
| 348 | + ] | |
| 349 | + }, | |
| 350 | + selectedPurchaseRecords: [], | |
| 351 | + showPurchaseDialog: false, | |
| 352 | + purchaseRecordList: [], | |
| 353 | + purchaseLoading: false, | |
| 354 | + purchaseSearchKeyword: '', | |
| 355 | + purchaseQuery: { | |
| 356 | + currentPage: 1, | |
| 357 | + pageSize: 20, | |
| 358 | + reimbursementCategoryName: '' | |
| 359 | + }, | |
| 360 | + purchaseTotal: 0, | |
| 361 | + purchaseSelection: [] | |
| 362 | + } | |
| 363 | + }, | |
| 364 | + created() { | |
| 365 | + this.loadStoreOptions() | |
| 366 | + // 初始化1个节点 | |
| 367 | + this.initNodes() | |
| 368 | + }, | |
| 369 | + methods: { | |
| 370 | + openPurchaseDialog() { | |
| 371 | + this.showPurchaseDialog = true | |
| 372 | + this.loadPurchaseRecords() | |
| 373 | + }, | |
| 374 | + async init() { | |
| 375 | + this.visible = true | |
| 376 | + this.loading = false | |
| 377 | + this.submitting = false | |
| 378 | + | |
| 379 | + // 获取当前登录用户信息 | |
| 380 | + const currentUser = this.$store.getters.userInfo | |
| 381 | + const defaultUserId = currentUser && (currentUser.userId || currentUser.id) ? (currentUser.userId || currentUser.id) : '' | |
| 382 | + const defaultUserName = currentUser && (currentUser.realName || currentUser.fullName || currentUser.userName) ? (currentUser.realName || currentUser.fullName || currentUser.userName) : '' | |
| 383 | + | |
| 384 | + this.dataForm = { | |
| 385 | + applicationUserId: defaultUserId, | |
| 386 | + applicationUserName: defaultUserName, | |
| 387 | + applicationStoreId: '', | |
| 388 | + applicationTime: new Date().getTime(), | |
| 389 | + amount: '0.00', | |
| 390 | + selectedPurchaseRecordIds: [], | |
| 391 | + nodes: [] | |
| 392 | + } | |
| 393 | + this.selectedPurchaseRecords = [] | |
| 394 | + this.initNodes() | |
| 395 | + | |
| 396 | + // 如果有默认用户,自动填充门店 | |
| 397 | + if (defaultUserId) { | |
| 398 | + this.handleUserChange(defaultUserId) | |
| 399 | + } | |
| 400 | + }, | |
| 401 | + | |
| 402 | + initNodes() { | |
| 403 | + // 初始化1个节点 | |
| 404 | + this.dataForm.nodes = [ | |
| 405 | + { | |
| 406 | + nodeOrder: 1, | |
| 407 | + nodeName: '', | |
| 408 | + approvalType: '', | |
| 409 | + approverIds: '', // user-select组件期望字符串,不是数组 | |
| 410 | + approverNames: [] | |
| 411 | + } | |
| 412 | + ] | |
| 413 | + }, | |
| 414 | + | |
| 415 | + async loadStoreOptions() { | |
| 416 | + try { | |
| 417 | + const response = await request({ | |
| 418 | + url: '/api/Extend/LqMdxx', | |
| 419 | + method: 'GET', | |
| 420 | + data: { | |
| 421 | + currentPage: 1, | |
| 422 | + pageSize: 1000 | |
| 423 | + } | |
| 424 | + }) | |
| 425 | + if (response.code === 200 && response.data && response.data.list) { | |
| 426 | + this.storeOptions = response.data.list | |
| 427 | + } | |
| 428 | + } catch (error) { | |
| 429 | + console.error('加载门店列表失败:', error) | |
| 430 | + } | |
| 431 | + }, | |
| 432 | + | |
| 433 | + // 申请人改变时,自动填充申请人姓名和申请门店 | |
| 434 | + handleUserChange(userId) { | |
| 435 | + if (userId) { | |
| 436 | + // 处理userId可能是字符串(逗号分隔)的情况,取第一个 | |
| 437 | + const userIdArray = typeof userId === 'string' | |
| 438 | + ? userId.split(',').filter(id => id && id.trim()) | |
| 439 | + : [userId] | |
| 440 | + const actualUserId = userIdArray.length > 0 ? userIdArray[0] : userId | |
| 441 | + | |
| 442 | + // 获取用户信息 | |
| 443 | + request({ | |
| 444 | + url: '/api/permission/Users/' + actualUserId, | |
| 445 | + method: 'GET', | |
| 446 | + }).then(res => { | |
| 447 | + if (res.code === 200 && res.data) { | |
| 448 | + this.dataForm.applicationUserName = res.data.realName || res.data.userName || res.data.fullName || '' | |
| 449 | + this.dataForm.applicationStoreId = res.data.mdid || '' | |
| 450 | + } | |
| 451 | + }).catch(error => { | |
| 452 | + console.error('获取用户信息失败:', error) | |
| 453 | + }) | |
| 454 | + } else { | |
| 455 | + // 清空申请人时,也清空姓名和门店 | |
| 456 | + this.dataForm.applicationUserName = '' | |
| 457 | + this.dataForm.applicationStoreId = '' | |
| 458 | + } | |
| 459 | + }, | |
| 460 | + | |
| 461 | + addNode() { | |
| 462 | + if (this.dataForm.nodes.length >= 5) { | |
| 463 | + this.$message.warning('最多只能添加5个节点') | |
| 464 | + return | |
| 465 | + } | |
| 466 | + const nextOrder = this.dataForm.nodes.length + 1 | |
| 467 | + this.dataForm.nodes.push({ | |
| 468 | + nodeOrder: nextOrder, | |
| 469 | + nodeName: '', | |
| 470 | + approvalType: '', | |
| 471 | + approverIds: '', // user-select组件期望字符串,不是数组 | |
| 472 | + approverNames: [] | |
| 473 | + }) | |
| 474 | + }, | |
| 475 | + | |
| 476 | + removeNode(index) { | |
| 477 | + if (this.dataForm.nodes.length <= 1) { | |
| 478 | + this.$message.warning('至少需要1个节点') | |
| 479 | + return | |
| 480 | + } | |
| 481 | + this.dataForm.nodes.splice(index, 1) | |
| 482 | + // 重新排序 | |
| 483 | + this.dataForm.nodes.forEach((node, idx) => { | |
| 484 | + node.nodeOrder = idx + 1 | |
| 485 | + }) | |
| 486 | + }, | |
| 487 | + | |
| 488 | + async handleApproverChange(index, userIds) { | |
| 489 | + const node = this.dataForm.nodes[index] | |
| 490 | + | |
| 491 | + // user-select组件返回的是字符串(逗号分隔的ID) | |
| 492 | + // 但我们需要保存为字符串格式,因为组件期望字符串 | |
| 493 | + const idsString = userIds || '' | |
| 494 | + node.approverIds = idsString | |
| 495 | + | |
| 496 | + // 转换为数组用于获取用户名 | |
| 497 | + const idsArray = idsString ? idsString.split(',').filter(id => id && id.trim()) : [] | |
| 498 | + | |
| 499 | + // 获取用户名列表 | |
| 500 | + if (idsArray.length > 0) { | |
| 501 | + try { | |
| 502 | + const response = await getUserInfoList(idsArray) | |
| 503 | + if (response.code === 200 && response.data && response.data.list) { | |
| 504 | + node.approverNames = response.data.list.map(user => user.fullName || user.realName || user.id) | |
| 505 | + } else { | |
| 506 | + node.approverNames = idsArray | |
| 507 | + } | |
| 508 | + } catch (error) { | |
| 509 | + console.error('获取用户名失败:', error) | |
| 510 | + // 如果获取失败,使用ID作为名称 | |
| 511 | + node.approverNames = idsArray | |
| 512 | + } | |
| 513 | + } else { | |
| 514 | + node.approverNames = [] | |
| 515 | + } | |
| 516 | + }, | |
| 517 | + | |
| 518 | + async loadPurchaseRecords() { | |
| 519 | + this.purchaseLoading = true | |
| 520 | + try { | |
| 521 | + const params = { | |
| 522 | + currentPage: this.purchaseQuery.currentPage, | |
| 523 | + pageSize: this.purchaseQuery.pageSize, | |
| 524 | + approveStatus: '未审批', // 只选择未审批的购买记录 | |
| 525 | + createUserStoreId: this.dataForm.applicationStoreId || '暂无', | |
| 526 | + } | |
| 527 | + if (this.purchaseSearchKeyword) { | |
| 528 | + params.reimbursementCategoryName = this.purchaseSearchKeyword | |
| 529 | + } | |
| 530 | + const response = await request({ | |
| 531 | + url: '/api/Extend/LqPurchaseRecords', | |
| 532 | + method: 'GET', | |
| 533 | + data: params | |
| 534 | + }) | |
| 535 | + if (response.code === 200 && response.data) { | |
| 536 | + this.purchaseRecordList = response.data.list || [] | |
| 537 | + this.purchaseTotal = (response.data.pagination && response.data.pagination.Total) || 0 | |
| 538 | + } | |
| 539 | + } catch (error) { | |
| 540 | + console.error('加载购买记录失败:', error) | |
| 541 | + this.$message.error('加载购买记录失败') | |
| 542 | + } finally { | |
| 543 | + this.purchaseLoading = false | |
| 544 | + } | |
| 545 | + }, | |
| 546 | + | |
| 547 | + handlePurchaseSearch() { | |
| 548 | + this.purchaseQuery.currentPage = 1 | |
| 549 | + this.loadPurchaseRecords() | |
| 550 | + }, | |
| 551 | + | |
| 552 | + handlePurchaseSelectionChange(selection) { | |
| 553 | + this.purchaseSelection = selection | |
| 554 | + }, | |
| 555 | + | |
| 556 | + confirmPurchaseSelection() { | |
| 557 | + // 合并新选择的记录 | |
| 558 | + const newRecords = this.purchaseSelection.filter(item => { | |
| 559 | + return !this.selectedPurchaseRecords.find(record => record.id === item.id) | |
| 560 | + }) | |
| 561 | + this.selectedPurchaseRecords = [...this.selectedPurchaseRecords, ...newRecords] | |
| 562 | + this.calculateAmount() | |
| 563 | + this.showPurchaseDialog = false | |
| 564 | + }, | |
| 565 | + | |
| 566 | + removePurchaseRecord(index) { | |
| 567 | + this.selectedPurchaseRecords.splice(index, 1) | |
| 568 | + this.calculateAmount() | |
| 569 | + }, | |
| 570 | + | |
| 571 | + calculateAmount() { | |
| 572 | + // 计算总金额 | |
| 573 | + let total = 0 | |
| 574 | + this.selectedPurchaseRecords.forEach(record => { | |
| 575 | + total += Number(record.amount || 0) | |
| 576 | + }) | |
| 577 | + this.dataForm.amount = total.toFixed(2) | |
| 578 | + this.dataForm.selectedPurchaseRecordIds = this.selectedPurchaseRecords.map(record => record.id) | |
| 579 | + }, | |
| 580 | + | |
| 581 | + handlePurchaseSizeChange(val) { | |
| 582 | + this.purchaseQuery.pageSize = val | |
| 583 | + this.loadPurchaseRecords() | |
| 584 | + }, | |
| 585 | + | |
| 586 | + handlePurchaseCurrentChange(val) { | |
| 587 | + this.purchaseQuery.currentPage = val | |
| 588 | + this.loadPurchaseRecords() | |
| 589 | + }, | |
| 590 | + | |
| 591 | + validatePurchaseRecords(rule, value, callback) { | |
| 592 | + if (!this.selectedPurchaseRecords || this.selectedPurchaseRecords.length === 0) { | |
| 593 | + callback(new Error('请至少选择一条购买记录')) | |
| 594 | + } else { | |
| 595 | + callback() | |
| 596 | + } | |
| 597 | + }, | |
| 598 | + | |
| 599 | + async handleSubmit() { | |
| 600 | + // 验证表单 | |
| 601 | + this.$refs.form.validate(async (valid) => { | |
| 602 | + if (!valid) { | |
| 603 | + return false | |
| 604 | + } | |
| 605 | + | |
| 606 | + // 验证节点数量 | |
| 607 | + if (this.dataForm.nodes.length < 1 || this.dataForm.nodes.length > 5) { | |
| 608 | + this.$message.warning('审批节点数量必须在1-5个之间') | |
| 609 | + return false | |
| 610 | + } | |
| 611 | + | |
| 612 | + // 验证每个节点 | |
| 613 | + for (let i = 0; i < this.dataForm.nodes.length; i++) { | |
| 614 | + const node = this.dataForm.nodes[i] | |
| 615 | + if (!node.nodeName) { | |
| 616 | + this.$message.warning(`请填写节点${node.nodeOrder}的名称`) | |
| 617 | + return false | |
| 618 | + } | |
| 619 | + if (!node.approvalType) { | |
| 620 | + this.$message.warning(`请选择节点${node.nodeOrder}的审批类型`) | |
| 621 | + return false | |
| 622 | + } | |
| 623 | + // 验证审批人:approverIds可能是字符串(逗号分隔)或数组 | |
| 624 | + let approverIdsArray = [] | |
| 625 | + if (node.approverIds) { | |
| 626 | + if (Array.isArray(node.approverIds)) { | |
| 627 | + approverIdsArray = node.approverIds.filter(id => id) | |
| 628 | + } else if (typeof node.approverIds === 'string') { | |
| 629 | + approverIdsArray = node.approverIds.split(',').filter(id => id && id.trim()) | |
| 630 | + } | |
| 631 | + } | |
| 632 | + if (approverIdsArray.length === 0) { | |
| 633 | + this.$message.warning(`请为节点${node.nodeOrder}选择至少一个审批人`) | |
| 634 | + return false | |
| 635 | + } | |
| 636 | + } | |
| 637 | + | |
| 638 | + // 验证购买记录 | |
| 639 | + if (!this.selectedPurchaseRecords || this.selectedPurchaseRecords.length === 0) { | |
| 640 | + this.$message.warning('请至少选择一条购买记录') | |
| 641 | + return false | |
| 642 | + } | |
| 643 | + | |
| 644 | + this.submitting = true | |
| 645 | + try { | |
| 646 | + // 准备提交数据 | |
| 647 | + const submitData = { | |
| 648 | + applicationUserId: this.dataForm.applicationUserId || undefined, | |
| 649 | + applicationUserName: this.dataForm.applicationUserName || undefined, | |
| 650 | + applicationStoreId: this.dataForm.applicationStoreId, | |
| 651 | + applicationTime: this.dataForm.applicationTime, | |
| 652 | + amount: this.dataForm.amount, | |
| 653 | + selectedPurchaseRecordIds: this.dataForm.selectedPurchaseRecordIds, | |
| 654 | + nodes: this.dataForm.nodes.map(node => { | |
| 655 | + // user-select组件返回的是字符串(逗号分隔),需要转换为数组 | |
| 656 | + let approverIds = [] | |
| 657 | + if (node.approverIds) { | |
| 658 | + if (Array.isArray(node.approverIds)) { | |
| 659 | + approverIds = node.approverIds.filter(id => id) | |
| 660 | + } else if (typeof node.approverIds === 'string') { | |
| 661 | + approverIds = node.approverIds.split(',').filter(id => id && id.trim()) | |
| 662 | + } | |
| 663 | + } | |
| 664 | + | |
| 665 | + return { | |
| 666 | + nodeOrder: node.nodeOrder, | |
| 667 | + nodeName: node.nodeName, | |
| 668 | + approvalType: node.approvalType, | |
| 669 | + approverIds: approverIds, | |
| 670 | + approverNames: node.approverNames && node.approverNames.length > 0 | |
| 671 | + ? node.approverNames | |
| 672 | + : approverIds // 如果没有名称,使用ID | |
| 673 | + } | |
| 674 | + }) | |
| 675 | + } | |
| 676 | + | |
| 677 | + // 创建报销申请 | |
| 678 | + const response = await request({ | |
| 679 | + url: '/api/Extend/LqReimbursementApplication', | |
| 680 | + method: 'POST', | |
| 681 | + data: submitData | |
| 682 | + }) | |
| 683 | + | |
| 684 | + if (response.code === 200) { | |
| 685 | + this.$message.success('创建成功') | |
| 686 | + | |
| 687 | + // 创建成功后,自动提交审批 | |
| 688 | + let applicationId = null | |
| 689 | + if (response.data) { | |
| 690 | + // 如果返回的是对象,取id字段 | |
| 691 | + if (typeof response.data === 'object' && response.data.id) { | |
| 692 | + applicationId = response.data.id | |
| 693 | + } else if (typeof response.data === 'string') { | |
| 694 | + // 如果返回的是字符串,直接使用 | |
| 695 | + applicationId = response.data | |
| 696 | + } else { | |
| 697 | + // 如果返回的是其他类型,尝试转换为字符串 | |
| 698 | + applicationId = String(response.data) | |
| 699 | + } | |
| 700 | + } | |
| 701 | + | |
| 702 | + if (applicationId) { | |
| 703 | + try { | |
| 704 | + await request({ | |
| 705 | + url: `/api/Extend/LqReimbursementApplication/${applicationId}/Actions/SubmitApproval`, | |
| 706 | + method: 'POST' | |
| 707 | + }) | |
| 708 | + this.$message.success('已自动提交审批') | |
| 709 | + } catch (error) { | |
| 710 | + console.error('提交审批失败:', error) | |
| 711 | + this.$message.warning('创建成功,但提交审批失败,请手动提交') | |
| 712 | + } | |
| 713 | + } else { | |
| 714 | + this.$message.warning('创建成功,但无法获取申请ID,请手动提交审批') | |
| 715 | + } | |
| 716 | + | |
| 717 | + this.visible = false | |
| 718 | + this.$emit('refresh') | |
| 719 | + } else { | |
| 720 | + this.$message.error(response.msg || '创建失败') | |
| 721 | + } | |
| 722 | + } catch (error) { | |
| 723 | + console.error('创建失败:', error) | |
| 724 | + this.$message.error('创建失败') | |
| 725 | + } finally { | |
| 726 | + this.submitting = false | |
| 727 | + } | |
| 728 | + }) | |
| 729 | + }, | |
| 730 | + | |
| 731 | + handleClose() { | |
| 732 | + this.visible = false | |
| 733 | + this.$refs.form && this.$refs.form.resetFields() | |
| 734 | + this.dataForm = { | |
| 735 | + applicationUserId: '', | |
| 736 | + applicationUserName: '', | |
| 737 | + applicationStoreId: '', | |
| 738 | + applicationTime: new Date().getTime(), | |
| 739 | + amount: '0.00', | |
| 740 | + selectedPurchaseRecordIds: [], | |
| 741 | + nodes: [] | |
| 742 | + } | |
| 743 | + this.selectedPurchaseRecords = [] | |
| 744 | + this.initNodes() | |
| 745 | + }, | |
| 746 | + | |
| 747 | + formatMoney(amount) { | |
| 748 | + if (!amount) return '0.00' | |
| 749 | + return Number(amount).toLocaleString('zh-CN', { | |
| 750 | + minimumFractionDigits: 2, | |
| 751 | + maximumFractionDigits: 2 | |
| 752 | + }) | |
| 753 | + }, | |
| 754 | + | |
| 755 | + formatDateTime(dateTime) { | |
| 756 | + if (!dateTime) return '无' | |
| 757 | + const date = new Date(dateTime) | |
| 758 | + const year = date.getFullYear() | |
| 759 | + const month = String(date.getMonth() + 1).padStart(2, '0') | |
| 760 | + const day = String(date.getDate()).padStart(2, '0') | |
| 761 | + const hours = String(date.getHours()).padStart(2, '0') | |
| 762 | + const minutes = String(date.getMinutes()).padStart(2, '0') | |
| 763 | + return `${year}-${month}-${day} ${hours}:${minutes}` | |
| 764 | + } | |
| 765 | + } | |
| 766 | +} | |
| 767 | +</script> | |
| 768 | + | |
| 769 | +<style lang="scss" scoped> | |
| 770 | +.form-content { | |
| 771 | + // max-height: 70vh; | |
| 772 | + overflow-y: auto; | |
| 773 | + padding: 0 10px; | |
| 774 | +} | |
| 775 | + | |
| 776 | +.form-card { | |
| 777 | + margin-bottom: 20px; | |
| 778 | + | |
| 779 | + .card-header { | |
| 780 | + display: flex; | |
| 781 | + align-items: center; | |
| 782 | + gap: 8px; | |
| 783 | + font-weight: 600; | |
| 784 | + color: #303133; | |
| 785 | + | |
| 786 | + i { | |
| 787 | + font-size: 18px; | |
| 788 | + color: #409EFF; | |
| 789 | + } | |
| 790 | + | |
| 791 | + .card-title { | |
| 792 | + flex: 1; | |
| 793 | + } | |
| 794 | + } | |
| 795 | +} | |
| 796 | + | |
| 797 | +.selected-count { | |
| 798 | + margin-left: 12px; | |
| 799 | + color: #67C23A; | |
| 800 | + font-size: 14px; | |
| 801 | +} | |
| 802 | + | |
| 803 | +.purchase-list { | |
| 804 | + margin-top: 16px; | |
| 805 | +} | |
| 806 | + | |
| 807 | +.nodes-list { | |
| 808 | + .node-item { | |
| 809 | + padding: 16px; | |
| 810 | + margin-bottom: 16px; | |
| 811 | + border: 1px solid #e4e7ed; | |
| 812 | + border-radius: 8px; | |
| 813 | + background: #fafafa; | |
| 814 | + | |
| 815 | + .node-header { | |
| 816 | + display: flex; | |
| 817 | + justify-content: space-between; | |
| 818 | + align-items: center; | |
| 819 | + margin-bottom: 16px; | |
| 820 | + padding-bottom: 12px; | |
| 821 | + border-bottom: 1px solid #e4e7ed; | |
| 822 | + | |
| 823 | + .node-order { | |
| 824 | + font-weight: 600; | |
| 825 | + color: #409EFF; | |
| 826 | + font-size: 16px; | |
| 827 | + } | |
| 828 | + } | |
| 829 | + } | |
| 830 | + | |
| 831 | + .empty-nodes { | |
| 832 | + padding: 40px; | |
| 833 | + text-align: center; | |
| 834 | + } | |
| 835 | +} | |
| 836 | + | |
| 837 | +.purchase-dialog-content { | |
| 838 | + .purchase-search { | |
| 839 | + margin-bottom: 16px; | |
| 840 | + } | |
| 841 | + | |
| 842 | + .purchase-pagination { | |
| 843 | + margin-top: 16px; | |
| 844 | + text-align: right; | |
| 845 | + } | |
| 846 | +} | |
| 847 | +</style> | |
| 848 | + | ... | ... |
antis-ncc-admin/src/views/lqReimbursementApplication/index.vue
| 1 | 1 | <template> |
| 2 | 2 | <div class="app-container"> |
| 3 | - <!-- 页面头部 --> | |
| 4 | - <div class="page-header"> | |
| 5 | - <div class="header-left"> | |
| 6 | - <h2>报销申请管理</h2> | |
| 7 | - <p class="page-desc">管理报销申请,包括待办审批、所有申请列表等</p> | |
| 8 | - </div> | |
| 9 | - <div class="header-right"> | |
| 10 | - <el-button | |
| 11 | - icon="el-icon-refresh" | |
| 12 | - @click="getList" | |
| 13 | - :loading="listLoading" | |
| 14 | - > | |
| 15 | - 刷新 | |
| 16 | - </el-button> | |
| 17 | - </div> | |
| 18 | - </div> | |
| 19 | - | |
| 20 | 3 | <!-- 标签页切换 --> |
| 21 | 4 | <div class="tabs-container"> |
| 22 | 5 | <el-tabs v-model="activeTab" @tab-click="handleTabChange"> |
| 23 | - <el-tab-pane label="所有待办" name="allPending"> | |
| 6 | + <!-- <el-tab-pane label="所有待办" name="allPending"> | |
| 24 | 7 | <template slot="label"> |
| 25 | 8 | <span><i class="el-icon-s-order"></i> 所有待办</span> |
| 26 | 9 | </template> |
| 27 | - </el-tab-pane> | |
| 10 | + </el-tab-pane> --> | |
| 28 | 11 | <el-tab-pane label="我的待办" name="myPending"> |
| 29 | 12 | <template slot="label"> |
| 30 | 13 | <span><i class="el-icon-user"></i> 我的待办</span> |
| ... | ... | @@ -40,17 +23,25 @@ |
| 40 | 23 | |
| 41 | 24 | <!-- 搜索区域 --> |
| 42 | 25 | <div class="search-container"> |
| 43 | - <el-form :model="queryParams" ref="queryForm" :inline="true" label-width="100px" label-position="right"> | |
| 44 | - <el-form-item label="申请门店ID" prop="applicationStoreId"> | |
| 45 | - <el-input | |
| 26 | + <el-form :model="queryParams" ref="queryForm" :inline="true" label-width="80px" label-position="right"> | |
| 27 | + <el-form-item label="申请门店" prop="applicationStoreId"> | |
| 28 | + <el-select | |
| 46 | 29 | v-model="queryParams.applicationStoreId" |
| 47 | - placeholder="请输入申请门店ID" | |
| 30 | + placeholder="请选择申请门店" | |
| 48 | 31 | clearable |
| 32 | + filterable | |
| 49 | 33 | style="width: 200px" |
| 50 | - /> | |
| 34 | + > | |
| 35 | + <el-option | |
| 36 | + v-for="store in storeOptions" | |
| 37 | + :key="store.id" | |
| 38 | + :label="store.dm" | |
| 39 | + :value="store.id" | |
| 40 | + /> | |
| 41 | + </el-select> | |
| 51 | 42 | </el-form-item> |
| 52 | 43 | <template v-if="activeTab === 'all'"> |
| 53 | - <el-form-item label="申请编号" prop="id"> | |
| 44 | + <!-- <el-form-item label="申请编号" prop="id"> | |
| 54 | 45 | <el-input |
| 55 | 46 | v-model="queryParams.id" |
| 56 | 47 | placeholder="请输入申请编号" |
| ... | ... | @@ -73,7 +64,7 @@ |
| 73 | 64 | clearable |
| 74 | 65 | style="width: 200px" |
| 75 | 66 | /> |
| 76 | - </el-form-item> | |
| 67 | + </el-form-item> --> | |
| 77 | 68 | <el-form-item label="申请时间" prop="applicationTime"> |
| 78 | 69 | <el-date-picker |
| 79 | 70 | v-model="queryParams.applicationTime" |
| ... | ... | @@ -86,14 +77,14 @@ |
| 86 | 77 | style="width: 240px" |
| 87 | 78 | /> |
| 88 | 79 | </el-form-item> |
| 89 | - <el-form-item label="金额" prop="amount"> | |
| 80 | + <!-- <el-form-item label="金额" prop="amount"> | |
| 90 | 81 | <el-input |
| 91 | 82 | v-model="queryParams.amount" |
| 92 | 83 | placeholder="请输入金额" |
| 93 | 84 | clearable |
| 94 | 85 | style="width: 200px" |
| 95 | 86 | /> |
| 96 | - </el-form-item> | |
| 87 | + </el-form-item> --> | |
| 97 | 88 | <el-form-item label="审批状态" prop="approveStatus"> |
| 98 | 89 | <el-select |
| 99 | 90 | v-model="queryParams.approveStatus" |
| ... | ... | @@ -108,18 +99,25 @@ |
| 108 | 99 | <el-option label="已退回" value="已退回" /> |
| 109 | 100 | </el-select> |
| 110 | 101 | </el-form-item> |
| 111 | - <el-form-item label="购买记录编号" prop="purchaseRecordsId"> | |
| 102 | + <!-- <el-form-item label="购买记录编号" prop="purchaseRecordsId"> | |
| 112 | 103 | <el-input |
| 113 | 104 | v-model="queryParams.purchaseRecordsId" |
| 114 | 105 | placeholder="请输入购买记录编号" |
| 115 | 106 | clearable |
| 116 | 107 | style="width: 200px" |
| 117 | 108 | /> |
| 118 | - </el-form-item> | |
| 109 | + </el-form-item> --> | |
| 119 | 110 | </template> |
| 120 | 111 | <el-form-item> |
| 121 | 112 | <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button> |
| 122 | 113 | <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button> |
| 114 | + <el-button | |
| 115 | + type="primary" | |
| 116 | + icon="el-icon-plus" | |
| 117 | + @click="handleAdd" | |
| 118 | + > | |
| 119 | + 新增 | |
| 120 | + </el-button> | |
| 123 | 121 | </el-form-item> |
| 124 | 122 | </el-form> |
| 125 | 123 | </div> |
| ... | ... | @@ -131,21 +129,20 @@ |
| 131 | 129 | :data="list" |
| 132 | 130 | border |
| 133 | 131 | stripe |
| 134 | - height="calc(100vh - 500px)" | |
| 135 | 132 | :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" |
| 136 | 133 | > |
| 137 | 134 | <!-- 申请编号 --> |
| 138 | - <el-table-column label="申请编号" width="180" align="center"> | |
| 135 | + <!-- <el-table-column label="申请编号" > | |
| 139 | 136 | <template slot-scope="scope"> |
| 140 | 137 | <div class="id-item"> |
| 141 | 138 | <i class="el-icon-document id-icon"></i> |
| 142 | 139 | <span>{{ scope.row.id || '无' }}</span> |
| 143 | 140 | </div> |
| 144 | 141 | </template> |
| 145 | - </el-table-column> | |
| 142 | + </el-table-column> --> | |
| 146 | 143 | |
| 147 | 144 | <!-- 申请人 --> |
| 148 | - <el-table-column label="申请人" width="140" align="center"> | |
| 145 | + <el-table-column label="申请人" > | |
| 149 | 146 | <template slot-scope="scope"> |
| 150 | 147 | <div class="user-item"> |
| 151 | 148 | <i class="el-icon-user user-icon"></i> |
| ... | ... | @@ -154,18 +151,18 @@ |
| 154 | 151 | </template> |
| 155 | 152 | </el-table-column> |
| 156 | 153 | |
| 157 | - <!-- 申请门店ID --> | |
| 158 | - <el-table-column label="申请门店ID" width="160" align="center"> | |
| 154 | + <!-- 申请门店 --> | |
| 155 | + <el-table-column label="申请门店" > | |
| 159 | 156 | <template slot-scope="scope"> |
| 160 | 157 | <div class="store-item"> |
| 161 | 158 | <i class="el-icon-shop store-icon"></i> |
| 162 | - <span>{{ scope.row.applicationStoreId || '无' }}</span> | |
| 159 | + <span>{{ scope.row.applicationStoreName || '无' }}</span> | |
| 163 | 160 | </div> |
| 164 | 161 | </template> |
| 165 | 162 | </el-table-column> |
| 166 | 163 | |
| 167 | 164 | <!-- 申请时间 --> |
| 168 | - <el-table-column label="申请时间" width="180" align="center"> | |
| 165 | + <el-table-column label="申请时间" > | |
| 169 | 166 | <template slot-scope="scope"> |
| 170 | 167 | <div class="time-item"> |
| 171 | 168 | <i class="el-icon-time time-icon"></i> |
| ... | ... | @@ -175,7 +172,7 @@ |
| 175 | 172 | </el-table-column> |
| 176 | 173 | |
| 177 | 174 | <!-- 金额 --> |
| 178 | - <el-table-column label="金额" width="140" align="center"> | |
| 175 | + <el-table-column label="金额" > | |
| 179 | 176 | <template slot-scope="scope"> |
| 180 | 177 | <div class="amount-item"> |
| 181 | 178 | <i class="el-icon-money amount-icon"></i> |
| ... | ... | @@ -185,7 +182,7 @@ |
| 185 | 182 | </el-table-column> |
| 186 | 183 | |
| 187 | 184 | <!-- 审批状态 --> |
| 188 | - <el-table-column label="审批状态" width="120" align="center"> | |
| 185 | + <el-table-column label="审批状态" > | |
| 189 | 186 | <template slot-scope="scope"> |
| 190 | 187 | <el-tag |
| 191 | 188 | :type="getStatusType(scope.row.approveStatus)" |
| ... | ... | @@ -197,7 +194,7 @@ |
| 197 | 194 | </el-table-column> |
| 198 | 195 | |
| 199 | 196 | <!-- 当前审批人 --> |
| 200 | - <el-table-column label="当前审批人" width="160" align="center" v-if="activeTab !== 'all'"> | |
| 197 | + <el-table-column label="当前审批人" v-if="activeTab !== 'all'"> | |
| 201 | 198 | <template slot-scope="scope"> |
| 202 | 199 | <div class="approver-item"> |
| 203 | 200 | <i class="el-icon-user-solid approver-icon"></i> |
| ... | ... | @@ -207,7 +204,7 @@ |
| 207 | 204 | </el-table-column> |
| 208 | 205 | |
| 209 | 206 | <!-- 当前节点 --> |
| 210 | - <el-table-column label="当前节点" width="120" align="center" v-if="activeTab !== 'all'"> | |
| 207 | + <el-table-column label="当前节点" v-if="activeTab !== 'all'"> | |
| 211 | 208 | <template slot-scope="scope"> |
| 212 | 209 | <div class="node-item"> |
| 213 | 210 | <i class="el-icon-s-order node-icon"></i> |
| ... | ... | @@ -217,7 +214,7 @@ |
| 217 | 214 | </el-table-column> |
| 218 | 215 | |
| 219 | 216 | <!-- 节点总数 --> |
| 220 | - <el-table-column label="节点总数" width="100" align="center" v-if="activeTab !== 'all'"> | |
| 217 | + <el-table-column label="节点总数" v-if="activeTab !== 'all'"> | |
| 221 | 218 | <template slot-scope="scope"> |
| 222 | 219 | <div class="node-count-item"> |
| 223 | 220 | <i class="el-icon-s-data node-count-icon"></i> |
| ... | ... | @@ -227,7 +224,7 @@ |
| 227 | 224 | </el-table-column> |
| 228 | 225 | |
| 229 | 226 | <!-- 操作 --> |
| 230 | - <el-table-column label="操作" width="200" align="left" fixed="right"> | |
| 227 | + <el-table-column label="操作" width="250" align="left" fixed="right"> | |
| 231 | 228 | <template slot-scope="scope"> |
| 232 | 229 | <div class="action-buttons"> |
| 233 | 230 | <el-button |
| ... | ... | @@ -248,6 +245,16 @@ |
| 248 | 245 | > |
| 249 | 246 | 审批历史 |
| 250 | 247 | </el-button> |
| 248 | + <el-button | |
| 249 | + v-if="scope.row.approveStatus === '已退回'" | |
| 250 | + type="text" | |
| 251 | + icon="el-icon-edit" | |
| 252 | + @click="handleEdit(scope.row)" | |
| 253 | + size="small" | |
| 254 | + class="edit-btn" | |
| 255 | + > | |
| 256 | + 修改 | |
| 257 | + </el-button> | |
| 251 | 258 | </div> |
| 252 | 259 | </template> |
| 253 | 260 | </el-table-column> |
| ... | ... | @@ -267,6 +274,13 @@ |
| 267 | 274 | /> |
| 268 | 275 | </div> |
| 269 | 276 | |
| 277 | + <!-- 创建表单弹窗 --> | |
| 278 | + <form-dialog | |
| 279 | + v-if="formVisible" | |
| 280 | + ref="FormDialog" | |
| 281 | + @refresh="getList" | |
| 282 | + /> | |
| 283 | + | |
| 270 | 284 | <!-- 详情/审批弹窗 --> |
| 271 | 285 | <detail-dialog |
| 272 | 286 | v-if="detailVisible" |
| ... | ... | @@ -279,28 +293,42 @@ |
| 279 | 293 | v-if="historyVisible" |
| 280 | 294 | ref="ApprovalHistoryDialog" |
| 281 | 295 | /> |
| 296 | + | |
| 297 | + <!-- 编辑弹窗 --> | |
| 298 | + <edit-dialog | |
| 299 | + v-if="editVisible" | |
| 300 | + ref="EditDialog" | |
| 301 | + @refresh="getList" | |
| 302 | + /> | |
| 282 | 303 | </div> |
| 283 | 304 | </template> |
| 284 | 305 | |
| 285 | 306 | <script> |
| 286 | 307 | import request from '@/utils/request' |
| 308 | +import FormDialog from './form-dialog.vue' | |
| 287 | 309 | import DetailDialog from './detail-dialog.vue' |
| 288 | 310 | import ApprovalHistoryDialog from './approval-history-dialog.vue' |
| 311 | +import EditDialog from './edit-dialog.vue' | |
| 289 | 312 | |
| 290 | 313 | export default { |
| 291 | 314 | name: 'LqReimbursementApplication', |
| 292 | 315 | components: { |
| 316 | + FormDialog, | |
| 293 | 317 | DetailDialog, |
| 294 | - ApprovalHistoryDialog | |
| 318 | + ApprovalHistoryDialog, | |
| 319 | + EditDialog | |
| 295 | 320 | }, |
| 296 | 321 | data() { |
| 297 | 322 | return { |
| 298 | - activeTab: 'allPending', // 当前激活的标签页 | |
| 323 | + activeTab: 'myPending', // 当前激活的标签页 | |
| 299 | 324 | listLoading: false, |
| 300 | 325 | list: [], |
| 301 | 326 | total: 0, |
| 327 | + storeOptions: [], // 门店选项列表 | |
| 328 | + formVisible: false, | |
| 302 | 329 | detailVisible: false, |
| 303 | 330 | historyVisible: false, |
| 331 | + editVisible: false, | |
| 304 | 332 | queryParams: { |
| 305 | 333 | applicationStoreId: '', |
| 306 | 334 | id: '', |
| ... | ... | @@ -320,9 +348,30 @@ export default { |
| 320 | 348 | } |
| 321 | 349 | }, |
| 322 | 350 | created() { |
| 351 | + this.loadStoreOptions() | |
| 323 | 352 | this.getList() |
| 324 | 353 | }, |
| 325 | 354 | methods: { |
| 355 | + // 加载门店选项列表 | |
| 356 | + async loadStoreOptions() { | |
| 357 | + try { | |
| 358 | + const response = await request({ | |
| 359 | + url: '/api/Extend/LqMdxx', | |
| 360 | + method: 'GET', | |
| 361 | + data: { | |
| 362 | + currentPage: 1, | |
| 363 | + pageSize: 1000 | |
| 364 | + } | |
| 365 | + }) | |
| 366 | + if (response.code === 200 && response.data && response.data.list) { | |
| 367 | + this.storeOptions = response.data.list | |
| 368 | + } | |
| 369 | + } catch (error) { | |
| 370 | + console.error('加载门店列表失败:', error) | |
| 371 | + this.storeOptions = [] | |
| 372 | + } | |
| 373 | + }, | |
| 374 | + | |
| 326 | 375 | // 获取列表数据 |
| 327 | 376 | async getList() { |
| 328 | 377 | this.listLoading = true |
| ... | ... | @@ -381,7 +430,7 @@ export default { |
| 381 | 430 | |
| 382 | 431 | if (response.code === 200) { |
| 383 | 432 | this.list = response.data.list || [] |
| 384 | - this.total = (response.data.pagination && response.data.pagination.Total) || 0 | |
| 433 | + this.total = response.data.pagination.total || 0 | |
| 385 | 434 | } else { |
| 386 | 435 | this.$message.error(response.msg || '获取列表失败') |
| 387 | 436 | this.list = [] |
| ... | ... | @@ -430,6 +479,14 @@ export default { |
| 430 | 479 | this.getList() |
| 431 | 480 | }, |
| 432 | 481 | |
| 482 | + // 新增 | |
| 483 | + handleAdd() { | |
| 484 | + this.formVisible = true | |
| 485 | + this.$nextTick(() => { | |
| 486 | + this.$refs.FormDialog.init() | |
| 487 | + }) | |
| 488 | + }, | |
| 489 | + | |
| 433 | 490 | // 查看详情/审批 |
| 434 | 491 | handleView(row) { |
| 435 | 492 | this.detailVisible = true |
| ... | ... | @@ -446,6 +503,14 @@ export default { |
| 446 | 503 | }) |
| 447 | 504 | }, |
| 448 | 505 | |
| 506 | + // 编辑(修改已退回的申请) | |
| 507 | + handleEdit(row) { | |
| 508 | + this.editVisible = true | |
| 509 | + this.$nextTick(() => { | |
| 510 | + this.$refs.EditDialog.init(row.id) | |
| 511 | + }) | |
| 512 | + }, | |
| 513 | + | |
| 449 | 514 | // 分页大小改变 |
| 450 | 515 | handleSizeChange(val) { |
| 451 | 516 | this.listQuery.pageSize = val |
| ... | ... | @@ -489,6 +554,13 @@ export default { |
| 489 | 554 | '已退回': 'warning' |
| 490 | 555 | } |
| 491 | 556 | return statusMap[status] || '' |
| 557 | + }, | |
| 558 | + | |
| 559 | + // 获取门店名称 | |
| 560 | + getStoreName(storeId) { | |
| 561 | + if (!storeId) return '无' | |
| 562 | + const store = this.storeOptions.find(item => item.id === storeId) | |
| 563 | + return store ? store.dm : storeId | |
| 492 | 564 | } |
| 493 | 565 | } |
| 494 | 566 | } |
| ... | ... | @@ -565,8 +637,8 @@ export default { |
| 565 | 637 | .node-item, |
| 566 | 638 | .node-count-item { |
| 567 | 639 | display: flex; |
| 568 | - align-items: center; | |
| 569 | - justify-content: center; | |
| 640 | + // align-items: center; | |
| 641 | + // justify-content: center; | |
| 570 | 642 | gap: 6px; |
| 571 | 643 | |
| 572 | 644 | .id-icon { |
| ... | ... | @@ -640,6 +712,14 @@ export default { |
| 640 | 712 | color: #606266; |
| 641 | 713 | } |
| 642 | 714 | } |
| 715 | + | |
| 716 | + .edit-btn { | |
| 717 | + color: #409EFF; | |
| 718 | + | |
| 719 | + &:hover { | |
| 720 | + color: #66b1ff; | |
| 721 | + } | |
| 722 | + } | |
| 643 | 723 | } |
| 644 | 724 | |
| 645 | 725 | .pagination-container { | ... | ... |
antis-ncc-admin/src/views/wageManagement/detail-dialog.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <el-dialog | |
| 3 | + title="健康师工资详情" | |
| 4 | + :visible="visible" | |
| 5 | + width="1400px" | |
| 6 | + :close-on-click-modal="false" | |
| 7 | + @close="handleClose" | |
| 8 | + class="detail-dialog" | |
| 9 | + > | |
| 10 | + <div v-if="detailData" class="detail-content"> | |
| 11 | + <!-- 基本信息 --> | |
| 12 | + <div class="detail-section"> | |
| 13 | + <div class="section-title"> | |
| 14 | + <i class="el-icon-user"></i> | |
| 15 | + <span>基本信息</span> | |
| 16 | + </div> | |
| 17 | + <div class="section-content"> | |
| 18 | + <div class="info-row"> | |
| 19 | + <div class="info-item"> | |
| 20 | + <span class="label">门店名称:</span> | |
| 21 | + <span class="value">{{ detailData.StoreName || '无' }}</span> | |
| 22 | + </div> | |
| 23 | + <div class="info-item"> | |
| 24 | + <span class="label">员工姓名:</span> | |
| 25 | + <span class="value">{{ detailData.EmployeeName || '无' }}</span> | |
| 26 | + </div> | |
| 27 | + <div class="info-item"> | |
| 28 | + <span class="label">岗位:</span> | |
| 29 | + <span class="value">{{ detailData.Position || '无' }}</span> | |
| 30 | + </div> | |
| 31 | + <div class="info-item"> | |
| 32 | + <span class="label">金三角战队:</span> | |
| 33 | + <span class="value">{{ detailData.GoldTriangleTeam || '无' }}</span> | |
| 34 | + </div> | |
| 35 | + <div class="info-item"> | |
| 36 | + <span class="label">是否新店:</span> | |
| 37 | + <span class="value">{{ detailData.IsNewStore || '无' }}</span> | |
| 38 | + </div> | |
| 39 | + <div class="info-item"> | |
| 40 | + <span class="label">新店保护阶段:</span> | |
| 41 | + <span class="value">{{ detailData.NewStoreProtectionStage || '0' }}</span> | |
| 42 | + </div> | |
| 43 | + <div class="info-item"> | |
| 44 | + <span class="label">锁定状态:</span> | |
| 45 | + <el-tag :type="detailData.IsLocked === 1 ? 'danger' : 'success'" size="mini"> | |
| 46 | + {{ detailData.IsLocked === 1 ? '已锁定' : '未锁定' }} | |
| 47 | + </el-tag> | |
| 48 | + </div> | |
| 49 | + </div> | |
| 50 | + </div> | |
| 51 | + </div> | |
| 52 | + | |
| 53 | + <!-- 业绩信息 --> | |
| 54 | + <div class="detail-section"> | |
| 55 | + <div class="section-title"> | |
| 56 | + <i class="el-icon-trophy"></i> | |
| 57 | + <span>业绩信息</span> | |
| 58 | + </div> | |
| 59 | + <div class="section-content"> | |
| 60 | + <div class="info-row"> | |
| 61 | + <div class="info-item"> | |
| 62 | + <span class="label">总业绩:</span> | |
| 63 | + <span class="value highlight">{{ formatMoney(detailData.TotalPerformance) }}</span> | |
| 64 | + </div> | |
| 65 | + <div class="info-item"> | |
| 66 | + <span class="label">基础业绩:</span> | |
| 67 | + <span class="value">{{ formatMoney(detailData.BasePerformance) }}</span> | |
| 68 | + </div> | |
| 69 | + <div class="info-item"> | |
| 70 | + <span class="label">合作业绩:</span> | |
| 71 | + <span class="value">{{ formatMoney(detailData.CooperationPerformance) }}</span> | |
| 72 | + </div> | |
| 73 | + <div class="info-item"> | |
| 74 | + <span class="label">奖励业绩:</span> | |
| 75 | + <span class="value">{{ formatMoney(detailData.RewardPerformance) }}</span> | |
| 76 | + </div> | |
| 77 | + <div class="info-item"> | |
| 78 | + <span class="label">实际基础业绩:</span> | |
| 79 | + <span class="value">{{ formatMoney(detailData.ActualBasePerformance) }}</span> | |
| 80 | + </div> | |
| 81 | + <div class="info-item"> | |
| 82 | + <span class="label">实际合作业绩:</span> | |
| 83 | + <span class="value">{{ formatMoney(detailData.ActualCooperationPerformance) }}</span> | |
| 84 | + </div> | |
| 85 | + <div class="info-item"> | |
| 86 | + <span class="label">新客业绩:</span> | |
| 87 | + <span class="value">{{ formatMoney(detailData.NewCustomerPerformance) }}</span> | |
| 88 | + </div> | |
| 89 | + <div class="info-item"> | |
| 90 | + <span class="label">升单业绩:</span> | |
| 91 | + <span class="value">{{ formatMoney(detailData.UpgradePerformance) }}</span> | |
| 92 | + </div> | |
| 93 | + <div class="info-item"> | |
| 94 | + <span class="label">基础奖励业绩:</span> | |
| 95 | + <span class="value">{{ formatMoney(detailData.BaseRewardPerformance) }}</span> | |
| 96 | + </div> | |
| 97 | + <div class="info-item"> | |
| 98 | + <span class="label">合作奖励业绩:</span> | |
| 99 | + <span class="value">{{ formatMoney(detailData.CooperationRewardPerformance) }}</span> | |
| 100 | + </div> | |
| 101 | + <div class="info-item"> | |
| 102 | + <span class="label">其他业绩加:</span> | |
| 103 | + <span class="value">{{ formatMoney(detailData.OtherPerformanceAdd) }}</span> | |
| 104 | + </div> | |
| 105 | + <div class="info-item"> | |
| 106 | + <span class="label">其他业绩减:</span> | |
| 107 | + <span class="value">{{ formatMoney(detailData.OtherPerformanceSubtract) }}</span> | |
| 108 | + </div> | |
| 109 | + <div class="info-item"> | |
| 110 | + <span class="label">门店总业绩:</span> | |
| 111 | + <span class="value">{{ formatMoney(detailData.StoreTotalPerformance) }}</span> | |
| 112 | + </div> | |
| 113 | + <div class="info-item"> | |
| 114 | + <span class="label">队伍业绩:</span> | |
| 115 | + <span class="value">{{ formatMoney(detailData.TeamPerformance) }}</span> | |
| 116 | + </div> | |
| 117 | + <div class="info-item"> | |
| 118 | + <span class="label">占比:</span> | |
| 119 | + <span class="value">{{ formatPercent(detailData.Percentage) }}</span> | |
| 120 | + </div> | |
| 121 | + </div> | |
| 122 | + </div> | |
| 123 | + </div> | |
| 124 | + | |
| 125 | + <!-- 消耗数据 --> | |
| 126 | + <div class="detail-section"> | |
| 127 | + <div class="section-title"> | |
| 128 | + <i class="el-icon-data-analysis"></i> | |
| 129 | + <span>消耗数据</span> | |
| 130 | + </div> | |
| 131 | + <div class="section-content"> | |
| 132 | + <div class="info-row"> | |
| 133 | + <div class="info-item"> | |
| 134 | + <span class="label">消耗:</span> | |
| 135 | + <span class="value">{{ formatMoney(detailData.Consumption) }}</span> | |
| 136 | + </div> | |
| 137 | + <div class="info-item"> | |
| 138 | + <span class="label">项目数:</span> | |
| 139 | + <span class="value">{{ detailData.ProjectCount || '0' }}</span> | |
| 140 | + </div> | |
| 141 | + <div class="info-item"> | |
| 142 | + <span class="label">到店人头:</span> | |
| 143 | + <span class="value">{{ detailData.CustomerCount || '0' }}</span> | |
| 144 | + </div> | |
| 145 | + <div class="info-item"> | |
| 146 | + <span class="label">在店天数:</span> | |
| 147 | + <span class="value">{{ detailData.WorkingDays || '0' }}</span> | |
| 148 | + </div> | |
| 149 | + <div class="info-item"> | |
| 150 | + <span class="label">请假天数:</span> | |
| 151 | + <span class="value">{{ detailData.LeaveDays || '0' }}</span> | |
| 152 | + </div> | |
| 153 | + <div class="info-item"> | |
| 154 | + <span class="label">日均消耗:</span> | |
| 155 | + <span class="value">{{ formatMoney(detailData.DailyAverageConsumption) }}</span> | |
| 156 | + </div> | |
| 157 | + <div class="info-item"> | |
| 158 | + <span class="label">日均项目数:</span> | |
| 159 | + <span class="value">{{ detailData.DailyAverageProjectCount || '0' }}</span> | |
| 160 | + </div> | |
| 161 | + <div class="info-item"> | |
| 162 | + <span class="label">战队总消耗:</span> | |
| 163 | + <span class="value">{{ formatMoney(detailData.TeamTotalConsumption) }}</span> | |
| 164 | + </div> | |
| 165 | + </div> | |
| 166 | + </div> | |
| 167 | + </div> | |
| 168 | + | |
| 169 | + <!-- 新客数据 --> | |
| 170 | + <div class="detail-section"> | |
| 171 | + <div class="section-title"> | |
| 172 | + <i class="el-icon-user-solid"></i> | |
| 173 | + <span>新客数据</span> | |
| 174 | + </div> | |
| 175 | + <div class="section-content"> | |
| 176 | + <div class="info-row"> | |
| 177 | + <div class="info-item"> | |
| 178 | + <span class="label">新客转化率:</span> | |
| 179 | + <span class="value">{{ formatPercent(detailData.NewCustomerConversionRate) }}</span> | |
| 180 | + </div> | |
| 181 | + <div class="info-item"> | |
| 182 | + <span class="label">新客提点:</span> | |
| 183 | + <span class="value">{{ formatPercent(detailData.NewCustomerPoint) }}</span> | |
| 184 | + </div> | |
| 185 | + <div class="info-item"> | |
| 186 | + <span class="label">新客业绩提成:</span> | |
| 187 | + <span class="value highlight">{{ formatMoney(detailData.NewCustomerCommission) }}</span> | |
| 188 | + </div> | |
| 189 | + </div> | |
| 190 | + </div> | |
| 191 | + </div> | |
| 192 | + | |
| 193 | + <!-- 升单数据 --> | |
| 194 | + <div class="detail-section"> | |
| 195 | + <div class="section-title"> | |
| 196 | + <i class="el-icon-arrow-up"></i> | |
| 197 | + <span>升单数据</span> | |
| 198 | + </div> | |
| 199 | + <div class="section-content"> | |
| 200 | + <div class="info-row"> | |
| 201 | + <div class="info-item"> | |
| 202 | + <span class="label">升单人头数:</span> | |
| 203 | + <span class="value">{{ detailData.UpgradeCustomerCount || '0' }}</span> | |
| 204 | + </div> | |
| 205 | + <div class="info-item"> | |
| 206 | + <span class="label">升单提点:</span> | |
| 207 | + <span class="value">{{ formatPercent(detailData.UpgradePoint) }}</span> | |
| 208 | + </div> | |
| 209 | + <div class="info-item"> | |
| 210 | + <span class="label">升单业绩提成:</span> | |
| 211 | + <span class="value highlight">{{ formatMoney(detailData.UpgradeCommission) }}</span> | |
| 212 | + </div> | |
| 213 | + </div> | |
| 214 | + </div> | |
| 215 | + </div> | |
| 216 | + | |
| 217 | + <!-- 提成信息 --> | |
| 218 | + <div class="detail-section"> | |
| 219 | + <div class="section-title"> | |
| 220 | + <i class="el-icon-money"></i> | |
| 221 | + <span>提成信息</span> | |
| 222 | + </div> | |
| 223 | + <div class="section-content"> | |
| 224 | + <div class="info-row"> | |
| 225 | + <div class="info-item"> | |
| 226 | + <span class="label">提点:</span> | |
| 227 | + <span class="value">{{ formatPercent(detailData.CommissionPoint) }}</span> | |
| 228 | + </div> | |
| 229 | + <div class="info-item"> | |
| 230 | + <span class="label">基础业绩提成:</span> | |
| 231 | + <span class="value">{{ formatMoney(detailData.BasePerformanceCommission) }}</span> | |
| 232 | + </div> | |
| 233 | + <div class="info-item"> | |
| 234 | + <span class="label">合作业绩提成:</span> | |
| 235 | + <span class="value">{{ formatMoney(detailData.CooperationPerformanceCommission) }}</span> | |
| 236 | + </div> | |
| 237 | + <div class="info-item"> | |
| 238 | + <span class="label">顾问提成:</span> | |
| 239 | + <span class="value">{{ formatMoney(detailData.ConsultantCommission) }}</span> | |
| 240 | + </div> | |
| 241 | + <div class="info-item"> | |
| 242 | + <span class="label">门店T区提成:</span> | |
| 243 | + <span class="value">{{ formatMoney(detailData.StoreTZoneCommission) }}</span> | |
| 244 | + </div> | |
| 245 | + <div class="info-item"> | |
| 246 | + <span class="label">提成合计:</span> | |
| 247 | + <span class="value highlight">{{ formatMoney(detailData.TotalCommission) }}</span> | |
| 248 | + </div> | |
| 249 | + </div> | |
| 250 | + </div> | |
| 251 | + </div> | |
| 252 | + | |
| 253 | + <!-- 工资信息 --> | |
| 254 | + <div class="detail-section"> | |
| 255 | + <div class="section-title"> | |
| 256 | + <i class="el-icon-wallet"></i> | |
| 257 | + <span>工资信息</span> | |
| 258 | + </div> | |
| 259 | + <div class="section-content"> | |
| 260 | + <div class="info-row"> | |
| 261 | + <div class="info-item"> | |
| 262 | + <span class="label">健康师底薪:</span> | |
| 263 | + <span class="value">{{ formatMoney(detailData.HealthCoachBaseSalary) }}</span> | |
| 264 | + </div> | |
| 265 | + <div class="info-item"> | |
| 266 | + <span class="label">手工费:</span> | |
| 267 | + <span class="value">{{ formatMoney(detailData.HandworkFee) }}</span> | |
| 268 | + </div> | |
| 269 | + <div class="info-item"> | |
| 270 | + <span class="label">额外手工费:</span> | |
| 271 | + <span class="value">{{ formatMoney(detailData.OutherHandworkFee) }}</span> | |
| 272 | + </div> | |
| 273 | + <div class="info-item"> | |
| 274 | + <span class="label">核算应发工资:</span> | |
| 275 | + <span class="value">{{ formatMoney(detailData.CalculatedGrossSalary) }}</span> | |
| 276 | + </div> | |
| 277 | + <div class="info-item"> | |
| 278 | + <span class="label">保底工资:</span> | |
| 279 | + <span class="value">{{ formatMoney(detailData.GuaranteedSalary) }}</span> | |
| 280 | + </div> | |
| 281 | + <div class="info-item"> | |
| 282 | + <span class="label">保底请假扣款:</span> | |
| 283 | + <span class="value">{{ formatMoney(detailData.GuaranteedLeaveDeduction) }}</span> | |
| 284 | + </div> | |
| 285 | + <div class="info-item"> | |
| 286 | + <span class="label">保底底薪:</span> | |
| 287 | + <span class="value">{{ formatMoney(detailData.GuaranteedBaseSalary) }}</span> | |
| 288 | + </div> | |
| 289 | + <div class="info-item"> | |
| 290 | + <span class="label">保底补差:</span> | |
| 291 | + <span class="value">{{ formatMoney(detailData.GuaranteedSupplement) }}</span> | |
| 292 | + </div> | |
| 293 | + <div class="info-item"> | |
| 294 | + <span class="label">最终应发工资:</span> | |
| 295 | + <span class="value highlight">{{ formatMoney(detailData.FinalGrossSalary) }}</span> | |
| 296 | + </div> | |
| 297 | + </div> | |
| 298 | + </div> | |
| 299 | + </div> | |
| 300 | + | |
| 301 | + <!-- 补贴信息 --> | |
| 302 | + <div class="detail-section"> | |
| 303 | + <div class="section-title"> | |
| 304 | + <i class="el-icon-circle-plus"></i> | |
| 305 | + <span>补贴信息</span> | |
| 306 | + </div> | |
| 307 | + <div class="section-content"> | |
| 308 | + <div class="info-row"> | |
| 309 | + <div class="info-item"> | |
| 310 | + <span class="label">车补:</span> | |
| 311 | + <span class="value">{{ formatMoney(detailData.TransportationAllowance) }}</span> | |
| 312 | + </div> | |
| 313 | + <div class="info-item"> | |
| 314 | + <span class="label">少休费:</span> | |
| 315 | + <span class="value">{{ formatMoney(detailData.LessRest) }}</span> | |
| 316 | + </div> | |
| 317 | + <div class="info-item"> | |
| 318 | + <span class="label">全勤奖:</span> | |
| 319 | + <span class="value">{{ formatMoney(detailData.FullAttendance) }}</span> | |
| 320 | + </div> | |
| 321 | + <div class="info-item"> | |
| 322 | + <span class="label">当月培训补贴:</span> | |
| 323 | + <span class="value">{{ formatMoney(detailData.MonthlyTrainingSubsidy) }}</span> | |
| 324 | + </div> | |
| 325 | + <div class="info-item"> | |
| 326 | + <span class="label">当月交通补贴:</span> | |
| 327 | + <span class="value">{{ formatMoney(detailData.MonthlyTransportSubsidy) }}</span> | |
| 328 | + </div> | |
| 329 | + <div class="info-item"> | |
| 330 | + <span class="label">上月培训补贴:</span> | |
| 331 | + <span class="value">{{ formatMoney(detailData.LastMonthTrainingSubsidy) }}</span> | |
| 332 | + </div> | |
| 333 | + <div class="info-item"> | |
| 334 | + <span class="label">上月交通补贴:</span> | |
| 335 | + <span class="value">{{ formatMoney(detailData.LastMonthTransportSubsidy) }}</span> | |
| 336 | + </div> | |
| 337 | + <div class="info-item"> | |
| 338 | + <span class="label">补贴合计:</span> | |
| 339 | + <span class="value highlight">{{ formatMoney(detailData.TotalSubsidy) }}</span> | |
| 340 | + </div> | |
| 341 | + </div> | |
| 342 | + </div> | |
| 343 | + </div> | |
| 344 | + | |
| 345 | + <!-- 扣款信息 --> | |
| 346 | + <div class="detail-section"> | |
| 347 | + <div class="section-title"> | |
| 348 | + <i class="el-icon-circle-minus"></i> | |
| 349 | + <span>扣款信息</span> | |
| 350 | + </div> | |
| 351 | + <div class="section-content"> | |
| 352 | + <div class="info-row"> | |
| 353 | + <div class="info-item"> | |
| 354 | + <span class="label">缺卡扣款:</span> | |
| 355 | + <span class="value">{{ formatMoney(detailData.MissingCard) }}</span> | |
| 356 | + </div> | |
| 357 | + <div class="info-item"> | |
| 358 | + <span class="label">迟到扣款:</span> | |
| 359 | + <span class="value">{{ formatMoney(detailData.LateArrival) }}</span> | |
| 360 | + </div> | |
| 361 | + <div class="info-item"> | |
| 362 | + <span class="label">请假扣款:</span> | |
| 363 | + <span class="value">{{ formatMoney(detailData.LeaveDeduction) }}</span> | |
| 364 | + </div> | |
| 365 | + <div class="info-item"> | |
| 366 | + <span class="label">扣社保:</span> | |
| 367 | + <span class="value">{{ formatMoney(detailData.SocialInsuranceDeduction) }}</span> | |
| 368 | + </div> | |
| 369 | + <div class="info-item"> | |
| 370 | + <span class="label">扣除奖励:</span> | |
| 371 | + <span class="value">{{ formatMoney(detailData.RewardDeduction) }}</span> | |
| 372 | + </div> | |
| 373 | + <div class="info-item"> | |
| 374 | + <span class="label">扣住宿费:</span> | |
| 375 | + <span class="value">{{ formatMoney(detailData.AccommodationDeduction) }}</span> | |
| 376 | + </div> | |
| 377 | + <div class="info-item"> | |
| 378 | + <span class="label">扣学习期费用:</span> | |
| 379 | + <span class="value">{{ formatMoney(detailData.StudyPeriodDeduction) }}</span> | |
| 380 | + </div> | |
| 381 | + <div class="info-item"> | |
| 382 | + <span class="label">扣工作服费用:</span> | |
| 383 | + <span class="value">{{ formatMoney(detailData.WorkClothesDeduction) }}</span> | |
| 384 | + </div> | |
| 385 | + <div class="info-item"> | |
| 386 | + <span class="label">扣款合计:</span> | |
| 387 | + <span class="value highlight">{{ formatMoney(detailData.TotalDeduction) }}</span> | |
| 388 | + </div> | |
| 389 | + </div> | |
| 390 | + </div> | |
| 391 | + </div> | |
| 392 | + | |
| 393 | + <!-- 其他信息 --> | |
| 394 | + <div class="detail-section"> | |
| 395 | + <div class="section-title"> | |
| 396 | + <i class="el-icon-document"></i> | |
| 397 | + <span>其他信息</span> | |
| 398 | + </div> | |
| 399 | + <div class="section-content"> | |
| 400 | + <div class="info-row"> | |
| 401 | + <div class="info-item"> | |
| 402 | + <span class="label">发奖金:</span> | |
| 403 | + <span class="value">{{ formatMoney(detailData.Bonus) }}</span> | |
| 404 | + </div> | |
| 405 | + <div class="info-item"> | |
| 406 | + <span class="label">退手机押金:</span> | |
| 407 | + <span class="value">{{ formatMoney(detailData.ReturnPhoneDeposit) }}</span> | |
| 408 | + </div> | |
| 409 | + <div class="info-item"> | |
| 410 | + <span class="label">退住宿押金:</span> | |
| 411 | + <span class="value">{{ formatMoney(detailData.ReturnAccommodationDeposit) }}</span> | |
| 412 | + </div> | |
| 413 | + <div class="info-item"> | |
| 414 | + <span class="label">补发上月:</span> | |
| 415 | + <span class="value">{{ formatMoney(detailData.LastMonthSupplement) }}</span> | |
| 416 | + </div> | |
| 417 | + </div> | |
| 418 | + </div> | |
| 419 | + </div> | |
| 420 | + | |
| 421 | + <!-- 支付信息 --> | |
| 422 | + <div class="detail-section"> | |
| 423 | + <div class="section-title"> | |
| 424 | + <i class="el-icon-credit-card"></i> | |
| 425 | + <span>支付信息</span> | |
| 426 | + </div> | |
| 427 | + <div class="section-content"> | |
| 428 | + <div class="info-row"> | |
| 429 | + <div class="info-item"> | |
| 430 | + <span class="label">实发工资:</span> | |
| 431 | + <span class="value highlight large">{{ formatMoney(detailData.ActualSalary) }}</span> | |
| 432 | + </div> | |
| 433 | + <div class="info-item"> | |
| 434 | + <span class="label">当月是否发放:</span> | |
| 435 | + <span class="value">{{ detailData.MonthlyPaymentStatus || '无' }}</span> | |
| 436 | + </div> | |
| 437 | + <div class="info-item"> | |
| 438 | + <span class="label">支付金额:</span> | |
| 439 | + <span class="value">{{ formatMoney(detailData.PaidAmount) }}</span> | |
| 440 | + </div> | |
| 441 | + <div class="info-item"> | |
| 442 | + <span class="label">待支付金额:</span> | |
| 443 | + <span class="value">{{ formatMoney(detailData.PendingAmount) }}</span> | |
| 444 | + </div> | |
| 445 | + <div class="info-item"> | |
| 446 | + <span class="label">当月支付总额:</span> | |
| 447 | + <span class="value">{{ formatMoney(detailData.MonthlyTotalPayment) }}</span> | |
| 448 | + </div> | |
| 449 | + </div> | |
| 450 | + </div> | |
| 451 | + </div> | |
| 452 | + </div> | |
| 453 | + | |
| 454 | + <div v-else class="no-data"> | |
| 455 | + <i class="el-icon-warning"></i> | |
| 456 | + <span>暂无数据</span> | |
| 457 | + </div> | |
| 458 | + </el-dialog> | |
| 459 | +</template> | |
| 460 | + | |
| 461 | +<script> | |
| 462 | +export default { | |
| 463 | + name: 'HealthCoachDetailDialog', | |
| 464 | + props: { | |
| 465 | + visible: { | |
| 466 | + type: Boolean, | |
| 467 | + default: false | |
| 468 | + }, | |
| 469 | + detailData: { | |
| 470 | + type: Object, | |
| 471 | + default: null | |
| 472 | + } | |
| 473 | + }, | |
| 474 | + methods: { | |
| 475 | + handleClose() { | |
| 476 | + this.$emit('update:visible', false) | |
| 477 | + this.$emit('close') | |
| 478 | + }, | |
| 479 | + formatMoney(value) { | |
| 480 | + if (value === null || value === undefined || value === '') { | |
| 481 | + return '0.00' | |
| 482 | + } | |
| 483 | + return Number(value).toFixed(2) | |
| 484 | + }, | |
| 485 | + formatPercent(value) { | |
| 486 | + if (value === null || value === undefined || value === '') { | |
| 487 | + return '0.00%' | |
| 488 | + } | |
| 489 | + return (Number(value) * 100).toFixed(2) + '%' | |
| 490 | + } | |
| 491 | + } | |
| 492 | +} | |
| 493 | +</script> | |
| 494 | + | |
| 495 | +<style lang="scss" scoped> | |
| 496 | +.detail-dialog { | |
| 497 | + .detail-content { | |
| 498 | + max-height: 70vh; | |
| 499 | + overflow-y: auto; | |
| 500 | + padding-right: 10px; | |
| 501 | + } | |
| 502 | + | |
| 503 | + .detail-section { | |
| 504 | + margin-bottom: 20px; | |
| 505 | + background: #fff; | |
| 506 | + border-radius: 8px; | |
| 507 | + padding: 20px; | |
| 508 | + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| 509 | + | |
| 510 | + .section-title { | |
| 511 | + display: flex; | |
| 512 | + align-items: center; | |
| 513 | + gap: 8px; | |
| 514 | + margin-bottom: 16px; | |
| 515 | + padding-bottom: 12px; | |
| 516 | + border-bottom: 2px solid #409EFF; | |
| 517 | + font-size: 16px; | |
| 518 | + font-weight: 600; | |
| 519 | + color: #303133; | |
| 520 | + | |
| 521 | + i { | |
| 522 | + color: #409EFF; | |
| 523 | + font-size: 18px; | |
| 524 | + } | |
| 525 | + } | |
| 526 | + | |
| 527 | + .section-content { | |
| 528 | + .info-row { | |
| 529 | + display: flex; | |
| 530 | + flex-wrap: wrap; | |
| 531 | + gap: 20px 40px; | |
| 532 | + | |
| 533 | + .info-item { | |
| 534 | + display: flex; | |
| 535 | + align-items: center; | |
| 536 | + min-width: 200px; | |
| 537 | + flex: 0 0 calc(33.333% - 27px); | |
| 538 | + | |
| 539 | + .label { | |
| 540 | + color: #606266; | |
| 541 | + font-size: 14px; | |
| 542 | + white-space: nowrap; | |
| 543 | + margin-right: 8px; | |
| 544 | + } | |
| 545 | + | |
| 546 | + .value { | |
| 547 | + color: #303133; | |
| 548 | + font-size: 14px; | |
| 549 | + font-weight: 500; | |
| 550 | + | |
| 551 | + &.highlight { | |
| 552 | + color: #409EFF; | |
| 553 | + font-weight: 600; | |
| 554 | + } | |
| 555 | + | |
| 556 | + &.large { | |
| 557 | + font-size: 18px; | |
| 558 | + color: #67C23A; | |
| 559 | + } | |
| 560 | + } | |
| 561 | + } | |
| 562 | + } | |
| 563 | + } | |
| 564 | + } | |
| 565 | + | |
| 566 | + .no-data { | |
| 567 | + display: flex; | |
| 568 | + flex-direction: column; | |
| 569 | + align-items: center; | |
| 570 | + justify-content: center; | |
| 571 | + padding: 60px 20px; | |
| 572 | + color: #909399; | |
| 573 | + | |
| 574 | + i { | |
| 575 | + font-size: 48px; | |
| 576 | + margin-bottom: 16px; | |
| 577 | + } | |
| 578 | + | |
| 579 | + span { | |
| 580 | + font-size: 14px; | |
| 581 | + } | |
| 582 | + } | |
| 583 | + | |
| 584 | + .dialog-footer { | |
| 585 | + text-align: right; | |
| 586 | + } | |
| 587 | +} | |
| 588 | + | |
| 589 | +// 滚动条样式 | |
| 590 | +.detail-content::-webkit-scrollbar { | |
| 591 | + width: 6px; | |
| 592 | +} | |
| 593 | + | |
| 594 | +.detail-content::-webkit-scrollbar-track { | |
| 595 | + background: #f1f1f1; | |
| 596 | + border-radius: 3px; | |
| 597 | +} | |
| 598 | + | |
| 599 | +.detail-content::-webkit-scrollbar-thumb { | |
| 600 | + background: #c1c1c1; | |
| 601 | + border-radius: 3px; | |
| 602 | + | |
| 603 | + &:hover { | |
| 604 | + background: #a8a8a8; | |
| 605 | + } | |
| 606 | +} | |
| 607 | +</style> | |
| 608 | + | ... | ... |
antis-ncc-admin/src/views/wageManagement/healthCoach.vue
| ... | ... | @@ -523,6 +523,18 @@ |
| 523 | 523 | </el-tag> |
| 524 | 524 | </template> |
| 525 | 525 | </el-table-column> |
| 526 | + <el-table-column label="操作" width="100" align="center" fixed="right"> | |
| 527 | + <template slot-scope="scope"> | |
| 528 | + <el-button | |
| 529 | + type="primary" | |
| 530 | + size="mini" | |
| 531 | + icon="el-icon-view" | |
| 532 | + @click="handleViewDetail(scope.row)" | |
| 533 | + > | |
| 534 | + 详情 | |
| 535 | + </el-button> | |
| 536 | + </template> | |
| 537 | + </el-table-column> | |
| 526 | 538 | </NCC-table> |
| 527 | 539 | </div> |
| 528 | 540 | |
| ... | ... | @@ -576,6 +588,14 @@ |
| 576 | 588 | @close="handleExtraDataDialogClose" |
| 577 | 589 | @refresh="getList" |
| 578 | 590 | /> |
| 591 | + | |
| 592 | + <!-- 详情弹窗 --> | |
| 593 | + <detail-dialog | |
| 594 | + :visible="detailDialogVisible" | |
| 595 | + :detail-data="currentDetailData" | |
| 596 | + @update:visible="detailDialogVisible = $event" | |
| 597 | + @close="handleDetailDialogClose" | |
| 598 | + /> | |
| 579 | 599 | </div> |
| 580 | 600 | </template> |
| 581 | 601 | |
| ... | ... | @@ -583,11 +603,13 @@ |
| 583 | 603 | import { getHealthCoachSalaryList, calculateHealthCoachSalary } from '@/api/extend/healthCoachSalary' |
| 584 | 604 | import { getStoreSelector } from '@/api/extend/store' |
| 585 | 605 | import ExtraDataDialog from './extra-data-dialog.vue' |
| 606 | +import DetailDialog from './detail-dialog.vue' | |
| 586 | 607 | |
| 587 | 608 | export default { |
| 588 | 609 | name: 'HealthCoachSalary', |
| 589 | 610 | components: { |
| 590 | - ExtraDataDialog | |
| 611 | + ExtraDataDialog, | |
| 612 | + DetailDialog | |
| 591 | 613 | }, |
| 592 | 614 | data() { |
| 593 | 615 | // 获取当前年月 |
| ... | ... | @@ -612,6 +634,8 @@ export default { |
| 612 | 634 | calculateYear: getCurrentYear(), |
| 613 | 635 | calculateMonth: getCurrentMonth(), |
| 614 | 636 | extraDataDialogVisible: false, |
| 637 | + detailDialogVisible: false, | |
| 638 | + currentDetailData: null, | |
| 615 | 639 | queryParams: { |
| 616 | 640 | currentPage: 1, |
| 617 | 641 | pageSize: 300, // 写死为300 |
| ... | ... | @@ -745,6 +769,18 @@ export default { |
| 745 | 769 | this.extraDataDialogVisible = false |
| 746 | 770 | }, |
| 747 | 771 | |
| 772 | + // 查看详情 | |
| 773 | + handleViewDetail(row) { | |
| 774 | + this.currentDetailData = row | |
| 775 | + this.detailDialogVisible = true | |
| 776 | + }, | |
| 777 | + | |
| 778 | + // 关闭详情弹窗 | |
| 779 | + handleDetailDialogClose() { | |
| 780 | + this.detailDialogVisible = false | |
| 781 | + this.currentDetailData = null | |
| 782 | + }, | |
| 783 | + | |
| 748 | 784 | // 确认计算工资 |
| 749 | 785 | async handleCalculateConfirm() { |
| 750 | 786 | if (!this.calculateYear || !this.calculateMonth) { | ... | ... |
绿纤uni-app/apis/modules/oauth.js
| ... | ... | @@ -23,4 +23,8 @@ export default { |
| 23 | 23 | UploadBase64Image(data) { |
| 24 | 24 | return request.post(`${config.getApiBaseUrl()}/api/file/UploadBase64Image`, data); |
| 25 | 25 | }, |
| 26 | + // 获取用户列表 | |
| 27 | + getUserList(params) { | |
| 28 | + return request.get(`${config.getApiBaseUrl()}/api/permission/Users`, params); | |
| 29 | + }, | |
| 26 | 30 | } |
| 27 | 31 | \ No newline at end of file | ... | ... |
绿纤uni-app/apis/modules/reimbursement.js
| ... | ... | @@ -26,13 +26,30 @@ export default { |
| 26 | 26 | getStoreList(params) { |
| 27 | 27 | return request.get(`${config.getApiBaseUrl()}/api/Extend/LqMdxx`, params); |
| 28 | 28 | }, |
| 29 | - // 通过审批 | |
| 30 | - approveReimbursement(id) { | |
| 31 | - return request.post(`${config.getApiBaseUrl()}/api/Extend/LqReimbursementApplication/${id}/Actions/Approve`); | |
| 29 | + // 审批(支持通过、不通过、退回) | |
| 30 | + approveReimbursement(id, params) { | |
| 31 | + const queryParams = new URLSearchParams() | |
| 32 | + if (params && params.result) { | |
| 33 | + queryParams.append('result', params.result) | |
| 34 | + } | |
| 35 | + if (params && params.opinion) { | |
| 36 | + queryParams.append('opinion', params.opinion) | |
| 37 | + } | |
| 38 | + const queryString = queryParams.toString() | |
| 39 | + const url = `${config.getApiBaseUrl()}/api/Extend/LqReimbursementApplication/${id}/Actions/Approve${queryString ? '?' + queryString : ''}` | |
| 40 | + return request.post(url); | |
| 32 | 41 | }, |
| 33 | 42 | // 拒绝审批 |
| 34 | 43 | rejectReimbursement(id) { |
| 35 | 44 | return request.post(`${config.getApiBaseUrl()}/api/Extend/LqReimbursementApplication/${id}/Actions/Reject`); |
| 45 | + }, | |
| 46 | + // 提交审批 | |
| 47 | + submitApproval(id) { | |
| 48 | + return request.post(`${config.getApiBaseUrl()}/api/Extend/LqReimbursementApplication/${id}/Actions/SubmitApproval`); | |
| 49 | + }, | |
| 50 | + // 获取我的待办列表 | |
| 51 | + getMyPendingList(params) { | |
| 52 | + return request.get(`${config.getApiBaseUrl()}/api/Extend/LqReimbursementApplication/Actions/PendingApproval`, params); | |
| 36 | 53 | } |
| 37 | 54 | } |
| 38 | 55 | ... | ... |
绿纤uni-app/pages/purchase-list/purchase-list.vue
| ... | ... | @@ -79,7 +79,7 @@ |
| 79 | 79 | |
| 80 | 80 | <text class="btn-text">查看</text> |
| 81 | 81 | </view> |
| 82 | - <view v-if="item.approveStatus === '未审批' || item.approveStatus === '未通过'" class="action-btn edit-btn" @click.stop="handleEdit(item)"> | |
| 82 | + <view v-if="item.approveStatus === '未审批' || item.approveStatus === '已退回'" class="action-btn edit-btn" @click.stop="handleEdit(item)"> | |
| 83 | 83 | |
| 84 | 84 | <text class="btn-text">编辑</text> |
| 85 | 85 | </view> |
| ... | ... | @@ -306,7 +306,7 @@ |
| 306 | 306 | |
| 307 | 307 | // 编辑记录(只有未审批状态才能编辑) |
| 308 | 308 | handleEdit(item) { |
| 309 | - if (item.approveStatus === '未审批' || item.approveStatus === '未通过') { | |
| 309 | + if (item.approveStatus === '未审批' || item.approveStatus === '已退回') { | |
| 310 | 310 | uni.navigateTo({ |
| 311 | 311 | url: `/pages/purchase-form/purchase-form?id=${item.id}` |
| 312 | 312 | }) | ... | ... |
绿纤uni-app/pages/reimbursement-audit-list/reimbursement-audit-list copy.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="container"> | |
| 3 | + <!-- 日期范围筛选 --> | |
| 4 | + <view class="date-filter-card"> | |
| 5 | + <view class="date-filter-header"> | |
| 6 | + <view class="date-range-display" @click="showCalendarFun"> | |
| 7 | + <text class="date-text">{{ formatDateRange() }}</text> | |
| 8 | + <text class="calendar-icon">📅</text> | |
| 9 | + </view> | |
| 10 | + </view> | |
| 11 | + <view class="status-filter"> | |
| 12 | + <text class="status-filter-label">审批状态</text> | |
| 13 | + <picker mode="selector" :range="approveStatusOptions" :range-key="'label'" :value="approveStatusIndex" @change="handleStatusChange"> | |
| 14 | + <view class="status-picker"> | |
| 15 | + <text class="status-text">{{ approveStatusOptions[approveStatusIndex].label }}</text> | |
| 16 | + <text class="picker-arrow">▼</text> | |
| 17 | + </view> | |
| 18 | + </picker> | |
| 19 | + </view> | |
| 20 | + </view> | |
| 21 | + | |
| 22 | + <!-- 日历组件 --> | |
| 23 | + <uni-calendar | |
| 24 | + :range="true" | |
| 25 | + ref="calendar" | |
| 26 | + @confirm="handleDateConfirm" | |
| 27 | + :insert="false" | |
| 28 | + ></uni-calendar> | |
| 29 | + | |
| 30 | + <!-- 数据列表 --> | |
| 31 | + <view class="list-card"> | |
| 32 | + <view class="list-header"> | |
| 33 | + <text class="header-text">报销审核列表</text> | |
| 34 | + <text class="total-count">共 {{ totalCount }} 条</text> | |
| 35 | + </view> | |
| 36 | + | |
| 37 | + <scroll-view scroll-y class="list-content" @scrolltolower="loadMore"> | |
| 38 | + <view v-for="(item, index) in dataList" :key="index" class="list-item"> | |
| 39 | + <view class="item-header"> | |
| 40 | + <view class="applicant-name">{{ item.applicationUserName || '无' }}</view> | |
| 41 | + <view class="application-time">{{ formatTime(item.applicationTime) }}</view> | |
| 42 | + </view> | |
| 43 | + <view class="item-details"> | |
| 44 | + <view class="detail-item"> | |
| 45 | + <text class="detail-label">门店:</text> | |
| 46 | + <text class="detail-value">{{ getStoreName(item.applicationStoreId) || '无' }}</text> | |
| 47 | + </view> | |
| 48 | + <view class="detail-item"> | |
| 49 | + <text class="detail-label">总金额:</text> | |
| 50 | + <text class="detail-value amount">¥{{ item.amount || 0 }}</text> | |
| 51 | + </view> | |
| 52 | + </view> | |
| 53 | + <view class="item-footer"> | |
| 54 | + <view class="status-badge" :class="item.approveStatus=='已审批'?'approved':item.approveStatus=='待审批'?'pending':item.approveStatus=='未通过'?'rejected':'unapproved'"> | |
| 55 | + {{ item.approveStatus || '未审批' }} | |
| 56 | + </view> | |
| 57 | + <view class="action-buttons"> | |
| 58 | + <view v-if="item.approveStatus != '未审批'" class="action-btn view-btn" @click.stop="handleView(item)"> | |
| 59 | + <text class="btn-text">查看</text> | |
| 60 | + </view> | |
| 61 | + <view v-if="item.approveStatus === '未审批'" class="action-btn audit-btn" @click.stop="handleAudit(item)"> | |
| 62 | + <text class="btn-text">审核</text> | |
| 63 | + </view> | |
| 64 | + </view> | |
| 65 | + </view> | |
| 66 | + </view> | |
| 67 | + | |
| 68 | + <!-- 空状态 --> | |
| 69 | + <view v-if="!loading && dataList.length === 0" class="empty-state"> | |
| 70 | + <view class="empty-icon">📋</view> | |
| 71 | + <view>暂无报销申请记录</view> | |
| 72 | + </view> | |
| 73 | + | |
| 74 | + <!-- 加载状态 --> | |
| 75 | + <view v-if="loading && dataList.length === 0" class="loading">正在加载数据...</view> | |
| 76 | + | |
| 77 | + <!-- 加载更多 --> | |
| 78 | + <u-loadmore v-if="dataList.length > 0" :status="loadmoreStatus" :load-text="loadText"></u-loadmore> | |
| 79 | + </scroll-view> | |
| 80 | + </view> | |
| 81 | + </view> | |
| 82 | +</template> | |
| 83 | + | |
| 84 | +<script> | |
| 85 | + import api from '@/apis/index.js' | |
| 86 | + import uniCalendar from '@/uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue' | |
| 87 | + | |
| 88 | + export default { | |
| 89 | + components: { | |
| 90 | + uniCalendar | |
| 91 | + }, | |
| 92 | + data() { | |
| 93 | + // 初始化日期范围(本月1号到今天) | |
| 94 | + const now = new Date() | |
| 95 | + const year = now.getFullYear() | |
| 96 | + const month = now.getMonth() | |
| 97 | + const firstDay = new Date(year, month, 1) | |
| 98 | + const today = new Date(year, now.getMonth(), now.getDate(), 23, 59, 59, 999) | |
| 99 | + | |
| 100 | + // 格式化日期为 YYYY-MM-DD(使用本地时间,避免时区问题) | |
| 101 | + const formatDateLocal = (date) => { | |
| 102 | + const y = date.getFullYear() | |
| 103 | + const m = String(date.getMonth() + 1).padStart(2, '0') | |
| 104 | + const d = String(date.getDate()).padStart(2, '0') | |
| 105 | + return `${y}-${m}-${d}` | |
| 106 | + } | |
| 107 | + | |
| 108 | + return { | |
| 109 | + loading: false, | |
| 110 | + dataList: [], | |
| 111 | + currentPage: 1, | |
| 112 | + pageSize: 10, | |
| 113 | + totalCount: 0, | |
| 114 | + hasMore: true, | |
| 115 | + loadmoreStatus: 'loadmore', | |
| 116 | + loadText: { | |
| 117 | + loadmore: '点击或上拉加载更多', | |
| 118 | + loading: '正在加载...', | |
| 119 | + nomore: '没有更多了' | |
| 120 | + }, | |
| 121 | + userInfo: uni.getStorageSync('userInfo'), | |
| 122 | + newuserInfo: uni.getStorageSync('newuserInfo'), | |
| 123 | + storeOptions: [], | |
| 124 | + // 日期范围筛选相关 | |
| 125 | + showCalendar: false, | |
| 126 | + startDate: formatDateLocal(firstDay), | |
| 127 | + endDate: formatDateLocal(now), | |
| 128 | + dateRange: [firstDay.getTime(), today.getTime()], | |
| 129 | + // 审批状态筛选 | |
| 130 | + approveStatusIndex: 1, // 默认选择"未审批"(索引1) | |
| 131 | + approveStatusOptions: [ | |
| 132 | + { label: '全部', value: '' }, | |
| 133 | + { label: '未审批', value: '未审批' }, | |
| 134 | + { label: '已审批', value: '已审批' }, | |
| 135 | + { label: '未通过', value: '未通过' } | |
| 136 | + ] | |
| 137 | + } | |
| 138 | + }, | |
| 139 | + | |
| 140 | + onLoad() { | |
| 141 | + this.loadStoreOptions() | |
| 142 | + this.loadReimbursementList() | |
| 143 | + }, | |
| 144 | + | |
| 145 | + onShow() { | |
| 146 | + // 从详情页返回时刷新列表 | |
| 147 | + this.refreshData() | |
| 148 | + }, | |
| 149 | + | |
| 150 | + methods: { | |
| 151 | + // 加载门店列表 | |
| 152 | + async loadStoreOptions() { | |
| 153 | + try { | |
| 154 | + const res = await api.getStoreList({ | |
| 155 | + currentPage: 1, | |
| 156 | + pageSize: 1000 | |
| 157 | + }) | |
| 158 | + if (res.code === 200 && res.data && res.data.list) { | |
| 159 | + this.storeOptions = res.data.list | |
| 160 | + } else { | |
| 161 | + this.storeOptions = [] | |
| 162 | + } | |
| 163 | + } catch (error) { | |
| 164 | + console.error('加载门店列表失败:', error) | |
| 165 | + this.storeOptions = [] | |
| 166 | + } | |
| 167 | + }, | |
| 168 | + | |
| 169 | + // 获取门店名称 | |
| 170 | + getStoreName(storeId) { | |
| 171 | + if (!storeId) return '无' | |
| 172 | + const store = this.storeOptions.find(item => item.id === storeId) | |
| 173 | + return store ? store.dm : '无' | |
| 174 | + }, | |
| 175 | + | |
| 176 | + // 显示日历 | |
| 177 | + showCalendarFun() { | |
| 178 | + this.$refs.calendar.open() | |
| 179 | + }, | |
| 180 | + | |
| 181 | + // 日期确认 | |
| 182 | + handleDateConfirm(e) { | |
| 183 | + console.log('日期选择结果:', e) | |
| 184 | + | |
| 185 | + // uni-calendar组件返回的是包含start和end的对象 | |
| 186 | + if (e && e.range && e.range.data) { | |
| 187 | + // 更新dateRange用于API调用(时间戳格式) | |
| 188 | + const startTime = new Date(e.range.data[0]).getTime() | |
| 189 | + const endTime = new Date(e.range.data[e.range.data.length-1]).getTime() | |
| 190 | + this.dateRange = [startTime, endTime] | |
| 191 | + | |
| 192 | + // 更新startDate和endDate用于显示 | |
| 193 | + this.startDate = e.range.data[0] | |
| 194 | + this.endDate = e.range.data[e.range.data.length-1] | |
| 195 | + | |
| 196 | + // 日期变化时重新加载数据 | |
| 197 | + this.currentPage = 1 | |
| 198 | + this.loadReimbursementList() | |
| 199 | + } | |
| 200 | + }, | |
| 201 | + | |
| 202 | + // 格式化日期范围显示 | |
| 203 | + formatDateRange() { | |
| 204 | + if (!this.startDate || !this.endDate) { | |
| 205 | + return '请选择日期' | |
| 206 | + } | |
| 207 | + | |
| 208 | + // 处理日期字符串,确保正确解析 | |
| 209 | + const startDate = new Date(this.startDate + 'T00:00:00') | |
| 210 | + const endDate = new Date(this.endDate + 'T00:00:00') | |
| 211 | + | |
| 212 | + const formatDate = (date) => { | |
| 213 | + const year = date.getFullYear() | |
| 214 | + const month = String(date.getMonth() + 1).padStart(2, '0') | |
| 215 | + const day = String(date.getDate()).padStart(2, '0') | |
| 216 | + return `${year}/${month}/${day}` | |
| 217 | + } | |
| 218 | + | |
| 219 | + return `${formatDate(startDate)} - ${formatDate(endDate)}` | |
| 220 | + }, | |
| 221 | + | |
| 222 | + // 审批状态变化 | |
| 223 | + handleStatusChange(e) { | |
| 224 | + this.approveStatusIndex = e.detail.value | |
| 225 | + this.currentPage = 1 | |
| 226 | + this.loadReimbursementList() | |
| 227 | + }, | |
| 228 | + | |
| 229 | + // 加载报销申请列表 | |
| 230 | + async loadReimbursementList() { | |
| 231 | + if (this.loading) return | |
| 232 | + | |
| 233 | + try { | |
| 234 | + this.loading = true | |
| 235 | + this.loadmoreStatus = 'loading' | |
| 236 | + | |
| 237 | + // 构建查询参数 | |
| 238 | + const params = { | |
| 239 | + currentPage: this.currentPage, | |
| 240 | + pageSize: this.pageSize | |
| 241 | + } | |
| 242 | + | |
| 243 | + // 添加审批状态筛选 | |
| 244 | + const selectedStatus = this.approveStatusOptions[this.approveStatusIndex] | |
| 245 | + if (selectedStatus && selectedStatus.value) { | |
| 246 | + params.approveStatus = selectedStatus.value | |
| 247 | + } | |
| 248 | + | |
| 249 | + // 添加日期范围筛选 | |
| 250 | + if (this.startDate && this.endDate) { | |
| 251 | + const startTimestamp = new Date(this.startDate + 'T00:00:00').getTime() | |
| 252 | + const endTimestamp = new Date(this.endDate + 'T23:59:59').getTime() | |
| 253 | + params.applicationTime = `${startTimestamp},${endTimestamp}` | |
| 254 | + } | |
| 255 | + | |
| 256 | + const res = await api.getReimbursementList(params) | |
| 257 | + | |
| 258 | + if (res.code === 200 && res.data) { | |
| 259 | + const list = res.data.list || [] | |
| 260 | + const pagination = res.data.pagination || {} | |
| 261 | + | |
| 262 | + if (this.currentPage === 1) { | |
| 263 | + this.dataList = list | |
| 264 | + } else { | |
| 265 | + this.dataList = [...this.dataList, ...list] | |
| 266 | + } | |
| 267 | + | |
| 268 | + this.totalCount = pagination.total || 0 | |
| 269 | + this.hasMore = this.dataList.length < this.totalCount | |
| 270 | + | |
| 271 | + if (!this.hasMore) { | |
| 272 | + this.loadmoreStatus = 'nomore' | |
| 273 | + } else { | |
| 274 | + this.loadmoreStatus = 'loadmore' | |
| 275 | + } | |
| 276 | + } else { | |
| 277 | + uni.showToast({ | |
| 278 | + title: res.message || '加载失败', | |
| 279 | + icon: 'none' | |
| 280 | + }) | |
| 281 | + } | |
| 282 | + } catch (error) { | |
| 283 | + console.error('加载报销审核列表失败:', error) | |
| 284 | + uni.showToast({ | |
| 285 | + title: '加载失败,请重试', | |
| 286 | + icon: 'none' | |
| 287 | + }) | |
| 288 | + } finally { | |
| 289 | + this.loading = false | |
| 290 | + } | |
| 291 | + }, | |
| 292 | + | |
| 293 | + // 刷新数据 | |
| 294 | + refreshData() { | |
| 295 | + this.currentPage = 1 | |
| 296 | + this.hasMore = true | |
| 297 | + this.loadmoreStatus = 'loadmore' | |
| 298 | + this.dataList = [] | |
| 299 | + this.loadReimbursementList() | |
| 300 | + }, | |
| 301 | + | |
| 302 | + // 加载更多 | |
| 303 | + async loadMore() { | |
| 304 | + if (this.loading || !this.hasMore || this.loadmoreStatus === 'nomore') return | |
| 305 | + | |
| 306 | + this.loadmoreStatus = 'loading' | |
| 307 | + this.currentPage++ | |
| 308 | + await this.loadReimbursementList() | |
| 309 | + }, | |
| 310 | + | |
| 311 | + // 查看详情 | |
| 312 | + handleView(item) { | |
| 313 | + uni.navigateTo({ | |
| 314 | + url: `/pages/reimbursement-audit-detail/reimbursement-audit-detail?id=${item.id}` | |
| 315 | + }) | |
| 316 | + }, | |
| 317 | + | |
| 318 | + // 审核(跳转到详情页进行审核) | |
| 319 | + handleAudit(item) { | |
| 320 | + uni.navigateTo({ | |
| 321 | + url: `/pages/reimbursement-audit-detail/reimbursement-audit-detail?id=${item.id}` | |
| 322 | + }) | |
| 323 | + }, | |
| 324 | + | |
| 325 | + // 格式化时间 | |
| 326 | + formatTime(timestamp) { | |
| 327 | + if (!timestamp) return '无' | |
| 328 | + const date = new Date(timestamp) | |
| 329 | + return date.toLocaleString('zh-CN', { | |
| 330 | + year: 'numeric', | |
| 331 | + month: '2-digit', | |
| 332 | + day: '2-digit', | |
| 333 | + hour: '2-digit', | |
| 334 | + minute: '2-digit' | |
| 335 | + }) | |
| 336 | + } | |
| 337 | + } | |
| 338 | + } | |
| 339 | +</script> | |
| 340 | + | |
| 341 | +<style lang="scss" scoped> | |
| 342 | + .container { | |
| 343 | + min-height: 100vh; | |
| 344 | + background: linear-gradient(135deg, #e8f5e9 0%, #b2dfdb 100%); | |
| 345 | + padding: 40rpx; | |
| 346 | + box-sizing: border-box; | |
| 347 | + } | |
| 348 | + | |
| 349 | + .date-filter-card { | |
| 350 | + background: #fff; | |
| 351 | + border-radius: 24rpx; | |
| 352 | + padding: 32rpx; | |
| 353 | + margin-bottom: 24rpx; | |
| 354 | + box-shadow: 0 4rpx 16rpx rgba(76, 175, 80, 0.1); | |
| 355 | + } | |
| 356 | + | |
| 357 | + .date-filter-header { | |
| 358 | + margin-bottom: 24rpx; | |
| 359 | + } | |
| 360 | + | |
| 361 | + .date-range-display { | |
| 362 | + display: flex; | |
| 363 | + align-items: center; | |
| 364 | + justify-content: space-between; | |
| 365 | + padding: 24rpx; | |
| 366 | + background: #f9fff9; | |
| 367 | + border: 3rpx solid #c8e6c9; | |
| 368 | + border-radius: 16rpx; | |
| 369 | + } | |
| 370 | + | |
| 371 | + .date-text { | |
| 372 | + font-size: 28rpx; | |
| 373 | + color: #2e7d32; | |
| 374 | + font-weight: 500; | |
| 375 | + } | |
| 376 | + | |
| 377 | + .calendar-icon { | |
| 378 | + font-size: 32rpx; | |
| 379 | + } | |
| 380 | + | |
| 381 | + .status-filter { | |
| 382 | + display: flex; | |
| 383 | + align-items: center; | |
| 384 | + gap: 24rpx; | |
| 385 | + justify-content: space-between; | |
| 386 | + } | |
| 387 | + | |
| 388 | + .status-filter-label { | |
| 389 | + font-size: 28rpx; | |
| 390 | + color: #2e7d32; | |
| 391 | + font-weight: 500; | |
| 392 | + } | |
| 393 | + | |
| 394 | + .status-picker { | |
| 395 | + flex: 1; | |
| 396 | + display: flex; | |
| 397 | + align-items: center; | |
| 398 | + justify-content: space-between; | |
| 399 | + padding: 24rpx; | |
| 400 | + background: #f9fff9; | |
| 401 | + border: 3rpx solid #c8e6c9; | |
| 402 | + border-radius: 16rpx; | |
| 403 | + } | |
| 404 | + | |
| 405 | + .status-text { | |
| 406 | + font-size: 28rpx; | |
| 407 | + color: #2e7d32; | |
| 408 | + } | |
| 409 | + | |
| 410 | + .picker-arrow { | |
| 411 | + font-size: 24rpx; | |
| 412 | + color: #6a9c6a; | |
| 413 | + margin-left: 10rpx; | |
| 414 | + } | |
| 415 | + | |
| 416 | + .list-card { | |
| 417 | + background: #fff; | |
| 418 | + border-radius: 24rpx; | |
| 419 | + overflow: hidden; | |
| 420 | + box-shadow: 0 4rpx 16rpx rgba(76, 175, 80, 0.1); | |
| 421 | + } | |
| 422 | + | |
| 423 | + .list-header { | |
| 424 | + display: flex; | |
| 425 | + justify-content: space-between; | |
| 426 | + align-items: center; | |
| 427 | + padding: 32rpx; | |
| 428 | + background: linear-gradient(120deg, #43e97b 0%, #38f9d7 100%); | |
| 429 | + } | |
| 430 | + | |
| 431 | + .header-text { | |
| 432 | + font-size: 32rpx; | |
| 433 | + color: #fff; | |
| 434 | + font-weight: 600; | |
| 435 | + letter-spacing: 2rpx; | |
| 436 | + } | |
| 437 | + | |
| 438 | + .total-count { | |
| 439 | + font-size: 24rpx; | |
| 440 | + color: #fff; | |
| 441 | + opacity: 0.9; | |
| 442 | + } | |
| 443 | + | |
| 444 | + .list-content { | |
| 445 | + max-height: calc(100vh - 400rpx); | |
| 446 | + } | |
| 447 | + | |
| 448 | + .list-item { | |
| 449 | + padding: 32rpx; | |
| 450 | + border-bottom: 2rpx solid #f0f0f0; | |
| 451 | + transition: all 0.2s ease; | |
| 452 | + } | |
| 453 | + | |
| 454 | + .list-item:last-child { | |
| 455 | + border-bottom: none; | |
| 456 | + } | |
| 457 | + | |
| 458 | + .list-item:active { | |
| 459 | + background: #f9fff9; | |
| 460 | + } | |
| 461 | + | |
| 462 | + .item-header { | |
| 463 | + display: flex; | |
| 464 | + justify-content: space-between; | |
| 465 | + align-items: center; | |
| 466 | + margin-bottom: 16rpx; | |
| 467 | + } | |
| 468 | + | |
| 469 | + .applicant-name { | |
| 470 | + font-size: 32rpx; | |
| 471 | + color: #2e7d32; | |
| 472 | + font-weight: 600; | |
| 473 | + } | |
| 474 | + | |
| 475 | + .application-time { | |
| 476 | + font-size: 24rpx; | |
| 477 | + color: #6a9c6a; | |
| 478 | + } | |
| 479 | + | |
| 480 | + .item-details { | |
| 481 | + display: flex; | |
| 482 | + flex-wrap: wrap; | |
| 483 | + gap: 24rpx; | |
| 484 | + margin-bottom: 16rpx; | |
| 485 | + } | |
| 486 | + | |
| 487 | + .detail-item { | |
| 488 | + display: flex; | |
| 489 | + align-items: center; | |
| 490 | + gap: 8rpx; | |
| 491 | + } | |
| 492 | + | |
| 493 | + .detail-label { | |
| 494 | + font-size: 24rpx; | |
| 495 | + color: #6a9c6a; | |
| 496 | + } | |
| 497 | + | |
| 498 | + .detail-value { | |
| 499 | + font-size: 28rpx; | |
| 500 | + color: #2e7d32; | |
| 501 | + font-weight: 500; | |
| 502 | + } | |
| 503 | + | |
| 504 | + .detail-value.amount { | |
| 505 | + color: #f57c00; | |
| 506 | + font-weight: 600; | |
| 507 | + font-size: 32rpx; | |
| 508 | + } | |
| 509 | + | |
| 510 | + .item-footer { | |
| 511 | + display: flex; | |
| 512 | + justify-content: space-between; | |
| 513 | + align-items: center; | |
| 514 | + margin-top: 16rpx; | |
| 515 | + } | |
| 516 | + | |
| 517 | + .status-badge { | |
| 518 | + display: inline-block; | |
| 519 | + padding: 8rpx 24rpx; | |
| 520 | + border-radius: 40rpx; | |
| 521 | + font-size: 24rpx; | |
| 522 | + font-weight: 500; | |
| 523 | + text-align: center; | |
| 524 | + } | |
| 525 | + | |
| 526 | + .status-badge.unapproved { | |
| 527 | + background: #fff3e0; | |
| 528 | + color: #ef6c00; | |
| 529 | + border: 2rpx solid #ffe0b2; | |
| 530 | + } | |
| 531 | + | |
| 532 | + .status-badge.pending { | |
| 533 | + background: #e3f2fd; | |
| 534 | + color: #1976d2; | |
| 535 | + border: 2rpx solid #bbdefb; | |
| 536 | + } | |
| 537 | + | |
| 538 | + .status-badge.approved { | |
| 539 | + background: #e8f5e9; | |
| 540 | + color: #2e7d32; | |
| 541 | + border: 2rpx solid #c8e6c9; | |
| 542 | + } | |
| 543 | + | |
| 544 | + .status-badge.rejected { | |
| 545 | + background: #ffebee; | |
| 546 | + color: #c62828; | |
| 547 | + border: 2rpx solid #ffcdd2; | |
| 548 | + } | |
| 549 | + | |
| 550 | + .action-buttons { | |
| 551 | + display: flex; | |
| 552 | + align-items: center; | |
| 553 | + gap: 16rpx; | |
| 554 | + } | |
| 555 | + | |
| 556 | + .action-btn { | |
| 557 | + display: flex; | |
| 558 | + align-items: center; | |
| 559 | + padding: 12rpx 24rpx; | |
| 560 | + border-radius: 24rpx; | |
| 561 | + font-size: 24rpx; | |
| 562 | + font-weight: 500; | |
| 563 | + transition: all 0.2s ease; | |
| 564 | + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1); | |
| 565 | + } | |
| 566 | + | |
| 567 | + .action-btn:active { | |
| 568 | + transform: scale(0.95); | |
| 569 | + box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.15); | |
| 570 | + } | |
| 571 | + | |
| 572 | + .view-btn { | |
| 573 | + background: linear-gradient(135deg, #42a5f5 0%, #1e88e5 100%); | |
| 574 | + color: #fff; | |
| 575 | + } | |
| 576 | + | |
| 577 | + .view-btn:active { | |
| 578 | + background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%); | |
| 579 | + } | |
| 580 | + | |
| 581 | + .audit-btn { | |
| 582 | + background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%); | |
| 583 | + color: #fff; | |
| 584 | + } | |
| 585 | + | |
| 586 | + .audit-btn:active { | |
| 587 | + background: linear-gradient(135deg, #f57c00 0%, #e65100 100%); | |
| 588 | + } | |
| 589 | + | |
| 590 | + .btn-text { | |
| 591 | + letter-spacing: 1rpx; | |
| 592 | + } | |
| 593 | + | |
| 594 | + .loading { | |
| 595 | + text-align: center; | |
| 596 | + padding: 80rpx 40rpx; | |
| 597 | + color: #6a9c6a; | |
| 598 | + font-size: 28rpx; | |
| 599 | + } | |
| 600 | + | |
| 601 | + .empty-state { | |
| 602 | + text-align: center; | |
| 603 | + padding: 120rpx 40rpx; | |
| 604 | + color: #6a9c6a; | |
| 605 | + } | |
| 606 | + | |
| 607 | + .empty-icon { | |
| 608 | + font-size: 120rpx; | |
| 609 | + margin-bottom: 32rpx; | |
| 610 | + opacity: 0.5; | |
| 611 | + } | |
| 612 | +</style> | |
| 613 | + | ... | ... |
绿纤uni-app/pages/reimbursement-audit-list/reimbursement-audit-list.vue
| 1 | 1 | <template> |
| 2 | 2 | <view class="container"> |
| 3 | 3 | <!-- 日期范围筛选 --> |
| 4 | - <view class="date-filter-card"> | |
| 4 | + <!-- <view class="date-filter-card"> | |
| 5 | 5 | <view class="date-filter-header"> |
| 6 | 6 | <view class="date-range-display" @click="showCalendarFun"> |
| 7 | 7 | <text class="date-text">{{ formatDateRange() }}</text> |
| ... | ... | @@ -17,7 +17,7 @@ |
| 17 | 17 | </view> |
| 18 | 18 | </picker> |
| 19 | 19 | </view> |
| 20 | - </view> | |
| 20 | + </view> --> | |
| 21 | 21 | |
| 22 | 22 | <!-- 日历组件 --> |
| 23 | 23 | <uni-calendar |
| ... | ... | @@ -51,16 +51,16 @@ |
| 51 | 51 | </view> |
| 52 | 52 | </view> |
| 53 | 53 | <view class="item-footer"> |
| 54 | - <view class="status-badge" :class="item.approveStatus=='已审批'?'approved':item.approveStatus=='待审批'?'pending':item.approveStatus=='未通过'?'rejected':'unapproved'"> | |
| 55 | - {{ item.approveStatus || '未审批' }} | |
| 54 | + <view class="status-badge" :class="getStatusClass(item.approveStatus)"> | |
| 55 | + {{ item.approveStatus || '待审批' }} | |
| 56 | 56 | </view> |
| 57 | 57 | <view class="action-buttons"> |
| 58 | - <view v-if="item.approveStatus != '未审批'" class="action-btn view-btn" @click.stop="handleView(item)"> | |
| 59 | - <text class="btn-text">查看</text> | |
| 60 | - </view> | |
| 61 | - <view v-if="item.approveStatus === '未审批'" class="action-btn audit-btn" @click.stop="handleAudit(item)"> | |
| 58 | + <view v-if="item.approveStatus === '待审批' || item.approveStatus === '审批中'" class="action-btn audit-btn" @click.stop="handleAudit(item)"> | |
| 62 | 59 | <text class="btn-text">审核</text> |
| 63 | 60 | </view> |
| 61 | + <view v-else class="action-btn view-btn" @click.stop="handleView(item)"> | |
| 62 | + <text class="btn-text">查看</text> | |
| 63 | + </view> | |
| 64 | 64 | </view> |
| 65 | 65 | </view> |
| 66 | 66 | </view> |
| ... | ... | @@ -127,12 +127,14 @@ |
| 127 | 127 | endDate: formatDateLocal(now), |
| 128 | 128 | dateRange: [firstDay.getTime(), today.getTime()], |
| 129 | 129 | // 审批状态筛选 |
| 130 | - approveStatusIndex: 1, // 默认选择"未审批"(索引1) | |
| 130 | + approveStatusIndex: 0, // 默认选择"全部"(索引0) | |
| 131 | 131 | approveStatusOptions: [ |
| 132 | 132 | { label: '全部', value: '' }, |
| 133 | - { label: '未审批', value: '未审批' }, | |
| 134 | - { label: '已审批', value: '已审批' }, | |
| 135 | - { label: '未通过', value: '未通过' } | |
| 133 | + { label: '待审批', value: '待审批' }, | |
| 134 | + { label: '审批中', value: '审批中' }, | |
| 135 | + { label: '已通过', value: '已通过' }, | |
| 136 | + { label: '未通过', value: '未通过' }, | |
| 137 | + { label: '已退回', value: '已退回' } | |
| 136 | 138 | ] |
| 137 | 139 | } |
| 138 | 140 | }, |
| ... | ... | @@ -226,7 +228,7 @@ |
| 226 | 228 | this.loadReimbursementList() |
| 227 | 229 | }, |
| 228 | 230 | |
| 229 | - // 加载报销申请列表 | |
| 231 | + // 加载报销申请列表(我的待办) | |
| 230 | 232 | async loadReimbursementList() { |
| 231 | 233 | if (this.loading) return |
| 232 | 234 | |
| ... | ... | @@ -234,39 +236,73 @@ |
| 234 | 236 | this.loading = true |
| 235 | 237 | this.loadmoreStatus = 'loading' |
| 236 | 238 | |
| 237 | - // 构建查询参数 | |
| 239 | + // 构建查询参数(我的待办接口只支持门店筛选和分页参数) | |
| 238 | 240 | const params = { |
| 239 | 241 | currentPage: this.currentPage, |
| 240 | - pageSize: this.pageSize | |
| 242 | + pageSize: this.pageSize, | |
| 243 | + sidx: 'applicationTime', | |
| 244 | + sort: 'desc' | |
| 241 | 245 | } |
| 242 | 246 | |
| 243 | - // 添加审批状态筛选 | |
| 244 | - const selectedStatus = this.approveStatusOptions[this.approveStatusIndex] | |
| 245 | - if (selectedStatus && selectedStatus.value) { | |
| 246 | - params.approveStatus = selectedStatus.value | |
| 247 | - } | |
| 248 | - | |
| 249 | - // 添加日期范围筛选 | |
| 250 | - if (this.startDate && this.endDate) { | |
| 251 | - const startTimestamp = new Date(this.startDate + 'T00:00:00').getTime() | |
| 252 | - const endTimestamp = new Date(this.endDate + 'T23:59:59').getTime() | |
| 253 | - params.applicationTime = `${startTimestamp},${endTimestamp}` | |
| 254 | - } | |
| 247 | + // 我的待办接口支持门店筛选(如果需要的话,可以添加门店选择功能) | |
| 248 | + // 注意:我的待办接口不支持审批状态和日期范围筛选 | |
| 249 | + // 如果需要这些筛选,需要在前端进行过滤,或者使用其他接口 | |
| 255 | 250 | |
| 256 | - const res = await api.getReimbursementList(params) | |
| 251 | + // 使用我的待办接口 | |
| 252 | + const res = await api.getMyPendingList(params) | |
| 257 | 253 | |
| 258 | 254 | if (res.code === 200 && res.data) { |
| 259 | - const list = res.data.list || [] | |
| 255 | + let list = res.data.list || [] | |
| 260 | 256 | const pagination = res.data.pagination || {} |
| 261 | 257 | |
| 258 | + // 前端过滤:根据审批状态筛选 | |
| 259 | + const selectedStatus = this.approveStatusOptions[this.approveStatusIndex] | |
| 260 | + if (selectedStatus && selectedStatus.value) { | |
| 261 | + list = list.filter(item => item.approveStatus === selectedStatus.value) | |
| 262 | + } | |
| 263 | + | |
| 264 | + // 前端过滤:根据日期范围筛选 | |
| 265 | + if (this.startDate && this.endDate) { | |
| 266 | + const startTimestamp = new Date(this.startDate + 'T00:00:00').getTime() | |
| 267 | + const endTimestamp = new Date(this.endDate + 'T23:59:59').getTime() | |
| 268 | + list = list.filter(item => { | |
| 269 | + if (!item.applicationTime) return false | |
| 270 | + const itemTime = new Date(item.applicationTime).getTime() | |
| 271 | + return itemTime >= startTimestamp && itemTime <= endTimestamp | |
| 272 | + }) | |
| 273 | + } | |
| 274 | + | |
| 262 | 275 | if (this.currentPage === 1) { |
| 263 | 276 | this.dataList = list |
| 264 | 277 | } else { |
| 265 | - this.dataList = [...this.dataList, ...list] | |
| 278 | + // 加载更多时,需要去重(避免重复数据) | |
| 279 | + const existingIds = new Set(this.dataList.map(item => item.id)) | |
| 280 | + const newItems = list.filter(item => !existingIds.has(item.id)) | |
| 281 | + this.dataList = [...this.dataList, ...newItems] | |
| 266 | 282 | } |
| 267 | 283 | |
| 268 | - this.totalCount = pagination.total || 0 | |
| 269 | - this.hasMore = this.dataList.length < this.totalCount | |
| 284 | + // 注意:由于前端过滤,总数可能不准确 | |
| 285 | + // 如果第一页且有筛选条件,使用过滤后的数量;否则使用后端返回的总数 | |
| 286 | + if (this.currentPage === 1) { | |
| 287 | + const hasFilter = (selectedStatus && selectedStatus.value) || (this.startDate && this.endDate) | |
| 288 | + if (hasFilter) { | |
| 289 | + // 有筛选条件时,总数不准确,使用当前列表长度 | |
| 290 | + this.totalCount = list.length | |
| 291 | + } else { | |
| 292 | + // 无筛选条件时,使用后端返回的总数 | |
| 293 | + this.totalCount = pagination.total || list.length | |
| 294 | + } | |
| 295 | + } | |
| 296 | + | |
| 297 | + // 判断是否还有更多数据 | |
| 298 | + const hasFilter = (selectedStatus && selectedStatus.value) || (this.startDate && this.endDate) | |
| 299 | + if (hasFilter) { | |
| 300 | + // 有筛选条件时,如果返回的数据少于pageSize,说明没有更多了 | |
| 301 | + this.hasMore = list.length >= this.pageSize | |
| 302 | + } else { | |
| 303 | + // 无筛选条件时,使用后端分页信息 | |
| 304 | + this.hasMore = this.dataList.length < (pagination.total || 0) | |
| 305 | + } | |
| 270 | 306 | |
| 271 | 307 | if (!this.hasMore) { |
| 272 | 308 | this.loadmoreStatus = 'nomore' |
| ... | ... | @@ -311,14 +347,14 @@ |
| 311 | 347 | // 查看详情 |
| 312 | 348 | handleView(item) { |
| 313 | 349 | uni.navigateTo({ |
| 314 | - url: `/pages/reimbursement-audit-detail/reimbursement-audit-detail?id=${item.id}` | |
| 350 | + url: `/pages/reimbursement-detail/reimbursement-detail?id=${item.id}` | |
| 315 | 351 | }) |
| 316 | 352 | }, |
| 317 | 353 | |
| 318 | 354 | // 审核(跳转到详情页进行审核) |
| 319 | 355 | handleAudit(item) { |
| 320 | 356 | uni.navigateTo({ |
| 321 | - url: `/pages/reimbursement-audit-detail/reimbursement-audit-detail?id=${item.id}` | |
| 357 | + url: `/pages/reimbursement-detail/reimbursement-detail?id=${item.id}` | |
| 322 | 358 | }) |
| 323 | 359 | }, |
| 324 | 360 | |
| ... | ... | @@ -333,6 +369,24 @@ |
| 333 | 369 | hour: '2-digit', |
| 334 | 370 | minute: '2-digit' |
| 335 | 371 | }) |
| 372 | + }, | |
| 373 | + | |
| 374 | + // 获取状态样式类 | |
| 375 | + getStatusClass(status) { | |
| 376 | + switch (status) { | |
| 377 | + case '待审批': | |
| 378 | + return 'pending' | |
| 379 | + case '审批中': | |
| 380 | + return 'pending' | |
| 381 | + case '已通过': | |
| 382 | + return 'approved' | |
| 383 | + case '未通过': | |
| 384 | + return 'rejected' | |
| 385 | + case '已退回': | |
| 386 | + return 'rejected' | |
| 387 | + default: | |
| 388 | + return 'unapproved' | |
| 389 | + } | |
| 336 | 390 | } |
| 337 | 391 | } |
| 338 | 392 | } | ... | ... |
绿纤uni-app/pages/reimbursement-detail/reimbursement-detail - 副本.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="container"> | |
| 3 | + <!-- 详情卡片 --> | |
| 4 | + <view class="detail-card"> | |
| 5 | + <view class="card-header">报销申请详情</view> | |
| 6 | + <view class="detail-content"> | |
| 7 | + <!-- 加载状态 --> | |
| 8 | + <view v-if="loading" class="loading">正在加载详情...</view> | |
| 9 | + | |
| 10 | + <!-- 错误状态 --> | |
| 11 | + <view v-else-if="error" class="error-state"> | |
| 12 | + <view>{{ error }}</view> | |
| 13 | + <u-button class="retry-btn" @click="retryLoad" type="primary" size="small">重试</u-button> | |
| 14 | + </view> | |
| 15 | + | |
| 16 | + <!-- 详情内容 --> | |
| 17 | + <view v-else-if="detailData" class="info-sections"> | |
| 18 | + <!-- 基本信息 --> | |
| 19 | + <view class="info-section"> | |
| 20 | + <view class="section-title">基本信息</view> | |
| 21 | + <view class="info-grid"> | |
| 22 | + <view class="info-item"> | |
| 23 | + <text class="info-label">申请人</text> | |
| 24 | + <text class="info-value">{{ detailData.applicationUserName || '无' }}</text> | |
| 25 | + </view> | |
| 26 | + <view class="info-item"> | |
| 27 | + <text class="info-label">申请门店</text> | |
| 28 | + <text class="info-value">{{ getStoreName(detailData.applicationStoreId) || '无' }}</text> | |
| 29 | + </view> | |
| 30 | + <view class="info-item"> | |
| 31 | + <text class="info-label">申请时间</text> | |
| 32 | + <text class="info-value">{{ formatTime(detailData.applicationTime) }}</text> | |
| 33 | + </view> | |
| 34 | + <view class="info-item"> | |
| 35 | + <text class="info-label">总金额</text> | |
| 36 | + <text class="info-value amount">¥{{ detailData.amount || 0 }}</text> | |
| 37 | + </view> | |
| 38 | + <view class="info-item"> | |
| 39 | + <text class="info-label">审批状态</text> | |
| 40 | + <view class="status-badge" :class="detailData.approveStatus=='已审批'?'approved':detailData.approveStatus=='待审批'?'pending':detailData.approveStatus=='未通过'?'rejected':'unapproved'"> | |
| 41 | + {{ detailData.approveStatus || '未审批' }} | |
| 42 | + </view> | |
| 43 | + </view> | |
| 44 | + </view> | |
| 45 | + </view> | |
| 46 | + | |
| 47 | + <!-- 购买物品清单 --> | |
| 48 | + <view class="info-section" v-if="purchaseRecords && purchaseRecords.length > 0"> | |
| 49 | + <view class="section-title">购买物品清单</view> | |
| 50 | + <view class="purchase-records-list"> | |
| 51 | + <view v-for="(item, index) in purchaseRecords" :key="item.id || index" class="purchase-record-item"> | |
| 52 | + <view class="record-header"> | |
| 53 | + <text class="record-category">{{ item.reimbursementCategoryName || '无' }}</text> | |
| 54 | + <text class="record-amount">¥{{ item.amount || 0 }}</text> | |
| 55 | + </view> | |
| 56 | + <view class="record-details"> | |
| 57 | + <view class="record-detail-item"> | |
| 58 | + <text class="record-label">单价:</text> | |
| 59 | + <text class="record-value">¥{{ item.unitPrice || 0 }}</text> | |
| 60 | + </view> | |
| 61 | + <view class="record-detail-item"> | |
| 62 | + <text class="record-label">数量:</text> | |
| 63 | + <text class="record-value">{{ item.quantity || 0 }}</text> | |
| 64 | + </view> | |
| 65 | + <view class="record-detail-item"> | |
| 66 | + <text class="record-label">购买时间:</text> | |
| 67 | + <text class="record-value">{{ formatTime(item.purchaseTime) }}</text> | |
| 68 | + </view> | |
| 69 | + </view> | |
| 70 | + <view class="record-memo" v-if="item.memo"> | |
| 71 | + <text class="record-label">备注:</text> | |
| 72 | + <text class="record-value">{{ item.memo }}</text> | |
| 73 | + </view> | |
| 74 | + <!-- 附件信息 --> | |
| 75 | + <view class="record-attachment" v-if="item.attachment && item.attachment.length > 0"> | |
| 76 | + <text class="record-label">附件:</text> | |
| 77 | + <view class="attachment-list"> | |
| 78 | + <view | |
| 79 | + v-for="(file, fileIndex) in item.attachment" | |
| 80 | + :key="fileIndex" | |
| 81 | + class="attachment-item" | |
| 82 | + @click="previewFile(file, item.attachment)"> | |
| 83 | + <text class="file-icon">📎</text> | |
| 84 | + <text class="file-name">{{ file.name || '文件' }}</text> | |
| 85 | + </view> | |
| 86 | + </view> | |
| 87 | + </view> | |
| 88 | + </view> | |
| 89 | + </view> | |
| 90 | + </view> | |
| 91 | + | |
| 92 | + <!-- 审批信息 --> | |
| 93 | + <view class="info-section" v-if="detailData.approveStatus && detailData.approveStatus !== '未审批'"> | |
| 94 | + <view class="section-title">审批信息</view> | |
| 95 | + <view class="info-grid"> | |
| 96 | + <view class="info-item" v-if="detailData.approveUser"> | |
| 97 | + <text class="info-label">审批人</text> | |
| 98 | + <text class="info-value">{{ detailData.approveUserName || detailData.approveUser || '无' }}</text> | |
| 99 | + </view> | |
| 100 | + <view class="info-item" v-if="detailData.approveTime"> | |
| 101 | + <text class="info-label">审批时间</text> | |
| 102 | + <text class="info-value">{{ formatTime(detailData.approveTime) }}</text> | |
| 103 | + </view> | |
| 104 | + </view> | |
| 105 | + </view> | |
| 106 | + </view> | |
| 107 | + </view> | |
| 108 | + </view> | |
| 109 | + </view> | |
| 110 | +</template> | |
| 111 | + | |
| 112 | +<script> | |
| 113 | + import api from '@/apis/index.js' | |
| 114 | + import purchaseApi from '@/apis/modules/purchase.js' | |
| 115 | + import config from '@/common/config.js' | |
| 116 | + | |
| 117 | + export default { | |
| 118 | + data() { | |
| 119 | + return { | |
| 120 | + loading: false, | |
| 121 | + error: null, | |
| 122 | + detailData: null, | |
| 123 | + purchaseRecords: [], | |
| 124 | + reimbursementId: null, | |
| 125 | + storeOptions: [], | |
| 126 | + baseUrl: config.getApiBaseUrl() | |
| 127 | + } | |
| 128 | + }, | |
| 129 | + | |
| 130 | + onLoad(options) { | |
| 131 | + if (options.id) { | |
| 132 | + this.reimbursementId = options.id | |
| 133 | + this.loadStoreOptions() | |
| 134 | + this.loadDetail() | |
| 135 | + } else { | |
| 136 | + this.error = '缺少记录ID' | |
| 137 | + } | |
| 138 | + }, | |
| 139 | + | |
| 140 | + methods: { | |
| 141 | + // 加载门店列表 | |
| 142 | + async loadStoreOptions() { | |
| 143 | + try { | |
| 144 | + const res = await api.getStoreList({ | |
| 145 | + currentPage: 1, | |
| 146 | + pageSize: 1000 | |
| 147 | + }) | |
| 148 | + if (res.code === 200 && res.data && res.data.list) { | |
| 149 | + this.storeOptions = res.data.list | |
| 150 | + } else { | |
| 151 | + this.storeOptions = [] | |
| 152 | + } | |
| 153 | + } catch (error) { | |
| 154 | + console.error('加载门店列表失败:', error) | |
| 155 | + this.storeOptions = [] | |
| 156 | + } | |
| 157 | + }, | |
| 158 | + | |
| 159 | + // 获取门店名称 | |
| 160 | + getStoreName(storeId) { | |
| 161 | + if (!storeId) return '无' | |
| 162 | + const store = this.storeOptions.find(item => item.id === storeId) | |
| 163 | + return store ? store.dm : '无' | |
| 164 | + }, | |
| 165 | + | |
| 166 | + // 加载详情数据 | |
| 167 | + async loadDetail() { | |
| 168 | + if (!this.reimbursementId) return | |
| 169 | + | |
| 170 | + try { | |
| 171 | + this.loading = true | |
| 172 | + this.error = null | |
| 173 | + | |
| 174 | + const res = await api.getReimbursementDetail(this.reimbursementId) | |
| 175 | + | |
| 176 | + if (res.code === 200 && res.data) { | |
| 177 | + this.detailData = res.data | |
| 178 | + | |
| 179 | + // 加载关联的购买记录 | |
| 180 | + if (this.detailData.purchaseRecordsId) { | |
| 181 | + await this.loadPurchaseRecords(this.detailData.purchaseRecordsId) | |
| 182 | + } | |
| 183 | + } else { | |
| 184 | + this.error = res.message || '加载详情失败' | |
| 185 | + } | |
| 186 | + } catch (error) { | |
| 187 | + console.error('加载详情失败:', error) | |
| 188 | + this.error = '加载详情失败,请重试' | |
| 189 | + } finally { | |
| 190 | + this.loading = false | |
| 191 | + } | |
| 192 | + }, | |
| 193 | + | |
| 194 | + // 加载购买记录 | |
| 195 | + async loadPurchaseRecords(purchaseRecordsId) { | |
| 196 | + if (!purchaseRecordsId) { | |
| 197 | + this.purchaseRecords = [] | |
| 198 | + return | |
| 199 | + } | |
| 200 | + | |
| 201 | + // 处理逗号分隔的ID字符串 | |
| 202 | + const normalizedStr = purchaseRecordsId.toString().replace(/[\r\n]+/g, ',').replace(/\s+/g, '') | |
| 203 | + const ids = normalizedStr.split(',').map(id => id.trim()).filter(id => id && id !== '') | |
| 204 | + | |
| 205 | + if (ids.length === 0) { | |
| 206 | + this.purchaseRecords = [] | |
| 207 | + return | |
| 208 | + } | |
| 209 | + | |
| 210 | + try { | |
| 211 | + // 批量加载购买记录 | |
| 212 | + const promises = ids.map(id => { | |
| 213 | + return purchaseApi.getPurchaseDetail(id) | |
| 214 | + .then(res => res.code === 200 ? res.data : null) | |
| 215 | + .catch(() => null) | |
| 216 | + }) | |
| 217 | + | |
| 218 | + const results = await Promise.all(promises) | |
| 219 | + this.purchaseRecords = results.filter(item => item !== null) | |
| 220 | + } catch (error) { | |
| 221 | + console.error('加载购买记录失败:', error) | |
| 222 | + this.purchaseRecords = [] | |
| 223 | + } | |
| 224 | + }, | |
| 225 | + | |
| 226 | + // 重试加载 | |
| 227 | + retryLoad() { | |
| 228 | + this.loadDetail() | |
| 229 | + }, | |
| 230 | + | |
| 231 | + // 格式化时间 | |
| 232 | + formatTime(timestamp) { | |
| 233 | + if (!timestamp) return '无' | |
| 234 | + const date = new Date(timestamp) | |
| 235 | + return date.toLocaleString('zh-CN', { | |
| 236 | + year: 'numeric', | |
| 237 | + month: '2-digit', | |
| 238 | + day: '2-digit', | |
| 239 | + hour: '2-digit', | |
| 240 | + minute: '2-digit' | |
| 241 | + }) | |
| 242 | + }, | |
| 243 | + | |
| 244 | + // 获取状态样式类 | |
| 245 | + getStatusClass(status) { | |
| 246 | + switch (status) { | |
| 247 | + case '已审批': | |
| 248 | + return 'approved' | |
| 249 | + case '待审批': | |
| 250 | + return 'pending' | |
| 251 | + case '未通过': | |
| 252 | + return 'rejected' | |
| 253 | + case '未审批': | |
| 254 | + default: | |
| 255 | + return 'unapproved' | |
| 256 | + } | |
| 257 | + }, | |
| 258 | + // 预览文件 | |
| 259 | + previewFile(file, attachmentList) { | |
| 260 | + if (!file || !file.url) { | |
| 261 | + uni.showToast({ | |
| 262 | + title: '附件URL不存在', | |
| 263 | + icon: 'none' | |
| 264 | + }) | |
| 265 | + return | |
| 266 | + } | |
| 267 | + | |
| 268 | + const fullUrl = file.url.startsWith('http') ? file.url : `${this.baseUrl}${file.url}` | |
| 269 | + | |
| 270 | + // 判断文件类型 | |
| 271 | + const fileExtension = this.getFileExtension(file.name || file.url) | |
| 272 | + const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'] | |
| 273 | + | |
| 274 | + if (imageExtensions.includes(fileExtension.toLowerCase())) { | |
| 275 | + // 图片类型:预览 | |
| 276 | + // 构建所有图片URL列表 | |
| 277 | + const imageUrls = attachmentList | |
| 278 | + .filter(item => { | |
| 279 | + const ext = this.getFileExtension(item.name || item.url) | |
| 280 | + return imageExtensions.includes(ext.toLowerCase()) | |
| 281 | + }) | |
| 282 | + .map(item => { | |
| 283 | + const url = item.url.startsWith('http') ? item.url : `${this.baseUrl}${item.url}` | |
| 284 | + return url | |
| 285 | + }) | |
| 286 | + | |
| 287 | + const currentIndex = imageUrls.findIndex(url => url === fullUrl) | |
| 288 | + | |
| 289 | + uni.previewImage({ | |
| 290 | + urls: imageUrls, | |
| 291 | + current: currentIndex >= 0 ? currentIndex : 0 | |
| 292 | + }) | |
| 293 | + } else { | |
| 294 | + // 其他类型:下载或打开 | |
| 295 | + uni.showToast({ | |
| 296 | + title: '非图片文件,请下载查看', | |
| 297 | + icon: 'none' | |
| 298 | + }) | |
| 299 | + // 可以在这里添加下载逻辑 | |
| 300 | + // uni.downloadFile({ | |
| 301 | + // url: fullUrl, | |
| 302 | + // success: (res) => { | |
| 303 | + // // 处理下载成功 | |
| 304 | + // } | |
| 305 | + // }) | |
| 306 | + } | |
| 307 | + }, | |
| 308 | + // 获取文件扩展名 | |
| 309 | + getFileExtension(fileName) { | |
| 310 | + if (!fileName) return '' | |
| 311 | + const lastDot = fileName.lastIndexOf('.') | |
| 312 | + return lastDot > -1 ? fileName.substring(lastDot + 1) : '' | |
| 313 | + } | |
| 314 | + } | |
| 315 | + } | |
| 316 | +</script> | |
| 317 | + | |
| 318 | +<style lang="scss" scoped> | |
| 319 | + .container { | |
| 320 | + min-height: 100vh; | |
| 321 | + background: linear-gradient(135deg, #e8f5e9 0%, #b2dfdb 100%); | |
| 322 | + padding: 40rpx; | |
| 323 | + box-sizing: border-box; | |
| 324 | + } | |
| 325 | + | |
| 326 | + .detail-card { | |
| 327 | + background: #fff; | |
| 328 | + border-radius: 32rpx; | |
| 329 | + box-shadow: 0 8rpx 32rpx rgba(76, 175, 80, 0.1); | |
| 330 | + overflow: hidden; | |
| 331 | + } | |
| 332 | + | |
| 333 | + .card-header { | |
| 334 | + background: linear-gradient(120deg, #43e97b 0%, #38f9d7 100%); | |
| 335 | + padding: 32rpx 40rpx; | |
| 336 | + color: #fff; | |
| 337 | + font-size: 36rpx; | |
| 338 | + font-weight: 600; | |
| 339 | + letter-spacing: 2rpx; | |
| 340 | + text-align: center; | |
| 341 | + } | |
| 342 | + | |
| 343 | + .detail-content { | |
| 344 | + padding: 40rpx; | |
| 345 | + } | |
| 346 | + | |
| 347 | + .loading { | |
| 348 | + text-align: center; | |
| 349 | + padding: 80rpx 40rpx; | |
| 350 | + color: #6a9c6a; | |
| 351 | + font-size: 28rpx; | |
| 352 | + } | |
| 353 | + | |
| 354 | + .error-state { | |
| 355 | + text-align: center; | |
| 356 | + padding: 80rpx 40rpx; | |
| 357 | + color: #f56c6c; | |
| 358 | + font-size: 28rpx; | |
| 359 | + } | |
| 360 | + | |
| 361 | + .retry-btn { | |
| 362 | + margin-top: 32rpx; | |
| 363 | + } | |
| 364 | + | |
| 365 | + .info-sections { | |
| 366 | + display: flex; | |
| 367 | + flex-direction: column; | |
| 368 | + gap: 32rpx; | |
| 369 | + } | |
| 370 | + | |
| 371 | + .info-section { | |
| 372 | + background: #f9fff9; | |
| 373 | + border-radius: 24rpx; | |
| 374 | + padding: 32rpx; | |
| 375 | + border: 2rpx solid #e8f5e9; | |
| 376 | + } | |
| 377 | + | |
| 378 | + .section-title { | |
| 379 | + font-size: 32rpx; | |
| 380 | + font-weight: 600; | |
| 381 | + color: #2e7d32; | |
| 382 | + margin-bottom: 24rpx; | |
| 383 | + padding-bottom: 16rpx; | |
| 384 | + border-bottom: 2rpx solid #c8e6c9; | |
| 385 | + } | |
| 386 | + | |
| 387 | + .info-grid { | |
| 388 | + display: grid; | |
| 389 | + grid-template-columns: 1fr 1fr; | |
| 390 | + gap: 24rpx; | |
| 391 | + } | |
| 392 | + | |
| 393 | + .info-item { | |
| 394 | + display: flex; | |
| 395 | + flex-direction: column; | |
| 396 | + gap: 8rpx; | |
| 397 | + } | |
| 398 | + | |
| 399 | + .info-label { | |
| 400 | + font-size: 24rpx; | |
| 401 | + color: #6a9c6a; | |
| 402 | + font-weight: 500; | |
| 403 | + } | |
| 404 | + | |
| 405 | + .info-value { | |
| 406 | + font-size: 28rpx; | |
| 407 | + color: #2e7d32; | |
| 408 | + font-weight: 500; | |
| 409 | + word-break: break-all; | |
| 410 | + } | |
| 411 | + | |
| 412 | + .info-value.amount { | |
| 413 | + color: #f57c00; | |
| 414 | + font-weight: 600; | |
| 415 | + font-size: 32rpx; | |
| 416 | + } | |
| 417 | + | |
| 418 | + .status-badge { | |
| 419 | + display: inline-block; | |
| 420 | + padding: 8rpx 24rpx; | |
| 421 | + border-radius: 40rpx; | |
| 422 | + font-size: 24rpx; | |
| 423 | + font-weight: 500; | |
| 424 | + text-align: center; | |
| 425 | + width: fit-content; | |
| 426 | + } | |
| 427 | + | |
| 428 | + .status-badge.unapproved { | |
| 429 | + background: #fff3e0; | |
| 430 | + color: #ef6c00; | |
| 431 | + border: 2rpx solid #ffe0b2; | |
| 432 | + } | |
| 433 | + | |
| 434 | + .status-badge.pending { | |
| 435 | + background: #e3f2fd; | |
| 436 | + color: #1976d2; | |
| 437 | + border: 2rpx solid #bbdefb; | |
| 438 | + } | |
| 439 | + | |
| 440 | + .status-badge.approved { | |
| 441 | + background: #e8f5e9; | |
| 442 | + color: #2e7d32; | |
| 443 | + border: 2rpx solid #c8e6c9; | |
| 444 | + } | |
| 445 | + | |
| 446 | + .status-badge.rejected { | |
| 447 | + background: #ffebee; | |
| 448 | + color: #c62828; | |
| 449 | + border: 2rpx solid #ffcdd2; | |
| 450 | + } | |
| 451 | + | |
| 452 | + .purchase-records-list { | |
| 453 | + display: flex; | |
| 454 | + flex-direction: column; | |
| 455 | + gap: 16rpx; | |
| 456 | + } | |
| 457 | + | |
| 458 | + .purchase-record-item { | |
| 459 | + background: #fff; | |
| 460 | + border-radius: 16rpx; | |
| 461 | + padding: 24rpx; | |
| 462 | + border: 2rpx solid #e0e0e0; | |
| 463 | + } | |
| 464 | + | |
| 465 | + .record-header { | |
| 466 | + display: flex; | |
| 467 | + justify-content: space-between; | |
| 468 | + align-items: center; | |
| 469 | + margin-bottom: 16rpx; | |
| 470 | + padding-bottom: 16rpx; | |
| 471 | + border-bottom: 2rpx solid #f0f0f0; | |
| 472 | + } | |
| 473 | + | |
| 474 | + .record-category { | |
| 475 | + font-size: 28rpx; | |
| 476 | + color: #2e7d32; | |
| 477 | + font-weight: 600; | |
| 478 | + } | |
| 479 | + | |
| 480 | + .record-amount { | |
| 481 | + font-size: 32rpx; | |
| 482 | + color: #f57c00; | |
| 483 | + font-weight: 600; | |
| 484 | + } | |
| 485 | + | |
| 486 | + .record-details { | |
| 487 | + display: flex; | |
| 488 | + flex-wrap: wrap; | |
| 489 | + gap: 24rpx; | |
| 490 | + margin-bottom: 12rpx; | |
| 491 | + } | |
| 492 | + | |
| 493 | + .record-detail-item { | |
| 494 | + display: flex; | |
| 495 | + align-items: center; | |
| 496 | + gap: 8rpx; | |
| 497 | + } | |
| 498 | + | |
| 499 | + .record-label { | |
| 500 | + font-size: 24rpx; | |
| 501 | + color: #6a9c6a; | |
| 502 | + } | |
| 503 | + | |
| 504 | + .record-value { | |
| 505 | + font-size: 26rpx; | |
| 506 | + color: #2e7d32; | |
| 507 | + font-weight: 500; | |
| 508 | + } | |
| 509 | + | |
| 510 | + .record-memo { | |
| 511 | + margin-top: 12rpx; | |
| 512 | + padding-top: 12rpx; | |
| 513 | + border-top: 1rpx solid #f0f0f0; | |
| 514 | + display: flex; | |
| 515 | + gap: 8rpx; | |
| 516 | + } | |
| 517 | + | |
| 518 | + .record-memo .record-value { | |
| 519 | + flex: 1; | |
| 520 | + word-break: break-all; | |
| 521 | + } | |
| 522 | + | |
| 523 | + .record-attachment { | |
| 524 | + margin-top: 12rpx; | |
| 525 | + padding-top: 12rpx; | |
| 526 | + border-top: 1rpx solid #f0f0f0; | |
| 527 | + display: flex; | |
| 528 | + flex-direction: column; | |
| 529 | + gap: 8rpx; | |
| 530 | + } | |
| 531 | + | |
| 532 | + .attachment-list { | |
| 533 | + display: flex; | |
| 534 | + flex-direction: column; | |
| 535 | + gap: 8rpx; | |
| 536 | + margin-top: 8rpx; | |
| 537 | + } | |
| 538 | + | |
| 539 | + .attachment-item { | |
| 540 | + display: flex; | |
| 541 | + align-items: center; | |
| 542 | + gap: 8rpx; | |
| 543 | + padding: 8rpx 12rpx; | |
| 544 | + background: #f5f5f5; | |
| 545 | + border-radius: 8rpx; | |
| 546 | + cursor: pointer; | |
| 547 | + } | |
| 548 | + | |
| 549 | + .file-icon { | |
| 550 | + font-size: 24rpx; | |
| 551 | + } | |
| 552 | + | |
| 553 | + .file-name { | |
| 554 | + font-size: 24rpx; | |
| 555 | + color: #409EFF; | |
| 556 | + flex: 1; | |
| 557 | + overflow: hidden; | |
| 558 | + text-overflow: ellipsis; | |
| 559 | + white-space: nowrap; | |
| 560 | + } | |
| 561 | +</style> | |
| 562 | + | ... | ... |
绿纤uni-app/pages/reimbursement-detail/reimbursement-detail.vue
| ... | ... | @@ -20,47 +20,41 @@ |
| 20 | 20 | <view class="section-title">基本信息</view> |
| 21 | 21 | <view class="info-grid"> |
| 22 | 22 | <view class="info-item"> |
| 23 | - <text class="info-label">申请人</text> | |
| 24 | - <text class="info-value">{{ detailData.applicationUserName || '无' }}</text> | |
| 23 | + <text class="info-label">申请人姓名</text> | |
| 24 | + <text class="info-value">{{ (formData && formData.applicationUserName) || detailData.applicationUserName || '无' }}</text> | |
| 25 | 25 | </view> |
| 26 | 26 | <view class="info-item"> |
| 27 | 27 | <text class="info-label">申请门店</text> |
| 28 | - <text class="info-value">{{ getStoreName(detailData.applicationStoreId) || '无' }}</text> | |
| 28 | + <text class="info-value">{{ formData ? (formData.applicationStoreName || getStoreName(formData.applicationStoreId)) : (getStoreName(detailData.applicationStoreId) || '无') }}</text> | |
| 29 | 29 | </view> |
| 30 | 30 | <view class="info-item"> |
| 31 | 31 | <text class="info-label">申请时间</text> |
| 32 | - <text class="info-value">{{ formatTime(detailData.applicationTime) }}</text> | |
| 32 | + <text class="info-value">{{ formatTime((formData && formData.applicationTime) || detailData.applicationTime) }}</text> | |
| 33 | 33 | </view> |
| 34 | 34 | <view class="info-item"> |
| 35 | 35 | <text class="info-label">总金额</text> |
| 36 | - <text class="info-value amount">¥{{ detailData.amount || 0 }}</text> | |
| 37 | - </view> | |
| 38 | - <view class="info-item"> | |
| 39 | - <text class="info-label">审批状态</text> | |
| 40 | - <view class="status-badge" :class="detailData.approveStatus=='已审批'?'approved':detailData.approveStatus=='待审批'?'pending':detailData.approveStatus=='未通过'?'rejected':'unapproved'"> | |
| 41 | - {{ detailData.approveStatus || '未审批' }} | |
| 42 | - </view> | |
| 36 | + <text class="info-value amount">¥{{ formatMoney((formData && formData.amount) || detailData.amount) }}</text> | |
| 43 | 37 | </view> |
| 44 | 38 | </view> |
| 45 | 39 | </view> |
| 46 | 40 | |
| 47 | - <!-- 购买物品清单 --> | |
| 41 | + <!-- 购买记录列表 --> | |
| 48 | 42 | <view class="info-section" v-if="purchaseRecords && purchaseRecords.length > 0"> |
| 49 | - <view class="section-title">购买物品清单</view> | |
| 43 | + <view class="section-title">购买记录列表</view> | |
| 50 | 44 | <view class="purchase-records-list"> |
| 51 | 45 | <view v-for="(item, index) in purchaseRecords" :key="item.id || index" class="purchase-record-item"> |
| 52 | 46 | <view class="record-header"> |
| 53 | 47 | <text class="record-category">{{ item.reimbursementCategoryName || '无' }}</text> |
| 54 | - <text class="record-amount">¥{{ item.amount || 0 }}</text> | |
| 48 | + <text class="record-amount">¥{{ formatMoney(item.amount) }}</text> | |
| 55 | 49 | </view> |
| 56 | 50 | <view class="record-details"> |
| 57 | 51 | <view class="record-detail-item"> |
| 58 | 52 | <text class="record-label">单价:</text> |
| 59 | - <text class="record-value">¥{{ item.unitPrice || 0 }}</text> | |
| 53 | + <text class="record-value">¥{{ formatMoney(item.unitPrice) }}</text> | |
| 60 | 54 | </view> |
| 61 | 55 | <view class="record-detail-item"> |
| 62 | 56 | <text class="record-label">数量:</text> |
| 63 | - <text class="record-value">{{ item.quantity || 0 }}</text> | |
| 57 | + <text class="record-value">{{ item.quantity || '无' }}</text> | |
| 64 | 58 | </view> |
| 65 | 59 | <view class="record-detail-item"> |
| 66 | 60 | <text class="record-label">购买时间:</text> |
| ... | ... | @@ -68,20 +62,38 @@ |
| 68 | 62 | </view> |
| 69 | 63 | </view> |
| 70 | 64 | <view class="record-memo" v-if="item.memo"> |
| 71 | - <text class="record-label">备注:</text> | |
| 72 | - <text class="record-value">{{ item.memo }}</text> | |
| 65 | + <text class="record-label">备注说明:</text> | |
| 66 | + <text class="record-value">{{ item.memo || '无' }}</text> | |
| 73 | 67 | </view> |
| 74 | 68 | <!-- 附件信息 --> |
| 75 | 69 | <view class="record-attachment" v-if="item.attachment && item.attachment.length > 0"> |
| 76 | 70 | <text class="record-label">附件:</text> |
| 77 | 71 | <view class="attachment-list"> |
| 78 | - <view | |
| 79 | - v-for="(file, fileIndex) in item.attachment" | |
| 80 | - :key="fileIndex" | |
| 81 | - class="attachment-item" | |
| 82 | - @click="previewFile(file, item.attachment)"> | |
| 72 | + <view class="attachment-count"> | |
| 83 | 73 | <text class="file-icon">📎</text> |
| 84 | - <text class="file-name">{{ file.name || '文件' }}</text> | |
| 74 | + <text class="file-count">{{ item.attachment.length }}个文件</text> | |
| 75 | + </view> | |
| 76 | + <!-- 显示所有附件 --> | |
| 77 | + <view class="attachment-items"> | |
| 78 | + <view | |
| 79 | + v-for="(file, fileIndex) in item.attachment" | |
| 80 | + :key="fileIndex" | |
| 81 | + class="attachment-item" | |
| 82 | + @click="previewFile(file, item.attachment)"> | |
| 83 | + <!-- 如果是图片,显示预览 --> | |
| 84 | + <view v-if="isImageFile(file)" class="attachment-image-wrapper"> | |
| 85 | + <image | |
| 86 | + :src="getImageUrl(file.url)" | |
| 87 | + mode="aspectFit" | |
| 88 | + class="attachment-image" | |
| 89 | + /> | |
| 90 | + </view> | |
| 91 | + <!-- 如果不是图片,显示文件信息 --> | |
| 92 | + <view v-else class="attachment-file-info"> | |
| 93 | + <text class="file-icon">📄</text> | |
| 94 | + <text class="file-name">{{ file.name || '文件' + (fileIndex + 1) }}</text> | |
| 95 | + </view> | |
| 96 | + </view> | |
| 85 | 97 | </view> |
| 86 | 98 | </view> |
| 87 | 99 | </view> |
| ... | ... | @@ -89,17 +101,137 @@ |
| 89 | 101 | </view> |
| 90 | 102 | </view> |
| 91 | 103 | |
| 92 | - <!-- 审批信息 --> | |
| 93 | - <view class="info-section" v-if="detailData.approveStatus && detailData.approveStatus !== '未审批'"> | |
| 94 | - <view class="section-title">审批信息</view> | |
| 95 | - <view class="info-grid"> | |
| 96 | - <view class="info-item" v-if="detailData.approveUser"> | |
| 97 | - <text class="info-label">审批人</text> | |
| 98 | - <text class="info-value">{{ detailData.approveUserName || detailData.approveUser || '无' }}</text> | |
| 104 | + <!-- 审批流程 --> | |
| 105 | + <view class="info-section" v-if="nodes && nodes.length > 0"> | |
| 106 | + <view class="section-title">审批流程</view> | |
| 107 | + <view class="approval-flow"> | |
| 108 | + <view | |
| 109 | + v-for="(node, index) in nodes" | |
| 110 | + :key="node.nodeId || index" | |
| 111 | + class="flow-node" | |
| 112 | + :class="{ | |
| 113 | + 'node-active': node.nodeOrder === currentNodeOrder, | |
| 114 | + 'node-completed': node.nodeOrder < currentNodeOrder, | |
| 115 | + 'node-pending': node.nodeOrder > currentNodeOrder | |
| 116 | + }" | |
| 117 | + > | |
| 118 | + <view class="node-header"> | |
| 119 | + <view class="node-order">{{ node.nodeOrder }}</view> | |
| 120 | + <view class="node-info"> | |
| 121 | + <view class="node-name">{{ node.nodeName || '无' }}</view> | |
| 122 | + <view class="node-type"> | |
| 123 | + <view class="node-type-tag" :class="node.approvalType === '会签' ? 'type-success' : 'type-warning'"> | |
| 124 | + {{ node.approvalType || '无' }} | |
| 125 | + </view> | |
| 126 | + <view v-if="node.isRequired === 1" class="node-type-tag type-info">必审</view> | |
| 127 | + </view> | |
| 128 | + </view> | |
| 129 | + </view> | |
| 130 | + | |
| 131 | + <!-- 审批人列表 --> | |
| 132 | + <view class="approvers-list" v-if="node.approvers && node.approvers.length > 0"> | |
| 133 | + <text class="approvers-label">审批人:</text> | |
| 134 | + <view class="approvers-items"> | |
| 135 | + <view | |
| 136 | + v-for="approver in node.approvers" | |
| 137 | + :key="approver.userId" | |
| 138 | + class="approver-tag" | |
| 139 | + > | |
| 140 | + {{ approver.userName || '无' }} | |
| 141 | + </view> | |
| 142 | + </view> | |
| 143 | + </view> | |
| 144 | + | |
| 145 | + <!-- 审批记录 --> | |
| 146 | + <view class="approval-records" v-if="node.approvalRecords && node.approvalRecords.length > 0"> | |
| 147 | + <text class="records-label">审批记录:</text> | |
| 148 | + <view class="records-list"> | |
| 149 | + <view | |
| 150 | + v-for="(record, recordIndex) in node.approvalRecords" | |
| 151 | + :key="recordIndex" | |
| 152 | + class="record-item" | |
| 153 | + > | |
| 154 | + <view class="record-header"> | |
| 155 | + <text class="record-approver">{{ record.approverName || '无' }}</text> | |
| 156 | + <view class="record-result-tag" :class="getRecordClass(record.approvalResult)"> | |
| 157 | + {{ record.approvalResult || '无' }} | |
| 158 | + </view> | |
| 159 | + </view> | |
| 160 | + <view class="record-opinion" v-if="record.approvalOpinion"> | |
| 161 | + 意见:{{ record.approvalOpinion }} | |
| 162 | + </view> | |
| 163 | + <view class="record-time" v-if="record.approvalTime"> | |
| 164 | + 时间:{{ formatTime(record.approvalTime) }} | |
| 165 | + </view> | |
| 166 | + </view> | |
| 167 | + </view> | |
| 168 | + </view> | |
| 99 | 169 | </view> |
| 100 | - <view class="info-item" v-if="detailData.approveTime"> | |
| 101 | - <text class="info-label">审批时间</text> | |
| 102 | - <text class="info-value">{{ formatTime(detailData.approveTime) }}</text> | |
| 170 | + </view> | |
| 171 | + </view> | |
| 172 | + | |
| 173 | + <!-- 当前审批人信息 --> | |
| 174 | + <view class="info-section" v-if="currentApprovers && currentApprovers.length > 0"> | |
| 175 | + <view class="section-title">当前审批人</view> | |
| 176 | + <view class="current-approvers"> | |
| 177 | + <view | |
| 178 | + v-for="approver in currentApprovers" | |
| 179 | + :key="approver.userId" | |
| 180 | + class="current-approver-tag" | |
| 181 | + > | |
| 182 | + {{ approver.userName || '无' }} | |
| 183 | + </view> | |
| 184 | + </view> | |
| 185 | + </view> | |
| 186 | + | |
| 187 | + <!-- 退回原因 --> | |
| 188 | + <view class="info-section" v-if="returnedReason"> | |
| 189 | + <view class="section-title">退回原因</view> | |
| 190 | + <view class="returned-reason"> | |
| 191 | + {{ returnedReason }} | |
| 192 | + </view> | |
| 193 | + </view> | |
| 194 | + | |
| 195 | + <!-- 审批操作区域 --> | |
| 196 | + <view class="info-section" v-if="canApprove"> | |
| 197 | + <view class="section-title">审批操作</view> | |
| 198 | + <view class="approval-form"> | |
| 199 | + <view class="form-item"> | |
| 200 | + <text class="form-label">审批结果</text> | |
| 201 | + <view class="radio-group"> | |
| 202 | + <view | |
| 203 | + class="radio-item" | |
| 204 | + :class="{ 'active': approvalForm.result === '通过' }" | |
| 205 | + @tap="approvalForm.result = '通过'"> | |
| 206 | + <text>通过</text> | |
| 207 | + </view> | |
| 208 | + <view | |
| 209 | + class="radio-item" | |
| 210 | + :class="{ 'active': approvalForm.result === '不通过' }" | |
| 211 | + @tap="approvalForm.result = '不通过'"> | |
| 212 | + <text>不通过</text> | |
| 213 | + </view> | |
| 214 | + <view | |
| 215 | + class="radio-item" | |
| 216 | + :class="{ 'active': approvalForm.result === '退回' }" | |
| 217 | + @tap="approvalForm.result = '退回'"> | |
| 218 | + <text>退回</text> | |
| 219 | + </view> | |
| 220 | + </view> | |
| 221 | + </view> | |
| 222 | + <view class="form-item"> | |
| 223 | + <text class="form-label">审批意见</text> | |
| 224 | + <textarea | |
| 225 | + v-model="approvalForm.opinion" | |
| 226 | + placeholder="请输入审批意见" | |
| 227 | + class="form-textarea" | |
| 228 | + maxlength="500" | |
| 229 | + /> | |
| 230 | + </view> | |
| 231 | + <view class="form-actions"> | |
| 232 | + <button class="approve-btn" @tap="handleApprove" :disabled="approving"> | |
| 233 | + {{ approving ? '提交中...' : '提交审批' }} | |
| 234 | + </button> | |
| 103 | 235 | </view> |
| 104 | 236 | </view> |
| 105 | 237 | </view> |
| ... | ... | @@ -120,16 +252,55 @@ |
| 120 | 252 | loading: false, |
| 121 | 253 | error: null, |
| 122 | 254 | detailData: null, |
| 255 | + formData: null, // 表单数据 | |
| 256 | + nodes: [], // 审批节点 | |
| 257 | + currentApprovers: [], // 当前审批人 | |
| 258 | + currentNodeOrder: 0, // 当前节点顺序 | |
| 259 | + approvalStatus: '', // 审批状态 | |
| 260 | + returnedReason: null, // 退回原因 | |
| 123 | 261 | purchaseRecords: [], |
| 124 | 262 | reimbursementId: null, |
| 125 | 263 | storeOptions: [], |
| 126 | - baseUrl: config.getApiBaseUrl() | |
| 264 | + baseUrl: config.getApiBaseUrl(), | |
| 265 | + userInfo: null, // 当前登录用户信息 | |
| 266 | + // 审批操作表单 | |
| 267 | + approvalForm: { | |
| 268 | + result: '通过', | |
| 269 | + opinion: '' | |
| 270 | + }, | |
| 271 | + approving: false // 审批提交中 | |
| 272 | + } | |
| 273 | + }, | |
| 274 | + | |
| 275 | + computed: { | |
| 276 | + // 判断是否可以审批 | |
| 277 | + canApprove() { | |
| 278 | + // 判断是否可以审批:状态为"审批中"且当前用户是当前审批人 | |
| 279 | + if (this.approvalStatus !== '审批中') { | |
| 280 | + return false | |
| 281 | + } | |
| 282 | + | |
| 283 | + if (!this.userInfo || !this.userInfo.userId) { | |
| 284 | + return false | |
| 285 | + } | |
| 286 | + | |
| 287 | + if (!this.currentApprovers || this.currentApprovers.length === 0) { | |
| 288 | + return false | |
| 289 | + } | |
| 290 | + | |
| 291 | + // 检查当前用户的userId是否在审批人列表中 | |
| 292 | + const isCurrentApprover = this.currentApprovers.some(approver => { | |
| 293 | + return approver.userId === this.userInfo.userId | |
| 294 | + }) | |
| 295 | + | |
| 296 | + return isCurrentApprover | |
| 127 | 297 | } |
| 128 | 298 | }, |
| 129 | 299 | |
| 130 | 300 | onLoad(options) { |
| 131 | 301 | if (options.id) { |
| 132 | 302 | this.reimbursementId = options.id |
| 303 | + this.userInfo = uni.getStorageSync('userInfo') | |
| 133 | 304 | this.loadStoreOptions() |
| 134 | 305 | this.loadDetail() |
| 135 | 306 | } else { |
| ... | ... | @@ -174,10 +345,34 @@ |
| 174 | 345 | const res = await api.getReimbursementDetail(this.reimbursementId) |
| 175 | 346 | |
| 176 | 347 | if (res.code === 200 && res.data) { |
| 177 | - this.detailData = res.data | |
| 348 | + // 根据PC端的数据结构处理 | |
| 349 | + this.formData = res.data.form || res.data | |
| 350 | + this.detailData = this.formData // 保持兼容 | |
| 351 | + this.nodes = res.data.nodes || [] | |
| 352 | + this.currentApprovers = res.data.currentApprovers || [] | |
| 353 | + this.currentNodeOrder = res.data.currentNodeOrder || 0 | |
| 354 | + this.approvalStatus = res.data.approvalStatus || this.formData.approveStatus || this.formData.approvalStatus || '' | |
| 355 | + this.returnedReason = res.data.returnedReason || null | |
| 178 | 356 | |
| 179 | - // 加载关联的购买记录 | |
| 180 | - if (this.detailData.purchaseRecordsId) { | |
| 357 | + // 处理购买记录 | |
| 358 | + if (res.data.purchaseRecords && res.data.purchaseRecords.length > 0) { | |
| 359 | + this.purchaseRecords = res.data.purchaseRecords.map(item => { | |
| 360 | + // 处理附件字段 | |
| 361 | + if (item.attachment) { | |
| 362 | + try { | |
| 363 | + item.attachment = typeof item.attachment === 'string' | |
| 364 | + ? JSON.parse(item.attachment) | |
| 365 | + : item.attachment | |
| 366 | + } catch (e) { | |
| 367 | + item.attachment = [] | |
| 368 | + } | |
| 369 | + } else { | |
| 370 | + item.attachment = [] | |
| 371 | + } | |
| 372 | + return item | |
| 373 | + }) | |
| 374 | + } else if (this.detailData.purchaseRecordsId) { | |
| 375 | + // 如果没有直接返回购买记录,则通过ID加载 | |
| 181 | 376 | await this.loadPurchaseRecords(this.detailData.purchaseRecordsId) |
| 182 | 377 | } |
| 183 | 378 | } else { |
| ... | ... | @@ -231,30 +426,129 @@ |
| 231 | 426 | // 格式化时间 |
| 232 | 427 | formatTime(timestamp) { |
| 233 | 428 | if (!timestamp) return '无' |
| 429 | + // 如果是时间戳(数字),需要转换 | |
| 430 | + if (typeof timestamp === 'number') { | |
| 431 | + const date = new Date(timestamp) | |
| 432 | + const year = date.getFullYear() | |
| 433 | + const month = String(date.getMonth() + 1).padStart(2, '0') | |
| 434 | + const day = String(date.getDate()).padStart(2, '0') | |
| 435 | + const hours = String(date.getHours()).padStart(2, '0') | |
| 436 | + const minutes = String(date.getMinutes()).padStart(2, '0') | |
| 437 | + const seconds = String(date.getSeconds()).padStart(2, '0') | |
| 438 | + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` | |
| 439 | + } | |
| 440 | + // 如果是字符串,尝试解析 | |
| 234 | 441 | const date = new Date(timestamp) |
| 235 | - return date.toLocaleString('zh-CN', { | |
| 236 | - year: 'numeric', | |
| 237 | - month: '2-digit', | |
| 238 | - day: '2-digit', | |
| 239 | - hour: '2-digit', | |
| 240 | - minute: '2-digit' | |
| 442 | + if (isNaN(date.getTime())) return timestamp | |
| 443 | + const year = date.getFullYear() | |
| 444 | + const month = String(date.getMonth() + 1).padStart(2, '0') | |
| 445 | + const day = String(date.getDate()).padStart(2, '0') | |
| 446 | + const hours = String(date.getHours()).padStart(2, '0') | |
| 447 | + const minutes = String(date.getMinutes()).padStart(2, '0') | |
| 448 | + const seconds = String(date.getSeconds()).padStart(2, '0') | |
| 449 | + return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` | |
| 450 | + }, | |
| 451 | + | |
| 452 | + // 格式化金额 | |
| 453 | + formatMoney(amount) { | |
| 454 | + if (!amount) return '0.00' | |
| 455 | + return Number(amount).toLocaleString('zh-CN', { | |
| 456 | + minimumFractionDigits: 2, | |
| 457 | + maximumFractionDigits: 2 | |
| 241 | 458 | }) |
| 242 | 459 | }, |
| 460 | + | |
| 461 | + // 获取图片URL | |
| 462 | + getImageUrl(url) { | |
| 463 | + if (!url) return '' | |
| 464 | + if (url.startsWith('http')) return url | |
| 465 | + return `${this.baseUrl}${url}` | |
| 466 | + }, | |
| 467 | + | |
| 468 | + // 判断是否为图片文件 | |
| 469 | + isImageFile(file) { | |
| 470 | + if (!file || !file.url) return false | |
| 471 | + const url = file.url.toLowerCase() | |
| 472 | + const name = (file.name || '').toLowerCase() | |
| 473 | + const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'] | |
| 474 | + return imageExtensions.some(ext => url.includes(ext) || name.includes(ext)) | |
| 475 | + }, | |
| 243 | 476 | |
| 244 | 477 | // 获取状态样式类 |
| 245 | 478 | getStatusClass(status) { |
| 246 | 479 | switch (status) { |
| 247 | - case '已审批': | |
| 248 | - return 'approved' | |
| 249 | 480 | case '待审批': |
| 250 | 481 | return 'pending' |
| 482 | + case '审批中': | |
| 483 | + return 'pending' | |
| 484 | + case '已通过': | |
| 485 | + return 'approved' | |
| 251 | 486 | case '未通过': |
| 252 | 487 | return 'rejected' |
| 253 | - case '未审批': | |
| 488 | + case '已退回': | |
| 489 | + return 'rejected' | |
| 254 | 490 | default: |
| 255 | 491 | return 'unapproved' |
| 256 | 492 | } |
| 257 | 493 | }, |
| 494 | + | |
| 495 | + // 获取审批记录样式类 | |
| 496 | + getRecordClass(result) { | |
| 497 | + switch (result) { | |
| 498 | + case '通过': | |
| 499 | + return 'result-success' | |
| 500 | + case '不通过': | |
| 501 | + return 'result-danger' | |
| 502 | + case '退回': | |
| 503 | + return 'result-warning' | |
| 504 | + case '待审批': | |
| 505 | + default: | |
| 506 | + return 'result-info' | |
| 507 | + } | |
| 508 | + }, | |
| 509 | + | |
| 510 | + // 提交审批 | |
| 511 | + async handleApprove() { | |
| 512 | + if (!this.approvalForm.result) { | |
| 513 | + uni.showToast({ | |
| 514 | + title: '请选择审批结果', | |
| 515 | + icon: 'none' | |
| 516 | + }) | |
| 517 | + return | |
| 518 | + } | |
| 519 | + | |
| 520 | + this.approving = true | |
| 521 | + try { | |
| 522 | + const res = await api.approveReimbursement(this.reimbursementId, { | |
| 523 | + result: this.approvalForm.result, | |
| 524 | + opinion: this.approvalForm.opinion || '' | |
| 525 | + }) | |
| 526 | + | |
| 527 | + if (res.code === 200) { | |
| 528 | + uni.showToast({ | |
| 529 | + title: '审批成功', | |
| 530 | + icon: 'success' | |
| 531 | + }) | |
| 532 | + // 重新加载详情 | |
| 533 | + setTimeout(() => { | |
| 534 | + this.loadDetail() | |
| 535 | + }, 1500) | |
| 536 | + } else { | |
| 537 | + uni.showToast({ | |
| 538 | + title: res.message || '审批失败', | |
| 539 | + icon: 'none' | |
| 540 | + }) | |
| 541 | + } | |
| 542 | + } catch (error) { | |
| 543 | + console.error('审批失败:', error) | |
| 544 | + uni.showToast({ | |
| 545 | + title: '审批失败', | |
| 546 | + icon: 'none' | |
| 547 | + }) | |
| 548 | + } finally { | |
| 549 | + this.approving = false | |
| 550 | + } | |
| 551 | + }, | |
| 258 | 552 | // 预览文件 |
| 259 | 553 | previewFile(file, attachmentList) { |
| 260 | 554 | if (!file || !file.url) { |
| ... | ... | @@ -536,27 +830,393 @@ |
| 536 | 830 | margin-top: 8rpx; |
| 537 | 831 | } |
| 538 | 832 | |
| 539 | - .attachment-item { | |
| 833 | + .attachment-count { | |
| 540 | 834 | display: flex; |
| 541 | 835 | align-items: center; |
| 542 | 836 | gap: 8rpx; |
| 543 | - padding: 8rpx 12rpx; | |
| 544 | - background: #f5f5f5; | |
| 545 | - border-radius: 8rpx; | |
| 546 | - cursor: pointer; | |
| 837 | + margin-bottom: 16rpx; | |
| 547 | 838 | } |
| 548 | 839 | |
| 549 | 840 | .file-icon { |
| 550 | 841 | font-size: 24rpx; |
| 551 | 842 | } |
| 552 | 843 | |
| 553 | - .file-name { | |
| 844 | + .file-count { | |
| 554 | 845 | font-size: 24rpx; |
| 555 | - color: #409EFF; | |
| 846 | + color: #606266; | |
| 847 | + } | |
| 848 | + | |
| 849 | + .attachment-items { | |
| 850 | + display: flex; | |
| 851 | + flex-wrap: wrap; | |
| 852 | + gap: 16rpx; | |
| 853 | + margin-top: 16rpx; | |
| 854 | + } | |
| 855 | + | |
| 856 | + .attachment-item { | |
| 857 | + display: flex; | |
| 858 | + align-items: center; | |
| 859 | + justify-content: center; | |
| 860 | + cursor: pointer; | |
| 861 | + transition: all 0.2s; | |
| 862 | + } | |
| 863 | + | |
| 864 | + .attachment-item:active { | |
| 865 | + opacity: 0.7; | |
| 866 | + transform: scale(0.98); | |
| 867 | + } | |
| 868 | + | |
| 869 | + .attachment-image-wrapper { | |
| 870 | + width: 200rpx; | |
| 871 | + height: 200rpx; | |
| 872 | + border-radius: 12rpx; | |
| 873 | + overflow: hidden; | |
| 874 | + border: 2rpx solid #e0e0e0; | |
| 875 | + background: #f5f5f5; | |
| 876 | + } | |
| 877 | + | |
| 878 | + .attachment-image { | |
| 879 | + width: 100%; | |
| 880 | + height: 100%; | |
| 881 | + } | |
| 882 | + | |
| 883 | + .attachment-file-info { | |
| 884 | + display: flex; | |
| 885 | + align-items: center; | |
| 886 | + gap: 12rpx; | |
| 887 | + padding: 16rpx 24rpx; | |
| 888 | + background: #f9fff9; | |
| 889 | + border: 2rpx solid #c8e6c9; | |
| 890 | + border-radius: 12rpx; | |
| 891 | + min-width: 200rpx; | |
| 892 | + } | |
| 893 | + | |
| 894 | + .attachment-file-info .file-icon { | |
| 895 | + font-size: 32rpx; | |
| 896 | + } | |
| 897 | + | |
| 898 | + .attachment-file-info .file-name { | |
| 899 | + font-size: 24rpx; | |
| 900 | + color: #2e7d32; | |
| 556 | 901 | flex: 1; |
| 557 | 902 | overflow: hidden; |
| 558 | 903 | text-overflow: ellipsis; |
| 559 | 904 | white-space: nowrap; |
| 560 | 905 | } |
| 906 | + | |
| 907 | + /* 审批流程样式 */ | |
| 908 | + .approval-flow { | |
| 909 | + display: flex; | |
| 910 | + flex-direction: column; | |
| 911 | + gap: 24rpx; | |
| 912 | + } | |
| 913 | + | |
| 914 | + .flow-node { | |
| 915 | + padding: 24rpx; | |
| 916 | + border: 2rpx solid #e4e7ed; | |
| 917 | + border-radius: 16rpx; | |
| 918 | + background: #fafafa; | |
| 919 | + transition: all 0.3s; | |
| 920 | + } | |
| 921 | + | |
| 922 | + .flow-node.node-active { | |
| 923 | + border-color: #409EFF; | |
| 924 | + background: #ecf5ff; | |
| 925 | + } | |
| 926 | + | |
| 927 | + .flow-node.node-completed { | |
| 928 | + border-color: #67C23A; | |
| 929 | + background: #f0f9ff; | |
| 930 | + } | |
| 931 | + | |
| 932 | + .flow-node.node-pending { | |
| 933 | + border-color: #e4e7ed; | |
| 934 | + background: #fafafa; | |
| 935 | + opacity: 0.6; | |
| 936 | + } | |
| 937 | + | |
| 938 | + .node-header { | |
| 939 | + display: flex; | |
| 940 | + align-items: center; | |
| 941 | + margin-bottom: 24rpx; | |
| 942 | + } | |
| 943 | + | |
| 944 | + .node-order { | |
| 945 | + width: 64rpx; | |
| 946 | + height: 64rpx; | |
| 947 | + line-height: 64rpx; | |
| 948 | + text-align: center; | |
| 949 | + background: #409EFF; | |
| 950 | + color: #fff; | |
| 951 | + border-radius: 50%; | |
| 952 | + font-weight: 600; | |
| 953 | + font-size: 28rpx; | |
| 954 | + margin-right: 24rpx; | |
| 955 | + flex-shrink: 0; | |
| 956 | + } | |
| 957 | + | |
| 958 | + .node-info { | |
| 959 | + flex: 1; | |
| 960 | + } | |
| 961 | + | |
| 962 | + .node-name { | |
| 963 | + font-size: 32rpx; | |
| 964 | + font-weight: 600; | |
| 965 | + color: #303133; | |
| 966 | + margin-bottom: 16rpx; | |
| 967 | + } | |
| 968 | + | |
| 969 | + .node-type { | |
| 970 | + display: flex; | |
| 971 | + gap: 16rpx; | |
| 972 | + } | |
| 973 | + | |
| 974 | + .node-type-tag { | |
| 975 | + display: inline-block; | |
| 976 | + padding: 4rpx 16rpx; | |
| 977 | + border-radius: 8rpx; | |
| 978 | + font-size: 22rpx; | |
| 979 | + font-weight: 500; | |
| 980 | + } | |
| 981 | + | |
| 982 | + .node-type-tag.type-success { | |
| 983 | + background: #f0f9ff; | |
| 984 | + color: #67C23A; | |
| 985 | + border: 2rpx solid #c8e6c9; | |
| 986 | + } | |
| 987 | + | |
| 988 | + .node-type-tag.type-warning { | |
| 989 | + background: #fff3e0; | |
| 990 | + color: #e6a23c; | |
| 991 | + border: 2rpx solid #ffe0b2; | |
| 992 | + } | |
| 993 | + | |
| 994 | + .node-type-tag.type-info { | |
| 995 | + background: #e3f2fd; | |
| 996 | + color: #909399; | |
| 997 | + border: 2rpx solid #bbdefb; | |
| 998 | + } | |
| 999 | + | |
| 1000 | + .approvers-list { | |
| 1001 | + margin-bottom: 24rpx; | |
| 1002 | + display: flex; | |
| 1003 | + align-items: flex-start; | |
| 1004 | + gap: 16rpx; | |
| 1005 | + } | |
| 1006 | + | |
| 1007 | + .approvers-label { | |
| 1008 | + min-width: 160rpx; | |
| 1009 | + color: #606266; | |
| 1010 | + font-weight: 500; | |
| 1011 | + font-size: 26rpx; | |
| 1012 | + } | |
| 1013 | + | |
| 1014 | + .approvers-items { | |
| 1015 | + flex: 1; | |
| 1016 | + display: flex; | |
| 1017 | + flex-wrap: wrap; | |
| 1018 | + gap: 12rpx; | |
| 1019 | + } | |
| 1020 | + | |
| 1021 | + .approver-tag { | |
| 1022 | + display: inline-block; | |
| 1023 | + padding: 8rpx 16rpx; | |
| 1024 | + background: #e8f5e9; | |
| 1025 | + border: 2rpx solid #c8e6c9; | |
| 1026 | + border-radius: 16rpx; | |
| 1027 | + font-size: 24rpx; | |
| 1028 | + color: #2e7d32; | |
| 1029 | + } | |
| 1030 | + | |
| 1031 | + .approval-records { | |
| 1032 | + margin-top: 24rpx; | |
| 1033 | + padding-top: 24rpx; | |
| 1034 | + border-top: 2rpx solid #e4e7ed; | |
| 1035 | + } | |
| 1036 | + | |
| 1037 | + .records-label { | |
| 1038 | + color: #606266; | |
| 1039 | + font-weight: 500; | |
| 1040 | + font-size: 26rpx; | |
| 1041 | + margin-bottom: 16rpx; | |
| 1042 | + display: block; | |
| 1043 | + } | |
| 1044 | + | |
| 1045 | + .records-list { | |
| 1046 | + display: flex; | |
| 1047 | + flex-direction: column; | |
| 1048 | + gap: 16rpx; | |
| 1049 | + } | |
| 1050 | + | |
| 1051 | + .record-item { | |
| 1052 | + padding: 16rpx; | |
| 1053 | + background: #fff; | |
| 1054 | + border-radius: 8rpx; | |
| 1055 | + border-left: 6rpx solid #409EFF; | |
| 1056 | + } | |
| 1057 | + | |
| 1058 | + .record-header { | |
| 1059 | + display: flex; | |
| 1060 | + justify-content: space-between; | |
| 1061 | + align-items: center; | |
| 1062 | + margin-bottom: 8rpx; | |
| 1063 | + } | |
| 1064 | + | |
| 1065 | + .record-approver { | |
| 1066 | + font-weight: 500; | |
| 1067 | + color: #303133; | |
| 1068 | + font-size: 26rpx; | |
| 1069 | + } | |
| 1070 | + | |
| 1071 | + .record-result-tag { | |
| 1072 | + display: inline-block; | |
| 1073 | + padding: 4rpx 12rpx; | |
| 1074 | + border-radius: 8rpx; | |
| 1075 | + font-size: 22rpx; | |
| 1076 | + font-weight: 500; | |
| 1077 | + } | |
| 1078 | + | |
| 1079 | + .record-result-tag.result-success { | |
| 1080 | + background: #f0f9ff; | |
| 1081 | + color: #67C23A; | |
| 1082 | + } | |
| 1083 | + | |
| 1084 | + .record-result-tag.result-danger { | |
| 1085 | + background: #ffebee; | |
| 1086 | + color: #F56C6C; | |
| 1087 | + } | |
| 1088 | + | |
| 1089 | + .record-result-tag.result-warning { | |
| 1090 | + background: #fff3e0; | |
| 1091 | + color: #e6a23c; | |
| 1092 | + } | |
| 1093 | + | |
| 1094 | + .record-result-tag.result-info { | |
| 1095 | + background: #e3f2fd; | |
| 1096 | + color: #909399; | |
| 1097 | + } | |
| 1098 | + | |
| 1099 | + .record-opinion { | |
| 1100 | + color: #606266; | |
| 1101 | + font-size: 24rpx; | |
| 1102 | + margin-bottom: 8rpx; | |
| 1103 | + line-height: 1.6; | |
| 1104 | + } | |
| 1105 | + | |
| 1106 | + .record-time { | |
| 1107 | + color: #909399; | |
| 1108 | + font-size: 22rpx; | |
| 1109 | + } | |
| 1110 | + | |
| 1111 | + /* 当前审批人样式 */ | |
| 1112 | + .current-approvers { | |
| 1113 | + padding: 24rpx; | |
| 1114 | + background: #fff3cd; | |
| 1115 | + border-radius: 16rpx; | |
| 1116 | + display: flex; | |
| 1117 | + flex-wrap: wrap; | |
| 1118 | + gap: 16rpx; | |
| 1119 | + } | |
| 1120 | + | |
| 1121 | + .current-approver-tag { | |
| 1122 | + display: inline-block; | |
| 1123 | + padding: 12rpx 24rpx; | |
| 1124 | + background: #fff; | |
| 1125 | + border: 2rpx solid #e6a23c; | |
| 1126 | + border-radius: 16rpx; | |
| 1127 | + font-size: 26rpx; | |
| 1128 | + color: #e6a23c; | |
| 1129 | + font-weight: 500; | |
| 1130 | + } | |
| 1131 | + | |
| 1132 | + /* 退回原因样式 */ | |
| 1133 | + .returned-reason { | |
| 1134 | + padding: 24rpx; | |
| 1135 | + background: #fef0f0; | |
| 1136 | + border-radius: 16rpx; | |
| 1137 | + color: #F56C6C; | |
| 1138 | + line-height: 1.6; | |
| 1139 | + font-size: 26rpx; | |
| 1140 | + } | |
| 1141 | + | |
| 1142 | + /* 审批操作表单样式 */ | |
| 1143 | + .approval-form { | |
| 1144 | + display: flex; | |
| 1145 | + flex-direction: column; | |
| 1146 | + gap: 32rpx; | |
| 1147 | + } | |
| 1148 | + | |
| 1149 | + .form-item { | |
| 1150 | + display: flex; | |
| 1151 | + flex-direction: column; | |
| 1152 | + gap: 16rpx; | |
| 1153 | + } | |
| 1154 | + | |
| 1155 | + .form-label { | |
| 1156 | + font-size: 28rpx; | |
| 1157 | + color: #2e7d32; | |
| 1158 | + font-weight: 600; | |
| 1159 | + } | |
| 1160 | + | |
| 1161 | + .radio-group { | |
| 1162 | + display: flex; | |
| 1163 | + gap: 24rpx; | |
| 1164 | + } | |
| 1165 | + | |
| 1166 | + .radio-item { | |
| 1167 | + flex: 1; | |
| 1168 | + padding: 20rpx; | |
| 1169 | + background: #f9fff9; | |
| 1170 | + border: 3rpx solid #c8e6c9; | |
| 1171 | + border-radius: 16rpx; | |
| 1172 | + text-align: center; | |
| 1173 | + font-size: 26rpx; | |
| 1174 | + color: #2e7d32; | |
| 1175 | + transition: all 0.2s; | |
| 1176 | + } | |
| 1177 | + | |
| 1178 | + .radio-item.active { | |
| 1179 | + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); | |
| 1180 | + border-color: #43e97b; | |
| 1181 | + color: #fff; | |
| 1182 | + font-weight: 600; | |
| 1183 | + } | |
| 1184 | + | |
| 1185 | + .form-textarea { | |
| 1186 | + width: 100%; | |
| 1187 | + min-height: 200rpx; | |
| 1188 | + padding: 24rpx; | |
| 1189 | + background: #f9fff9; | |
| 1190 | + border: 3rpx solid #c8e6c9; | |
| 1191 | + border-radius: 16rpx; | |
| 1192 | + font-size: 26rpx; | |
| 1193 | + color: #2e7d32; | |
| 1194 | + box-sizing: border-box; | |
| 1195 | + } | |
| 1196 | + | |
| 1197 | + .form-actions { | |
| 1198 | + margin-top: 16rpx; | |
| 1199 | + } | |
| 1200 | + | |
| 1201 | + .approve-btn { | |
| 1202 | + width: 100%; | |
| 1203 | + padding:10rpx 32rpx; | |
| 1204 | + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); | |
| 1205 | + color: #fff; | |
| 1206 | + border-radius: 48rpx; | |
| 1207 | + font-size: 32rpx; | |
| 1208 | + font-weight: 600; | |
| 1209 | + border: none; | |
| 1210 | + box-shadow: 0 8rpx 24rpx rgba(67, 233, 123, 0.3); | |
| 1211 | + } | |
| 1212 | + | |
| 1213 | + .approve-btn:active { | |
| 1214 | + transform: scale(0.98); | |
| 1215 | + box-shadow: 0 4rpx 12rpx rgba(67, 233, 123, 0.4); | |
| 1216 | + } | |
| 1217 | + | |
| 1218 | + .approve-btn:disabled { | |
| 1219 | + opacity: 0.5; | |
| 1220 | + } | |
| 561 | 1221 | </style> |
| 562 | 1222 | ... | ... |
绿纤uni-app/pages/reimbursement-form/reimbursement-form - 副本.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="form-container"> | |
| 3 | + <view class="form-card"> | |
| 4 | + <view class="form-content"> | |
| 5 | + <form @submit="handleFormSubmit"> | |
| 6 | + <!-- 申请人 --> | |
| 7 | + <!-- <view class="form-group"> | |
| 8 | + <text class="form-label">申请人</text> | |
| 9 | + <view class="input-wrapper"> | |
| 10 | + <u-input v-model="formData.applicationUserName" placeholder="自动填充" readonly | |
| 11 | + class="select-input" /> | |
| 12 | + </view> | |
| 13 | + </view> --> | |
| 14 | + | |
| 15 | + <!-- 申请门店 --> | |
| 16 | + <!-- <view class="form-group"> | |
| 17 | + <text class="form-label">申请门店</text> | |
| 18 | + <view class="input-wrapper"> | |
| 19 | + <u-input v-model="formData.applicationStoreName" placeholder="自动填充" readonly | |
| 20 | + class="select-input" /> | |
| 21 | + </view> | |
| 22 | + </view> --> | |
| 23 | + | |
| 24 | + <!-- 申请时间 --> | |
| 25 | + <view class="form-group"> | |
| 26 | + <text class="form-label">申请时间</text> | |
| 27 | + <view class="input-wrapper"> | |
| 28 | + <picker mode="date" :value="formData.applicationTimeStr" @change="onApplicationTimeChange"> | |
| 29 | + <view class="custom-select"> | |
| 30 | + <text class="select-text">{{ formData.applicationTimeStr || '请选择申请时间' }}</text> | |
| 31 | + <text class="select-arrow">▼</text> | |
| 32 | + </view> | |
| 33 | + </picker> | |
| 34 | + </view> | |
| 35 | + </view> | |
| 36 | + | |
| 37 | + <!-- 总金额 --> | |
| 38 | + <view class="form-group"> | |
| 39 | + <text class="form-label">总金额</text> | |
| 40 | + <view class="input-wrapper"> | |
| 41 | + <u-input v-model="formData.amount" placeholder="自动计算" type="digit" readonly | |
| 42 | + class="select-input" /> | |
| 43 | + </view> | |
| 44 | + </view> | |
| 45 | + | |
| 46 | + <!-- 购买物品清单 --> | |
| 47 | + <view class="form-group"> | |
| 48 | + <text class="form-label">购买物品清单</text> | |
| 49 | + <view class="input-wrapper"> | |
| 50 | + <view class="purchase-select-btn" @tap="openPurchaseDialog"> | |
| 51 | + <text class="btn-text">选择购买物品</text> | |
| 52 | + </view> | |
| 53 | + <text v-if="selectedPurchaseRecords.length > 0" class="selected-count"> | |
| 54 | + 已选择 {{ selectedPurchaseRecords.length }} 条记录 | |
| 55 | + </text> | |
| 56 | + </view> | |
| 57 | + </view> | |
| 58 | + | |
| 59 | + <!-- 已选择的购买物品清单 --> | |
| 60 | + <view class="form-group" v-if="selectedPurchaseRecords.length > 0"> | |
| 61 | + <view class="purchase-list"> | |
| 62 | + <view v-for="(item, index) in selectedPurchaseRecords" :key="item.id || index" class="purchase-item"> | |
| 63 | + <view class="purchase-item-header"> | |
| 64 | + <text class="purchase-category">{{ item.reimbursementCategoryName || '无' }}</text> | |
| 65 | + <view class="purchase-remove" @tap="removePurchaseRecord(index)"> | |
| 66 | + <text class="remove-icon">✕</text> | |
| 67 | + </view> | |
| 68 | + </view> | |
| 69 | + <view class="purchase-item-details"> | |
| 70 | + <text class="purchase-detail">单价: ¥{{ item.unitPrice || 0 }}</text> | |
| 71 | + <text class="purchase-detail">数量: {{ item.quantity || 0 }}</text> | |
| 72 | + <text class="purchase-detail amount">金额: ¥{{ item.amount || 0 }}</text> | |
| 73 | + </view> | |
| 74 | + <view class="purchase-item-time"> | |
| 75 | + <text class="purchase-time">购买时间: {{ formatTime(item.purchaseTime) }}</text> | |
| 76 | + </view> | |
| 77 | + </view> | |
| 78 | + </view> | |
| 79 | + </view> | |
| 80 | + | |
| 81 | + <!-- 提交按钮 --> | |
| 82 | + <view class="btn-group"> | |
| 83 | + <button type="submit" class="btn btn-primary" | |
| 84 | + :style="{opacity: isSubmitting ? 0.5 : 1}" | |
| 85 | + @tap="isSubmitting ? null : handleFormSubmit()"> | |
| 86 | + {{ isSubmitting ? '提交中...' : (formData.id ? '更新' : '提交') }} | |
| 87 | + </button> | |
| 88 | + </view> | |
| 89 | + </form> | |
| 90 | + </view> | |
| 91 | + </view> | |
| 92 | + | |
| 93 | + <!-- 选择购买物品弹窗 --> | |
| 94 | + <view v-if="showPurchaseDialog" class="modal-overlay" @tap="closePurchaseDialog"> | |
| 95 | + <view class="modal-content" @tap.stop> | |
| 96 | + <view class="modal-header"> | |
| 97 | + <text class="modal-title">选择购买物品</text> | |
| 98 | + <view class="modal-close" @tap="closePurchaseDialog"> | |
| 99 | + <text class="close-icon">✕</text> | |
| 100 | + </view> | |
| 101 | + </view> | |
| 102 | + <view class="modal-body"> | |
| 103 | + <!-- 搜索框 --> | |
| 104 | + <view class="search-box"> | |
| 105 | + <u-input v-model="searchKeyword" placeholder="搜索分类名称" @input="handleSearch" | |
| 106 | + class="search-input" /> | |
| 107 | + </view> | |
| 108 | + <!-- 购买记录列表 --> | |
| 109 | + <scroll-view scroll-y class="purchase-dialog-list" @scrolltolower="loadMorePurchaseRecords"> | |
| 110 | + <view v-if="purchaseListLoading" class="loading-text">加载中...</view> | |
| 111 | + <view v-else-if="filteredPurchaseList.length === 0" class="empty-text">暂无可用记录</view> | |
| 112 | + <view v-else> | |
| 113 | + <view | |
| 114 | + v-for="(item, index) in filteredPurchaseList" | |
| 115 | + :key="item.id || index" | |
| 116 | + class="purchase-dialog-item" | |
| 117 | + :class="{ 'selected': isPurchaseSelected(item.id) }" | |
| 118 | + @tap="togglePurchaseSelection(item)"> | |
| 119 | + <view class="purchase-dialog-header"> | |
| 120 | + <text class="purchase-dialog-category">{{ item.reimbursementCategoryName || '无' }}</text> | |
| 121 | + <view v-if="isPurchaseSelected(item.id)" class="selected-mark">✓</view> | |
| 122 | + </view> | |
| 123 | + <view class="purchase-dialog-details"> | |
| 124 | + <text class="purchase-dialog-detail">单价: ¥{{ item.unitPrice || 0 }}</text> | |
| 125 | + <text class="purchase-dialog-detail">数量: {{ item.quantity || 0 }}</text> | |
| 126 | + <text class="purchase-dialog-detail amount">金额: ¥{{ item.amount || 0 }}</text> | |
| 127 | + </view> | |
| 128 | + <view class="purchase-dialog-time"> | |
| 129 | + <text>{{ formatTime(item.purchaseTime) }}</text> | |
| 130 | + </view> | |
| 131 | + </view> | |
| 132 | + </view> | |
| 133 | + </scroll-view> | |
| 134 | + </view> | |
| 135 | + <view class="modal-footer"> | |
| 136 | + <button class="modal-btn cancel-btn" @tap="closePurchaseDialog">取消</button> | |
| 137 | + <button class="modal-btn confirm-btn" @tap="confirmPurchaseSelection">确定</button> | |
| 138 | + </view> | |
| 139 | + </view> | |
| 140 | + </view> | |
| 141 | + </view> | |
| 142 | +</template> | |
| 143 | + | |
| 144 | +<script> | |
| 145 | + import api from '@/apis/index.js' | |
| 146 | + import purchaseApi from '@/apis/modules/purchase.js' | |
| 147 | + | |
| 148 | + export default { | |
| 149 | + data() { | |
| 150 | + return { | |
| 151 | + isSubmitting: false, | |
| 152 | + formData: { | |
| 153 | + id: undefined, | |
| 154 | + applicationUserId: undefined, | |
| 155 | + applicationUserName: undefined, | |
| 156 | + applicationStoreId: undefined, | |
| 157 | + applicationStoreName: undefined, | |
| 158 | + applicationTime: undefined, | |
| 159 | + applicationTimeStr: undefined, | |
| 160 | + amount: undefined, | |
| 161 | + approveStatus: '未审批', | |
| 162 | + selectedPurchaseRecordIds: [] | |
| 163 | + }, | |
| 164 | + selectedPurchaseRecords: [], | |
| 165 | + // 购买物品选择弹窗相关 | |
| 166 | + showPurchaseDialog: false, | |
| 167 | + purchaseList: [], | |
| 168 | + filteredPurchaseList: [], | |
| 169 | + purchaseListLoading: false, | |
| 170 | + tempSelectedPurchaseIds: [], | |
| 171 | + searchKeyword: '', | |
| 172 | + userInfo: null, | |
| 173 | + newuserInfo: null | |
| 174 | + } | |
| 175 | + }, | |
| 176 | + | |
| 177 | + onLoad(options) { | |
| 178 | + this.initializePage(options) | |
| 179 | + }, | |
| 180 | + | |
| 181 | + methods: { | |
| 182 | + // 初始化页面 | |
| 183 | + async initializePage(options) { | |
| 184 | + try { | |
| 185 | + // 获取用户信息 | |
| 186 | + this.userInfo = uni.getStorageSync('userInfo') | |
| 187 | + this.newuserInfo = uni.getStorageSync('newuserInfo') | |
| 188 | + | |
| 189 | + if (!this.userInfo || Object.keys(this.userInfo).length === 0) { | |
| 190 | + uni.showToast({ | |
| 191 | + title: '请先登录', | |
| 192 | + icon: 'none' | |
| 193 | + }) | |
| 194 | + setTimeout(() => { | |
| 195 | + uni.reLaunch({ | |
| 196 | + url: '/pages/login/login' | |
| 197 | + }) | |
| 198 | + }, 1500) | |
| 199 | + return | |
| 200 | + } | |
| 201 | + | |
| 202 | + // 设置默认值 | |
| 203 | + this.setDefaultValues() | |
| 204 | + | |
| 205 | + // 如果有ID,加载编辑数据 | |
| 206 | + if (options.id) { | |
| 207 | + await this.loadEditData(options.id) | |
| 208 | + } | |
| 209 | + } catch (error) { | |
| 210 | + console.error('页面初始化失败:', error) | |
| 211 | + uni.showToast({ | |
| 212 | + title: '页面初始化失败', | |
| 213 | + icon: 'none' | |
| 214 | + }) | |
| 215 | + } | |
| 216 | + }, | |
| 217 | + | |
| 218 | + // 设置默认值 | |
| 219 | + setDefaultValues() { | |
| 220 | + // 申请人:当前登录用户 | |
| 221 | + if (this.userInfo && this.userInfo.userId) { | |
| 222 | + this.formData.applicationUserId = this.userInfo.userId | |
| 223 | + this.formData.applicationUserName = this.userInfo.realName || this.userInfo.userName || '当前用户' | |
| 224 | + } | |
| 225 | + | |
| 226 | + // 申请门店:当前登录用户的门店 | |
| 227 | + if (this.newuserInfo && this.newuserInfo.mdid) { | |
| 228 | + this.formData.applicationStoreId = this.newuserInfo.mdid | |
| 229 | + this.formData.applicationStoreName = this.newuserInfo.mdmc || '当前门店' | |
| 230 | + } else if (this.userInfo && this.userInfo.mdid) { | |
| 231 | + this.formData.applicationStoreId = this.userInfo.mdid | |
| 232 | + this.formData.applicationStoreName = this.userInfo.mdmc || '当前门店' | |
| 233 | + } | |
| 234 | + | |
| 235 | + // 申请时间:默认今日 | |
| 236 | + const today = new Date() | |
| 237 | + today.setHours(0, 0, 0, 0) | |
| 238 | + this.formData.applicationTime = today.getTime() | |
| 239 | + this.formData.applicationTimeStr = today.toISOString().split('T')[0] | |
| 240 | + }, | |
| 241 | + | |
| 242 | + // 申请时间变化 | |
| 243 | + onApplicationTimeChange(e) { | |
| 244 | + this.formData.applicationTimeStr = e.detail.value | |
| 245 | + const date = new Date(e.detail.value) | |
| 246 | + date.setHours(0, 0, 0, 0) | |
| 247 | + this.formData.applicationTime = date.getTime() | |
| 248 | + }, | |
| 249 | + | |
| 250 | + // 打开购买物品选择弹窗 | |
| 251 | + openPurchaseDialog() { | |
| 252 | + this.showPurchaseDialog = true | |
| 253 | + this.tempSelectedPurchaseIds = this.selectedPurchaseRecords.map(item => item.id) | |
| 254 | + this.searchKeyword = '' | |
| 255 | + this.loadPurchaseRecords() | |
| 256 | + }, | |
| 257 | + | |
| 258 | + // 关闭购买物品选择弹窗 | |
| 259 | + closePurchaseDialog() { | |
| 260 | + this.showPurchaseDialog = false | |
| 261 | + this.searchKeyword = '' | |
| 262 | + }, | |
| 263 | + | |
| 264 | + // 加载购买记录(未报销的) | |
| 265 | + async loadPurchaseRecords() { | |
| 266 | + try { | |
| 267 | + this.purchaseListLoading = true | |
| 268 | + | |
| 269 | + const params = { | |
| 270 | + createUser:this.newuserInfo&&this.newuserInfo.id?this.newuserInfo.id:'暂无', | |
| 271 | + // createUserStoreId: this.formData.applicationStoreId || '暂无', | |
| 272 | + approveStatus: '未审批' | |
| 273 | + } | |
| 274 | + | |
| 275 | + const res = await api.getUnreimbursedPurchaseList(params) | |
| 276 | + console.error(res) | |
| 277 | + if (res.code === 200 && res.data) { | |
| 278 | + // 过滤出未报销的记录(ApplicationId为空或未关联) | |
| 279 | + // this.purchaseList = (res.data || []).filter(item => !item.applicationId) | |
| 280 | + this.purchaseList = res.data || [] | |
| 281 | + this.filteredPurchaseList = this.purchaseList | |
| 282 | + } else { | |
| 283 | + this.purchaseList = [] | |
| 284 | + this.filteredPurchaseList = [] | |
| 285 | + } | |
| 286 | + console.error(this.purchaseList) | |
| 287 | + } catch (error) { | |
| 288 | + console.error('加载购买记录失败:', error) | |
| 289 | + uni.showToast({ | |
| 290 | + title: '加载购买记录失败', | |
| 291 | + icon: 'none' | |
| 292 | + }) | |
| 293 | + this.purchaseList = [] | |
| 294 | + this.filteredPurchaseList = [] | |
| 295 | + } finally { | |
| 296 | + this.purchaseListLoading = false | |
| 297 | + } | |
| 298 | + }, | |
| 299 | + | |
| 300 | + // 加载更多购买记录(如果需要分页) | |
| 301 | + loadMorePurchaseRecords() { | |
| 302 | + // 当前使用不分页接口,暂不需要 | |
| 303 | + }, | |
| 304 | + | |
| 305 | + // 搜索购买记录 | |
| 306 | + handleSearch() { | |
| 307 | + if (!this.searchKeyword.trim()) { | |
| 308 | + this.filteredPurchaseList = this.purchaseList | |
| 309 | + return | |
| 310 | + } | |
| 311 | + const keyword = this.searchKeyword.toLowerCase() | |
| 312 | + this.filteredPurchaseList = this.purchaseList.filter(item => { | |
| 313 | + const categoryName = (item.reimbursementCategoryName || '').toLowerCase() | |
| 314 | + return categoryName.includes(keyword) | |
| 315 | + }) | |
| 316 | + }, | |
| 317 | + | |
| 318 | + // 切换购买记录选择状态 | |
| 319 | + togglePurchaseSelection(item) { | |
| 320 | + const index = this.tempSelectedPurchaseIds.indexOf(item.id) | |
| 321 | + if (index > -1) { | |
| 322 | + this.tempSelectedPurchaseIds.splice(index, 1) | |
| 323 | + } else { | |
| 324 | + this.tempSelectedPurchaseIds.push(item.id) | |
| 325 | + } | |
| 326 | + }, | |
| 327 | + | |
| 328 | + // 判断购买记录是否已选中 | |
| 329 | + isPurchaseSelected(id) { | |
| 330 | + return this.tempSelectedPurchaseIds.indexOf(id) > -1 | |
| 331 | + }, | |
| 332 | + | |
| 333 | + // 确认选择购买物品 | |
| 334 | + confirmPurchaseSelection() { | |
| 335 | + // 根据选中的ID找到对应的记录 | |
| 336 | + const selectedRecords = this.purchaseList.filter(item => | |
| 337 | + this.tempSelectedPurchaseIds.indexOf(item.id) > -1 | |
| 338 | + ) | |
| 339 | + | |
| 340 | + // 去重:移除已存在的记录 | |
| 341 | + const existingIds = new Set(this.selectedPurchaseRecords.map(item => item.id)) | |
| 342 | + const newRecords = selectedRecords.filter(item => !existingIds.has(item.id)) | |
| 343 | + | |
| 344 | + // 追加新选中的记录 | |
| 345 | + this.selectedPurchaseRecords = [...this.selectedPurchaseRecords, ...newRecords] | |
| 346 | + | |
| 347 | + // 更新ID列表 | |
| 348 | + this.formData.selectedPurchaseRecordIds = this.selectedPurchaseRecords.map(item => item.id) | |
| 349 | + | |
| 350 | + // 自动计算总金额 | |
| 351 | + this.calculateTotalAmount() | |
| 352 | + | |
| 353 | + this.closePurchaseDialog() | |
| 354 | + }, | |
| 355 | + | |
| 356 | + // 移除已选中的购买记录 | |
| 357 | + removePurchaseRecord(index) { | |
| 358 | + this.selectedPurchaseRecords.splice(index, 1) | |
| 359 | + this.formData.selectedPurchaseRecordIds = this.selectedPurchaseRecords.map(item => item.id) | |
| 360 | + this.calculateTotalAmount() | |
| 361 | + }, | |
| 362 | + | |
| 363 | + // 计算总金额 | |
| 364 | + calculateTotalAmount() { | |
| 365 | + const totalAmount = this.selectedPurchaseRecords.reduce((sum, item) => { | |
| 366 | + return sum + (parseFloat(item.amount) || 0) | |
| 367 | + }, 0) | |
| 368 | + this.formData.amount = totalAmount.toFixed(2) | |
| 369 | + }, | |
| 370 | + | |
| 371 | + // 加载编辑数据 | |
| 372 | + async loadEditData(id) { | |
| 373 | + try { | |
| 374 | + uni.showLoading({ | |
| 375 | + title: '加载中...' | |
| 376 | + }) | |
| 377 | + | |
| 378 | + const res = await api.getReimbursementDetail(id) | |
| 379 | + | |
| 380 | + if (res.code === 200 && res.data) { | |
| 381 | + const data = res.data | |
| 382 | + this.formData = { | |
| 383 | + id: data.id, | |
| 384 | + applicationUserId: data.applicationUserId, | |
| 385 | + applicationUserName: data.applicationUserName, | |
| 386 | + applicationStoreId: data.applicationStoreId, | |
| 387 | + applicationStoreName: data.applicationStoreName, | |
| 388 | + applicationTime: data.applicationTime, | |
| 389 | + applicationTimeStr: data.applicationTime ? new Date(data.applicationTime).toISOString().split('T')[0] : undefined, | |
| 390 | + amount: data.amount, | |
| 391 | + approveStatus: data.approveStatus || '未审批', | |
| 392 | + selectedPurchaseRecordIds: [] | |
| 393 | + } | |
| 394 | + | |
| 395 | + // 加载已关联的购买记录 | |
| 396 | + if (data.purchaseRecordsId) { | |
| 397 | + await this.loadSelectedPurchaseRecords(data.purchaseRecordsId) | |
| 398 | + } | |
| 399 | + } else { | |
| 400 | + throw new Error(res.message || '加载数据失败') | |
| 401 | + } | |
| 402 | + } catch (error) { | |
| 403 | + console.error('加载编辑数据失败:', error) | |
| 404 | + uni.showToast({ | |
| 405 | + title: '加载数据失败', | |
| 406 | + icon: 'none' | |
| 407 | + }) | |
| 408 | + } finally { | |
| 409 | + uni.hideLoading() | |
| 410 | + } | |
| 411 | + }, | |
| 412 | + | |
| 413 | + // 加载已选中的购买记录(编辑时使用) | |
| 414 | + async loadSelectedPurchaseRecords(purchaseRecordsId) { | |
| 415 | + if (!purchaseRecordsId) { | |
| 416 | + this.selectedPurchaseRecords = [] | |
| 417 | + this.formData.selectedPurchaseRecordIds = [] | |
| 418 | + return | |
| 419 | + } | |
| 420 | + | |
| 421 | + // 处理逗号分隔的ID字符串 | |
| 422 | + const normalizedStr = purchaseRecordsId.toString().replace(/[\r\n]+/g, ',').replace(/\s+/g, '') | |
| 423 | + const ids = normalizedStr.split(',').map(id => id.trim()).filter(id => id && id !== '') | |
| 424 | + | |
| 425 | + if (ids.length === 0) { | |
| 426 | + this.selectedPurchaseRecords = [] | |
| 427 | + this.formData.selectedPurchaseRecordIds = [] | |
| 428 | + return | |
| 429 | + } | |
| 430 | + | |
| 431 | + try { | |
| 432 | + // 批量加载购买记录 | |
| 433 | + const promises = ids.map(id => { | |
| 434 | + return purchaseApi.getPurchaseDetail(id) | |
| 435 | + .then(res => res.code === 200 ? res.data : null) | |
| 436 | + .catch(() => null) | |
| 437 | + }) | |
| 438 | + | |
| 439 | + const results = await Promise.all(promises) | |
| 440 | + this.selectedPurchaseRecords = results.filter(item => item !== null) | |
| 441 | + this.formData.selectedPurchaseRecordIds = this.selectedPurchaseRecords.map(item => item.id) | |
| 442 | + | |
| 443 | + // 计算总金额 | |
| 444 | + this.calculateTotalAmount() | |
| 445 | + } catch (error) { | |
| 446 | + console.error('加载购买记录失败:', error) | |
| 447 | + uni.showToast({ | |
| 448 | + title: '加载购买记录失败', | |
| 449 | + icon: 'none' | |
| 450 | + }) | |
| 451 | + } | |
| 452 | + }, | |
| 453 | + | |
| 454 | + // 表单提交 | |
| 455 | + async handleFormSubmit() { | |
| 456 | + // 验证必填项 | |
| 457 | + if (!this.formData.selectedPurchaseRecordIds || this.formData.selectedPurchaseRecordIds.length === 0) { | |
| 458 | + uni.showToast({ | |
| 459 | + title: '请至少选择一条购买记录', | |
| 460 | + icon: 'none' | |
| 461 | + }) | |
| 462 | + return | |
| 463 | + } | |
| 464 | + | |
| 465 | + if (this.isSubmitting) return | |
| 466 | + | |
| 467 | + try { | |
| 468 | + this.isSubmitting = true | |
| 469 | + uni.showLoading({ | |
| 470 | + title: '提交中...' | |
| 471 | + }) | |
| 472 | + | |
| 473 | + // 设置关联购买编号(多个ID用逗号分隔) | |
| 474 | + const submitData = { | |
| 475 | + ...this.formData, | |
| 476 | + purchaseRecordsId: this.formData.selectedPurchaseRecordIds.join(',') | |
| 477 | + } | |
| 478 | + | |
| 479 | + let res | |
| 480 | + if (this.formData.id) { | |
| 481 | + // 更新 | |
| 482 | + res = await api.updateReimbursement(this.formData.id, submitData) | |
| 483 | + } else { | |
| 484 | + // 创建 | |
| 485 | + res = await api.createReimbursement(submitData) | |
| 486 | + } | |
| 487 | + | |
| 488 | + if (res.code === 200) { | |
| 489 | + uni.showToast({ | |
| 490 | + title: res.message || '操作成功', | |
| 491 | + icon: 'success' | |
| 492 | + }) | |
| 493 | + setTimeout(() => { | |
| 494 | + uni.navigateBack() | |
| 495 | + }, 1500) | |
| 496 | + } else { | |
| 497 | + throw new Error(res.message || '操作失败') | |
| 498 | + } | |
| 499 | + } catch (error) { | |
| 500 | + console.error('提交失败:', error) | |
| 501 | + uni.showToast({ | |
| 502 | + title: error.message || '提交失败,请重试', | |
| 503 | + icon: 'none' | |
| 504 | + }) | |
| 505 | + } finally { | |
| 506 | + this.isSubmitting = false | |
| 507 | + uni.hideLoading() | |
| 508 | + } | |
| 509 | + }, | |
| 510 | + | |
| 511 | + // 格式化时间 | |
| 512 | + formatTime(timestamp) { | |
| 513 | + if (!timestamp) return '无' | |
| 514 | + const date = new Date(timestamp) | |
| 515 | + return date.toLocaleString('zh-CN', { | |
| 516 | + year: 'numeric', | |
| 517 | + month: '2-digit', | |
| 518 | + day: '2-digit', | |
| 519 | + hour: '2-digit', | |
| 520 | + minute: '2-digit' | |
| 521 | + }) | |
| 522 | + } | |
| 523 | + } | |
| 524 | + } | |
| 525 | +</script> | |
| 526 | + | |
| 527 | +<style lang="scss" scoped> | |
| 528 | + .form-container { | |
| 529 | + min-height: 100vh; | |
| 530 | + background: linear-gradient(135deg, #e8f5e9 0%, #b2dfdb 100%); | |
| 531 | + padding: 40rpx; | |
| 532 | + box-sizing: border-box; | |
| 533 | + } | |
| 534 | + | |
| 535 | + .form-card { | |
| 536 | + background: #fff; | |
| 537 | + border-radius: 32rpx; | |
| 538 | + box-shadow: 0 8rpx 32rpx rgba(76, 175, 80, 0.1); | |
| 539 | + overflow: hidden; | |
| 540 | + } | |
| 541 | + | |
| 542 | + .form-content { | |
| 543 | + padding: 40rpx; | |
| 544 | + } | |
| 545 | + | |
| 546 | + .form-group { | |
| 547 | + margin-bottom: 32rpx; | |
| 548 | + } | |
| 549 | + | |
| 550 | + .form-label { | |
| 551 | + display: block; | |
| 552 | + font-size: 28rpx; | |
| 553 | + color: #2e7d32; | |
| 554 | + font-weight: 600; | |
| 555 | + margin-bottom: 16rpx; | |
| 556 | + } | |
| 557 | + | |
| 558 | + .input-wrapper { | |
| 559 | + width: 100%; | |
| 560 | + } | |
| 561 | + | |
| 562 | + .select-input { | |
| 563 | + background: #f9fff9; | |
| 564 | + border: 3rpx solid #c8e6c9; | |
| 565 | + border-radius: 24rpx; | |
| 566 | + padding: 24rpx 32rpx; | |
| 567 | + } | |
| 568 | + | |
| 569 | + .custom-select { | |
| 570 | + display: flex; | |
| 571 | + align-items: center; | |
| 572 | + justify-content: space-between; | |
| 573 | + background: #f9fff9; | |
| 574 | + border: 3rpx solid #c8e6c9; | |
| 575 | + border-radius: 24rpx; | |
| 576 | + padding: 24rpx 32rpx; | |
| 577 | + min-height: 80rpx; | |
| 578 | + box-sizing: border-box; | |
| 579 | + } | |
| 580 | + | |
| 581 | + .select-text { | |
| 582 | + font-size: 28rpx; | |
| 583 | + color: #2e7d32; | |
| 584 | + flex: 1; | |
| 585 | + } | |
| 586 | + | |
| 587 | + .select-arrow { | |
| 588 | + font-size: 24rpx; | |
| 589 | + color: #6a9c6a; | |
| 590 | + } | |
| 591 | + | |
| 592 | + .purchase-select-btn { | |
| 593 | + display: flex; | |
| 594 | + align-items: center; | |
| 595 | + justify-content: center; | |
| 596 | + gap: 12rpx; | |
| 597 | + padding: 24rpx; | |
| 598 | + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); | |
| 599 | + border-radius: 24rpx; | |
| 600 | + box-shadow: 0 4rpx 12rpx rgba(67, 233, 123, 0.3); | |
| 601 | + } | |
| 602 | + | |
| 603 | + .btn-icon { | |
| 604 | + font-size: 32rpx; | |
| 605 | + color: #fff; | |
| 606 | + } | |
| 607 | + | |
| 608 | + .btn-text { | |
| 609 | + font-size: 28rpx; | |
| 610 | + color: #fff; | |
| 611 | + font-weight: 600; | |
| 612 | + } | |
| 613 | + | |
| 614 | + .selected-count { | |
| 615 | + display: block; | |
| 616 | + margin-top: 16rpx; | |
| 617 | + font-size: 24rpx; | |
| 618 | + color: #6a9c6a; | |
| 619 | + text-align: center; | |
| 620 | + } | |
| 621 | + | |
| 622 | + .purchase-list { | |
| 623 | + display: flex; | |
| 624 | + flex-direction: column; | |
| 625 | + gap: 16rpx; | |
| 626 | + } | |
| 627 | + | |
| 628 | + .purchase-item { | |
| 629 | + background: #f9fff9; | |
| 630 | + border: 2rpx solid #c8e6c9; | |
| 631 | + border-radius: 16rpx; | |
| 632 | + padding: 24rpx; | |
| 633 | + } | |
| 634 | + | |
| 635 | + .purchase-item-header { | |
| 636 | + display: flex; | |
| 637 | + justify-content: space-between; | |
| 638 | + align-items: center; | |
| 639 | + margin-bottom: 12rpx; | |
| 640 | + } | |
| 641 | + | |
| 642 | + .purchase-category { | |
| 643 | + font-size: 28rpx; | |
| 644 | + color: #2e7d32; | |
| 645 | + font-weight: 600; | |
| 646 | + } | |
| 647 | + | |
| 648 | + .purchase-remove { | |
| 649 | + width: 48rpx; | |
| 650 | + height: 48rpx; | |
| 651 | + display: flex; | |
| 652 | + align-items: center; | |
| 653 | + justify-content: center; | |
| 654 | + background: #ffebee; | |
| 655 | + border-radius: 50%; | |
| 656 | + } | |
| 657 | + | |
| 658 | + .remove-icon { | |
| 659 | + font-size: 24rpx; | |
| 660 | + color: #c62828; | |
| 661 | + font-weight: 600; | |
| 662 | + } | |
| 663 | + | |
| 664 | + .purchase-item-details { | |
| 665 | + display: flex; | |
| 666 | + gap: 24rpx; | |
| 667 | + margin-bottom: 12rpx; | |
| 668 | + } | |
| 669 | + | |
| 670 | + .purchase-detail { | |
| 671 | + font-size: 24rpx; | |
| 672 | + color: #6a9c6a; | |
| 673 | + } | |
| 674 | + | |
| 675 | + .purchase-detail.amount { | |
| 676 | + color: #f57c00; | |
| 677 | + font-weight: 600; | |
| 678 | + } | |
| 679 | + | |
| 680 | + .purchase-item-time { | |
| 681 | + font-size: 22rpx; | |
| 682 | + color: #909399; | |
| 683 | + } | |
| 684 | + | |
| 685 | + .purchase-time { | |
| 686 | + font-size: 22rpx; | |
| 687 | + color: #909399; | |
| 688 | + } | |
| 689 | + | |
| 690 | + .btn-group { | |
| 691 | + margin-top: 48rpx; | |
| 692 | + } | |
| 693 | + | |
| 694 | + .btn { | |
| 695 | + width: 100%; | |
| 696 | + padding: 32rpx; | |
| 697 | + border-radius: 48rpx; | |
| 698 | + font-size: 32rpx; | |
| 699 | + font-weight: 600; | |
| 700 | + letter-spacing: 2rpx; | |
| 701 | + border: none; | |
| 702 | + } | |
| 703 | + | |
| 704 | + .btn-primary { | |
| 705 | + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); | |
| 706 | + color: #fff; | |
| 707 | + box-shadow: 0 8rpx 24rpx rgba(67, 233, 123, 0.3); | |
| 708 | + padding: 0; | |
| 709 | + } | |
| 710 | + | |
| 711 | + .btn-primary:active { | |
| 712 | + transform: scale(0.98); | |
| 713 | + box-shadow: 0 4rpx 12rpx rgba(67, 233, 123, 0.4); | |
| 714 | + } | |
| 715 | + | |
| 716 | + /* 弹窗样式 */ | |
| 717 | + .modal-overlay { | |
| 718 | + position: fixed; | |
| 719 | + top: 0; | |
| 720 | + left: 0; | |
| 721 | + right: 0; | |
| 722 | + bottom: 0; | |
| 723 | + background: rgba(0, 0, 0, 0.5); | |
| 724 | + display: flex; | |
| 725 | + align-items: center; | |
| 726 | + justify-content: center; | |
| 727 | + z-index: 1000; | |
| 728 | + } | |
| 729 | + | |
| 730 | + .modal-content { | |
| 731 | + width: 90%; | |
| 732 | + max-height: 80vh; | |
| 733 | + background: #fff; | |
| 734 | + border-radius: 24rpx; | |
| 735 | + display: flex; | |
| 736 | + flex-direction: column; | |
| 737 | + overflow: hidden; | |
| 738 | + } | |
| 739 | + | |
| 740 | + .modal-header { | |
| 741 | + display: flex; | |
| 742 | + justify-content: space-between; | |
| 743 | + align-items: center; | |
| 744 | + padding: 32rpx; | |
| 745 | + background: linear-gradient(120deg, #43e97b 0%, #38f9d7 100%); | |
| 746 | + } | |
| 747 | + | |
| 748 | + .modal-title { | |
| 749 | + font-size: 32rpx; | |
| 750 | + color: #fff; | |
| 751 | + font-weight: 600; | |
| 752 | + } | |
| 753 | + | |
| 754 | + .modal-close { | |
| 755 | + width: 48rpx; | |
| 756 | + height: 48rpx; | |
| 757 | + display: flex; | |
| 758 | + align-items: center; | |
| 759 | + justify-content: center; | |
| 760 | + background: rgba(255, 255, 255, 0.3); | |
| 761 | + border-radius: 50%; | |
| 762 | + } | |
| 763 | + | |
| 764 | + .close-icon { | |
| 765 | + font-size: 28rpx; | |
| 766 | + color: #fff; | |
| 767 | + font-weight: 600; | |
| 768 | + } | |
| 769 | + | |
| 770 | + .modal-body { | |
| 771 | + flex: 1; | |
| 772 | + padding: 32rpx; | |
| 773 | + overflow: hidden; | |
| 774 | + display: flex; | |
| 775 | + flex-direction: column; | |
| 776 | + } | |
| 777 | + | |
| 778 | + .search-box { | |
| 779 | + margin-bottom: 24rpx; | |
| 780 | + } | |
| 781 | + | |
| 782 | + .search-input { | |
| 783 | + background: #f5f5f5; | |
| 784 | + border-radius: 16rpx; | |
| 785 | + } | |
| 786 | + | |
| 787 | + .purchase-dialog-list { | |
| 788 | + flex: 1; | |
| 789 | + max-height: 600rpx; | |
| 790 | + } | |
| 791 | + | |
| 792 | + .loading-text, | |
| 793 | + .empty-text { | |
| 794 | + text-align: center; | |
| 795 | + padding: 80rpx 40rpx; | |
| 796 | + color: #909399; | |
| 797 | + font-size: 28rpx; | |
| 798 | + } | |
| 799 | + | |
| 800 | + .purchase-dialog-item { | |
| 801 | + background: #f9fff9; | |
| 802 | + border: 2rpx solid #c8e6c9; | |
| 803 | + border-radius: 16rpx; | |
| 804 | + padding: 24rpx; | |
| 805 | + margin-bottom: 16rpx; | |
| 806 | + transition: all 0.2s ease; | |
| 807 | + } | |
| 808 | + | |
| 809 | + .purchase-dialog-item.selected { | |
| 810 | + background: #e8f5e9; | |
| 811 | + border-color: #43e97b; | |
| 812 | + } | |
| 813 | + | |
| 814 | + .purchase-dialog-header { | |
| 815 | + display: flex; | |
| 816 | + justify-content: space-between; | |
| 817 | + align-items: center; | |
| 818 | + margin-bottom: 12rpx; | |
| 819 | + } | |
| 820 | + | |
| 821 | + .purchase-dialog-category { | |
| 822 | + font-size: 28rpx; | |
| 823 | + color: #2e7d32; | |
| 824 | + font-weight: 600; | |
| 825 | + } | |
| 826 | + | |
| 827 | + .selected-mark { | |
| 828 | + width: 40rpx; | |
| 829 | + height: 40rpx; | |
| 830 | + display: flex; | |
| 831 | + align-items: center; | |
| 832 | + justify-content: center; | |
| 833 | + background: #43e97b; | |
| 834 | + border-radius: 50%; | |
| 835 | + color: #fff; | |
| 836 | + font-size: 24rpx; | |
| 837 | + font-weight: 600; | |
| 838 | + } | |
| 839 | + | |
| 840 | + .purchase-dialog-details { | |
| 841 | + display: flex; | |
| 842 | + gap: 24rpx; | |
| 843 | + margin-bottom: 12rpx; | |
| 844 | + } | |
| 845 | + | |
| 846 | + .purchase-dialog-detail { | |
| 847 | + font-size: 24rpx; | |
| 848 | + color: #6a9c6a; | |
| 849 | + } | |
| 850 | + | |
| 851 | + .purchase-dialog-detail.amount { | |
| 852 | + color: #f57c00; | |
| 853 | + font-weight: 600; | |
| 854 | + } | |
| 855 | + | |
| 856 | + .purchase-dialog-time { | |
| 857 | + font-size: 22rpx; | |
| 858 | + color: #909399; | |
| 859 | + } | |
| 860 | + | |
| 861 | + .modal-footer { | |
| 862 | + display: flex; | |
| 863 | + gap: 24rpx; | |
| 864 | + padding: 32rpx; | |
| 865 | + border-top: 2rpx solid #f0f0f0; | |
| 866 | + } | |
| 867 | + | |
| 868 | + .modal-btn { | |
| 869 | + flex: 1; | |
| 870 | + padding:15rpx 24rpx; | |
| 871 | + border-radius: 24rpx; | |
| 872 | + font-size: 28rpx; | |
| 873 | + font-weight: 600; | |
| 874 | + border: none; | |
| 875 | + } | |
| 876 | + | |
| 877 | + .cancel-btn { | |
| 878 | + background: #f5f5f5; | |
| 879 | + color: #909399; | |
| 880 | + } | |
| 881 | + | |
| 882 | + .confirm-btn { | |
| 883 | + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); | |
| 884 | + color: #fff; | |
| 885 | + } | |
| 886 | +</style> | |
| 887 | + | ... | ... |
绿纤uni-app/pages/reimbursement-form/reimbursement-form.vue
| ... | ... | @@ -62,8 +62,11 @@ |
| 62 | 62 | <view v-for="(item, index) in selectedPurchaseRecords" :key="item.id || index" class="purchase-item"> |
| 63 | 63 | <view class="purchase-item-header"> |
| 64 | 64 | <text class="purchase-category">{{ item.reimbursementCategoryName || '无' }}</text> |
| 65 | - <view class="purchase-remove" @tap="removePurchaseRecord(index)"> | |
| 66 | - <text class="remove-icon">✕</text> | |
| 65 | + <view class="purchase-item-actions"> | |
| 66 | + <text v-if="item.isInitial" class="initial-mark">初始记录</text> | |
| 67 | + <view v-if="!item.isInitial" class="purchase-remove" @tap="removePurchaseRecord(index)"> | |
| 68 | + <text class="remove-icon">✕</text> | |
| 69 | + </view> | |
| 67 | 70 | </view> |
| 68 | 71 | </view> |
| 69 | 72 | <view class="purchase-item-details"> |
| ... | ... | @@ -78,6 +81,54 @@ |
| 78 | 81 | </view> |
| 79 | 82 | </view> |
| 80 | 83 | |
| 84 | + <!-- 审批节点配置(仅新建时显示) --> | |
| 85 | + <view class="form-group" v-if="!formData.id"> | |
| 86 | + <view class="nodes-header"> | |
| 87 | + <text class="form-label">审批节点配置(1-5个节点)</text> | |
| 88 | + <view class="add-node-btn" @tap="addNode" v-if="formData.nodes.length < 5"> | |
| 89 | + <text class="add-node-text">+ 添加节点</text> | |
| 90 | + </view> | |
| 91 | + </view> | |
| 92 | + <view class="nodes-list"> | |
| 93 | + <view v-for="(node, index) in formData.nodes" :key="index" class="node-item"> | |
| 94 | + <view class="node-header"> | |
| 95 | + <text class="node-order">节点 {{ node.nodeOrder }}</text> | |
| 96 | + <view v-if="formData.nodes.length > 1" class="remove-node-btn" @tap="removeNode(index)"> | |
| 97 | + <text class="remove-node-text">删除</text> | |
| 98 | + </view> | |
| 99 | + </view> | |
| 100 | + <view class="node-form"> | |
| 101 | + <view class="node-form-item"> | |
| 102 | + <text class="node-label">节点名称</text> | |
| 103 | + <u-input v-model="node.nodeName" placeholder="请输入节点名称" class="node-input" /> | |
| 104 | + </view> | |
| 105 | + <view class="node-form-item"> | |
| 106 | + <text class="node-label">审批类型</text> | |
| 107 | + <picker mode="selector" :range="['会签', '或签']" :value="node.approvalType === '会签' ? 0 : (node.approvalType === '或签' ? 1 : -1)" @change="(e) => { node.approvalType = ['会签', '或签'][e.detail.value] }"> | |
| 108 | + <view class="custom-select"> | |
| 109 | + <text class="select-text">{{ node.approvalType || '请选择审批类型' }}</text> | |
| 110 | + <text class="select-arrow">▼</text> | |
| 111 | + </view> | |
| 112 | + </picker> | |
| 113 | + </view> | |
| 114 | + <view class="node-form-item"> | |
| 115 | + <text class="node-label">审批人</text> | |
| 116 | + <view class="approver-select-wrapper"> | |
| 117 | + <view class="approver-select-btn" @tap="openUserDialog(index)"> | |
| 118 | + <text class="approver-btn-text">{{ getApproverDisplayText(node) }}</text> | |
| 119 | + </view> | |
| 120 | + <view v-if="node.approverNames && node.approverNames.length > 0" class="approver-tags"> | |
| 121 | + <view v-for="(name, nameIndex) in node.approverNames" :key="nameIndex" class="approver-tag"> | |
| 122 | + {{ name }} | |
| 123 | + </view> | |
| 124 | + </view> | |
| 125 | + </view> | |
| 126 | + </view> | |
| 127 | + </view> | |
| 128 | + </view> | |
| 129 | + </view> | |
| 130 | + </view> | |
| 131 | + | |
| 81 | 132 | <!-- 提交按钮 --> |
| 82 | 133 | <view class="btn-group"> |
| 83 | 134 | <button type="submit" class="btn btn-primary" |
| ... | ... | @@ -107,8 +158,8 @@ |
| 107 | 158 | </view> |
| 108 | 159 | <!-- 购买记录列表 --> |
| 109 | 160 | <scroll-view scroll-y class="purchase-dialog-list" @scrolltolower="loadMorePurchaseRecords"> |
| 110 | - <view v-if="purchaseListLoading" class="loading-text">加载中...</view> | |
| 111 | - <view v-else-if="filteredPurchaseList.length === 0" class="empty-text">暂无可用记录</view> | |
| 161 | + <view v-if="purchaseListLoading && filteredPurchaseList.length === 0" class="loading-text">加载中...</view> | |
| 162 | + <view v-else-if="!purchaseListLoading && filteredPurchaseList.length === 0" class="empty-text">暂无可用记录</view> | |
| 112 | 163 | <view v-else> |
| 113 | 164 | <view |
| 114 | 165 | v-for="(item, index) in filteredPurchaseList" |
| ... | ... | @@ -129,6 +180,7 @@ |
| 129 | 180 | <text>{{ formatTime(item.purchaseTime) }}</text> |
| 130 | 181 | </view> |
| 131 | 182 | </view> |
| 183 | + <view v-if="purchaseListLoading && filteredPurchaseList.length > 0" class="loading-more">加载更多...</view> | |
| 132 | 184 | </view> |
| 133 | 185 | </scroll-view> |
| 134 | 186 | </view> |
| ... | ... | @@ -138,12 +190,63 @@ |
| 138 | 190 | </view> |
| 139 | 191 | </view> |
| 140 | 192 | </view> |
| 193 | + | |
| 194 | + <!-- 选择审批人弹窗 --> | |
| 195 | + <view v-if="showUserDialog" class="modal-overlay" @tap="closeUserDialog"> | |
| 196 | + <view class="modal-content" @tap.stop> | |
| 197 | + <view class="modal-header"> | |
| 198 | + <text class="modal-title">选择审批人</text> | |
| 199 | + <view class="modal-close" @tap="closeUserDialog"> | |
| 200 | + <text class="close-icon">✕</text> | |
| 201 | + </view> | |
| 202 | + </view> | |
| 203 | + <view class="modal-body"> | |
| 204 | + <!-- 搜索框 --> | |
| 205 | + <view class="search-box"> | |
| 206 | + <u-input v-model="userSearchKeyword" placeholder="搜索用户姓名或账号" @input="handleUserSearch" | |
| 207 | + class="search-input" /> | |
| 208 | + </view> | |
| 209 | + <!-- 用户列表 --> | |
| 210 | + <scroll-view scroll-y class="user-dialog-list" @scrolltolower="loadMoreUsers"> | |
| 211 | + <view v-if="userListLoading && userList.length === 0" class="loading-text">加载中...</view> | |
| 212 | + <view v-else-if="!userListLoading && userList.length === 0" class="empty-text">暂无用户</view> | |
| 213 | + <view v-else> | |
| 214 | + <view | |
| 215 | + v-for="(item, index) in filteredUserList" | |
| 216 | + :key="item.id || index" | |
| 217 | + class="user-dialog-item" | |
| 218 | + :class="{ 'selected': isUserSelected(item.id) }" | |
| 219 | + @tap="toggleUserSelection(item)"> | |
| 220 | + <view class="user-dialog-header"> | |
| 221 | + <text class="user-dialog-name">{{ item.fullName || item.realName || item.userName || item.id || '无' }}</text> | |
| 222 | + <view v-if="isUserSelected(item.id)" class="selected-mark">✓</view> | |
| 223 | + </view> | |
| 224 | + <view class="user-dialog-details"> | |
| 225 | + <text class="user-dialog-detail">账号: {{ item.account || '无' }}</text> | |
| 226 | + <text class="user-dialog-detail">ID: {{ item.id || '无' }}</text> | |
| 227 | + </view> | |
| 228 | + </view> | |
| 229 | + <view v-if="userListLoading && filteredUserList.length > 0" class="loading-more">加载更多...</view> | |
| 230 | + <view v-else-if="!userListLoading && filteredUserList.length >= userTotal && userTotal > 0" class="no-more">没有更多了</view> | |
| 231 | + <view v-if="userTotal > 0" class="pagination-info"> | |
| 232 | + <text>共 {{ userTotal }} 人,已加载 {{ filteredUserList.length }} 人</text> | |
| 233 | + </view> | |
| 234 | + </view> | |
| 235 | + </scroll-view> | |
| 236 | + </view> | |
| 237 | + <view class="modal-footer"> | |
| 238 | + <button class="modal-btn cancel-btn" @tap="closeUserDialog">取消</button> | |
| 239 | + <button class="modal-btn confirm-btn" @tap="confirmUserSelection">确定</button> | |
| 240 | + </view> | |
| 241 | + </view> | |
| 242 | + </view> | |
| 141 | 243 | </view> |
| 142 | 244 | </template> |
| 143 | 245 | |
| 144 | 246 | <script> |
| 145 | 247 | import api from '@/apis/index.js' |
| 146 | 248 | import purchaseApi from '@/apis/modules/purchase.js' |
| 249 | + import oauthApi from '@/apis/modules/oauth.js' | |
| 147 | 250 | |
| 148 | 251 | export default { |
| 149 | 252 | data() { |
| ... | ... | @@ -159,18 +262,40 @@ |
| 159 | 262 | applicationTimeStr: undefined, |
| 160 | 263 | amount: undefined, |
| 161 | 264 | approveStatus: '未审批', |
| 162 | - selectedPurchaseRecordIds: [] | |
| 265 | + selectedPurchaseRecordIds: [], | |
| 266 | + nodes: [] // 审批节点配置 | |
| 163 | 267 | }, |
| 164 | 268 | selectedPurchaseRecords: [], |
| 269 | + initialPurchaseRecordIds: [], // 存储初始的购买记录ID列表(编辑时使用) | |
| 165 | 270 | // 购买物品选择弹窗相关 |
| 166 | 271 | showPurchaseDialog: false, |
| 167 | 272 | purchaseList: [], |
| 168 | 273 | filteredPurchaseList: [], |
| 169 | 274 | purchaseListLoading: false, |
| 275 | + purchaseQuery: { | |
| 276 | + currentPage: 1, | |
| 277 | + pageSize: 20, | |
| 278 | + reimbursementCategoryName: '' | |
| 279 | + }, | |
| 280 | + purchaseTotal: 0, | |
| 170 | 281 | tempSelectedPurchaseIds: [], |
| 171 | 282 | searchKeyword: '', |
| 172 | 283 | userInfo: null, |
| 173 | - newuserInfo: null | |
| 284 | + newuserInfo: null, | |
| 285 | + // 用户选择弹窗相关 | |
| 286 | + showUserDialog: false, | |
| 287 | + currentNodeIndex: -1, // 当前正在编辑的节点索引 | |
| 288 | + userList: [], | |
| 289 | + filteredUserList: [], | |
| 290 | + userListLoading: false, | |
| 291 | + userQuery: { | |
| 292 | + currentPage: 1, | |
| 293 | + pageSize: 20, | |
| 294 | + keyword: '' | |
| 295 | + }, | |
| 296 | + userTotal: 0, | |
| 297 | + tempSelectedUserIds: [], // 临时选中的用户ID列表 | |
| 298 | + searchTimer: null // 搜索防抖定时器 | |
| 174 | 299 | } |
| 175 | 300 | }, |
| 176 | 301 | |
| ... | ... | @@ -237,6 +362,317 @@ |
| 237 | 362 | today.setHours(0, 0, 0, 0) |
| 238 | 363 | this.formData.applicationTime = today.getTime() |
| 239 | 364 | this.formData.applicationTimeStr = today.toISOString().split('T')[0] |
| 365 | + | |
| 366 | + // 初始化审批节点(仅新建时) | |
| 367 | + if (!this.formData.id) { | |
| 368 | + this.initNodes() | |
| 369 | + } | |
| 370 | + }, | |
| 371 | + | |
| 372 | + // 初始化审批节点(新建时默认1个节点) | |
| 373 | + initNodes() { | |
| 374 | + this.formData.nodes = [ | |
| 375 | + { | |
| 376 | + nodeOrder: 1, | |
| 377 | + nodeName: '', | |
| 378 | + approvalType: '', | |
| 379 | + approverIds: '', | |
| 380 | + approverNames: [] | |
| 381 | + } | |
| 382 | + ] | |
| 383 | + }, | |
| 384 | + | |
| 385 | + // 添加节点 | |
| 386 | + addNode() { | |
| 387 | + if (this.formData.nodes.length >= 5) { | |
| 388 | + uni.showToast({ | |
| 389 | + title: '最多只能添加5个节点', | |
| 390 | + icon: 'none' | |
| 391 | + }) | |
| 392 | + return | |
| 393 | + } | |
| 394 | + const nextOrder = this.formData.nodes.length + 1 | |
| 395 | + this.formData.nodes.push({ | |
| 396 | + nodeOrder: nextOrder, | |
| 397 | + nodeName: '', | |
| 398 | + approvalType: '', | |
| 399 | + approverIds: '', | |
| 400 | + approverNames: [] | |
| 401 | + }) | |
| 402 | + }, | |
| 403 | + | |
| 404 | + // 删除节点 | |
| 405 | + removeNode(index) { | |
| 406 | + if (this.formData.nodes.length <= 1) { | |
| 407 | + uni.showToast({ | |
| 408 | + title: '至少需要1个节点', | |
| 409 | + icon: 'none' | |
| 410 | + }) | |
| 411 | + return | |
| 412 | + } | |
| 413 | + this.formData.nodes.splice(index, 1) | |
| 414 | + // 重新排序 | |
| 415 | + this.formData.nodes.forEach((node, idx) => { | |
| 416 | + node.nodeOrder = idx + 1 | |
| 417 | + }) | |
| 418 | + }, | |
| 419 | + | |
| 420 | + // 打开用户选择弹窗 | |
| 421 | + openUserDialog(nodeIndex) { | |
| 422 | + this.currentNodeIndex = nodeIndex | |
| 423 | + this.showUserDialog = true | |
| 424 | + const node = this.formData.nodes[nodeIndex] | |
| 425 | + // 初始化已选中的用户ID | |
| 426 | + if (node.approverIds) { | |
| 427 | + const idsArray = typeof node.approverIds === 'string' | |
| 428 | + ? node.approverIds.split(',').filter(id => id && id.trim()) | |
| 429 | + : (Array.isArray(node.approverIds) ? node.approverIds : []) | |
| 430 | + this.tempSelectedUserIds = [...idsArray] | |
| 431 | + } else { | |
| 432 | + this.tempSelectedUserIds = [] | |
| 433 | + } | |
| 434 | + this.userSearchKeyword = '' | |
| 435 | + this.userQuery.currentPage = 1 | |
| 436 | + this.userList = [] | |
| 437 | + this.filteredUserList = [] | |
| 438 | + this.loadUsers() | |
| 439 | + }, | |
| 440 | + | |
| 441 | + // 关闭用户选择弹窗 | |
| 442 | + closeUserDialog() { | |
| 443 | + this.showUserDialog = false | |
| 444 | + this.currentNodeIndex = -1 | |
| 445 | + this.userSearchKeyword = '' | |
| 446 | + this.tempSelectedUserIds = [] | |
| 447 | + // 清除搜索定时器 | |
| 448 | + if (this.searchTimer) { | |
| 449 | + clearTimeout(this.searchTimer) | |
| 450 | + this.searchTimer = null | |
| 451 | + } | |
| 452 | + }, | |
| 453 | + | |
| 454 | + // 加载用户列表 | |
| 455 | + async loadUsers() { | |
| 456 | + try { | |
| 457 | + this.userListLoading = true | |
| 458 | + | |
| 459 | + const params = { | |
| 460 | + currentPage: this.userQuery.currentPage, | |
| 461 | + pageSize: this.userQuery.pageSize | |
| 462 | + } | |
| 463 | + | |
| 464 | + // 如果有搜索关键词,添加keyword参数 | |
| 465 | + if (this.userSearchKeyword && this.userSearchKeyword.trim()) { | |
| 466 | + params.keyword = this.userSearchKeyword.trim() | |
| 467 | + } | |
| 468 | + | |
| 469 | + const res = await oauthApi.getUserList(params) | |
| 470 | + | |
| 471 | + if (res.code === 200 && res.data) { | |
| 472 | + const newList = res.data.list || [] | |
| 473 | + | |
| 474 | + if (this.userQuery.currentPage === 1) { | |
| 475 | + // 第一页,替换数据 | |
| 476 | + this.userList = newList | |
| 477 | + this.filteredUserList = newList | |
| 478 | + } else { | |
| 479 | + // 后续页,追加数据 | |
| 480 | + this.userList = [...this.userList, ...newList] | |
| 481 | + this.filteredUserList = [...this.filteredUserList, ...newList] | |
| 482 | + } | |
| 483 | + | |
| 484 | + // 获取总数,优先使用pagination.Total,如果没有则使用当前列表长度 | |
| 485 | + const total = res.data.pagination && res.data.pagination.Total | |
| 486 | + if (total !== undefined && total !== null) { | |
| 487 | + this.userTotal = total | |
| 488 | + } else { | |
| 489 | + // 如果没有总数信息,根据当前页和返回数据判断 | |
| 490 | + if (newList.length < this.userQuery.pageSize) { | |
| 491 | + // 返回的数据少于每页数量,说明是最后一页 | |
| 492 | + this.userTotal = this.filteredUserList.length + newList.length | |
| 493 | + } else { | |
| 494 | + // 可能还有更多数据,暂时设置为当前数量+1(表示可能还有) | |
| 495 | + this.userTotal = this.filteredUserList.length + newList.length + 1 | |
| 496 | + } | |
| 497 | + } | |
| 498 | + } else { | |
| 499 | + if (this.userQuery.currentPage === 1) { | |
| 500 | + this.userList = [] | |
| 501 | + this.filteredUserList = [] | |
| 502 | + } | |
| 503 | + this.userTotal = 0 | |
| 504 | + } | |
| 505 | + } catch (error) { | |
| 506 | + console.error('加载用户列表失败:', error) | |
| 507 | + uni.showToast({ | |
| 508 | + title: '加载用户列表失败', | |
| 509 | + icon: 'none' | |
| 510 | + }) | |
| 511 | + if (this.userQuery.currentPage === 1) { | |
| 512 | + this.userList = [] | |
| 513 | + this.filteredUserList = [] | |
| 514 | + } | |
| 515 | + this.userTotal = 0 | |
| 516 | + } finally { | |
| 517 | + this.userListLoading = false | |
| 518 | + } | |
| 519 | + }, | |
| 520 | + | |
| 521 | + // 调用getUserInfoList API | |
| 522 | + async loadUserInfoList(userIds) { | |
| 523 | + const request = require('@/service/request.js').default | |
| 524 | + const config = require('@/common/config.js').default | |
| 525 | + try { | |
| 526 | + return await request.post(`${config.getApiBaseUrl()}/api/permission/Users/getUserList`, { | |
| 527 | + userId: userIds | |
| 528 | + }) | |
| 529 | + } catch (error) { | |
| 530 | + console.error('获取用户信息失败:', error) | |
| 531 | + return { code: 500, data: { list: [] } } | |
| 532 | + } | |
| 533 | + }, | |
| 534 | + | |
| 535 | + // 通过用户ID搜索用户 | |
| 536 | + async searchUserById() { | |
| 537 | + if (!this.userSearchKeyword.trim()) { | |
| 538 | + uni.showToast({ | |
| 539 | + title: '请输入用户ID', | |
| 540 | + icon: 'none' | |
| 541 | + }) | |
| 542 | + return | |
| 543 | + } | |
| 544 | + | |
| 545 | + try { | |
| 546 | + this.userListLoading = true | |
| 547 | + const userIds = this.userSearchKeyword.split(',').map(id => id.trim()).filter(id => id) | |
| 548 | + | |
| 549 | + if (userIds.length === 0) { | |
| 550 | + uni.showToast({ | |
| 551 | + title: '请输入有效的用户ID', | |
| 552 | + icon: 'none' | |
| 553 | + }) | |
| 554 | + return | |
| 555 | + } | |
| 556 | + | |
| 557 | + const res = await this.loadUserInfoList(userIds) | |
| 558 | + if (res.code === 200 && res.data && res.data.list) { | |
| 559 | + // 合并到现有列表,去重 | |
| 560 | + const existingIds = new Set(this.userList.map(u => u.id)) | |
| 561 | + const newUsers = res.data.list.filter(u => !existingIds.has(u.id)) | |
| 562 | + this.userList = [...this.userList, ...newUsers] | |
| 563 | + this.filteredUserList = this.userList | |
| 564 | + this.userTotal = this.userList.length | |
| 565 | + | |
| 566 | + // 如果搜索的用户不在列表中,提示 | |
| 567 | + if (newUsers.length === 0 && res.data.list.length > 0) { | |
| 568 | + uni.showToast({ | |
| 569 | + title: '用户已在列表中', | |
| 570 | + icon: 'none' | |
| 571 | + }) | |
| 572 | + } | |
| 573 | + } else { | |
| 574 | + uni.showToast({ | |
| 575 | + title: '未找到用户', | |
| 576 | + icon: 'none' | |
| 577 | + }) | |
| 578 | + } | |
| 579 | + } catch (error) { | |
| 580 | + console.error('搜索用户失败:', error) | |
| 581 | + uni.showToast({ | |
| 582 | + title: '搜索用户失败', | |
| 583 | + icon: 'none' | |
| 584 | + }) | |
| 585 | + } finally { | |
| 586 | + this.userListLoading = false | |
| 587 | + } | |
| 588 | + }, | |
| 589 | + | |
| 590 | + // 加载更多用户 | |
| 591 | + loadMoreUsers() { | |
| 592 | + if (this.userListLoading) return | |
| 593 | + if (this.filteredUserList.length >= this.userTotal && this.userTotal > 0) { | |
| 594 | + return | |
| 595 | + } | |
| 596 | + | |
| 597 | + this.userQuery.currentPage++ | |
| 598 | + this.loadUsers() | |
| 599 | + }, | |
| 600 | + | |
| 601 | + // 搜索用户 | |
| 602 | + handleUserSearch() { | |
| 603 | + // 防抖处理,延迟500ms后执行搜索 | |
| 604 | + clearTimeout(this.searchTimer) | |
| 605 | + this.searchTimer = setTimeout(() => { | |
| 606 | + this.userQuery.currentPage = 1 | |
| 607 | + this.userList = [] | |
| 608 | + this.filteredUserList = [] | |
| 609 | + this.loadUsers() | |
| 610 | + }, 500) | |
| 611 | + }, | |
| 612 | + | |
| 613 | + // 切换用户选择状态 | |
| 614 | + toggleUserSelection(user) { | |
| 615 | + const index = this.tempSelectedUserIds.indexOf(user.id) | |
| 616 | + if (index > -1) { | |
| 617 | + this.tempSelectedUserIds.splice(index, 1) | |
| 618 | + } else { | |
| 619 | + this.tempSelectedUserIds.push(user.id) | |
| 620 | + } | |
| 621 | + }, | |
| 622 | + | |
| 623 | + // 判断用户是否已选中 | |
| 624 | + isUserSelected(userId) { | |
| 625 | + return this.tempSelectedUserIds.indexOf(userId) > -1 | |
| 626 | + }, | |
| 627 | + | |
| 628 | + // 确认选择用户 | |
| 629 | + async confirmUserSelection() { | |
| 630 | + if (this.currentNodeIndex < 0) return | |
| 631 | + | |
| 632 | + const node = this.formData.nodes[this.currentNodeIndex] | |
| 633 | + | |
| 634 | + // 保存选中的用户ID(逗号分隔的字符串) | |
| 635 | + node.approverIds = this.tempSelectedUserIds.join(',') | |
| 636 | + | |
| 637 | + // 获取用户名列表 | |
| 638 | + if (this.tempSelectedUserIds.length > 0) { | |
| 639 | + // 从已加载的用户列表中获取用户名 | |
| 640 | + const selectedUsers = this.userList.filter(user => | |
| 641 | + this.tempSelectedUserIds.indexOf(user.id) > -1 | |
| 642 | + ) | |
| 643 | + | |
| 644 | + // 如果有些用户不在当前列表中,需要单独获取 | |
| 645 | + const missingIds = this.tempSelectedUserIds.filter(id => | |
| 646 | + !selectedUsers.find(u => u.id === id) | |
| 647 | + ) | |
| 648 | + | |
| 649 | + if (missingIds.length > 0) { | |
| 650 | + try { | |
| 651 | + const res = await this.loadUserInfoList(missingIds) | |
| 652 | + if (res.code === 200 && res.data && res.data.list) { | |
| 653 | + selectedUsers.push(...res.data.list) | |
| 654 | + } | |
| 655 | + } catch (error) { | |
| 656 | + console.error('获取用户信息失败:', error) | |
| 657 | + } | |
| 658 | + } | |
| 659 | + | |
| 660 | + node.approverNames = selectedUsers.map(user => | |
| 661 | + user.fullName || user.realName || user.userName || user.id | |
| 662 | + ) | |
| 663 | + } else { | |
| 664 | + node.approverNames = [] | |
| 665 | + } | |
| 666 | + | |
| 667 | + this.closeUserDialog() | |
| 668 | + }, | |
| 669 | + | |
| 670 | + // 获取审批人显示文本 | |
| 671 | + getApproverDisplayText(node) { | |
| 672 | + if (node.approverNames && node.approverNames.length > 0) { | |
| 673 | + return `已选择 ${node.approverNames.length} 人` | |
| 674 | + } | |
| 675 | + return '请选择审批人' | |
| 240 | 676 | }, |
| 241 | 677 | |
| 242 | 678 | // 申请时间变化 |
| ... | ... | @@ -252,6 +688,7 @@ |
| 252 | 688 | this.showPurchaseDialog = true |
| 253 | 689 | this.tempSelectedPurchaseIds = this.selectedPurchaseRecords.map(item => item.id) |
| 254 | 690 | this.searchKeyword = '' |
| 691 | + this.purchaseQuery.currentPage = 1 | |
| 255 | 692 | this.loadPurchaseRecords() |
| 256 | 693 | }, |
| 257 | 694 | |
| ... | ... | @@ -261,58 +698,74 @@ |
| 261 | 698 | this.searchKeyword = '' |
| 262 | 699 | }, |
| 263 | 700 | |
| 264 | - // 加载购买记录(未报销的) | |
| 701 | + // 加载购买记录(使用分页接口) | |
| 265 | 702 | async loadPurchaseRecords() { |
| 266 | 703 | try { |
| 267 | 704 | this.purchaseListLoading = true |
| 268 | 705 | |
| 269 | 706 | const params = { |
| 270 | - createUser:this.newuserInfo&&this.newuserInfo.id?this.newuserInfo.id:'暂无', | |
| 707 | + currentPage: this.purchaseQuery.currentPage, | |
| 708 | + pageSize: this.purchaseQuery.pageSize, | |
| 709 | + approveStatus: '未审批', // 只选择未审批的购买记录 | |
| 271 | 710 | // createUserStoreId: this.formData.applicationStoreId || '暂无', |
| 272 | - approveStatus: '未审批' | |
| 711 | + createUser:this.newuserInfo&&this.newuserInfo.id?this.newuserInfo.id:'暂无', | |
| 712 | + } | |
| 713 | + | |
| 714 | + if (this.searchKeyword) { | |
| 715 | + params.reimbursementCategoryName = this.searchKeyword | |
| 273 | 716 | } |
| 274 | 717 | |
| 275 | - const res = await api.getUnreimbursedPurchaseList(params) | |
| 276 | - console.error(res) | |
| 718 | + const res = await purchaseApi.getPurchaseList(params) | |
| 277 | 719 | if (res.code === 200 && res.data) { |
| 278 | - // 过滤出未报销的记录(ApplicationId为空或未关联) | |
| 279 | - // this.purchaseList = (res.data || []).filter(item => !item.applicationId) | |
| 280 | - this.purchaseList = res.data || [] | |
| 281 | - this.filteredPurchaseList = this.purchaseList | |
| 720 | + const newList = res.data.list || [] | |
| 721 | + if (this.purchaseQuery.currentPage === 1) { | |
| 722 | + // 第一页,替换数据 | |
| 723 | + this.purchaseList = newList | |
| 724 | + this.filteredPurchaseList = newList | |
| 725 | + } else { | |
| 726 | + // 后续页,追加数据 | |
| 727 | + this.purchaseList = [...this.purchaseList, ...newList] | |
| 728 | + this.filteredPurchaseList = [...this.filteredPurchaseList, ...newList] | |
| 729 | + } | |
| 730 | + this.purchaseTotal = (res.data.pagination && res.data.pagination.Total) || 0 | |
| 282 | 731 | } else { |
| 283 | - this.purchaseList = [] | |
| 284 | - this.filteredPurchaseList = [] | |
| 732 | + if (this.purchaseQuery.currentPage === 1) { | |
| 733 | + this.purchaseList = [] | |
| 734 | + this.filteredPurchaseList = [] | |
| 735 | + } | |
| 736 | + this.purchaseTotal = 0 | |
| 285 | 737 | } |
| 286 | - console.error(this.purchaseList) | |
| 287 | 738 | } catch (error) { |
| 288 | 739 | console.error('加载购买记录失败:', error) |
| 289 | 740 | uni.showToast({ |
| 290 | 741 | title: '加载购买记录失败', |
| 291 | 742 | icon: 'none' |
| 292 | 743 | }) |
| 293 | - this.purchaseList = [] | |
| 294 | - this.filteredPurchaseList = [] | |
| 744 | + if (this.purchaseQuery.currentPage === 1) { | |
| 745 | + this.purchaseList = [] | |
| 746 | + this.filteredPurchaseList = [] | |
| 747 | + } | |
| 748 | + this.purchaseTotal = 0 | |
| 295 | 749 | } finally { |
| 296 | 750 | this.purchaseListLoading = false |
| 297 | 751 | } |
| 298 | 752 | }, |
| 299 | 753 | |
| 300 | - // 加载更多购买记录(如果需要分页) | |
| 754 | + // 加载更多购买记录 | |
| 301 | 755 | loadMorePurchaseRecords() { |
| 302 | - // 当前使用不分页接口,暂不需要 | |
| 756 | + if (this.purchaseListLoading) return | |
| 757 | + if (this.filteredPurchaseList.length >= this.purchaseTotal) return | |
| 758 | + | |
| 759 | + this.purchaseQuery.currentPage++ | |
| 760 | + this.loadPurchaseRecords() | |
| 303 | 761 | }, |
| 304 | 762 | |
| 305 | 763 | // 搜索购买记录 |
| 306 | 764 | handleSearch() { |
| 307 | - if (!this.searchKeyword.trim()) { | |
| 308 | - this.filteredPurchaseList = this.purchaseList | |
| 309 | - return | |
| 310 | - } | |
| 311 | - const keyword = this.searchKeyword.toLowerCase() | |
| 312 | - this.filteredPurchaseList = this.purchaseList.filter(item => { | |
| 313 | - const categoryName = (item.reimbursementCategoryName || '').toLowerCase() | |
| 314 | - return categoryName.includes(keyword) | |
| 315 | - }) | |
| 765 | + this.purchaseQuery.currentPage = 1 | |
| 766 | + this.purchaseList = [] | |
| 767 | + this.filteredPurchaseList = [] | |
| 768 | + this.loadPurchaseRecords() | |
| 316 | 769 | }, |
| 317 | 770 | |
| 318 | 771 | // 切换购买记录选择状态 |
| ... | ... | @@ -339,7 +792,11 @@ |
| 339 | 792 | |
| 340 | 793 | // 去重:移除已存在的记录 |
| 341 | 794 | const existingIds = new Set(this.selectedPurchaseRecords.map(item => item.id)) |
| 342 | - const newRecords = selectedRecords.filter(item => !existingIds.has(item.id)) | |
| 795 | + const newRecords = selectedRecords.filter(item => !existingIds.has(item.id)).map(record => { | |
| 796 | + // 标记新增的记录 | |
| 797 | + record.isInitial = false | |
| 798 | + return record | |
| 799 | + }) | |
| 343 | 800 | |
| 344 | 801 | // 追加新选中的记录 |
| 345 | 802 | this.selectedPurchaseRecords = [...this.selectedPurchaseRecords, ...newRecords] |
| ... | ... | @@ -355,6 +812,15 @@ |
| 355 | 812 | |
| 356 | 813 | // 移除已选中的购买记录 |
| 357 | 814 | removePurchaseRecord(index) { |
| 815 | + const record = this.selectedPurchaseRecords[index] | |
| 816 | + // 检查是否是初始记录,初始记录不允许移除(编辑时) | |
| 817 | + if (record && record.isInitial) { | |
| 818 | + uni.showToast({ | |
| 819 | + title: '初始购买记录不能移除', | |
| 820 | + icon: 'none' | |
| 821 | + }) | |
| 822 | + return | |
| 823 | + } | |
| 358 | 824 | this.selectedPurchaseRecords.splice(index, 1) |
| 359 | 825 | this.formData.selectedPurchaseRecordIds = this.selectedPurchaseRecords.map(item => item.id) |
| 360 | 826 | this.calculateTotalAmount() |
| ... | ... | @@ -379,23 +845,49 @@ |
| 379 | 845 | |
| 380 | 846 | if (res.code === 200 && res.data) { |
| 381 | 847 | const data = res.data |
| 848 | + const formData = data.form || data | |
| 849 | + const purchaseRecords = data.purchaseRecords || [] | |
| 850 | + | |
| 382 | 851 | this.formData = { |
| 383 | - id: data.id, | |
| 384 | - applicationUserId: data.applicationUserId, | |
| 385 | - applicationUserName: data.applicationUserName, | |
| 386 | - applicationStoreId: data.applicationStoreId, | |
| 387 | - applicationStoreName: data.applicationStoreName, | |
| 388 | - applicationTime: data.applicationTime, | |
| 389 | - applicationTimeStr: data.applicationTime ? new Date(data.applicationTime).toISOString().split('T')[0] : undefined, | |
| 390 | - amount: data.amount, | |
| 391 | - approveStatus: data.approveStatus || '未审批', | |
| 392 | - selectedPurchaseRecordIds: [] | |
| 852 | + id: formData.id, | |
| 853 | + applicationUserId: formData.applicationUserId, | |
| 854 | + applicationUserName: formData.applicationUserName, | |
| 855 | + applicationStoreId: formData.applicationStoreId, | |
| 856 | + applicationStoreName: formData.applicationStoreName, | |
| 857 | + applicationTime: formData.applicationTime, | |
| 858 | + applicationTimeStr: formData.applicationTime ? new Date(formData.applicationTime).toISOString().split('T')[0] : undefined, | |
| 859 | + amount: formData.amount, | |
| 860 | + approveStatus: formData.approveStatus || formData.approvalStatus || '待审批', | |
| 861 | + selectedPurchaseRecordIds: [], | |
| 862 | + nodes: [] // 编辑时不需要节点配置 | |
| 393 | 863 | } |
| 394 | 864 | |
| 395 | - // 加载已关联的购买记录 | |
| 396 | - if (data.purchaseRecordsId) { | |
| 397 | - await this.loadSelectedPurchaseRecords(data.purchaseRecordsId) | |
| 398 | - } | |
| 865 | + // 保存初始的购买记录ID列表 | |
| 866 | + this.initialPurchaseRecordIds = purchaseRecords.map(record => record.id) | |
| 867 | + | |
| 868 | + // 填充购买记录列表,标记初始记录 | |
| 869 | + this.selectedPurchaseRecords = purchaseRecords.map(record => { | |
| 870 | + // 处理附件字段 | |
| 871 | + if (record.attachment) { | |
| 872 | + try { | |
| 873 | + record.attachment = typeof record.attachment === 'string' | |
| 874 | + ? JSON.parse(record.attachment) | |
| 875 | + : record.attachment | |
| 876 | + } catch (e) { | |
| 877 | + record.attachment = [] | |
| 878 | + } | |
| 879 | + } else { | |
| 880 | + record.attachment = [] | |
| 881 | + } | |
| 882 | + // 标记为初始记录 | |
| 883 | + record.isInitial = true | |
| 884 | + return record | |
| 885 | + }) | |
| 886 | + | |
| 887 | + this.formData.selectedPurchaseRecordIds = this.selectedPurchaseRecords.map(item => item.id) | |
| 888 | + | |
| 889 | + // 计算总金额 | |
| 890 | + this.calculateTotalAmount() | |
| 399 | 891 | } else { |
| 400 | 892 | throw new Error(res.message || '加载数据失败') |
| 401 | 893 | } |
| ... | ... | @@ -410,46 +902,6 @@ |
| 410 | 902 | } |
| 411 | 903 | }, |
| 412 | 904 | |
| 413 | - // 加载已选中的购买记录(编辑时使用) | |
| 414 | - async loadSelectedPurchaseRecords(purchaseRecordsId) { | |
| 415 | - if (!purchaseRecordsId) { | |
| 416 | - this.selectedPurchaseRecords = [] | |
| 417 | - this.formData.selectedPurchaseRecordIds = [] | |
| 418 | - return | |
| 419 | - } | |
| 420 | - | |
| 421 | - // 处理逗号分隔的ID字符串 | |
| 422 | - const normalizedStr = purchaseRecordsId.toString().replace(/[\r\n]+/g, ',').replace(/\s+/g, '') | |
| 423 | - const ids = normalizedStr.split(',').map(id => id.trim()).filter(id => id && id !== '') | |
| 424 | - | |
| 425 | - if (ids.length === 0) { | |
| 426 | - this.selectedPurchaseRecords = [] | |
| 427 | - this.formData.selectedPurchaseRecordIds = [] | |
| 428 | - return | |
| 429 | - } | |
| 430 | - | |
| 431 | - try { | |
| 432 | - // 批量加载购买记录 | |
| 433 | - const promises = ids.map(id => { | |
| 434 | - return purchaseApi.getPurchaseDetail(id) | |
| 435 | - .then(res => res.code === 200 ? res.data : null) | |
| 436 | - .catch(() => null) | |
| 437 | - }) | |
| 438 | - | |
| 439 | - const results = await Promise.all(promises) | |
| 440 | - this.selectedPurchaseRecords = results.filter(item => item !== null) | |
| 441 | - this.formData.selectedPurchaseRecordIds = this.selectedPurchaseRecords.map(item => item.id) | |
| 442 | - | |
| 443 | - // 计算总金额 | |
| 444 | - this.calculateTotalAmount() | |
| 445 | - } catch (error) { | |
| 446 | - console.error('加载购买记录失败:', error) | |
| 447 | - uni.showToast({ | |
| 448 | - title: '加载购买记录失败', | |
| 449 | - icon: 'none' | |
| 450 | - }) | |
| 451 | - } | |
| 452 | - }, | |
| 453 | 905 | |
| 454 | 906 | // 表单提交 |
| 455 | 907 | async handleFormSubmit() { |
| ... | ... | @@ -462,6 +914,52 @@ |
| 462 | 914 | return |
| 463 | 915 | } |
| 464 | 916 | |
| 917 | + // 新建时验证节点配置 | |
| 918 | + if (!this.formData.id) { | |
| 919 | + if (!this.formData.nodes || this.formData.nodes.length < 1 || this.formData.nodes.length > 5) { | |
| 920 | + uni.showToast({ | |
| 921 | + title: '审批节点数量必须在1-5个之间', | |
| 922 | + icon: 'none' | |
| 923 | + }) | |
| 924 | + return | |
| 925 | + } | |
| 926 | + | |
| 927 | + // 验证每个节点 | |
| 928 | + for (let i = 0; i < this.formData.nodes.length; i++) { | |
| 929 | + const node = this.formData.nodes[i] | |
| 930 | + if (!node.nodeName) { | |
| 931 | + uni.showToast({ | |
| 932 | + title: `请填写节点${node.nodeOrder}的名称`, | |
| 933 | + icon: 'none' | |
| 934 | + }) | |
| 935 | + return | |
| 936 | + } | |
| 937 | + if (!node.approvalType) { | |
| 938 | + uni.showToast({ | |
| 939 | + title: `请选择节点${node.nodeOrder}的审批类型`, | |
| 940 | + icon: 'none' | |
| 941 | + }) | |
| 942 | + return | |
| 943 | + } | |
| 944 | + // 验证审批人 | |
| 945 | + let approverIdsArray = [] | |
| 946 | + if (node.approverIds) { | |
| 947 | + if (Array.isArray(node.approverIds)) { | |
| 948 | + approverIdsArray = node.approverIds.filter(id => id) | |
| 949 | + } else if (typeof node.approverIds === 'string') { | |
| 950 | + approverIdsArray = node.approverIds.split(',').filter(id => id && id.trim()) | |
| 951 | + } | |
| 952 | + } | |
| 953 | + if (approverIdsArray.length === 0) { | |
| 954 | + uni.showToast({ | |
| 955 | + title: `请为节点${node.nodeOrder}选择至少一个审批人`, | |
| 956 | + icon: 'none' | |
| 957 | + }) | |
| 958 | + return | |
| 959 | + } | |
| 960 | + } | |
| 961 | + } | |
| 962 | + | |
| 465 | 963 | if (this.isSubmitting) return |
| 466 | 964 | |
| 467 | 965 | try { |
| ... | ... | @@ -470,31 +968,154 @@ |
| 470 | 968 | title: '提交中...' |
| 471 | 969 | }) |
| 472 | 970 | |
| 473 | - // 设置关联购买编号(多个ID用逗号分隔) | |
| 474 | - const submitData = { | |
| 475 | - ...this.formData, | |
| 476 | - purchaseRecordsId: this.formData.selectedPurchaseRecordIds.join(',') | |
| 477 | - } | |
| 478 | - | |
| 479 | - let res | |
| 971 | + let submitData = {} | |
| 972 | + | |
| 480 | 973 | if (this.formData.id) { |
| 481 | - // 更新 | |
| 482 | - res = await api.updateReimbursement(this.formData.id, submitData) | |
| 974 | + // 更新:只传递有值的字段 | |
| 975 | + submitData = { | |
| 976 | + id: this.formData.id | |
| 977 | + } | |
| 978 | + if (this.formData.applicationUserId) { | |
| 979 | + submitData.applicationUserId = this.formData.applicationUserId | |
| 980 | + } | |
| 981 | + if (this.formData.applicationUserName) { | |
| 982 | + submitData.applicationUserName = this.formData.applicationUserName | |
| 983 | + } | |
| 984 | + if (this.formData.applicationStoreId) { | |
| 985 | + submitData.applicationStoreId = this.formData.applicationStoreId | |
| 986 | + } | |
| 987 | + if (this.formData.applicationTime) { | |
| 988 | + submitData.applicationTime = this.formData.applicationTime | |
| 989 | + } | |
| 990 | + if (this.formData.amount) { | |
| 991 | + submitData.amount = this.formData.amount | |
| 992 | + } | |
| 993 | + if (this.formData.selectedPurchaseRecordIds && this.formData.selectedPurchaseRecordIds.length > 0) { | |
| 994 | + submitData.selectedPurchaseRecordIds = this.formData.selectedPurchaseRecordIds | |
| 995 | + } | |
| 996 | + | |
| 997 | + const res = await api.updateReimbursement(this.formData.id, submitData) | |
| 998 | + | |
| 999 | + if (res.code === 200) { | |
| 1000 | + uni.showToast({ | |
| 1001 | + title: '修改成功', | |
| 1002 | + icon: 'success' | |
| 1003 | + }) | |
| 1004 | + | |
| 1005 | + // 修改成功后,自动重新提交审批 | |
| 1006 | + try { | |
| 1007 | + const submitRes = await api.submitApproval(this.formData.id) | |
| 1008 | + if (submitRes.code === 200) { | |
| 1009 | + uni.showToast({ | |
| 1010 | + title: '已重新提交审批', | |
| 1011 | + icon: 'success' | |
| 1012 | + }) | |
| 1013 | + } else { | |
| 1014 | + uni.showToast({ | |
| 1015 | + title: '修改成功,但重新提交审批失败', | |
| 1016 | + icon: 'none' | |
| 1017 | + }) | |
| 1018 | + } | |
| 1019 | + } catch (error) { | |
| 1020 | + console.error('重新提交审批失败:', error) | |
| 1021 | + uni.showToast({ | |
| 1022 | + title: '修改成功,但重新提交审批失败,请手动提交', | |
| 1023 | + icon: 'none' | |
| 1024 | + }) | |
| 1025 | + } | |
| 1026 | + | |
| 1027 | + setTimeout(() => { | |
| 1028 | + uni.navigateBack() | |
| 1029 | + }, 1500) | |
| 1030 | + } else { | |
| 1031 | + throw new Error(res.message || '修改失败') | |
| 1032 | + } | |
| 483 | 1033 | } else { |
| 484 | 1034 | // 创建 |
| 485 | - res = await api.createReimbursement(submitData) | |
| 486 | - } | |
| 487 | - | |
| 488 | - if (res.code === 200) { | |
| 489 | - uni.showToast({ | |
| 490 | - title: res.message || '操作成功', | |
| 491 | - icon: 'success' | |
| 492 | - }) | |
| 493 | - setTimeout(() => { | |
| 494 | - uni.navigateBack() | |
| 495 | - }, 1500) | |
| 496 | - } else { | |
| 497 | - throw new Error(res.message || '操作失败') | |
| 1035 | + submitData = { | |
| 1036 | + applicationUserId: this.formData.applicationUserId, | |
| 1037 | + applicationUserName: this.formData.applicationUserName, | |
| 1038 | + applicationStoreId: this.formData.applicationStoreId, | |
| 1039 | + applicationTime: this.formData.applicationTime, | |
| 1040 | + amount: this.formData.amount, | |
| 1041 | + selectedPurchaseRecordIds: this.formData.selectedPurchaseRecordIds, | |
| 1042 | + nodes: this.formData.nodes.map(node => { | |
| 1043 | + // 转换审批人ID为数组 | |
| 1044 | + let approverIds = [] | |
| 1045 | + if (node.approverIds) { | |
| 1046 | + if (Array.isArray(node.approverIds)) { | |
| 1047 | + approverIds = node.approverIds.filter(id => id) | |
| 1048 | + } else if (typeof node.approverIds === 'string') { | |
| 1049 | + approverIds = node.approverIds.split(',').filter(id => id && id.trim()) | |
| 1050 | + } | |
| 1051 | + } | |
| 1052 | + | |
| 1053 | + return { | |
| 1054 | + nodeOrder: node.nodeOrder, | |
| 1055 | + nodeName: node.nodeName, | |
| 1056 | + approvalType: node.approvalType, | |
| 1057 | + approverIds: approverIds, | |
| 1058 | + approverNames: node.approverNames && node.approverNames.length > 0 | |
| 1059 | + ? node.approverNames | |
| 1060 | + : approverIds | |
| 1061 | + } | |
| 1062 | + }) | |
| 1063 | + } | |
| 1064 | + | |
| 1065 | + const res = await api.createReimbursement(submitData) | |
| 1066 | + | |
| 1067 | + if (res.code === 200) { | |
| 1068 | + uni.showToast({ | |
| 1069 | + title: '创建成功', | |
| 1070 | + icon: 'success' | |
| 1071 | + }) | |
| 1072 | + | |
| 1073 | + // 创建成功后,自动提交审批 | |
| 1074 | + let applicationId = null | |
| 1075 | + if (res.data) { | |
| 1076 | + if (typeof res.data === 'object' && res.data.id) { | |
| 1077 | + applicationId = res.data.id | |
| 1078 | + } else if (typeof res.data === 'string') { | |
| 1079 | + applicationId = res.data | |
| 1080 | + } else { | |
| 1081 | + applicationId = String(res.data) | |
| 1082 | + } | |
| 1083 | + } | |
| 1084 | + | |
| 1085 | + if (applicationId) { | |
| 1086 | + try { | |
| 1087 | + const submitRes = await api.submitApproval(applicationId) | |
| 1088 | + if (submitRes.code === 200) { | |
| 1089 | + uni.showToast({ | |
| 1090 | + title: '已自动提交审批', | |
| 1091 | + icon: 'success' | |
| 1092 | + }) | |
| 1093 | + } else { | |
| 1094 | + uni.showToast({ | |
| 1095 | + title: '创建成功,但提交审批失败', | |
| 1096 | + icon: 'none' | |
| 1097 | + }) | |
| 1098 | + } | |
| 1099 | + } catch (error) { | |
| 1100 | + console.error('提交审批失败:', error) | |
| 1101 | + uni.showToast({ | |
| 1102 | + title: '创建成功,但提交审批失败,请手动提交', | |
| 1103 | + icon: 'none' | |
| 1104 | + }) | |
| 1105 | + } | |
| 1106 | + } else { | |
| 1107 | + uni.showToast({ | |
| 1108 | + title: '创建成功,但无法获取申请ID,请手动提交审批', | |
| 1109 | + icon: 'none' | |
| 1110 | + }) | |
| 1111 | + } | |
| 1112 | + | |
| 1113 | + setTimeout(() => { | |
| 1114 | + uni.navigateBack() | |
| 1115 | + }, 1500) | |
| 1116 | + } else { | |
| 1117 | + throw new Error(res.message || '创建失败') | |
| 1118 | + } | |
| 498 | 1119 | } |
| 499 | 1120 | } catch (error) { |
| 500 | 1121 | console.error('提交失败:', error) |
| ... | ... | @@ -639,6 +1260,20 @@ |
| 639 | 1260 | margin-bottom: 12rpx; |
| 640 | 1261 | } |
| 641 | 1262 | |
| 1263 | + .purchase-item-actions { | |
| 1264 | + display: flex; | |
| 1265 | + align-items: center; | |
| 1266 | + gap: 16rpx; | |
| 1267 | + } | |
| 1268 | + | |
| 1269 | + .initial-mark { | |
| 1270 | + font-size: 22rpx; | |
| 1271 | + color: #909399; | |
| 1272 | + padding: 4rpx 12rpx; | |
| 1273 | + background: #f5f5f5; | |
| 1274 | + border-radius: 8rpx; | |
| 1275 | + } | |
| 1276 | + | |
| 642 | 1277 | .purchase-category { |
| 643 | 1278 | font-size: 28rpx; |
| 644 | 1279 | color: #2e7d32; |
| ... | ... | @@ -883,5 +1518,203 @@ |
| 883 | 1518 | background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); |
| 884 | 1519 | color: #fff; |
| 885 | 1520 | } |
| 1521 | + | |
| 1522 | + /* 审批节点配置样式 */ | |
| 1523 | + .nodes-header { | |
| 1524 | + display: flex; | |
| 1525 | + justify-content: space-between; | |
| 1526 | + align-items: center; | |
| 1527 | + margin-bottom: 24rpx; | |
| 1528 | + } | |
| 1529 | + | |
| 1530 | + .add-node-btn { | |
| 1531 | + padding: 12rpx 24rpx; | |
| 1532 | + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); | |
| 1533 | + border-radius: 16rpx; | |
| 1534 | + } | |
| 1535 | + | |
| 1536 | + .add-node-text { | |
| 1537 | + font-size: 24rpx; | |
| 1538 | + color: #fff; | |
| 1539 | + font-weight: 600; | |
| 1540 | + } | |
| 1541 | + | |
| 1542 | + .nodes-list { | |
| 1543 | + display: flex; | |
| 1544 | + flex-direction: column; | |
| 1545 | + gap: 24rpx; | |
| 1546 | + } | |
| 1547 | + | |
| 1548 | + .node-item { | |
| 1549 | + background: #f9fff9; | |
| 1550 | + border: 2rpx solid #c8e6c9; | |
| 1551 | + border-radius: 16rpx; | |
| 1552 | + padding: 24rpx; | |
| 1553 | + } | |
| 1554 | + | |
| 1555 | + .node-header { | |
| 1556 | + display: flex; | |
| 1557 | + justify-content: space-between; | |
| 1558 | + align-items: center; | |
| 1559 | + margin-bottom: 24rpx; | |
| 1560 | + padding-bottom: 16rpx; | |
| 1561 | + border-bottom: 2rpx solid #e0e0e0; | |
| 1562 | + } | |
| 1563 | + | |
| 1564 | + .node-order { | |
| 1565 | + font-size: 28rpx; | |
| 1566 | + color: #2e7d32; | |
| 1567 | + font-weight: 600; | |
| 1568 | + } | |
| 1569 | + | |
| 1570 | + .remove-node-btn { | |
| 1571 | + padding: 8rpx 16rpx; | |
| 1572 | + background: #ffebee; | |
| 1573 | + border-radius: 8rpx; | |
| 1574 | + } | |
| 1575 | + | |
| 1576 | + .remove-node-text { | |
| 1577 | + font-size: 24rpx; | |
| 1578 | + color: #c62828; | |
| 1579 | + font-weight: 500; | |
| 1580 | + } | |
| 1581 | + | |
| 1582 | + .node-form { | |
| 1583 | + display: flex; | |
| 1584 | + flex-direction: column; | |
| 1585 | + gap: 24rpx; | |
| 1586 | + } | |
| 1587 | + | |
| 1588 | + .node-form-item { | |
| 1589 | + display: flex; | |
| 1590 | + flex-direction: column; | |
| 1591 | + gap: 12rpx; | |
| 1592 | + } | |
| 1593 | + | |
| 1594 | + .node-label { | |
| 1595 | + font-size: 26rpx; | |
| 1596 | + color: #2e7d32; | |
| 1597 | + font-weight: 500; | |
| 1598 | + } | |
| 1599 | + | |
| 1600 | + .node-input { | |
| 1601 | + background: #fff; | |
| 1602 | + border: 2rpx solid #c8e6c9; | |
| 1603 | + border-radius: 16rpx; | |
| 1604 | + padding: 20rpx 24rpx; | |
| 1605 | + } | |
| 1606 | + | |
| 1607 | + .node-tip { | |
| 1608 | + font-size: 22rpx; | |
| 1609 | + color: #909399; | |
| 1610 | + margin-top: -8rpx; | |
| 1611 | + } | |
| 1612 | + | |
| 1613 | + .loading-more { | |
| 1614 | + text-align: center; | |
| 1615 | + padding: 32rpx; | |
| 1616 | + color: #909399; | |
| 1617 | + font-size: 24rpx; | |
| 1618 | + } | |
| 1619 | + | |
| 1620 | + .no-more { | |
| 1621 | + text-align: center; | |
| 1622 | + padding: 32rpx; | |
| 1623 | + color: #909399; | |
| 1624 | + font-size: 24rpx; | |
| 1625 | + } | |
| 1626 | + | |
| 1627 | + .pagination-info { | |
| 1628 | + text-align: center; | |
| 1629 | + padding: 24rpx; | |
| 1630 | + color: #6a9c6a; | |
| 1631 | + font-size: 22rpx; | |
| 1632 | + background: #f9fff9; | |
| 1633 | + border-top: 2rpx solid #e0e0e0; | |
| 1634 | + } | |
| 1635 | + | |
| 1636 | + /* 审批人选择相关样式 */ | |
| 1637 | + .approver-select-wrapper { | |
| 1638 | + display: flex; | |
| 1639 | + flex-direction: column; | |
| 1640 | + gap: 16rpx; | |
| 1641 | + } | |
| 1642 | + | |
| 1643 | + .approver-select-btn { | |
| 1644 | + display: flex; | |
| 1645 | + align-items: center; | |
| 1646 | + justify-content: space-between; | |
| 1647 | + background: #f9fff9; | |
| 1648 | + border: 3rpx solid #c8e6c9; | |
| 1649 | + border-radius: 24rpx; | |
| 1650 | + padding: 24rpx 32rpx; | |
| 1651 | + min-height: 80rpx; | |
| 1652 | + box-sizing: border-box; | |
| 1653 | + } | |
| 1654 | + | |
| 1655 | + .approver-btn-text { | |
| 1656 | + font-size: 28rpx; | |
| 1657 | + color: #2e7d32; | |
| 1658 | + flex: 1; | |
| 1659 | + } | |
| 1660 | + | |
| 1661 | + .approver-tags { | |
| 1662 | + display: flex; | |
| 1663 | + flex-wrap: wrap; | |
| 1664 | + gap: 12rpx; | |
| 1665 | + } | |
| 1666 | + | |
| 1667 | + .approver-tag { | |
| 1668 | + display: inline-block; | |
| 1669 | + padding: 8rpx 16rpx; | |
| 1670 | + background: #e8f5e9; | |
| 1671 | + border: 2rpx solid #c8e6c9; | |
| 1672 | + border-radius: 16rpx; | |
| 1673 | + font-size: 24rpx; | |
| 1674 | + color: #2e7d32; | |
| 1675 | + } | |
| 1676 | + | |
| 1677 | + /* 用户选择弹窗样式 */ | |
| 1678 | + .user-dialog-list { | |
| 1679 | + flex: 1; | |
| 1680 | + max-height: 600rpx; | |
| 1681 | + } | |
| 1682 | + | |
| 1683 | + .user-dialog-item { | |
| 1684 | + background: #f9fff9; | |
| 1685 | + border: 2rpx solid #c8e6c9; | |
| 1686 | + border-radius: 16rpx; | |
| 1687 | + padding: 24rpx; | |
| 1688 | + margin-bottom: 16rpx; | |
| 1689 | + transition: all 0.2s ease; | |
| 1690 | + } | |
| 1691 | + | |
| 1692 | + .user-dialog-item.selected { | |
| 1693 | + background: #e8f5e9; | |
| 1694 | + border-color: #43e97b; | |
| 1695 | + } | |
| 1696 | + | |
| 1697 | + .user-dialog-header { | |
| 1698 | + display: flex; | |
| 1699 | + justify-content: space-between; | |
| 1700 | + align-items: center; | |
| 1701 | + margin-bottom: 12rpx; | |
| 1702 | + } | |
| 1703 | + | |
| 1704 | + .user-dialog-name { | |
| 1705 | + font-size: 28rpx; | |
| 1706 | + color: #2e7d32; | |
| 1707 | + font-weight: 600; | |
| 1708 | + } | |
| 1709 | + | |
| 1710 | + .user-dialog-details { | |
| 1711 | + display: flex; | |
| 1712 | + gap: 24rpx; | |
| 1713 | + } | |
| 1714 | + | |
| 1715 | + .user-dialog-detail { | |
| 1716 | + font-size: 24rpx; | |
| 1717 | + color: #6a9c6a; | |
| 1718 | + } | |
| 886 | 1719 | </style> |
| 887 | 1720 | ... | ... |
绿纤uni-app/pages/reimbursement-list/reimbursement-list - 副本.vue
0 → 100644
| 1 | +<template> | |
| 2 | + <view class="container"> | |
| 3 | + <!-- 日期范围筛选 --> | |
| 4 | + <view class="date-filter-card"> | |
| 5 | + <view class="date-filter-header"> | |
| 6 | + <view class="date-range-display" @click="showCalendarFun"> | |
| 7 | + <text class="date-text">{{ formatDateRange() }}</text> | |
| 8 | + <text class="calendar-icon">📅</text> | |
| 9 | + </view> | |
| 10 | + </view> | |
| 11 | + <view class="status-filter"> | |
| 12 | + <text class="status-filter-label">审批状态</text> | |
| 13 | + <picker mode="selector" :range="approveStatusOptions" :range-key="'label'" :value="approveStatusIndex" @change="handleStatusChange"> | |
| 14 | + <view class="status-picker"> | |
| 15 | + <text class="status-text">{{ approveStatusOptions[approveStatusIndex].label }}</text> | |
| 16 | + <text class="picker-arrow">▼</text> | |
| 17 | + </view> | |
| 18 | + </picker> | |
| 19 | + </view> | |
| 20 | + </view> | |
| 21 | + | |
| 22 | + <!-- 添加按钮 --> | |
| 23 | + <view class="add-btn-wrapper"> | |
| 24 | + <view class="add-btn" @click="goToAdd"> | |
| 25 | + <text class="add-text">添加</text> | |
| 26 | + </view> | |
| 27 | + </view> | |
| 28 | + | |
| 29 | + <!-- 日历组件 --> | |
| 30 | + <uni-calendar | |
| 31 | + :range="true" | |
| 32 | + ref="calendar" | |
| 33 | + @confirm="handleDateConfirm" | |
| 34 | + :insert="false" | |
| 35 | + ></uni-calendar> | |
| 36 | + | |
| 37 | + <!-- 数据列表 --> | |
| 38 | + <view class="list-card"> | |
| 39 | + <view class="list-header"> | |
| 40 | + <text class="header-text">报销申请记录</text> | |
| 41 | + <text class="total-count">共 {{ totalCount }} 条</text> | |
| 42 | + </view> | |
| 43 | + | |
| 44 | + <scroll-view scroll-y class="list-content" @scrolltolower="loadMore"> | |
| 45 | + <view v-for="(item, index) in dataList" :key="index" class="list-item" @click.stop="handleItemClick(item)"> | |
| 46 | + <view class="item-header"> | |
| 47 | + <view class="applicant-name">{{ item.applicationUserName || '无' }}</view> | |
| 48 | + <view class="application-time">{{ formatTime(item.applicationTime) }}</view> | |
| 49 | + </view> | |
| 50 | + <view class="item-details"> | |
| 51 | + <!-- <view class="detail-item"> | |
| 52 | + <text class="detail-label">门店:</text> | |
| 53 | + <text class="detail-value">{{ getStoreName(item.applicationStoreId) || '无' }}</text> | |
| 54 | + </view> --> | |
| 55 | + <view class="detail-item"> | |
| 56 | + <text class="detail-label">总金额:</text> | |
| 57 | + <text class="detail-value amount">¥{{ item.amount || 0 }}</text> | |
| 58 | + </view> | |
| 59 | + </view> | |
| 60 | + <view class="item-footer"> | |
| 61 | + <view class="status-badge" :class="item.approveStatus=='已审批'?'approved':item.approveStatus=='待审批'?'pending':item.approveStatus=='未通过'?'rejected':'unapproved'"> | |
| 62 | + {{ item.approveStatus || '未审批' }} | |
| 63 | + </view> | |
| 64 | + <view class="action-buttons"> | |
| 65 | + <view class="action-btn view-btn" @click.stop="handleView(item)"> | |
| 66 | + <text class="btn-text">查看</text> | |
| 67 | + </view> | |
| 68 | + <view v-if="item.approveStatus === '未审批'" class="action-btn edit-btn" @click.stop="handleEdit(item)"> | |
| 69 | + <text class="btn-text">编辑</text> | |
| 70 | + </view> | |
| 71 | + </view> | |
| 72 | + </view> | |
| 73 | + </view> | |
| 74 | + | |
| 75 | + <!-- 空状态 --> | |
| 76 | + <view v-if="!loading && dataList.length === 0" class="empty-state"> | |
| 77 | + <view class="empty-icon">💰</view> | |
| 78 | + <view>暂无报销申请记录</view> | |
| 79 | + </view> | |
| 80 | + | |
| 81 | + <!-- 加载状态 --> | |
| 82 | + <view v-if="loading && dataList.length === 0" class="loading">正在加载数据...</view> | |
| 83 | + | |
| 84 | + <!-- 加载更多 --> | |
| 85 | + <u-loadmore v-if="dataList.length > 0" :status="loadmoreStatus" :load-text="loadText"></u-loadmore> | |
| 86 | + </scroll-view> | |
| 87 | + </view> | |
| 88 | + </view> | |
| 89 | +</template> | |
| 90 | + | |
| 91 | +<script> | |
| 92 | + import api from '@/apis/index.js' | |
| 93 | + import config from '@/common/config.js' | |
| 94 | + import uniCalendar from '@/uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue' | |
| 95 | + | |
| 96 | + export default { | |
| 97 | + components: { | |
| 98 | + uniCalendar | |
| 99 | + }, | |
| 100 | + data() { | |
| 101 | + // 初始化日期范围(本月1号到今天) | |
| 102 | + const now = new Date() | |
| 103 | + const year = now.getFullYear() | |
| 104 | + const month = now.getMonth() | |
| 105 | + const firstDay = new Date(year, month, 1) | |
| 106 | + const today = new Date(year, now.getMonth(), now.getDate(), 23, 59, 59, 999) | |
| 107 | + | |
| 108 | + // 格式化日期为 YYYY-MM-DD(使用本地时间,避免时区问题) | |
| 109 | + const formatDateLocal = (date) => { | |
| 110 | + const y = date.getFullYear() | |
| 111 | + const m = String(date.getMonth() + 1).padStart(2, '0') | |
| 112 | + const d = String(date.getDate()).padStart(2, '0') | |
| 113 | + return `${y}-${m}-${d}` | |
| 114 | + } | |
| 115 | + | |
| 116 | + return { | |
| 117 | + loading: false, | |
| 118 | + dataList: [], | |
| 119 | + currentPage: 1, | |
| 120 | + pageSize: 10, | |
| 121 | + totalCount: 0, | |
| 122 | + hasMore: true, | |
| 123 | + loadmoreStatus: 'loadmore', | |
| 124 | + loadText: { | |
| 125 | + loadmore: '点击或上拉加载更多', | |
| 126 | + loading: '正在加载...', | |
| 127 | + nomore: '没有更多了' | |
| 128 | + }, | |
| 129 | + userInfo: uni.getStorageSync('userInfo'), | |
| 130 | + newuserInfo: uni.getStorageSync('newuserInfo'), | |
| 131 | + storeOptions: [], | |
| 132 | + // 日期范围筛选相关 | |
| 133 | + showCalendar: false, | |
| 134 | + startDate: formatDateLocal(firstDay), | |
| 135 | + endDate: formatDateLocal(now), | |
| 136 | + dateRange: [firstDay.getTime(), today.getTime()], | |
| 137 | + // 审批状态筛选 | |
| 138 | + approveStatusIndex: 0, | |
| 139 | + approveStatusOptions: [ | |
| 140 | + { label: '全部', value: '' }, | |
| 141 | + { label: '未审批', value: '未审批' }, | |
| 142 | + { label: '待审批', value: '待审批' }, | |
| 143 | + { label: '已审批', value: '已审批' }, | |
| 144 | + { label: '未通过', value: '未通过' } | |
| 145 | + ] | |
| 146 | + } | |
| 147 | + }, | |
| 148 | + | |
| 149 | + onLoad() { | |
| 150 | + // this.loadStoreOptions() | |
| 151 | + this.loadReimbursementList() | |
| 152 | + }, | |
| 153 | + | |
| 154 | + methods: { | |
| 155 | + // 加载门店列表 | |
| 156 | + async loadStoreOptions() { | |
| 157 | + try { | |
| 158 | + const res = await api.getStoreList({ | |
| 159 | + currentPage: 1, | |
| 160 | + pageSize: 1000 | |
| 161 | + }) | |
| 162 | + if (res.code === 200 && res.data && res.data.list) { | |
| 163 | + this.storeOptions = res.data.list | |
| 164 | + } else { | |
| 165 | + this.storeOptions = [] | |
| 166 | + } | |
| 167 | + } catch (error) { | |
| 168 | + console.error('加载门店列表失败:', error) | |
| 169 | + this.storeOptions = [] | |
| 170 | + } | |
| 171 | + }, | |
| 172 | + | |
| 173 | + // 获取门店名称 | |
| 174 | + getStoreName(storeId) { | |
| 175 | + if (!storeId) return '无' | |
| 176 | + const store = this.storeOptions.find(item => item.id === storeId) | |
| 177 | + return store ? store.dm : '无' | |
| 178 | + }, | |
| 179 | + | |
| 180 | + // 显示日历 | |
| 181 | + showCalendarFun() { | |
| 182 | + this.$refs.calendar.open() | |
| 183 | + }, | |
| 184 | + | |
| 185 | + // 日期确认 | |
| 186 | + handleDateConfirm(e) { | |
| 187 | + console.log('日期选择结果:', e) | |
| 188 | + | |
| 189 | + // uni-calendar组件返回的是包含start和end的对象 | |
| 190 | + if (e && e.range && e.range.data) { | |
| 191 | + // 更新dateRange用于API调用(时间戳格式) | |
| 192 | + const startTime = new Date(e.range.data[0]).getTime() | |
| 193 | + const endTime = new Date(e.range.data[e.range.data.length-1]).getTime() | |
| 194 | + this.dateRange = [startTime, endTime] | |
| 195 | + | |
| 196 | + // 更新startDate和endDate用于显示 | |
| 197 | + this.startDate = e.range.data[0] | |
| 198 | + this.endDate = e.range.data[e.range.data.length-1] | |
| 199 | + | |
| 200 | + // 日期变化时重新加载数据 | |
| 201 | + this.currentPage = 1 | |
| 202 | + this.loadReimbursementList() | |
| 203 | + } | |
| 204 | + }, | |
| 205 | + | |
| 206 | + // 格式化日期范围显示 | |
| 207 | + formatDateRange() { | |
| 208 | + if (!this.startDate || !this.endDate) { | |
| 209 | + return '请选择日期' | |
| 210 | + } | |
| 211 | + | |
| 212 | + // 处理日期字符串,确保正确解析 | |
| 213 | + const startDate = new Date(this.startDate + 'T00:00:00') | |
| 214 | + const endDate = new Date(this.endDate + 'T00:00:00') | |
| 215 | + | |
| 216 | + const formatDate = (date) => { | |
| 217 | + const year = date.getFullYear() | |
| 218 | + const month = String(date.getMonth() + 1).padStart(2, '0') | |
| 219 | + const day = String(date.getDate()).padStart(2, '0') | |
| 220 | + return `${year}/${month}/${day}` | |
| 221 | + } | |
| 222 | + | |
| 223 | + return `${formatDate(startDate)} - ${formatDate(endDate)}` | |
| 224 | + }, | |
| 225 | + | |
| 226 | + // 审批状态变化 | |
| 227 | + handleStatusChange(e) { | |
| 228 | + this.approveStatusIndex = e.detail.value | |
| 229 | + this.currentPage = 1 | |
| 230 | + this.loadReimbursementList() | |
| 231 | + }, | |
| 232 | + | |
| 233 | + // 加载报销申请列表 | |
| 234 | + async loadReimbursementList() { | |
| 235 | + if (this.loading) return | |
| 236 | + | |
| 237 | + try { | |
| 238 | + this.loading = true | |
| 239 | + this.loadmoreStatus = 'loading' | |
| 240 | + | |
| 241 | + // 构建查询参数 | |
| 242 | + const params = { | |
| 243 | + currentPage: this.currentPage, | |
| 244 | + pageSize: this.pageSize | |
| 245 | + } | |
| 246 | + | |
| 247 | + // 添加日期范围筛选 | |
| 248 | + if (this.startDate && this.endDate) { | |
| 249 | + const startTimestamp = new Date(this.startDate + 'T00:00:00').getTime() | |
| 250 | + const endTimestamp = new Date(this.endDate + 'T23:59:59').getTime() | |
| 251 | + params.applicationTime = `${startTimestamp},${endTimestamp}` | |
| 252 | + } | |
| 253 | + | |
| 254 | + // 添加审批状态筛选 | |
| 255 | + const selectedStatus = this.approveStatusOptions[this.approveStatusIndex] | |
| 256 | + if (selectedStatus && selectedStatus.value) { | |
| 257 | + params.approveStatus = selectedStatus.value | |
| 258 | + } | |
| 259 | + | |
| 260 | + // 添加门店筛选(默认当前登录人的门店) | |
| 261 | + // params.applicationStoreId = this.newuserInfo&&this.newuserInfo.mdid?this.newuserInfo.mdid:'暂无' | |
| 262 | + params.applicationUserId = this.newuserInfo&&this.newuserInfo.id?this.newuserInfo.id:'暂无' | |
| 263 | + const res = await api.getReimbursementList(params) | |
| 264 | + | |
| 265 | + if (res.code === 200 && res.data) { | |
| 266 | + const list = res.data.list || [] | |
| 267 | + const pagination = res.data.pagination || {} | |
| 268 | + | |
| 269 | + if (this.currentPage === 1) { | |
| 270 | + this.dataList = list | |
| 271 | + } else { | |
| 272 | + this.dataList = [...this.dataList, ...list] | |
| 273 | + } | |
| 274 | + | |
| 275 | + this.totalCount = pagination.total || 0 | |
| 276 | + this.hasMore = this.dataList.length < this.totalCount | |
| 277 | + | |
| 278 | + if (!this.hasMore) { | |
| 279 | + this.loadmoreStatus = 'nomore' | |
| 280 | + } else { | |
| 281 | + this.loadmoreStatus = 'loadmore' | |
| 282 | + } | |
| 283 | + } else { | |
| 284 | + uni.showToast({ | |
| 285 | + title: res.message || '加载失败', | |
| 286 | + icon: 'none' | |
| 287 | + }) | |
| 288 | + } | |
| 289 | + } catch (error) { | |
| 290 | + console.error('加载报销申请列表失败:', error) | |
| 291 | + uni.showToast({ | |
| 292 | + title: '加载失败,请重试', | |
| 293 | + icon: 'none' | |
| 294 | + }) | |
| 295 | + } finally { | |
| 296 | + this.loading = false | |
| 297 | + } | |
| 298 | + }, | |
| 299 | + | |
| 300 | + // 加载更多 | |
| 301 | + async loadMore() { | |
| 302 | + if (this.loading || !this.hasMore || this.loadmoreStatus === 'nomore') return | |
| 303 | + | |
| 304 | + this.loadmoreStatus = 'loading' | |
| 305 | + this.currentPage++ | |
| 306 | + await this.loadReimbursementList() | |
| 307 | + }, | |
| 308 | + | |
| 309 | + // 跳转到添加页面 | |
| 310 | + goToAdd() { | |
| 311 | + uni.navigateTo({ | |
| 312 | + url: '/pages/reimbursement-form/reimbursement-form' | |
| 313 | + }) | |
| 314 | + }, | |
| 315 | + | |
| 316 | + // 查看详情 | |
| 317 | + handleView(item) { | |
| 318 | + uni.navigateTo({ | |
| 319 | + url: `/pages/reimbursement-detail/reimbursement-detail?id=${item.id}` | |
| 320 | + }) | |
| 321 | + }, | |
| 322 | + | |
| 323 | + // 编辑记录(只有未审批状态才能编辑) | |
| 324 | + handleEdit(item) { | |
| 325 | + if (item.approveStatus === '未审批') { | |
| 326 | + uni.navigateTo({ | |
| 327 | + url: `/pages/reimbursement-form/reimbursement-form?id=${item.id}` | |
| 328 | + }) | |
| 329 | + } else { | |
| 330 | + uni.showToast({ | |
| 331 | + title: '已审批的记录不能编辑', | |
| 332 | + icon: 'none' | |
| 333 | + }) | |
| 334 | + } | |
| 335 | + }, | |
| 336 | + | |
| 337 | + // 处理列表项点击(用于编辑) | |
| 338 | + handleItemClick(item) { | |
| 339 | + // 只有未审批状态才能编辑 | |
| 340 | + if (item.approveStatus === '未审批') { | |
| 341 | + this.handleEdit(item) | |
| 342 | + } | |
| 343 | + }, | |
| 344 | + | |
| 345 | + // 格式化时间 | |
| 346 | + formatTime(timestamp) { | |
| 347 | + if (!timestamp) return '无' | |
| 348 | + const date = new Date(timestamp) | |
| 349 | + return date.toLocaleString('zh-CN', { | |
| 350 | + year: 'numeric', | |
| 351 | + month: '2-digit', | |
| 352 | + day: '2-digit', | |
| 353 | + hour: '2-digit', | |
| 354 | + minute: '2-digit' | |
| 355 | + }) | |
| 356 | + }, | |
| 357 | + | |
| 358 | + // 获取状态样式类 | |
| 359 | + getStatusClass(status) { | |
| 360 | + switch (status) { | |
| 361 | + case '已审批': | |
| 362 | + return 'approved' | |
| 363 | + case '待审批': | |
| 364 | + return 'pending' | |
| 365 | + case '未通过': | |
| 366 | + return 'rejected' | |
| 367 | + case '未审批': | |
| 368 | + default: | |
| 369 | + return 'unapproved' | |
| 370 | + } | |
| 371 | + } | |
| 372 | + } | |
| 373 | + } | |
| 374 | +</script> | |
| 375 | + | |
| 376 | +<style lang="scss" scoped> | |
| 377 | + .container { | |
| 378 | + min-height: 100vh; | |
| 379 | + background: linear-gradient(135deg, #e8f5e9 0%, #b2dfdb 100%); | |
| 380 | + padding: 40rpx; | |
| 381 | + box-sizing: border-box; | |
| 382 | + } | |
| 383 | + | |
| 384 | + .date-filter-card { | |
| 385 | + background: #fff; | |
| 386 | + border-radius: 24rpx; | |
| 387 | + padding: 32rpx; | |
| 388 | + margin-bottom: 24rpx; | |
| 389 | + box-shadow: 0 4rpx 16rpx rgba(76, 175, 80, 0.1); | |
| 390 | + } | |
| 391 | + | |
| 392 | + .date-filter-header { | |
| 393 | + margin-bottom: 24rpx; | |
| 394 | + } | |
| 395 | + | |
| 396 | + .date-range-display { | |
| 397 | + display: flex; | |
| 398 | + align-items: center; | |
| 399 | + justify-content: space-between; | |
| 400 | + padding: 24rpx; | |
| 401 | + background: #f9fff9; | |
| 402 | + border: 3rpx solid #c8e6c9; | |
| 403 | + border-radius: 16rpx; | |
| 404 | + } | |
| 405 | + | |
| 406 | + .date-text { | |
| 407 | + font-size: 28rpx; | |
| 408 | + color: #2e7d32; | |
| 409 | + font-weight: 500; | |
| 410 | + } | |
| 411 | + | |
| 412 | + .calendar-icon { | |
| 413 | + font-size: 32rpx; | |
| 414 | + } | |
| 415 | + | |
| 416 | + .status-filter { | |
| 417 | + display: flex; | |
| 418 | + align-items: center; | |
| 419 | + gap: 24rpx; | |
| 420 | + justify-content: space-between; | |
| 421 | + } | |
| 422 | + | |
| 423 | + .status-filter-label { | |
| 424 | + font-size: 28rpx; | |
| 425 | + color: #2e7d32; | |
| 426 | + font-weight: 500; | |
| 427 | + } | |
| 428 | + | |
| 429 | + .status-picker { | |
| 430 | + flex: 1; | |
| 431 | + display: flex; | |
| 432 | + align-items: center; | |
| 433 | + justify-content: space-between; | |
| 434 | + padding: 24rpx; | |
| 435 | + background: #f9fff9; | |
| 436 | + border: 3rpx solid #c8e6c9; | |
| 437 | + border-radius: 16rpx; | |
| 438 | + } | |
| 439 | + | |
| 440 | + .status-text { | |
| 441 | + font-size: 28rpx; | |
| 442 | + color: #2e7d32; | |
| 443 | + } | |
| 444 | + | |
| 445 | + .picker-arrow { | |
| 446 | + font-size: 24rpx; | |
| 447 | + color: #6a9c6a; | |
| 448 | + margin-left: 10rpx; | |
| 449 | + } | |
| 450 | + | |
| 451 | + .add-btn-wrapper { | |
| 452 | + margin-bottom: 24rpx; | |
| 453 | + display: flex; | |
| 454 | + justify-content: flex-end; | |
| 455 | + } | |
| 456 | + | |
| 457 | + .add-btn { | |
| 458 | + display: flex; | |
| 459 | + align-items: center; | |
| 460 | + justify-content: center; | |
| 461 | + padding: 20rpx 48rpx; | |
| 462 | + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); | |
| 463 | + border-radius: 48rpx; | |
| 464 | + box-shadow: 0 8rpx 24rpx rgba(67, 233, 123, 0.3); | |
| 465 | + } | |
| 466 | + | |
| 467 | + .add-text { | |
| 468 | + font-size: 28rpx; | |
| 469 | + color: #fff; | |
| 470 | + font-weight: 600; | |
| 471 | + letter-spacing: 2rpx; | |
| 472 | + } | |
| 473 | + | |
| 474 | + .list-card { | |
| 475 | + background: #fff; | |
| 476 | + border-radius: 24rpx; | |
| 477 | + overflow: hidden; | |
| 478 | + box-shadow: 0 4rpx 16rpx rgba(76, 175, 80, 0.1); | |
| 479 | + } | |
| 480 | + | |
| 481 | + .list-header { | |
| 482 | + display: flex; | |
| 483 | + justify-content: space-between; | |
| 484 | + align-items: center; | |
| 485 | + padding: 32rpx; | |
| 486 | + background: linear-gradient(120deg, #43e97b 0%, #38f9d7 100%); | |
| 487 | + } | |
| 488 | + | |
| 489 | + .header-text { | |
| 490 | + font-size: 32rpx; | |
| 491 | + color: #fff; | |
| 492 | + font-weight: 600; | |
| 493 | + letter-spacing: 2rpx; | |
| 494 | + } | |
| 495 | + | |
| 496 | + .total-count { | |
| 497 | + font-size: 24rpx; | |
| 498 | + color: #fff; | |
| 499 | + opacity: 0.9; | |
| 500 | + } | |
| 501 | + | |
| 502 | + .list-content { | |
| 503 | + max-height: calc(100vh - 500rpx); | |
| 504 | + } | |
| 505 | + | |
| 506 | + .list-item { | |
| 507 | + padding: 32rpx; | |
| 508 | + border-bottom: 2rpx solid #f0f0f0; | |
| 509 | + transition: all 0.2s ease; | |
| 510 | + } | |
| 511 | + | |
| 512 | + .list-item:last-child { | |
| 513 | + border-bottom: none; | |
| 514 | + } | |
| 515 | + | |
| 516 | + .list-item:active { | |
| 517 | + background: #f9fff9; | |
| 518 | + } | |
| 519 | + | |
| 520 | + .item-header { | |
| 521 | + display: flex; | |
| 522 | + justify-content: space-between; | |
| 523 | + align-items: center; | |
| 524 | + margin-bottom: 16rpx; | |
| 525 | + } | |
| 526 | + | |
| 527 | + .applicant-name { | |
| 528 | + font-size: 32rpx; | |
| 529 | + color: #2e7d32; | |
| 530 | + font-weight: 600; | |
| 531 | + } | |
| 532 | + | |
| 533 | + .application-time { | |
| 534 | + font-size: 24rpx; | |
| 535 | + color: #6a9c6a; | |
| 536 | + } | |
| 537 | + | |
| 538 | + .item-details { | |
| 539 | + display: flex; | |
| 540 | + flex-wrap: wrap; | |
| 541 | + gap: 24rpx; | |
| 542 | + margin-bottom: 16rpx; | |
| 543 | + } | |
| 544 | + | |
| 545 | + .detail-item { | |
| 546 | + display: flex; | |
| 547 | + align-items: center; | |
| 548 | + gap: 8rpx; | |
| 549 | + } | |
| 550 | + | |
| 551 | + .detail-label { | |
| 552 | + font-size: 24rpx; | |
| 553 | + color: #6a9c6a; | |
| 554 | + } | |
| 555 | + | |
| 556 | + .detail-value { | |
| 557 | + font-size: 28rpx; | |
| 558 | + color: #2e7d32; | |
| 559 | + font-weight: 500; | |
| 560 | + } | |
| 561 | + | |
| 562 | + .detail-value.amount { | |
| 563 | + color: #f57c00; | |
| 564 | + font-weight: 600; | |
| 565 | + font-size: 32rpx; | |
| 566 | + } | |
| 567 | + | |
| 568 | + .item-footer { | |
| 569 | + display: flex; | |
| 570 | + justify-content: space-between; | |
| 571 | + align-items: center; | |
| 572 | + margin-top: 16rpx; | |
| 573 | + } | |
| 574 | + | |
| 575 | + .status-badge { | |
| 576 | + display: inline-block; | |
| 577 | + padding: 8rpx 24rpx; | |
| 578 | + border-radius: 40rpx; | |
| 579 | + font-size: 24rpx; | |
| 580 | + font-weight: 500; | |
| 581 | + text-align: center; | |
| 582 | + } | |
| 583 | + | |
| 584 | + .status-badge.unapproved { | |
| 585 | + background: #fff3e0; | |
| 586 | + color: #ef6c00; | |
| 587 | + border: 2rpx solid #ffe0b2; | |
| 588 | + } | |
| 589 | + | |
| 590 | + .status-badge.pending { | |
| 591 | + background: #e3f2fd; | |
| 592 | + color: #1976d2; | |
| 593 | + border: 2rpx solid #bbdefb; | |
| 594 | + } | |
| 595 | + | |
| 596 | + .status-badge.approved { | |
| 597 | + background: #e8f5e9; | |
| 598 | + color: #2e7d32; | |
| 599 | + border: 2rpx solid #c8e6c9; | |
| 600 | + } | |
| 601 | + | |
| 602 | + .status-badge.rejected { | |
| 603 | + background: #ffebee; | |
| 604 | + color: #c62828; | |
| 605 | + border: 2rpx solid #ffcdd2; | |
| 606 | + } | |
| 607 | + | |
| 608 | + .action-buttons { | |
| 609 | + display: flex; | |
| 610 | + align-items: center; | |
| 611 | + gap: 16rpx; | |
| 612 | + } | |
| 613 | + | |
| 614 | + .action-btn { | |
| 615 | + display: flex; | |
| 616 | + align-items: center; | |
| 617 | + padding: 12rpx 24rpx; | |
| 618 | + border-radius: 24rpx; | |
| 619 | + font-size: 24rpx; | |
| 620 | + font-weight: 500; | |
| 621 | + transition: all 0.2s ease; | |
| 622 | + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1); | |
| 623 | + } | |
| 624 | + | |
| 625 | + .action-btn:active { | |
| 626 | + transform: scale(0.95); | |
| 627 | + box-shadow: 0 1rpx 4rpx rgba(0, 0, 0, 0.15); | |
| 628 | + } | |
| 629 | + | |
| 630 | + .view-btn { | |
| 631 | + background: linear-gradient(135deg, #42a5f5 0%, #1e88e5 100%); | |
| 632 | + color: #fff; | |
| 633 | + } | |
| 634 | + | |
| 635 | + .view-btn:active { | |
| 636 | + background: linear-gradient(135deg, #1e88e5 0%, #1565c0 100%); | |
| 637 | + } | |
| 638 | + | |
| 639 | + .edit-btn { | |
| 640 | + background: linear-gradient(135deg, #66bb6a 0%, #43a047 100%); | |
| 641 | + color: #fff; | |
| 642 | + } | |
| 643 | + | |
| 644 | + .edit-btn:active { | |
| 645 | + background: linear-gradient(135deg, #43a047 0%, #388e3c 100%); | |
| 646 | + } | |
| 647 | + | |
| 648 | + .btn-text { | |
| 649 | + letter-spacing: 1rpx; | |
| 650 | + } | |
| 651 | + | |
| 652 | + .loading { | |
| 653 | + text-align: center; | |
| 654 | + padding: 80rpx 40rpx; | |
| 655 | + color: #6a9c6a; | |
| 656 | + font-size: 28rpx; | |
| 657 | + } | |
| 658 | + | |
| 659 | + .empty-state { | |
| 660 | + text-align: center; | |
| 661 | + padding: 120rpx 40rpx; | |
| 662 | + color: #6a9c6a; | |
| 663 | + } | |
| 664 | + | |
| 665 | + .empty-icon { | |
| 666 | + font-size: 120rpx; | |
| 667 | + margin-bottom: 32rpx; | |
| 668 | + opacity: 0.5; | |
| 669 | + } | |
| 670 | +</style> | |
| 671 | + | ... | ... |
绿纤uni-app/pages/reimbursement-list/reimbursement-list.vue
| ... | ... | @@ -58,14 +58,14 @@ |
| 58 | 58 | </view> |
| 59 | 59 | </view> |
| 60 | 60 | <view class="item-footer"> |
| 61 | - <view class="status-badge" :class="item.approveStatus=='已审批'?'approved':item.approveStatus=='待审批'?'pending':item.approveStatus=='未通过'?'rejected':'unapproved'"> | |
| 62 | - {{ item.approveStatus || '未审批' }} | |
| 61 | + <view class="status-badge" :class="item.approveStatus=='待审批'?'pending':item.approveStatus=='审批中'?'pending':item.approveStatus=='已通过'?'approved':item.approveStatus=='未通过'?'rejected':item.approveStatus=='已退回'?'rejected':'unapproved'"> | |
| 62 | + {{ item.approveStatus || '待审批' }} | |
| 63 | 63 | </view> |
| 64 | 64 | <view class="action-buttons"> |
| 65 | 65 | <view class="action-btn view-btn" @click.stop="handleView(item)"> |
| 66 | 66 | <text class="btn-text">查看</text> |
| 67 | 67 | </view> |
| 68 | - <view v-if="item.approveStatus === '未审批'" class="action-btn edit-btn" @click.stop="handleEdit(item)"> | |
| 68 | + <view v-if="item.approveStatus === '已退回' || item.approveStatus === '未通过'" class="action-btn edit-btn" @click.stop="handleEdit(item)"> | |
| 69 | 69 | <text class="btn-text">编辑</text> |
| 70 | 70 | </view> |
| 71 | 71 | </view> |
| ... | ... | @@ -138,16 +138,18 @@ |
| 138 | 138 | approveStatusIndex: 0, |
| 139 | 139 | approveStatusOptions: [ |
| 140 | 140 | { label: '全部', value: '' }, |
| 141 | - { label: '未审批', value: '未审批' }, | |
| 142 | 141 | { label: '待审批', value: '待审批' }, |
| 143 | - { label: '已审批', value: '已审批' }, | |
| 144 | - { label: '未通过', value: '未通过' } | |
| 142 | + { label: '审批中', value: '审批中' }, | |
| 143 | + { label: '已通过', value: '已通过' }, | |
| 144 | + { label: '未通过', value: '未通过' }, | |
| 145 | + { label: '已退回', value: '已退回' } | |
| 145 | 146 | ] |
| 146 | 147 | } |
| 147 | 148 | }, |
| 148 | 149 | |
| 149 | - onLoad() { | |
| 150 | + onShow() { | |
| 150 | 151 | // this.loadStoreOptions() |
| 152 | + this.currentPage = 1 | |
| 151 | 153 | this.loadReimbursementList() |
| 152 | 154 | }, |
| 153 | 155 | |
| ... | ... | @@ -320,15 +322,15 @@ |
| 320 | 322 | }) |
| 321 | 323 | }, |
| 322 | 324 | |
| 323 | - // 编辑记录(只有未审批状态才能编辑) | |
| 325 | + // 编辑记录(已退回或未通过状态才能编辑) | |
| 324 | 326 | handleEdit(item) { |
| 325 | - if (item.approveStatus === '未审批') { | |
| 327 | + if (item.approveStatus === '已退回' || item.approveStatus === '未通过') { | |
| 326 | 328 | uni.navigateTo({ |
| 327 | 329 | url: `/pages/reimbursement-form/reimbursement-form?id=${item.id}` |
| 328 | 330 | }) |
| 329 | 331 | } else { |
| 330 | 332 | uni.showToast({ |
| 331 | - title: '已审批的记录不能编辑', | |
| 333 | + title: '该状态的记录不能编辑', | |
| 332 | 334 | icon: 'none' |
| 333 | 335 | }) |
| 334 | 336 | } |
| ... | ... | @@ -336,8 +338,8 @@ |
| 336 | 338 | |
| 337 | 339 | // 处理列表项点击(用于编辑) |
| 338 | 340 | handleItemClick(item) { |
| 339 | - // 只有未审批状态才能编辑 | |
| 340 | - if (item.approveStatus === '未审批') { | |
| 341 | + // 已退回或未通过状态才能编辑 | |
| 342 | + if (item.approveStatus === '已退回' || item.approveStatus === '未通过') { | |
| 341 | 343 | this.handleEdit(item) |
| 342 | 344 | } |
| 343 | 345 | }, |
| ... | ... | @@ -353,21 +355,6 @@ |
| 353 | 355 | hour: '2-digit', |
| 354 | 356 | minute: '2-digit' |
| 355 | 357 | }) |
| 356 | - }, | |
| 357 | - | |
| 358 | - // 获取状态样式类 | |
| 359 | - getStatusClass(status) { | |
| 360 | - switch (status) { | |
| 361 | - case '已审批': | |
| 362 | - return 'approved' | |
| 363 | - case '待审批': | |
| 364 | - return 'pending' | |
| 365 | - case '未通过': | |
| 366 | - return 'rejected' | |
| 367 | - case '未审批': | |
| 368 | - default: | |
| 369 | - return 'unapproved' | |
| 370 | - } | |
| 371 | 358 | } |
| 372 | 359 | } |
| 373 | 360 | } | ... | ... |