Files
im-uniapp/pages/workbench/profit/cost.vue

850 lines
22 KiB
Vue
Raw Normal View History

<template>
<view class="container">
<!-- 项目基本信息展示 -->
<view class="project-header" v-if="this.projectId != 0">
<view class="project-info-item">
<text class="info-label">项目名称</text>
<text class="info-value">{{ currentProject.projectName || '未知项目' }}</text>
</view>
<view class="project-info-item">
<text class="info-label">项目编号</text>
<text class="info-value">{{ currentProject.projectNo || '无编号' }}</text>
</view>
<view class="project-info-item">
<text class="info-label">项目总金额</text>
<text class="info-value">¥ {{ formatCurrency(currentProject.funds || 0) }}</text>
</view>
</view>
<view class="project-header" v-if="this.projectId == 0">
<view class="project-info-item">
<text class="info-value">其他收支</text>
</view>
</view>
<!-- 新增按钮 -->
<view class="btn-add-container">
<uni-button type="primary" @click="openAddPopup">
<uni-icons type="plus" size="18" class="btn-icon"></uni-icons>
新增财务记录
</uni-button>
</view>
<!-- 财务明细列表 -->
<view class="finance-list-container">
<view class="list-title">财务明细记录</view>
<uni-list v-if="finances.length > 0" border>
<uni-list-item
v-for="(item, index) in finances"
:key="item.financeId"
:title="item.financeTitle"
:note="`${item.financeType === '1' ? '出账' : '入账'} · ${formatDate(item.financeTime)} · 共${item.detailList.length || 0}条明细`"
showArrow
:clickable="true"
@click="openDetailPopup(item)"
>
<template v-slot:extra>
<view class="amount-tag" :class="item.financeType === '1' ? 'expense' : 'income'">
{{ item.financeType === '1' ? '-' : '+' }}¥{{ calculateFinanceTotal(item.detailList) }}
</view>
</template>
</uni-list-item>
</uni-list>
<view v-else class="empty-list">
<uni-icons type="empty" size="60" color="#ccc"></uni-icons>
<text class="empty-text">暂无财务记录</text>
</view>
</view>
<!-- 新增财务记录弹窗 -->
<uni-popup ref="addPopup" type="bottom" :style="{ width: '100%' }">
<view class="popup-content">
<view class="popup-title">{{ form.financeId ? '编辑财务记录' : '新增财务记录' }}</view>
<uni-forms ref="financeForm" :model="form" :rules="rules" label-width="150rpx">
<uni-forms-item label="财务类型" name="financeType">
<uni-data-checkbox mode="tag" v-model="form.financeType" :localdata="financeTypeOption"></uni-data-checkbox>
</uni-forms-item>
<uni-forms-item label="记录名称" name="financeTitle">
<uni-easyinput
v-model="form.financeTitle"
placeholder="请输入记录名称"
clearable
></uni-easyinput>
</uni-forms-item>
<uni-forms-item label="交易时间" name="financeTime">
<uni-datetime-picker
v-model="form.financeTime"
type="datetime"
placeholder="请选择交易时间"
:start="minDate"
></uni-datetime-picker>
</uni-forms-item>
<uni-forms-item label="交易方" name="financeParties">
<uni-easyinput
v-model="form.financeParties"
placeholder="请输入交易方"
clearable
></uni-easyinput>
</uni-forms-item>
<uni-forms-item label="支付类型" name="payType">
<oa-dict-select v-model="form.payType" dict-type='sys_pay_type'></oa-dict-select>
</uni-forms-item>
<!-- 明细列表区域替换uni-table -->
<view class="detail-section">
<view class="detail-header">
<text class="detail-title">明细列表</text>
<uni-button type="primary" size="mini" @click="addDetail">
<uni-icons type="plus" size="16"></uni-icons>添加明细
</uni-button>
</view>
<!-- 明细标题行 -->
<view class="detail-row header-row">
<view class="detail-col index-col">序号</view>
<view class="detail-col name-col">明细名称</view>
<view class="detail-col price-col">金额()</view>
<view class="detail-col action-col">操作</view>
</view>
<!-- 明细内容行 -->
<view
class="detail-row content-row"
v-for="(item, index) in form.detailList"
:key="item.detailId"
>
<view class="detail-col index-col">{{ index + 1 }}</view>
<view class="detail-col name-col">
<uni-easyinput
v-model="item.detailTitle"
placeholder="请输入明细名称"
class="detail-input"
></uni-easyinput>
</view>
<view class="detail-col price-col">
<uni-easyinput
v-model="item.price"
placeholder="0.00"
type="number"
class="detail-input"
@input="calculateTotal"
></uni-easyinput>
</view>
<view class="detail-col action-col">
<uni-button
type="warn"
size="mini"
plain
@click="removeDetail(index)"
v-if="form.detailList.length > 1"
>
<uni-icons type="trash" size="16"></uni-icons>
</uni-button>
</view>
</view>
<!-- 合计金额 -->
<view class="total-amount" v-if="form.detailList.length > 0">
<text class="total-label">合计金额</text>
<text class="total-value" :class="form.financeType === '1' ? 'text-expense' : 'text-income'">
¥{{ formatCurrency(totalAmount) }}
</text>
</view>
</view>
<uni-forms-item label="备注" name="remark">
<uni-easyinput
v-model="form.remark"
placeholder="请输入备注信息"
type="textarea"
rows="3"
></uni-easyinput>
</uni-forms-item>
</uni-forms>
<view class="popup-btn-group">
<uni-button type="default" @click="closeAddPopup">取消</uni-button>
<uni-button type="primary" @click="submitFinanceForm">确认提交</uni-button>
</view>
</view>
</uni-popup>
<uni-popup ref="detailPopup" type="bottom" :style="{ width: '100%' }">
<view class="popup-content">
<view class="popup-title">财务记录详情</view>
<view class="detail-info">
<view class="detail-item">
<text class="detail-label">记录名称</text>
<text class="detail-value">{{ currentFinance.financeTitle || '-' }}</text>
</view>
<view class="detail-item">
<text class="detail-label">记录类型</text>
<text class="detail-value">{{ currentFinance.financeType === '1' ? '出账' : '入账' }}</text>
</view>
<view class="detail-item">
<text class="detail-label">交易时间</text>
<text class="detail-value">{{ formatDate(currentFinance.financeTime) || '-' }}</text>
</view>
<view class="detail-item">
<text class="detail-label">交易方</text>
<text class="detail-value">{{ currentFinance.financeParties || '-' }}</text>
</view>
<view class="detail-item">
<text class="detail-label">支付类型</text>
<text class="detail-value">{{ getPayTypeName(currentFinance.payType) || '-' }}</text>
</view>
<view class="detail-item">
<text class="detail-label">总金额</text>
<text class="detail-value" :class="currentFinance.financeType === '1' ? 'text-expense' : 'text-income'">
{{ currentFinance.financeType === '1' ? '-' : '+' }}¥{{ calculateFinanceTotal(currentFinance.detailList) }}
</text>
</view>
<!-- 明细列表展示替换uni-table -->
<view class="detail-section-view">
<view class="detail-header-view">
<text class="detail-title-view">明细列表 ({{ currentFinance.detailList.length || 0 }})</text>
</view>
<!-- 明细标题行 -->
<view class="detail-row header-row">
<view class="detail-col index-col">序号</view>
<view class="detail-col name-col">明细名称</view>
<view class="detail-col price-col">金额()</view>
</view>
<!-- 明细内容行 -->
<view
class="detail-row content-row"
v-for="(item, index) in currentFinance.detailList"
:key="index"
>
<view class="detail-col index-col">{{ index + 1 }}</view>
<view class="detail-col name-col">{{ item.detailTitle || '-' }}</view>
<view class="detail-col price-col">¥{{ formatCurrency(item.price || 0) }}</view>
</view>
<view v-if="!currentFinance.detailList || currentFinance.detailList.length === 0" class="empty-detail">
<text>无明细记录</text>
</view>
</view>
<view class="detail-item">
<text class="detail-label">备注</text>
<text class="detail-value">{{ currentFinance.remark || '无' }}</text>
</view>
<view class="detail-item">
<text class="detail-label">创建人</text>
<text class="detail-value">{{ currentFinance.createBy || '-' }}</text>
</view>
<view class="detail-item">
<text class="detail-label">创建时间</text>
<text class="detail-value">{{ formatDate(currentFinance.createTime) || '-' }}</text>
</view>
</view>
<view class="popup-btn-group">
<uni-button type="default" @click="closeDetailPopup">关闭</uni-button>
<uni-button type="primary" @click="handleEdit">编辑</uni-button>
<uni-button type="warn" @click="handleDelete">删除</uni-button>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import { getProject } from '@/api/oa/project';
import {
addFinance,
delFinance,
listFinance,
getFinance,
updateFinance
} from "@/api/oa/finance/finance.js";
export default {
data() {
return {
projectId: '',
currentProject: {},
finances: [],
currentFinance: {
detailList: []
},
financeTypeOption: [
{ value: "1", text: '出账' },
{ value: '2', text: '入账' }
],
loading: false,
// 表单数据 - 默认包含一条空明细
form: {
financeId: '',
projectId: '',
financeType: '1', // 1-出账 2-入账
financeTitle: '',
financeTime: '',
financeParties: '',
payType: '',
remark: '',
detailList: [] // 明细列表将在打开弹窗时初始化
},
// 合计金额(前端计算)
totalAmount: 0,
// 表单验证规则
rules: {
financeType: [{ required: true, message: '请选择记录类型', trigger: 'change' }],
financeTitle: [{ required: true, message: '请输入记录名称', trigger: 'blur' }],
financeTime: [{ required: true, message: '请选择交易时间', trigger: 'change' }],
financeParties: [{ required: true, message: '请输入交易方', trigger: 'blur' }],
payType: [{ required: true, message: '请选择支付类型', trigger: 'change' }]
},
// 支付类型选项
payTypes: [
{ value: '1', label: '微信支付' },
{ value: '2', label: '支付宝' },
{ value: '3', label: '银行转账' },
{ value: '4', label: '现金' },
{ value: '5', label: '其他' }
],
// 最小日期限制
minDate: ''
};
},
onLoad(options) {
// 获取项目ID
this.projectId = options.id;
if (!this.projectId && this.projectId !== '0') {
uni.showToast({ title: '项目ID不存在', icon: 'none' });
return;
}
// 初始化最小日期3年前
const now = new Date();
this.minDate = new Date(now.getFullYear() - 3, now.getMonth(), now.getDate()).getTime();
// 加载项目信息和财务记录
this.loadProjectInfo();
this.loadFinanceList();
},
methods: {
// 加载项目信息
loadProjectInfo() {
this.loading = true;
getProject(this.projectId).then(res => {
if (res.code === 200) {
this.currentProject = res.data;
} else {
uni.showToast({ title: res.msg || '获取项目信息失败', icon: 'none' });
}
}).catch(err => {
console.error('获取项目信息错误:', err);
uni.showToast({ title: '获取项目信息失败', icon: 'none' });
}).finally(() => {
this.loading = false;
});
},
// 加载财务记录列表
loadFinanceList() {
this.loading = true;
listFinance({
projectId: this.projectId,
pageNum: 1,
pageSize: 100
}).then(res => {
if (res.code === 200) {
this.finances = res.rows || [];
} else {
uni.showToast({ title: res.msg || '获取财务记录失败', icon: 'none' });
}
}).catch(err => {
console.error('获取财务记录错误:', err);
uni.showToast({ title: '获取财务记录失败', icon: 'none' });
}).finally(() => {
this.loading = false;
});
},
// 添加明细
addDetail() {
this.form.detailList.push({
detailId: Date.now(), // 临时ID
detailTitle: '',
price: ''
});
},
// 移除明细
removeDetail(index) {
// 确保至少保留一条明细
if (this.form.detailList.length <= 1) {
uni.showToast({ title: '至少保留一条明细', icon: 'none' });
return;
}
uni.showModal({
title: '确认删除',
content: '确定要删除这条明细吗?',
success: (res) => {
if (res.confirm) {
this.form.detailList.splice(index, 1);
this.calculateTotal();
}
}
});
},
// 计算表单总金额
calculateTotal() {
this.totalAmount = this.form.detailList.reduce((sum, item) => {
const price = Number(item.price) || 0;
return sum + price;
}, 0);
},
// 计算财务记录总金额(通用方法)
calculateFinanceTotal(detailList) {
if (!detailList || !detailList.length) return '0.00';
const total = detailList.reduce((sum, item) => {
const price = Number(item.price) || 0;
return sum + price;
}, 0);
return this.formatCurrency(total);
},
// 打开新增弹窗 - 默认添加一条空明细
openAddPopup() {
// 重置表单并默认添加一条空明细
this.form = {
financeId: '',
projectId: this.projectId,
financeType: '1',
financeTitle: '',
financeTime: new Date().toISOString().slice(0, 16),
financeParties: '',
payType: '',
remark: '',
detailList: [
{
detailId: Date.now(),
detailTitle: '',
price: ''
}
]
};
this.totalAmount = 0;
this.$refs.addPopup.open();
},
// 关闭新增弹窗
closeAddPopup() {
this.$refs.addPopup.close();
},
// 打开详情弹窗
openDetailPopup(item) {
this.currentFinance = { ...item };
this.$refs.detailPopup.open();
},
// 关闭详情弹窗
closeDetailPopup() {
this.$refs.detailPopup.close();
},
// 提交表单
submitFinanceForm() {
// 验证每条明细
const validDetail = this.form.detailList.every(item => {
return item.detailTitle && item.price && !isNaN(Number(item.price));
});
if (!validDetail) {
uni.showToast({ title: '请完善所有明细的名称和金额', icon: 'none' });
return;
}
this.$refs.financeForm.validate().then(() => {
this.loading = true;
const params = { ...this.form };
const submitApi = params.financeId ? updateFinance : addFinance;
submitApi(params).then(res => {
if (res.code === 200) {
uni.showToast({ title: params.financeId ? '修改成功' : '新增成功', icon: 'success' });
this.closeAddPopup();
this.loadFinanceList();
} else {
uni.showToast({ title: res.msg || '操作失败', icon: 'none' });
}
}).catch(err => {
console.error('提交财务记录错误:', err);
uni.showToast({ title: '操作失败', icon: 'none' });
}).finally(() => {
this.loading = false;
});
}).catch(err => {
console.log('表单验证失败:', err);
});
},
// 编辑记录
handleEdit() {
this.closeDetailPopup();
// 确保编辑时有至少一条明细
const detailList = this.currentFinance.detailList && this.currentFinance.detailList.length
? [...this.currentFinance.detailList]
: [{ detailId: Date.now(), detailTitle: '', price: '' }];
this.form = {
financeId: this.currentFinance.financeId,
projectId: this.projectId,
financeType: this.currentFinance.financeType,
financeTitle: this.currentFinance.financeTitle,
financeTime: this.currentFinance.financeTime ? this.formatDateTimeForInput(this.currentFinance.financeTime) : '',
financeParties: this.currentFinance.financeParties,
payType: this.currentFinance.payType,
remark: this.currentFinance.remark,
detailList: detailList
};
this.calculateTotal();
this.$refs.addPopup.open();
},
// 删除记录
handleDelete() {
uni.showModal({
title: '确认删除',
content: '确定要删除这条财务记录吗?',
success: (res) => {
if (res.confirm) {
this.loading = true;
delFinance(this.currentFinance.financeId).then(res => {
if (res.code === 200) {
uni.showToast({ title: '删除成功', icon: 'success' });
this.closeDetailPopup();
this.loadFinanceList();
} else {
uni.showToast({ title: res.msg || '删除失败', icon: 'none' });
}
}).catch(err => {
console.error('删除财务记录错误:', err);
uni.showToast({ title: '删除失败', icon: 'none' });
}).finally(() => {
this.loading = false;
});
}
}
});
},
// 格式化金额
formatCurrency(value) {
return parseFloat(value || 0).toFixed(2);
},
// 格式化日期
formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
},
// 格式化日期时间(用于输入框)
formatDateTimeForInput(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr);
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
},
// 获取支付类型名称
getPayTypeName(value) {
const type = this.payTypes.find(item => item.value === value);
return type ? type.label : '';
}
}
};
</script>
<style scoped>
.container {
padding: 16rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.project-header {
background-color: #fff;
border-radius: 8rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.project-info-item {
display: flex;
padding: 12rpx 0;
border-bottom: 1px solid #f5f5f5;
}
.project-info-item:last-child {
border-bottom: none;
}
.info-label {
color: #666;
width: 200rpx;
}
.info-value {
color: #333;
flex: 1;
}
.btn-add-container {
margin-bottom: 20rpx;
}
.btn-icon {
margin-right: 8rpx;
}
.finance-list-container {
background-color: #fff;
border-radius: 8rpx;
padding: 20rpx;
}
.list-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
margin-bottom: 20rpx;
padding-bottom: 10rpx;
border-bottom: 1px solid #f5f5f5;
}
.empty-list {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 0;
}
.empty-text {
color: #999;
font-size: 28rpx;
margin-top: 20rpx;
}
.amount-tag {
padding: 4rpx 12rpx;
border-radius: 16rpx;
font-size: 24rpx;
}
.amount-tag.income {
background-color: #e8f4e9;
color: #2e7d32;
}
.amount-tag.expense {
background-color: #ffebee;
color: #c62828;
}
.popup-content {
background-color: #fff;
border-radius: 16rpx 16rpx 0 0;
padding: 30rpx 20rpx;
max-height: 90vh;
overflow-y: auto;
}
.popup-title {
font-size: 36rpx;
font-weight: bold;
text-align: center;
margin-bottom: 30rpx;
padding-bottom: 20rpx;
border-bottom: 1px solid #f5f5f5;
}
.popup-btn-group {
display: flex;
justify-content: flex-end;
gap: 16rpx;
margin-top: 30rpx;
padding-top: 20rpx;
border-top: 1px solid #f5f5f5;
}
.detail-info {
margin-top: 10rpx;
}
.detail-item {
display: flex;
padding: 16rpx 0;
border-bottom: 1px solid #f5f5f5;
}
.detail-item:last-child {
border-bottom: none;
}
.detail-label {
color: #666;
width: 180rpx;
}
.detail-value {
color: #333;
flex: 1;
word-break: break-all;
}
.text-income {
color: #2e7d32;
}
.text-expense {
color: #c62828;
}
/* 明细区域样式替换table */
.detail-section {
margin: 30rpx 0;
padding: 20rpx;
background-color: #f9f9f9;
border-radius: 8rpx;
}
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.detail-title {
font-size: 28rpx;
font-weight: bold;
color: #333;
}
/* 明细行样式 */
.detail-row {
display: flex;
width: 100%;
padding: 12rpx 0;
}
.header-row {
background-color: #f0f0f0;
font-weight: bold;
border-radius: 4rpx 4rpx 0 0;
}
.content-row {
border-bottom: 1px solid #f0f0f0;
align-items: center;
}
.content-row:last-child {
border-bottom: none;
}
/* 明细列样式 */
.detail-col {
display: flex;
justify-content: center;
align-items: center;
}
.index-col {
width: 10%;
font-size: 26rpx;
}
.name-col {
width: 40%;
padding: 0 10rpx;
}
.price-col {
width: 30%;
padding: 0 10rpx;
}
.action-col {
width: 20%;
}
/* 明细输入框样式 */
.detail-input {
width: 100%;
}
.empty-detail {
text-align: center;
color: #999;
padding: 40rpx 0;
font-size: 26rpx;
}
.total-amount {
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 20rpx;
padding-top: 10rpx;
border-top: 1px dashed #ddd;
}
.total-label {
font-size: 28rpx;
color: #666;
margin-right: 10rpx;
}
.total-value {
font-size: 28rpx;
font-weight: bold;
}
/* 详情弹窗中的明细样式 */
.detail-section-view {
margin: 20rpx 0;
padding: 10rpx;
background-color: #f9f9f9;
border-radius: 8rpx;
}
.detail-header-view {
margin-bottom: 15rpx;
}
.detail-title-view {
font-size: 26rpx;
font-weight: bold;
color: #333;
}
</style>