<!--
|
报销明细单条编辑(底部弹层)
|
-->
|
<template>
|
<up-popup :show="show"
|
mode="bottom"
|
round="16"
|
:safe-area-inset-bottom="true"
|
@close="close">
|
<view class="detail-sheet">
|
<view class="sheet-handle" />
|
<view class="sheet-head">
|
<text class="sheet-cancel"
|
@click="close">取消</text>
|
<text class="sheet-title">{{ title }}</text>
|
<text class="sheet-confirm"
|
@click="confirm">保存</text>
|
</view>
|
|
<scroll-view scroll-y
|
class="sheet-body"
|
:show-scrollbar="false">
|
<view class="sheet-group">
|
<view class="sheet-cell sheet-cell--tap"
|
@click="showDatePicker = true">
|
<text class="sheet-label required">发票日期</text>
|
<view class="sheet-value-wrap">
|
<text class="sheet-value"
|
:class="{ placeholder: !draft.invoiceDate }">
|
{{ draft.invoiceDate || '请选择' }}
|
</text>
|
<up-icon name="calendar"
|
size="18"
|
color="#c0c4cc" />
|
</view>
|
</view>
|
<view class="sheet-cell sheet-cell--tap"
|
@click="showSubjectSheet = true">
|
<text class="sheet-label required">费用科目</text>
|
<view class="sheet-value-wrap">
|
<text class="sheet-value"
|
:class="{ placeholder: !draft.expenseSubject }">
|
{{ subjectText }}
|
</text>
|
<up-icon name="arrow-right"
|
size="14"
|
color="#c0c4cc" />
|
</view>
|
</view>
|
<view class="sheet-cell">
|
<text class="sheet-label required">金额</text>
|
<view class="sheet-input-wrap">
|
<up-input v-model="draft.amount"
|
type="digit"
|
placeholder="0.00"
|
border="none"
|
input-align="right" />
|
<text class="sheet-unit">元</text>
|
</view>
|
</view>
|
<view class="sheet-cell sheet-cell--col">
|
<text class="sheet-label">描述</text>
|
<view class="sheet-textarea-wrap">
|
<up-textarea v-model="draft.description"
|
placeholder="费用说明(选填)"
|
maxlength="200"
|
border="none"
|
height="64" />
|
</view>
|
</view>
|
</view>
|
|
<view v-if="showDelete"
|
class="sheet-delete"
|
@click="emit('delete')">
|
删除本条明细
|
</view>
|
</scroll-view>
|
</view>
|
|
<up-action-sheet :show="showSubjectSheet"
|
title="费用科目"
|
:actions="subjectActions"
|
@select="onSubjectSelect"
|
@close="showSubjectSheet = false" />
|
|
<up-popup :show="showDatePicker"
|
mode="bottom"
|
round="16"
|
@close="showDatePicker = false">
|
<up-datetime-picker :show="true"
|
v-model="datePickerTs"
|
mode="date"
|
@confirm="onDateConfirm"
|
@cancel="showDatePicker = false" />
|
</up-popup>
|
</up-popup>
|
</template>
|
|
<script setup>
|
import { computed, reactive, ref, watch } from "vue";
|
import { parseTime } from "@/utils/ruoyi";
|
import { expenseSubjectLabel as costSubjectLabel } from "../_utils/costReimburseUtils.js";
|
import { expenseSubjectLabel as travelSubjectLabel } from "../_utils/travelReimburseUtils.js";
|
|
const props = defineProps({
|
show: { type: Boolean, default: false },
|
modelValue: { type: Object, default: () => ({}) },
|
index: { type: Number, default: 0 },
|
isTravel: { type: Boolean, default: true },
|
subjectOptions: { type: Array, default: () => [] },
|
showDelete: { type: Boolean, default: true },
|
});
|
|
const emit = defineEmits(["update:show", "update:modelValue", "confirm", "delete"]);
|
|
const draft = reactive({
|
invoiceDate: "",
|
expenseSubject: "",
|
amount: "",
|
description: "",
|
});
|
|
const showDatePicker = ref(false);
|
const showSubjectSheet = ref(false);
|
const datePickerTs = ref(Date.now());
|
|
const title = computed(() => `明细 ${props.index + 1}`);
|
|
const subjectActions = computed(() =>
|
(props.subjectOptions || []).map(x => ({ name: x.label, value: x.value }))
|
);
|
|
const subjectText = computed(() => resolveSubjectLabel(draft.expenseSubject));
|
|
function resolveSubjectLabel(v) {
|
if (!v) return "请选择";
|
const labelFn = props.isTravel ? travelSubjectLabel : costSubjectLabel;
|
const t = labelFn(v);
|
if (t && t !== "—") return t;
|
const hit = (props.subjectOptions || []).find(x => x.value === v || x.label === v);
|
return hit?.label || v;
|
}
|
|
watch(
|
() => props.show,
|
v => {
|
if (v && props.modelValue) {
|
Object.assign(draft, {
|
invoiceDate: "",
|
expenseSubject: "",
|
amount: "",
|
description: "",
|
...JSON.parse(JSON.stringify(props.modelValue)),
|
});
|
}
|
}
|
);
|
|
function close() {
|
emit("update:show", false);
|
}
|
|
function confirm() {
|
if (!draft.invoiceDate) {
|
uni.showToast({ title: "请选择发票日期", icon: "none" });
|
return;
|
}
|
if (!draft.expenseSubject) {
|
uni.showToast({ title: "请选择费用科目", icon: "none" });
|
return;
|
}
|
if (draft.amount === "" || draft.amount == null) {
|
uni.showToast({ title: "请填写金额", icon: "none" });
|
return;
|
}
|
emit("update:modelValue", { ...draft });
|
emit("confirm", { ...draft });
|
emit("update:show", false);
|
}
|
|
function onSubjectSelect(action) {
|
draft.expenseSubject = action.value;
|
showSubjectSheet.value = false;
|
}
|
|
function onDateConfirm(e) {
|
const ts = e?.value ?? datePickerTs.value;
|
draft.invoiceDate = parseTime(ts, "{y}-{m}-{d}");
|
showDatePicker.value = false;
|
}
|
</script>
|
|
<style scoped lang="scss">
|
.detail-sheet {
|
background: #fff;
|
border-radius: 16px 16px 0 0;
|
max-height: 85vh;
|
display: flex;
|
flex-direction: column;
|
}
|
.sheet-handle {
|
width: 36px;
|
height: 4px;
|
background: #e4e7ed;
|
border-radius: 2px;
|
margin: 8px auto 4px;
|
}
|
.sheet-head {
|
display: flex;
|
align-items: center;
|
padding: 8px 16px 12px;
|
border-bottom: 1px solid #f0f2f5;
|
}
|
.sheet-cancel {
|
font-size: 15px;
|
color: #909399;
|
min-width: 48px;
|
}
|
.sheet-title {
|
flex: 1;
|
text-align: center;
|
font-size: 16px;
|
font-weight: 600;
|
color: #303133;
|
}
|
.sheet-confirm {
|
font-size: 15px;
|
color: #2979ff;
|
font-weight: 600;
|
min-width: 48px;
|
text-align: right;
|
}
|
.sheet-body {
|
max-height: 70vh;
|
padding-bottom: env(safe-area-inset-bottom);
|
}
|
.sheet-group {
|
margin: 12px 16px;
|
background: #f8f9fb;
|
border-radius: 12px;
|
overflow: hidden;
|
}
|
.sheet-cell {
|
display: flex;
|
align-items: center;
|
min-height: 52px;
|
padding: 12px 14px;
|
background: #fff;
|
border-bottom: 1px solid #f5f6f8;
|
&--col {
|
flex-direction: column;
|
align-items: stretch;
|
}
|
&--tap:active {
|
background: #fafbfc;
|
}
|
&:last-child {
|
border-bottom: none;
|
}
|
}
|
.sheet-label {
|
width: 80px;
|
font-size: 15px;
|
color: #303133;
|
flex-shrink: 0;
|
&.required::before {
|
content: "*";
|
color: #f56c6c;
|
margin-right: 2px;
|
}
|
}
|
.sheet-value-wrap {
|
flex: 1;
|
display: flex;
|
align-items: center;
|
justify-content: flex-end;
|
gap: 4px;
|
}
|
.sheet-value {
|
font-size: 15px;
|
color: #303133;
|
&.placeholder {
|
color: #c0c4cc;
|
}
|
}
|
.sheet-input-wrap {
|
flex: 1;
|
display: flex;
|
align-items: center;
|
justify-content: flex-end;
|
}
|
.sheet-unit {
|
font-size: 14px;
|
color: #909399;
|
margin-left: 4px;
|
}
|
.sheet-textarea-wrap {
|
width: 100%;
|
margin-top: 8px;
|
background: #f5f7fa;
|
border-radius: 8px;
|
padding: 4px 8px;
|
}
|
.sheet-delete {
|
margin: 16px;
|
text-align: center;
|
font-size: 15px;
|
color: #f56c6c;
|
padding: 14px;
|
background: #fff;
|
border-radius: 12px;
|
border: 1px solid #fde2e2;
|
}
|
</style>
|