|
@@ -0,0 +1,686 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="bx-page bx-page--form">
|
|
|
|
|
+ <view class="topbar" :style="safeAreaStyle">
|
|
|
|
|
+ <view class="topbar-left">
|
|
|
|
|
+ <text class="topbar-back" @click="goBack">←</text>
|
|
|
|
|
+ <text class="topbar-title">{{ pageTitle }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="topbar-right" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="page-padded">
|
|
|
|
|
+ <view class="form-card">
|
|
|
|
|
+ <view class="form-header">
|
|
|
|
|
+ <text class="form-header-title">申请信息</text>
|
|
|
|
|
+ <text class="form-badge">{{ formBadge }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view v-if="form.applyNo" class="field">
|
|
|
|
|
+ <text class="field-label">申请单号</text>
|
|
|
|
|
+ <input class="form-input" :value="form.applyNo" disabled />
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="field">
|
|
|
|
|
+ <text class="field-label">申请人</text>
|
|
|
|
|
+ <input class="form-input" :value="applicantDisplay" disabled />
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="field">
|
|
|
|
|
+ <text class="field-label">车牌号<text class="req">*</text></text>
|
|
|
|
|
+ <view
|
|
|
|
|
+ class="picker-trigger"
|
|
|
|
|
+ :class="{ error: fieldErr.licensePlate }"
|
|
|
|
|
+ @click="openVehiclePicker"
|
|
|
|
|
+ >
|
|
|
|
|
+ <text class="value" :class="{ ph: !form.licensePlate }">
|
|
|
|
|
+ {{ form.licensePlate || '请选择本人车辆' }}
|
|
|
|
|
+ </text>
|
|
|
|
|
+ <text class="arrow">›</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="field">
|
|
|
|
|
+ <text class="field-label">充值类型<text class="req">*</text></text>
|
|
|
|
|
+ <view class="fuel-seg">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="opt in typeOptions"
|
|
|
|
|
+ :key="opt.value"
|
|
|
|
|
+ class="fuel-seg__item"
|
|
|
|
|
+ :class="{
|
|
|
|
|
+ 'fuel-seg__item--active': form.rechargeType === opt.value,
|
|
|
|
|
+ 'fuel-seg__item--disabled': readonly
|
|
|
|
|
+ }"
|
|
|
|
|
+ @click="onPickType(opt.value)"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ opt.label }}
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="field">
|
|
|
|
|
+ <text class="field-label">申请日期<text class="req">*</text></text>
|
|
|
|
|
+ <picker
|
|
|
|
|
+ mode="date"
|
|
|
|
|
+ :value="form.applyDate"
|
|
|
|
|
+ :disabled="readonly"
|
|
|
|
|
+ @change="onDateChange"
|
|
|
|
|
+ >
|
|
|
|
|
+ <view class="picker-trigger" :class="{ error: fieldErr.applyDate }">
|
|
|
|
|
+ <text class="value" :class="{ ph: !form.applyDate }">
|
|
|
|
|
+ {{ form.applyDate || '请选择日期' }}
|
|
|
|
|
+ </text>
|
|
|
|
|
+ <text class="arrow">›</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </picker>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="field">
|
|
|
|
|
+ <text class="field-label">期初公里数/KM</text>
|
|
|
|
|
+ <input
|
|
|
|
|
+ class="form-input"
|
|
|
|
|
+ type="digit"
|
|
|
|
|
+ :value="form.odometerKm"
|
|
|
|
|
+ :disabled="readonly"
|
|
|
|
|
+ placeholder="选填"
|
|
|
|
|
+ @input="onOdometerInput"
|
|
|
|
|
+ />
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="field">
|
|
|
|
|
+ <text class="field-label">备注</text>
|
|
|
|
|
+ <textarea
|
|
|
|
|
+ class="form-textarea"
|
|
|
|
|
+ :value="form.remark"
|
|
|
|
|
+ :disabled="readonly"
|
|
|
|
|
+ placeholder="选填"
|
|
|
|
|
+ :maxlength="500"
|
|
|
|
|
+ @input="onRemarkInput"
|
|
|
|
|
+ />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view class="form-card">
|
|
|
|
|
+ <view class="form-header">
|
|
|
|
|
+ <text class="form-header-title">附件</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-if="!readonly"
|
|
|
|
|
+ class="btn btn-primary btn-sm"
|
|
|
|
|
+ :disabled="uploading"
|
|
|
|
|
+ @click="chooseAttachments"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ uploading ? '上传中…' : '上传图片' }}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <view v-if="imageUrls.length" class="att-grid">
|
|
|
|
|
+ <view v-for="(url, idx) in imageUrls" :key="url + idx" class="att-img" @click="previewAt(idx)">
|
|
|
|
|
+ <image :src="resolveUrl(url)" mode="aspectFill" />
|
|
|
|
|
+ <view v-if="!readonly" class="del" @click.stop="removeImage(idx)">×</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-else class="detail-section-empty" style="margin-top: 8px">
|
|
|
|
|
+ {{ readonly ? '暂无附件' : '暂无附件(可选,支持相册/拍照)' }}
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view v-if="editId" class="form-card">
|
|
|
|
|
+ <view class="form-header">
|
|
|
|
|
+ <text class="form-header-title">审批进度</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="!approvalNodes.length" class="detail-section-empty">暂无审批信息</view>
|
|
|
|
|
+ <view v-for="(node, idx) in approvalNodes" :key="node.id || idx" class="list-card-kv" style="margin-bottom: 10px">
|
|
|
|
|
+ <view class="list-card-kv__row">
|
|
|
|
|
+ <text class="list-card-kv__k">节点</text>
|
|
|
|
|
+ <text class="list-card-kv__v">{{ node.nodeName || '—' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="list-card-kv__row">
|
|
|
|
|
+ <text class="list-card-kv__k">状态</text>
|
|
|
|
|
+ <text class="list-card-kv__v">{{ approvalStatusLabel(node.approvalStatus) }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="list-card-kv__row">
|
|
|
|
|
+ <text class="list-card-kv__k">审批人</text>
|
|
|
|
|
+ <text class="list-card-kv__v">{{ node.approverName || '—' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="node.approvalOpinion" class="list-card-kv__row">
|
|
|
|
|
+ <text class="list-card-kv__k">意见</text>
|
|
|
|
|
+ <text class="list-card-kv__v">{{ node.approvalOpinion }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="node.approvalTime" class="list-card-kv__row">
|
|
|
|
|
+ <text class="list-card-kv__k">时间</text>
|
|
|
|
|
+ <text class="list-card-kv__v">{{ formatTime(node.approvalTime) }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view v-if="editId" class="form-card">
|
|
|
|
|
+ <view class="form-header">
|
|
|
|
|
+ <text class="form-header-title">发放信息</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="!record" class="detail-section-empty">暂无</view>
|
|
|
|
|
+ <view v-else class="list-card-kv">
|
|
|
|
|
+ <view class="list-card-kv__row">
|
|
|
|
|
+ <text class="list-card-kv__k">充值订单号</text>
|
|
|
|
|
+ <text class="list-card-kv__v">{{ record.rechargeOrderNo || '—' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="list-card-kv__row">
|
|
|
|
|
+ <text class="list-card-kv__k">充值日期</text>
|
|
|
|
|
+ <text class="list-card-kv__v">{{ record.rechargeDate || '—' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="list-card-kv__row">
|
|
|
|
|
+ <text class="list-card-kv__k">金额</text>
|
|
|
|
|
+ <text class="list-card-kv__v">
|
|
|
|
|
+ {{ record.amount != null ? `${Number(record.amount).toFixed(2)} 元` : '—' }}
|
|
|
|
|
+ </text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="list-card-kv__row">
|
|
|
|
|
+ <text class="list-card-kv__k">卡号</text>
|
|
|
|
|
+ <text class="list-card-kv__v">{{ record.cardNo || '—' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="list-card-kv__row">
|
|
|
|
|
+ <text class="list-card-kv__k">加油站/渠道</text>
|
|
|
|
|
+ <text class="list-card-kv__v">{{ record.gasStation || '—' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="list-card-kv__row">
|
|
|
|
|
+ <text class="list-card-kv__k">发放人</text>
|
|
|
|
|
+ <text class="list-card-kv__v">{{ record.createByName || '—' }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="record.remark" class="list-card-kv__row">
|
|
|
|
|
+ <text class="list-card-kv__k">备注</text>
|
|
|
|
|
+ <text class="list-card-kv__v">{{ record.remark }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="recordImageUrls.length" class="att-grid" style="margin-top: 8px">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="(url, idx) in recordImageUrls"
|
|
|
|
|
+ :key="'r' + url + idx"
|
|
|
|
|
+ class="att-img"
|
|
|
|
|
+ @click="previewRecordAt(idx)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <image :src="resolveUrl(url)" mode="aspectFill" />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view v-if="!readonly" class="bottom-action-bar">
|
|
|
|
|
+ <button
|
|
|
|
|
+ class="btn btn-ghost"
|
|
|
|
|
+ style="flex: 1"
|
|
|
|
|
+ :disabled="submitting || uploading"
|
|
|
|
|
+ @click="onSave"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ submitting ? '保存中…' : '保存' }}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ class="btn btn-primary"
|
|
|
|
|
+ style="flex: 1"
|
|
|
|
|
+ :disabled="submitting || uploading"
|
|
|
|
|
+ @click="onSaveAndSubmit"
|
|
|
|
|
+ >
|
|
|
|
|
+ 保存并提交
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-else-if="canCancel" class="bottom-action-bar">
|
|
|
|
|
+ <button class="btn btn-danger btn-block" :disabled="submitting" @click="onCancel">作废</button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <view v-if="showVehiclePicker" class="bx-sheet-mask" @click="closeVehiclePicker">
|
|
|
|
|
+ <view class="bx-sheet bx-sheet--tall" @click.stop>
|
|
|
|
|
+ <view class="bx-sheet__title">选择车牌</view>
|
|
|
|
|
+ <view class="bx-sheet__search">
|
|
|
|
|
+ <input
|
|
|
|
|
+ class="bx-sheet__search-input"
|
|
|
|
|
+ :value="vehicleKeyword"
|
|
|
|
|
+ placeholder="搜索车牌"
|
|
|
|
|
+ confirm-type="search"
|
|
|
|
|
+ @input="onVehicleKeywordInput"
|
|
|
|
|
+ @confirm="searchVehiclesNow"
|
|
|
|
|
+ />
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-if="vehicleKeyword.trim()"
|
|
|
|
|
+ class="bx-sheet__search-clear"
|
|
|
|
|
+ @click="clearVehicleKeyword"
|
|
|
|
|
+ >
|
|
|
|
|
+ ×
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <scroll-view scroll-y class="bx-sheet__list">
|
|
|
|
|
+ <view v-if="vehicleLoading" class="bx-sheet__empty">搜索中…</view>
|
|
|
|
|
+ <template v-else>
|
|
|
|
|
+ <view v-if="!vehicles.length" class="bx-sheet__empty">
|
|
|
|
|
+ {{ vehicleKeyword.trim() ? '无匹配车牌' : '暂无可用车辆' }}
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="item in vehicles"
|
|
|
|
|
+ :key="vehicleRowKey(item)"
|
|
|
|
|
+ class="bx-sheet__item"
|
|
|
|
|
+ :class="{ 'is-selected': vehiclePlate(item) === form.licensePlate }"
|
|
|
|
|
+ @click="confirmVehiclePick(item)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <view>
|
|
|
|
|
+ <text>{{ vehiclePlate(item) }}</text>
|
|
|
|
|
+ <view v-if="vehicleTypeTag(item)" class="bx-sheet__item-sub">
|
|
|
|
|
+ {{ vehicleTypeTag(item) }}
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <text v-if="vehiclePlate(item) === form.licensePlate">✓</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </scroll-view>
|
|
|
|
|
+ <view class="bx-sheet__footer">
|
|
|
|
|
+ <button class="btn btn-ghost btn-block" @click="closeVehiclePicker">取消</button>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { ref, reactive, computed } from 'vue'
|
|
|
|
|
+import { onLoad } from '@dcloudio/uni-app'
|
|
|
|
|
+import { PAGE_PATHS } from '@/config/api'
|
|
|
|
|
+import { resolveMinioUrl } from '@/config/env'
|
|
|
|
|
+import { vehicleRechargeApplyApi, fetchVehicleOptions } from '@/api/pl'
|
|
|
|
|
+import { chooseAndUploadImages, previewImages } from '@/utils/upload'
|
|
|
|
|
+import { useUserStore } from '@/stores/user'
|
|
|
|
|
+import { getSafeAreaStyle } from '@/utils/safeArea'
|
|
|
|
|
+
|
|
|
|
|
+const safeAreaStyle = getSafeAreaStyle()
|
|
|
|
|
+const userStore = useUserStore()
|
|
|
|
|
+
|
|
|
|
|
+const MAX_ATTACHMENTS = 5
|
|
|
|
|
+const typeOptions = [
|
|
|
|
|
+ { label: 'ETC', value: 'etc' },
|
|
|
|
|
+ { label: '加油', value: 'fuel' }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+const editId = ref('')
|
|
|
|
|
+const readonly = ref(false)
|
|
|
|
|
+const submitting = ref(false)
|
|
|
|
|
+const uploading = ref(false)
|
|
|
|
|
+const imageUrls = ref([])
|
|
|
|
|
+const detail = ref(null)
|
|
|
|
|
+const vehicles = ref([])
|
|
|
|
|
+const showVehiclePicker = ref(false)
|
|
|
|
|
+const vehicleKeyword = ref('')
|
|
|
|
|
+const vehicleLoading = ref(false)
|
|
|
|
|
+let vehicleSearchTimer = null
|
|
|
|
|
+let vehicleSearchSeq = 0
|
|
|
|
|
+
|
|
|
|
|
+const form = reactive({
|
|
|
|
|
+ applyNo: '',
|
|
|
|
|
+ applicantId: '',
|
|
|
|
|
+ licensePlate: '',
|
|
|
|
|
+ rechargeType: 'etc',
|
|
|
|
|
+ applyDate: '',
|
|
|
|
|
+ odometerKm: '',
|
|
|
|
|
+ remark: ''
|
|
|
|
|
+})
|
|
|
|
|
+const fieldErr = reactive({
|
|
|
|
|
+ applyDate: false,
|
|
|
|
|
+ licensePlate: false
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const isEdit = computed(() => !!editId.value)
|
|
|
|
|
+const pageTitle = computed(() => {
|
|
|
|
|
+ if (readonly.value) return '充值申请详情'
|
|
|
|
|
+ return isEdit.value ? '编辑充值申请' : '新增充值申请'
|
|
|
|
|
+})
|
|
|
|
|
+const formBadge = computed(() => {
|
|
|
|
|
+ if (readonly.value) return '只读'
|
|
|
|
|
+ return isEdit.value ? '编辑' : '新建'
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const applicantDisplay = computed(() => {
|
|
|
|
|
+ if (detail.value?.applicantName) return detail.value.applicantName
|
|
|
|
|
+ const info = userStore.userInfo || {}
|
|
|
|
|
+ return info.realName || info.nickName || info.username || '当前登录人'
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const currentUserId = computed(() => {
|
|
|
|
|
+ const info = userStore.userInfo || {}
|
|
|
|
|
+ return String(info.id || info.userId || '')
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const isOwn = computed(() => {
|
|
|
|
|
+ if (!editId.value) return true
|
|
|
|
|
+ return String(detail.value?.applicantId || '') === currentUserId.value
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const canCancel = computed(
|
|
|
|
|
+ () =>
|
|
|
|
|
+ isOwn.value &&
|
|
|
|
|
+ editId.value &&
|
|
|
|
|
+ ['draft', 'rejected'].includes(detail.value?.status)
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+const approvalNodes = computed(() => {
|
|
|
|
|
+ const d = detail.value
|
|
|
|
|
+ if (!d) return []
|
|
|
|
|
+ const flow = d.approvalFlow || d.approval_flow || []
|
|
|
|
|
+ const history = d.approvalHistory || d.approval_history || []
|
|
|
|
|
+ return [...flow, ...history]
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const record = computed(() => detail.value?.record || null)
|
|
|
|
|
+
|
|
|
|
|
+const recordImageUrls = computed(() => {
|
|
|
|
|
+ const urls = record.value?.attachmentUrls || record.value?.attachment_urls || ''
|
|
|
|
|
+ return String(urls)
|
|
|
|
|
+ .split(',')
|
|
|
|
|
+ .map((s) => s.trim())
|
|
|
|
|
+ .filter(Boolean)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+function vehiclePlate(v) {
|
|
|
|
|
+ return String(v?.licensePlate || v?.license_plate || '').trim()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function vehicleTypeTag(v) {
|
|
|
|
|
+ const type = v?.vehicleType || v?.vehicle_type
|
|
|
|
|
+ if (type === 'self') return '自有'
|
|
|
|
|
+ if (type === 'carrier') return '三方'
|
|
|
|
|
+ return ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function vehicleRowKey(v) {
|
|
|
|
|
+ return String(v?.id || vehiclePlate(v) || '')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function todayStr() {
|
|
|
|
|
+ const d = new Date()
|
|
|
|
|
+ const m = `${d.getMonth() + 1}`.padStart(2, '0')
|
|
|
|
|
+ const day = `${d.getDate()}`.padStart(2, '0')
|
|
|
|
|
+ return `${d.getFullYear()}-${m}-${day}`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function resolveUrl(url) {
|
|
|
|
|
+ return resolveMinioUrl(url)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function formatTime(v) {
|
|
|
|
|
+ if (!v) return '—'
|
|
|
|
|
+ return String(v).replace('T', ' ').slice(0, 19)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function approvalStatusLabel(v) {
|
|
|
|
|
+ const map = {
|
|
|
|
|
+ approved: '通过',
|
|
|
|
|
+ rejected: '驳回',
|
|
|
|
|
+ disbursed: '已发放',
|
|
|
|
|
+ pending: '待审批'
|
|
|
|
|
+ }
|
|
|
|
|
+ return map[v] || v || '—'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function goBack() {
|
|
|
|
|
+ uni.navigateBack({
|
|
|
|
|
+ fail: () => uni.redirectTo({ url: PAGE_PATHS.plVehicleRechargeApply })
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onPickType(value) {
|
|
|
|
|
+ if (readonly.value) return
|
|
|
|
|
+ form.rechargeType = value
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onDateChange(e) {
|
|
|
|
|
+ form.applyDate = e.detail.value
|
|
|
|
|
+ fieldErr.applyDate = false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onOdometerInput(e) {
|
|
|
|
|
+ form.odometerKm = e.detail.value
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onRemarkInput(e) {
|
|
|
|
|
+ form.remark = e.detail.value
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function loadVehicles(keyword = '') {
|
|
|
|
|
+ const seq = ++vehicleSearchSeq
|
|
|
|
|
+ vehicleLoading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const list = await fetchVehicleOptions(keyword, { mineVehicles: true })
|
|
|
|
|
+ if (seq !== vehicleSearchSeq) return
|
|
|
|
|
+ vehicles.value = Array.isArray(list) ? list : []
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ if (seq !== vehicleSearchSeq) return
|
|
|
|
|
+ vehicles.value = []
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ if (seq === vehicleSearchSeq) vehicleLoading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function openVehiclePicker() {
|
|
|
|
|
+ if (readonly.value) return
|
|
|
|
|
+ showVehiclePicker.value = true
|
|
|
|
|
+ vehicleKeyword.value = ''
|
|
|
|
|
+ loadVehicles('')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function closeVehiclePicker() {
|
|
|
|
|
+ showVehiclePicker.value = false
|
|
|
|
|
+ if (vehicleSearchTimer) {
|
|
|
|
|
+ clearTimeout(vehicleSearchTimer)
|
|
|
|
|
+ vehicleSearchTimer = null
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onVehicleKeywordInput(e) {
|
|
|
|
|
+ vehicleKeyword.value = e.detail.value || ''
|
|
|
|
|
+ if (vehicleSearchTimer) clearTimeout(vehicleSearchTimer)
|
|
|
|
|
+ vehicleSearchTimer = setTimeout(() => {
|
|
|
|
|
+ loadVehicles(vehicleKeyword.value)
|
|
|
|
|
+ }, 300)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function searchVehiclesNow() {
|
|
|
|
|
+ if (vehicleSearchTimer) {
|
|
|
|
|
+ clearTimeout(vehicleSearchTimer)
|
|
|
|
|
+ vehicleSearchTimer = null
|
|
|
|
|
+ }
|
|
|
|
|
+ loadVehicles(vehicleKeyword.value)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function clearVehicleKeyword() {
|
|
|
|
|
+ vehicleKeyword.value = ''
|
|
|
|
|
+ if (vehicleSearchTimer) {
|
|
|
|
|
+ clearTimeout(vehicleSearchTimer)
|
|
|
|
|
+ vehicleSearchTimer = null
|
|
|
|
|
+ }
|
|
|
|
|
+ loadVehicles('')
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function confirmVehiclePick(item) {
|
|
|
|
|
+ const plate = vehiclePlate(item)
|
|
|
|
|
+ if (!plate) return
|
|
|
|
|
+ form.licensePlate = plate
|
|
|
|
|
+ fieldErr.licensePlate = false
|
|
|
|
|
+ closeVehiclePicker()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function chooseAttachments() {
|
|
|
|
|
+ uploading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const urls = await chooseAndUploadImages({
|
|
|
|
|
+ folder: 'pl/vehicle-recharge/apply',
|
|
|
|
|
+ maxCount: MAX_ATTACHMENTS,
|
|
|
|
|
+ currentUrls: imageUrls.value
|
|
|
|
|
+ })
|
|
|
|
|
+ if (urls.length) imageUrls.value = urls
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ uploading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function removeImage(idx) {
|
|
|
|
|
+ imageUrls.value = imageUrls.value.filter((_, i) => i !== idx)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function previewAt(idx) {
|
|
|
|
|
+ previewImages(
|
|
|
|
|
+ imageUrls.value.map((u) => resolveUrl(u)),
|
|
|
|
|
+ idx
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function previewRecordAt(idx) {
|
|
|
|
|
+ previewImages(
|
|
|
|
|
+ recordImageUrls.value.map((u) => resolveUrl(u)),
|
|
|
|
|
+ idx
|
|
|
|
|
+ )
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function validate() {
|
|
|
|
|
+ fieldErr.applyDate = !form.applyDate
|
|
|
|
|
+ fieldErr.licensePlate = !form.licensePlate
|
|
|
|
|
+ if (fieldErr.applyDate || fieldErr.licensePlate) {
|
|
|
|
|
+ uni.showToast({ title: '请完善必填项', icon: 'none' })
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!form.rechargeType) {
|
|
|
|
|
+ uni.showToast({ title: '请选择充值类型', icon: 'none' })
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ return true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function buildPayload() {
|
|
|
|
|
+ const odo = form.odometerKm === '' || form.odometerKm == null ? null : Number(form.odometerKm)
|
|
|
|
|
+ return {
|
|
|
|
|
+ licensePlate: form.licensePlate,
|
|
|
|
|
+ rechargeType: form.rechargeType,
|
|
|
|
|
+ applyDate: form.applyDate,
|
|
|
|
|
+ odometerKm: Number.isNaN(odo) ? null : odo,
|
|
|
|
|
+ attachmentUrls: imageUrls.value.join(',') || null,
|
|
|
|
|
+ remark: form.remark || null,
|
|
|
|
|
+ applicantId: currentUserId.value || null
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function fillFromDetail(data) {
|
|
|
|
|
+ detail.value = data
|
|
|
|
|
+ form.applyNo = data.applyNo || ''
|
|
|
|
|
+ form.applicantId = data.applicantId || ''
|
|
|
|
|
+ form.licensePlate = data.licensePlate || ''
|
|
|
|
|
+ form.rechargeType = data.rechargeType || 'etc'
|
|
|
|
|
+ form.applyDate = data.applyDate || ''
|
|
|
|
|
+ form.odometerKm =
|
|
|
|
|
+ data.odometerKm != null && data.odometerKm !== '' ? String(data.odometerKm) : ''
|
|
|
|
|
+ form.remark = data.remark || ''
|
|
|
|
|
+ imageUrls.value = String(data.attachmentUrls || '')
|
|
|
|
|
+ .split(',')
|
|
|
|
|
+ .map((s) => s.trim())
|
|
|
|
|
+ .filter(Boolean)
|
|
|
|
|
+ const own = String(data.applicantId || '') === currentUserId.value
|
|
|
|
|
+ const editable = ['draft', 'rejected'].includes(data.status)
|
|
|
|
|
+ readonly.value = !(own && editable)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function onSave() {
|
|
|
|
|
+ if (!validate() || submitting.value) return
|
|
|
|
|
+ submitting.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const payload = buildPayload()
|
|
|
|
|
+ let saved
|
|
|
|
|
+ if (editId.value) {
|
|
|
|
|
+ saved = await vehicleRechargeApplyApi.update(editId.value, payload)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ saved = await vehicleRechargeApplyApi.create(payload)
|
|
|
|
|
+ editId.value = saved.id
|
|
|
|
|
+ }
|
|
|
|
|
+ fillFromDetail(saved)
|
|
|
|
|
+ uni.showToast({ title: '已保存', icon: 'success' })
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ // toasted
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ submitting.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function onSaveAndSubmit() {
|
|
|
|
|
+ if (!validate() || submitting.value) return
|
|
|
|
|
+ submitting.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const payload = buildPayload()
|
|
|
|
|
+ let saved
|
|
|
|
|
+ if (editId.value) {
|
|
|
|
|
+ saved = await vehicleRechargeApplyApi.update(editId.value, payload)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ saved = await vehicleRechargeApplyApi.create(payload)
|
|
|
|
|
+ editId.value = saved.id
|
|
|
|
|
+ }
|
|
|
|
|
+ await vehicleRechargeApplyApi.submit(editId.value)
|
|
|
|
|
+ const latest = await vehicleRechargeApplyApi.get(editId.value)
|
|
|
|
|
+ fillFromDetail(latest)
|
|
|
|
|
+ uni.showToast({ title: '已提交', icon: 'success' })
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ // toasted
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ submitting.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function onCancel() {
|
|
|
|
|
+ uni.showModal({
|
|
|
|
|
+ title: '作废确认',
|
|
|
|
|
+ content: '确定作废该充值申请吗?',
|
|
|
|
|
+ confirmText: '作废',
|
|
|
|
|
+ confirmColor: '#ef4444',
|
|
|
|
|
+ success: async (res) => {
|
|
|
|
|
+ if (!res.confirm) return
|
|
|
|
|
+ submitting.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ await vehicleRechargeApplyApi.cancel(editId.value)
|
|
|
|
|
+ uni.showToast({ title: '已作废', icon: 'success' })
|
|
|
|
|
+ setTimeout(goBack, 500)
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ // toasted
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ submitting.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function initNew() {
|
|
|
|
|
+ const info = userStore.userInfo || {}
|
|
|
|
|
+ form.applicantId = String(info.id || info.userId || '')
|
|
|
|
|
+ form.applyDate = todayStr()
|
|
|
|
|
+ form.rechargeType = 'etc'
|
|
|
|
|
+ readonly.value = false
|
|
|
|
|
+ await loadVehicles('')
|
|
|
|
|
+ if (vehicles.value.length === 1) {
|
|
|
|
|
+ form.licensePlate = vehiclePlate(vehicles.value[0])
|
|
|
|
|
+ } else if (!vehicles.value.length) {
|
|
|
|
|
+ uni.showToast({
|
|
|
|
|
+ title: '暂无可用车辆,请联系管理员绑定',
|
|
|
|
|
+ icon: 'none',
|
|
|
|
|
+ duration: 2500
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function loadDetail(id) {
|
|
|
|
|
+ const data = await vehicleRechargeApplyApi.get(id)
|
|
|
|
|
+ fillFromDetail(data)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onLoad(async (query) => {
|
|
|
|
|
+ const id = query?.id || ''
|
|
|
|
|
+ if (id) {
|
|
|
|
|
+ editId.value = id
|
|
|
|
|
+ try {
|
|
|
|
|
+ await loadDetail(id)
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ uni.showToast({ title: '加载失败', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ await initNew()
|
|
|
|
|
+ }
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss">
|
|
|
|
|
+@import '@/styles/bx-theme.scss';
|
|
|
|
|
+</style>
|