1
This commit is contained in:
parent
5659a15636
commit
f105cb369c
@ -1,6 +1,10 @@
|
||||
const { get } = require("../../utils/api");
|
||||
const { del, get, put, uploadFile } = require("../../utils/api");
|
||||
const { hasManagePermission } = require("../../utils/permissions");
|
||||
const { showError } = require("../../utils/page-helpers");
|
||||
|
||||
const FUND_INCOME_CATEGORIES = ["班费收取", "活动赞助", "其他收入"];
|
||||
const FUND_EXPENSE_CATEGORIES = ["聚餐", "活动物资", "场地费", "交通费", "礼品", "其他支出"];
|
||||
|
||||
function formatAmount(value) {
|
||||
return Number(value || 0).toFixed(2);
|
||||
}
|
||||
@ -30,7 +34,17 @@ Page({
|
||||
data: {
|
||||
id: null,
|
||||
record: null,
|
||||
loading: false
|
||||
canManage: false,
|
||||
editOpen: false,
|
||||
editForm: {},
|
||||
editImageUrls: [],
|
||||
editCategories: FUND_EXPENSE_CATEGORIES,
|
||||
editIncomeClass: "",
|
||||
editExpenseClass: "active expense",
|
||||
loading: false,
|
||||
saving: false,
|
||||
deleting: false,
|
||||
uploadingImages: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
@ -49,7 +63,9 @@ Page({
|
||||
this.setData({ loading: true });
|
||||
try {
|
||||
const record = await get(`/api/fund/${id}`);
|
||||
this.setData({ record: normalizeRecord(record) });
|
||||
const user = getApp().globalData.user || wx.getStorageSync("auth_user");
|
||||
const canManage = hasManagePermission(user, record.class_id, "fund");
|
||||
this.setData({ record: normalizeRecord(record), canManage });
|
||||
} catch (error) {
|
||||
showError(error, "加载班费详情失败");
|
||||
} finally {
|
||||
@ -62,5 +78,166 @@ Page({
|
||||
const urls = this.data.record && this.data.record.image_urls ? this.data.record.image_urls : [];
|
||||
if (!current || !urls.length) return;
|
||||
wx.previewImage({ current, urls });
|
||||
},
|
||||
|
||||
noop() {},
|
||||
|
||||
openEdit() {
|
||||
const record = this.data.record;
|
||||
if (!record || !this.data.canManage) return;
|
||||
const categories = record.type === "income" ? FUND_INCOME_CATEGORIES : FUND_EXPENSE_CATEGORIES;
|
||||
this.setData({
|
||||
editOpen: true,
|
||||
editForm: {
|
||||
type: record.type,
|
||||
amount: String(record.amount || ""),
|
||||
category: record.category || categories[0],
|
||||
description: record.description || "",
|
||||
record_date: record.record_date || ""
|
||||
},
|
||||
editImageUrls: [...record.image_urls],
|
||||
editCategories: categories,
|
||||
editIncomeClass: record.type === "income" ? "active income" : "",
|
||||
editExpenseClass: record.type === "expense" ? "active expense" : ""
|
||||
});
|
||||
},
|
||||
|
||||
closeEdit() {
|
||||
if (this.data.saving || this.data.uploadingImages) return;
|
||||
this.setData({ editOpen: false });
|
||||
},
|
||||
|
||||
onEditInput(event) {
|
||||
const field = event.currentTarget.dataset.field;
|
||||
this.setData({ [`editForm.${field}`]: event.detail.value });
|
||||
},
|
||||
|
||||
setEditFundType(event) {
|
||||
const type = event.currentTarget.dataset.type;
|
||||
const categories = type === "income" ? FUND_INCOME_CATEGORIES : FUND_EXPENSE_CATEGORIES;
|
||||
this.setData({
|
||||
"editForm.type": type,
|
||||
"editForm.category": categories[0],
|
||||
editCategories: categories,
|
||||
editIncomeClass: type === "income" ? "active income" : "",
|
||||
editExpenseClass: type === "expense" ? "active expense" : ""
|
||||
});
|
||||
},
|
||||
|
||||
onEditCategoryChange(event) {
|
||||
const index = Number(event.detail.value);
|
||||
this.setData({ "editForm.category": this.data.editCategories[index] });
|
||||
},
|
||||
|
||||
onEditDateChange(event) {
|
||||
this.setData({ "editForm.record_date": event.detail.value });
|
||||
},
|
||||
|
||||
chooseEditImages() {
|
||||
if (this.data.uploadingImages) return;
|
||||
if (this.data.editImageUrls.length >= 6) {
|
||||
wx.showToast({ title: "最多 6 张图片", icon: "none" });
|
||||
return;
|
||||
}
|
||||
wx.chooseMedia({
|
||||
count: 6 - this.data.editImageUrls.length,
|
||||
mediaType: ["image"],
|
||||
sourceType: ["album", "camera"],
|
||||
success: async (res) => {
|
||||
const paths = (res.tempFiles || []).map((item) => item.tempFilePath);
|
||||
if (!paths.length) return;
|
||||
this.setData({ uploadingImages: true });
|
||||
try {
|
||||
const uploaded = [...this.data.editImageUrls];
|
||||
for (const path of paths) {
|
||||
const result = await uploadFile("/api/upload/image", path, {}, "file");
|
||||
if (result && result.url) uploaded.push(result.url);
|
||||
}
|
||||
this.setData({ editImageUrls: uploaded.slice(0, 6) });
|
||||
} catch (error) {
|
||||
showError(error, "上传图片失败");
|
||||
} finally {
|
||||
this.setData({ uploadingImages: false });
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
removeEditImage(event) {
|
||||
const index = Number(event.currentTarget.dataset.index);
|
||||
this.setData({
|
||||
editImageUrls: this.data.editImageUrls.filter((_, itemIndex) => itemIndex !== index)
|
||||
});
|
||||
},
|
||||
|
||||
async saveEdit() {
|
||||
if (this.data.saving || this.data.uploadingImages) return;
|
||||
const form = this.data.editForm;
|
||||
const amount = Number(form.amount);
|
||||
if (!amount || amount <= 0) {
|
||||
wx.showToast({ title: "请输入有效金额", icon: "none" });
|
||||
return;
|
||||
}
|
||||
if (!form.category) {
|
||||
wx.showToast({ title: "请选择分类", icon: "none" });
|
||||
return;
|
||||
}
|
||||
if (!form.record_date) {
|
||||
wx.showToast({ title: "请选择日期", icon: "none" });
|
||||
return;
|
||||
}
|
||||
this.setData({ saving: true });
|
||||
try {
|
||||
const updated = await put(`/api/fund/${this.data.id}`, {
|
||||
type: form.type,
|
||||
amount,
|
||||
category: form.category,
|
||||
description: form.description || null,
|
||||
image_urls: this.data.editImageUrls,
|
||||
record_date: form.record_date
|
||||
});
|
||||
this.setData({
|
||||
record: normalizeRecord(updated),
|
||||
editOpen: false
|
||||
});
|
||||
this.refreshPreviousPage();
|
||||
wx.showToast({ title: "已更新", icon: "success" });
|
||||
} catch (error) {
|
||||
showError(error, "保存失败");
|
||||
} finally {
|
||||
this.setData({ saving: false });
|
||||
}
|
||||
},
|
||||
|
||||
deleteRecord() {
|
||||
if (this.data.deleting || !this.data.canManage || !this.data.record) return;
|
||||
wx.showModal({
|
||||
title: "删除班费记录",
|
||||
content: "删除后无法恢复,确认删除这条班费记录?",
|
||||
confirmText: "删除",
|
||||
confirmColor: "#b42318",
|
||||
success: async (res) => {
|
||||
if (!res.confirm) return;
|
||||
this.setData({ deleting: true });
|
||||
try {
|
||||
await del(`/api/fund/${this.data.id}`);
|
||||
this.refreshPreviousPage();
|
||||
wx.showToast({ title: "已删除", icon: "success" });
|
||||
setTimeout(() => wx.navigateBack(), 500);
|
||||
} catch (error) {
|
||||
showError(error, "删除失败");
|
||||
} finally {
|
||||
this.setData({ deleting: false });
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refreshPreviousPage() {
|
||||
const pages = getCurrentPages();
|
||||
const previousPage = pages[pages.length - 2];
|
||||
if (previousPage && previousPage.load) {
|
||||
previousPage.load();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -4,6 +4,10 @@
|
||||
<view class="hero-title">{{record.signed_amount_text}}</view>
|
||||
<view class="hero-subtitle">{{record.category}} · {{record.record_date}}</view>
|
||||
<view class="fund-proof-pill">{{record.image_count_text}}</view>
|
||||
<view wx:if="{{canManage}}" class="fund-action-bar">
|
||||
<view class="fund-action-button" bindtap="openEdit">编辑</view>
|
||||
<view class="fund-action-button danger" bindtap="deleteRecord">{{deleting ? "删除中..." : "删除"}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
@ -71,3 +75,61 @@
|
||||
<view class="muted">未找到班费记录</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{editOpen}}" class="edit-mask" catchtap="closeEdit">
|
||||
<view class="edit-panel" catchtap="noop">
|
||||
<view class="edit-head">
|
||||
<view>
|
||||
<view class="edit-kicker">FUND RECORD</view>
|
||||
<view class="edit-title">修改班费记录</view>
|
||||
</view>
|
||||
<view class="edit-close" bindtap="closeEdit">×</view>
|
||||
</view>
|
||||
|
||||
<view class="form-field">
|
||||
<view class="form-label">收支类型</view>
|
||||
<view class="fund-type-switch">
|
||||
<view class="fund-type {{editIncomeClass}}" data-type="income" bindtap="setEditFundType">入账</view>
|
||||
<view class="fund-type {{editExpenseClass}}" data-type="expense" bindtap="setEditFundType">出账</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-field">
|
||||
<view class="form-label">金额</view>
|
||||
<input class="form-input" type="digit" value="{{editForm.amount}}" data-field="amount" bindinput="onEditInput" placeholder="0.00" />
|
||||
</view>
|
||||
<view class="form-field">
|
||||
<view class="form-label">小类型</view>
|
||||
<picker mode="selector" range="{{editCategories}}" bindchange="onEditCategoryChange">
|
||||
<view class="form-select">{{editForm.category}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-field">
|
||||
<view class="form-label">日期</view>
|
||||
<picker mode="date" value="{{editForm.record_date}}" bindchange="onEditDateChange">
|
||||
<view class="form-select">{{editForm.record_date}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="form-field">
|
||||
<view class="form-label">备注</view>
|
||||
<textarea class="form-textarea" value="{{editForm.description}}" data-field="description" bindinput="onEditInput" placeholder="补充说明,可不填" />
|
||||
</view>
|
||||
<view class="form-field">
|
||||
<view class="edit-label-row">
|
||||
<view class="form-label">小票图片</view>
|
||||
<view class="section-action" bindtap="chooseEditImages">{{uploadingImages ? "上传中..." : "添加图片"}}</view>
|
||||
</view>
|
||||
<view class="form-hint">最多 6 张,可拍照或从相册选择。</view>
|
||||
<view wx:if="{{editImageUrls.length}}" class="edit-image-grid">
|
||||
<view wx:for="{{editImageUrls}}" wx:key="*this" class="edit-image-cell">
|
||||
<image src="{{item}}" mode="aspectFill" />
|
||||
<view class="edit-image-remove" data-index="{{index}}" bindtap="removeEditImage">×</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="edit-submit-row">
|
||||
<button class="button secondary" bindtap="closeEdit" disabled="{{saving || uploadingImages}}">取消</button>
|
||||
<button class="button" loading="{{saving}}" bindtap="saveEdit">保存修改</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -21,6 +21,31 @@
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.fund-action-bar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
margin-top: 26rpx;
|
||||
}
|
||||
|
||||
.fund-action-button {
|
||||
min-width: 132rpx;
|
||||
height: 58rpx;
|
||||
border: 1rpx solid rgba(255, 248, 237, 0.22);
|
||||
border-radius: 999rpx;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: #fff8ed;
|
||||
font-size: 25rpx;
|
||||
font-weight: 720;
|
||||
line-height: 58rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fund-action-button.danger {
|
||||
background: rgba(255, 248, 237, 0.9);
|
||||
color: #9a3a2f;
|
||||
}
|
||||
|
||||
.fund-detail-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -58,3 +83,136 @@
|
||||
background: #efe0ca;
|
||||
box-shadow: 0 14rpx 32rpx rgba(68, 39, 27, 0.08);
|
||||
}
|
||||
|
||||
.edit-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 50;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
background: rgba(47, 33, 28, 0.42);
|
||||
}
|
||||
|
||||
.edit-panel {
|
||||
width: 100%;
|
||||
max-height: 88vh;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
padding: 30rpx 28rpx calc(34rpx + env(safe-area-inset-bottom));
|
||||
border-radius: 34rpx 34rpx 0 0;
|
||||
background: #fffaf3;
|
||||
box-shadow: 0 -18rpx 60rpx rgba(47, 33, 28, 0.18);
|
||||
}
|
||||
|
||||
.edit-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 24rpx;
|
||||
margin-bottom: 26rpx;
|
||||
}
|
||||
|
||||
.edit-kicker {
|
||||
color: #9d8068;
|
||||
font-size: 20rpx;
|
||||
font-weight: 760;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.edit-title {
|
||||
margin-top: 6rpx;
|
||||
color: #2f211c;
|
||||
font-size: 34rpx;
|
||||
font-weight: 780;
|
||||
}
|
||||
|
||||
.edit-close {
|
||||
width: 54rpx;
|
||||
height: 54rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #f1e4d4;
|
||||
color: #6b1f2b;
|
||||
font-size: 42rpx;
|
||||
line-height: 48rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fund-type-switch {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
margin-top: 12rpx;
|
||||
padding: 8rpx;
|
||||
border-radius: 24rpx;
|
||||
background: #f1e4d4;
|
||||
}
|
||||
|
||||
.fund-type {
|
||||
flex: 1;
|
||||
height: 72rpx;
|
||||
border-radius: 20rpx;
|
||||
color: #7f7065;
|
||||
font-size: 27rpx;
|
||||
font-weight: 700;
|
||||
line-height: 72rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fund-type.active {
|
||||
background: #fffaf3;
|
||||
color: #6b1f2b;
|
||||
}
|
||||
|
||||
.fund-type.income {
|
||||
color: #1f7a4d;
|
||||
}
|
||||
|
||||
.fund-type.expense {
|
||||
color: #9a3a2f;
|
||||
}
|
||||
|
||||
.edit-label-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.edit-image-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.edit-image-cell {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 180rpx;
|
||||
border-radius: 18rpx;
|
||||
background: #efe0ca;
|
||||
}
|
||||
|
||||
.edit-image-cell image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.edit-image-remove {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
right: 8rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 999rpx;
|
||||
background: rgba(47, 33, 28, 0.72);
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
line-height: 36rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.edit-submit-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.35fr;
|
||||
gap: 16rpx;
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user