列表内高频操作直达-初稿(ID问题未解决)
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user