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 { showError } = require("../../utils/page-helpers");
|
||||||
|
|
||||||
|
const FUND_INCOME_CATEGORIES = ["班费收取", "活动赞助", "其他收入"];
|
||||||
|
const FUND_EXPENSE_CATEGORIES = ["聚餐", "活动物资", "场地费", "交通费", "礼品", "其他支出"];
|
||||||
|
|
||||||
function formatAmount(value) {
|
function formatAmount(value) {
|
||||||
return Number(value || 0).toFixed(2);
|
return Number(value || 0).toFixed(2);
|
||||||
}
|
}
|
||||||
@ -30,7 +34,17 @@ Page({
|
|||||||
data: {
|
data: {
|
||||||
id: null,
|
id: null,
|
||||||
record: 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) {
|
onLoad(options) {
|
||||||
@ -49,7 +63,9 @@ Page({
|
|||||||
this.setData({ loading: true });
|
this.setData({ loading: true });
|
||||||
try {
|
try {
|
||||||
const record = await get(`/api/fund/${id}`);
|
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) {
|
} catch (error) {
|
||||||
showError(error, "加载班费详情失败");
|
showError(error, "加载班费详情失败");
|
||||||
} finally {
|
} finally {
|
||||||
@ -62,5 +78,166 @@ Page({
|
|||||||
const urls = this.data.record && this.data.record.image_urls ? this.data.record.image_urls : [];
|
const urls = this.data.record && this.data.record.image_urls ? this.data.record.image_urls : [];
|
||||||
if (!current || !urls.length) return;
|
if (!current || !urls.length) return;
|
||||||
wx.previewImage({ current, urls });
|
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-title">{{record.signed_amount_text}}</view>
|
||||||
<view class="hero-subtitle">{{record.category}} · {{record.record_date}}</view>
|
<view class="hero-subtitle">{{record.category}} · {{record.record_date}}</view>
|
||||||
<view class="fund-proof-pill">{{record.image_count_text}}</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>
|
||||||
|
|
||||||
<view class="section">
|
<view class="section">
|
||||||
@ -71,3 +75,61 @@
|
|||||||
<view class="muted">未找到班费记录</view>
|
<view class="muted">未找到班费记录</view>
|
||||||
</view>
|
</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;
|
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 {
|
.fund-detail-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -58,3 +83,136 @@
|
|||||||
background: #efe0ca;
|
background: #efe0ca;
|
||||||
box-shadow: 0 14rpx 32rpx rgba(68, 39, 27, 0.08);
|
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