列表内高频操作直达-初稿(ID问题未解决)

This commit is contained in:
Cheng Zhou
2025-12-17 23:16:46 +08:00
parent ad4fa6ae7a
commit 7d2ae5658d
2840 changed files with 592 additions and 2020 deletions
+135 -2
View File
@@ -53,6 +53,26 @@
<el-table-column prop="submitted_at" label="提交时间" width="160" />
<el-table-column prop="approved_at" label="审批时间" width="160" />
<el-table-column prop="paid_at" label="支付时间" width="160" />
<el-table-column label="操作" width="220">
<template #default="scope">
<el-button
v-if="showAction(scope.row, 'approve')"
type="primary"
size="small"
@click.stop="onApprove(scope.row)"
>
审批
</el-button>
<el-button
v-if="showAction(scope.row, 'reject')"
type="danger"
size="small"
@click.stop="onReject(scope.row)"
>
驳回
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
@@ -71,14 +91,17 @@
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { ElMessage, ElMessageBox } from "element-plus";
import FinanceSummary from "../components/FinanceSummary.vue";
import FinanceForm from "../components/FinanceForm.vue";
import { fetchFinanceItems, fetchFinanceSummary } from "../api/finance";
import { fetchFinanceItems, fetchFinanceSummary, changeFinanceStatus } from "../api/finance";
import { useStudyStore } from "../store/study";
import { useAuthStore } from "../store/auth";
import PermissionAction from "../components/PermissionAction.vue";
import { usePermission } from "../utils/permission";
import { financeMachine, getAvailableActions } from "../state-machine";
import { evaluateAction } from "../guards/actionGuard";
import { logAudit } from "../audit";
const study = useStudyStore();
const auth = useAuthStore();
@@ -105,6 +128,10 @@ const showForm = ref(false);
const { can } = usePermission();
const canCreate = computed(() => can("finance.create"));
const permissionMap: Record<string, string> = {
approve: "finance.approve",
reject: "finance.approve",
};
const formatAmount = (num?: number | string) => {
const n = Number(num);
@@ -144,6 +171,112 @@ const categoryLabel = (c?: string) =>
OTHER: "其他",
}[c || ""] || c || "");
const showAction = (row: any, key: string) => {
const actions = getAvailableActions(financeMachine, row.status).filter((a) => a.key === key);
if (!actions.length) return false;
const decision = evaluateAction({
actorRole: auth.user?.role || null,
requiredPermission: permissionMap[key],
stateMachine: financeMachine,
currentState: row.status,
actionKey: key,
});
return decision.allowed;
};
const doChange = async (row: any, status: string, reason?: string) => {
if (!study.currentStudy) return false;
try {
await changeFinanceStatus(study.currentStudy.id, row.id, { status, reject_reason: reason });
return true;
} catch (e: any) {
ElMessage.error(e?.response?.data?.message || "操作失败");
return false;
}
};
const onApprove = async (row: any) => {
const action = getAvailableActions(financeMachine, row.status).find((a) => a.key === "approve");
const decision = evaluateAction({
actorRole: auth.user?.role || null,
requiredPermission: permissionMap.approve,
stateMachine: financeMachine,
currentState: row.status,
actionKey: "approve",
});
if (!decision.allowed) {
ElMessage.warning(decision.reason || "当前不可执行该操作");
logAudit(decision.auditType, {
targetId: row.id,
targetName: row.title,
reason: decision.reason,
severity: decision.severity,
});
return;
}
if (action?.confirm) {
const ok = await ElMessageBox.confirm(action.confirmText || "确认审批通过?", "提示").catch(() => null);
if (!ok) return;
}
const ok = await doChange(row, action?.to || "APPROVED");
logAudit("FINANCE_APPROVED", {
targetId: row.id,
targetName: row.title,
before: { status: row.status },
after: { status: action?.to || "APPROVED" },
severity: ok ? "normal" : "warning",
});
if (ok) {
ElMessage.success("已审批");
loadList();
}
};
const onReject = async (row: any) => {
const action = getAvailableActions(financeMachine, row.status).find((a) => a.key === "reject");
const decision = evaluateAction({
actorRole: auth.user?.role || null,
requiredPermission: permissionMap.reject,
stateMachine: financeMachine,
currentState: row.status,
actionKey: "reject",
});
if (!decision.allowed) {
ElMessage.warning(decision.reason || "当前不可执行该操作");
logAudit(decision.auditType, {
targetId: row.id,
targetName: row.title,
reason: decision.reason,
severity: decision.severity,
});
return;
}
if (action?.confirm) {
const ok = await ElMessageBox.confirm(action.confirmText || "确认驳回?", "提示", {
type: action.danger ? "warning" : "info",
}).catch(() => null);
if (!ok) return;
}
const reason = await ElMessageBox.prompt("请输入驳回原因", "驳回", {
confirmButtonText: "确定",
cancelButtonText: "取消",
}).catch(() => null);
if (!reason || !reason.value) return;
const ok = await doChange(row, action?.to || "REJECTED", reason.value);
logAudit("FINANCE_REJECTED", {
targetId: row.id,
targetName: row.title,
before: { status: row.status },
after: { status: action?.to || "REJECTED" },
severity: ok ? "normal" : "warning",
reason: reason.value,
});
if (ok) {
ElMessage.success("已驳回");
loadList();
}
};
const loadSummary = async () => {
if (!study.currentStudy) return;
try {