日志
This commit is contained in:
@@ -53,6 +53,13 @@ export function copyLaunchDraft(id) {
|
||||
});
|
||||
}
|
||||
|
||||
export function createLaunchChangeDraft(id) {
|
||||
return request({
|
||||
url: `/contract/launch/${id}/change`,
|
||||
method: "post",
|
||||
});
|
||||
}
|
||||
|
||||
export function submitLaunchRecord(id) {
|
||||
return request({
|
||||
url: `/contract/launch/${id}/submit`,
|
||||
@@ -115,6 +122,14 @@ export function archiveLaunch(id, data = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
export function terminateLaunchRecord(id, data = {}) {
|
||||
return request({
|
||||
url: `/contract/launch/${id}/terminate`,
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteLaunchRecord(id) {
|
||||
return request({
|
||||
url: `/contract/launch/${id}`,
|
||||
@@ -160,3 +175,28 @@ export function downloadLaunchFile(fileName) {
|
||||
responseType: "arraybuffer",
|
||||
});
|
||||
}
|
||||
|
||||
export function queryContractLogList(data) {
|
||||
return request({
|
||||
url: "/contract/log/list",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
export function queryContractLogAll(data) {
|
||||
return request({
|
||||
url: "/contract/log/all",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
export function exportContractLogExcel(data) {
|
||||
return request({
|
||||
url: "/contract/log/export/excel",
|
||||
method: "post",
|
||||
data,
|
||||
responseType: "arraybuffer",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
<team-outlined />
|
||||
<span>相关方管理</span>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="/contract-console/log">
|
||||
<profile-outlined />
|
||||
<span>日志管理</span>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
|
||||
<div class="sider-footer">
|
||||
@@ -50,7 +54,7 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { FileTextOutlined, FormOutlined, TeamOutlined } from '@ant-design/icons-vue'
|
||||
import { FileTextOutlined, FormOutlined, ProfileOutlined, TeamOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -64,6 +68,10 @@ const selectedKey = computed(() => {
|
||||
return '/contract-console/partner'
|
||||
}
|
||||
|
||||
if (route.path.startsWith('/contract-console/log')) {
|
||||
return '/contract-console/log'
|
||||
}
|
||||
|
||||
return '/contract-console/launch'
|
||||
})
|
||||
|
||||
|
||||
@@ -35,6 +35,14 @@ const routes = [
|
||||
title: '相关方管理',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'log',
|
||||
name: 'ContractLogManage',
|
||||
component: () => import('@/views/contract/log/index.vue'),
|
||||
meta: {
|
||||
title: '日志管理',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -101,11 +101,12 @@
|
||||
<vxe-column field="initiatorDept" title="所属部门" width="140" show-overflow="title" />
|
||||
<vxe-column field="createTime" title="发起时间" width="170" show-overflow="title" />
|
||||
<vxe-column field="updateTime" title="最后更新时间" width="170" show-overflow="title" />
|
||||
<vxe-column title="操作" width="580" fixed="right">
|
||||
<vxe-column title="操作" width="720" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div class="table-actions">
|
||||
<a @click="handleView(row)">查看</a>
|
||||
<a @click="handleCopy(row)">复制</a>
|
||||
<a @click="handleChange(row)">变更</a>
|
||||
<a v-if="canEdit(row)" @click="handleEdit(row)">编辑</a>
|
||||
<a v-if="canSubmit(row)" @click="handleSubmit(row)">提交法务</a>
|
||||
<a v-if="canViewApprove(row)" @click="handleViewApprove(row)">审批记录</a>
|
||||
@@ -117,6 +118,7 @@
|
||||
<a v-if="canMockFinishSign(row)" @click="handleMockFinishSign(row)">测试签署完成</a>
|
||||
<a v-if="canCollect(row)" @click="handleMockCollect(row)">测试采集</a>
|
||||
<a v-if="canArchive(row)" @click="handleArchive(row)">测试归档</a>
|
||||
<a v-if="canTerminate(row)" @click="handleTerminate(row)">终止</a>
|
||||
<a v-if="canDelete(row)" @click="handleDelete(row)">删除</a>
|
||||
</div>
|
||||
</template>
|
||||
@@ -151,13 +153,14 @@ import {
|
||||
RedoOutlined,
|
||||
SearchOutlined,
|
||||
} from "@ant-design/icons-vue";
|
||||
import { message, Modal } from "ant-design-vue";
|
||||
import { Input, message, Modal } from "ant-design-vue";
|
||||
import ContractPartyPlaceholderModal from "./components/ContractPartyPlaceholderModal.vue";
|
||||
import MixedSignModal from "./components/mixedSignModal.vue";
|
||||
import { getContractTypeSimpleLabel } from "./contractLaunchOptions.js";
|
||||
import {
|
||||
archiveLaunch,
|
||||
copyLaunchDraft,
|
||||
createLaunchChangeDraft,
|
||||
createLaunchDraft,
|
||||
deleteLaunchRecord,
|
||||
getLaunchDetail,
|
||||
@@ -168,6 +171,7 @@ import {
|
||||
queryApproveViewUrl,
|
||||
queryLaunchList,
|
||||
submitLaunchRecord,
|
||||
terminateLaunchRecord,
|
||||
updateLaunchDraft,
|
||||
} from "@/api/launch.js";
|
||||
|
||||
@@ -315,6 +319,10 @@ function canViewApprove(row) {
|
||||
return ["reviewing", "pending_sign", "signing", "pending_collect", "pending_archive", "completed"].includes(row.status);
|
||||
}
|
||||
|
||||
function canTerminate(row) {
|
||||
return row.status !== "completed" && row.status !== "terminated";
|
||||
}
|
||||
|
||||
function selectChangeEvent({ records }) {
|
||||
selectedRows.value = records;
|
||||
selectedRowKeys.value = records.map((item) => item.id);
|
||||
@@ -418,6 +426,19 @@ function handleCopy(row) {
|
||||
});
|
||||
}
|
||||
|
||||
function handleChange(row) {
|
||||
createLaunchChangeDraft(row.id).then((res) => {
|
||||
if (!isSuccessResponse(res) || !getDataResult(res)) {
|
||||
message.error(getResponseMessage(res, "发起变更合同失败"));
|
||||
return;
|
||||
}
|
||||
const changedRecord = getDataResult(res);
|
||||
message.success("已生成变更草稿,并自动关联主合同编码");
|
||||
getList();
|
||||
contractModalRef.value?.showModal(changedRecord, "edit");
|
||||
});
|
||||
}
|
||||
|
||||
function handleSubmit(row) {
|
||||
submitLaunchRecord(row.id).then((res) => {
|
||||
if (!isSuccessResponse(res)) {
|
||||
@@ -548,6 +569,36 @@ function handleArchive(row) {
|
||||
});
|
||||
}
|
||||
|
||||
function handleTerminate(row) {
|
||||
let terminateReason = "";
|
||||
Modal.confirm({
|
||||
title: `确认终止合同【${row.contractName}】吗?`,
|
||||
content: h("div", { style: { display: "flex", flexDirection: "column", gap: "12px" } }, [
|
||||
h("div", "除已完成外,任意环节都可终止。当前仅终止业务系统内流程,法务系统需分别操作。"),
|
||||
h(Input.TextArea, {
|
||||
rows: 4,
|
||||
placeholder: "可选:请输入终止原因",
|
||||
onChange: (event) => {
|
||||
terminateReason = event?.target?.value || "";
|
||||
},
|
||||
}),
|
||||
]),
|
||||
okText: "确认终止",
|
||||
cancelText: "取消",
|
||||
okType: "danger",
|
||||
onOk: () =>
|
||||
terminateLaunchRecord(row.id, { terminateReason }).then((res) => {
|
||||
if (!isSuccessResponse(res)) {
|
||||
message.error(getResponseMessage(res, "终止合同失败"));
|
||||
return;
|
||||
}
|
||||
message.success("合同已终止");
|
||||
getList();
|
||||
}),
|
||||
icon: null,
|
||||
});
|
||||
}
|
||||
|
||||
function handleDelete(row) {
|
||||
Modal.confirm({
|
||||
title: `确认删除合同【${row.contractName}】吗?`,
|
||||
|
||||
@@ -0,0 +1,547 @@
|
||||
<template>
|
||||
<section class="section-container">
|
||||
<a-card :bordered="false" class="query-card" :body-style="queryCardBodyStyle">
|
||||
<div ref="queryContainer" class="section-query-container">
|
||||
<a-form ref="formRef" :model="queryParam" layout="inline">
|
||||
<a-row :gutter="16" style="width: 100%">
|
||||
<a-col :md="6" :sm="24">
|
||||
<a-form-item label="合同名称">
|
||||
<a-input v-model:value="queryParam.contractName" allow-clear placeholder="请输入合同名称" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="6" :sm="24">
|
||||
<a-form-item label="合同编码">
|
||||
<a-input v-model:value="queryParam.contractCode" allow-clear placeholder="请输入合同编码" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="6" :sm="24">
|
||||
<a-form-item label="操作人">
|
||||
<a-input v-model:value="queryParam.operatorName" allow-clear placeholder="请输入操作人" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="6" :sm="24">
|
||||
<a-form-item label="操作类型">
|
||||
<a-select
|
||||
v-model:value="queryParam.actionType"
|
||||
allow-clear
|
||||
placeholder="请选择操作类型"
|
||||
:options="actionTypeOptions"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="6" :sm="24">
|
||||
<a-form-item label="操作结果">
|
||||
<a-select
|
||||
v-model:value="queryParam.actionStatus"
|
||||
allow-clear
|
||||
placeholder="请选择操作结果"
|
||||
:options="actionStatusOptions"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :sm="24">
|
||||
<a-form-item label="操作时间">
|
||||
<a-range-picker
|
||||
v-model:value="queryParam.dateRange"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" class="table-card" :body-style="tableCardBodyStyle">
|
||||
<vxe-toolbar ref="toolbarRef" custom>
|
||||
<template #buttons>
|
||||
<a-space wrap>
|
||||
<a-button type="primary" :icon="h(SearchOutlined)" @click="handleQuery">查询</a-button>
|
||||
<a-button :icon="h(RedoOutlined)" @click="handleReset">重置</a-button>
|
||||
<a-button :icon="h(FileExcelOutlined)" @click="handleExportExcel">导出 Excel</a-button>
|
||||
<a-button :icon="h(FilePdfOutlined)" @click="handleExportPdf">导出 PDF</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</vxe-toolbar>
|
||||
|
||||
<vxe-table
|
||||
ref="tableRef"
|
||||
border
|
||||
:height="tableHeight"
|
||||
:row-config="{ isHover: true, height: 44 }"
|
||||
:column-config="{ resizable: true, minWidth: 120 }"
|
||||
:custom-config="{ storage: true }"
|
||||
:scroll-y="{ enabled: true, gt: 0, mode: 'wheel' }"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:loading-config="{ icon: 'vxe-icon-indicator roll', text: '正在拼命加载中...' }"
|
||||
>
|
||||
<vxe-column type="seq" title="序号" width="68" />
|
||||
<vxe-column field="contractCode" title="合同编码" width="170" show-overflow="title" />
|
||||
<vxe-column field="contractName" title="合同名称" width="220" show-overflow="title" />
|
||||
<vxe-column field="contractStatus" title="合同状态" width="110">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="getStatusColor(row.contractStatus)">{{ getStatusLabel(row.contractStatus) }}</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="actionName" title="操作名称" width="160" show-overflow="title" />
|
||||
<vxe-column field="actionType" title="操作类型" width="130">
|
||||
<template #default="{ row }">
|
||||
{{ getActionTypeLabel(row.actionType) }}
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="actionStatus" title="操作结果" width="110">
|
||||
<template #default="{ row }">
|
||||
<a-tag :color="getActionStatusColor(row.actionStatus)">{{ getActionStatusLabel(row.actionStatus) }}</a-tag>
|
||||
</template>
|
||||
</vxe-column>
|
||||
<vxe-column field="operatorName" title="操作人" width="120" show-overflow="title" />
|
||||
<vxe-column field="operatorPhone" title="联系电话" width="140" show-overflow="title" />
|
||||
<vxe-column field="actionTime" title="操作时间" width="170" show-overflow="title" />
|
||||
<vxe-column field="actionContent" title="操作内容" width="260" show-overflow="title" />
|
||||
<vxe-column title="详情" width="90" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<a @click="handleViewDetail(row)">查看</a>
|
||||
</template>
|
||||
</vxe-column>
|
||||
</vxe-table>
|
||||
|
||||
<a-pagination
|
||||
id="pageWrapper"
|
||||
ref="pageWrapperRef"
|
||||
v-model:pageSize="queryParam.pageSize"
|
||||
v-model:current="queryParam.pageNo"
|
||||
class="table-pagination"
|
||||
show-size-changer
|
||||
:page-size-options="['10', '20', '50', '100']"
|
||||
:total="total"
|
||||
:show-total="(value) => `共 ${value} 条`"
|
||||
@change="onShowSizeChange"
|
||||
@show-size-change="onShowSizeChange"
|
||||
/>
|
||||
</a-card>
|
||||
|
||||
<a-modal v-model:open="detailVisible" title="日志详情" width="860px" :footer="null">
|
||||
<div v-if="currentLog" class="log-detail">
|
||||
<div class="detail-grid">
|
||||
<div><span>合同名称:</span><strong>{{ currentLog.contractName || "-" }}</strong></div>
|
||||
<div><span>合同编码:</span><strong>{{ currentLog.contractCode || "-" }}</strong></div>
|
||||
<div><span>操作名称:</span><strong>{{ currentLog.actionName || "-" }}</strong></div>
|
||||
<div><span>操作结果:</span><strong>{{ getActionStatusLabel(currentLog.actionStatus) }}</strong></div>
|
||||
<div><span>操作人:</span><strong>{{ currentLog.operatorName || "-" }}</strong></div>
|
||||
<div><span>联系电话:</span><strong>{{ currentLog.operatorPhone || "-" }}</strong></div>
|
||||
<div><span>操作时间:</span><strong>{{ currentLog.actionTime || "-" }}</strong></div>
|
||||
<div><span>合同状态:</span><strong>{{ getStatusLabel(currentLog.contractStatus) }}</strong></div>
|
||||
</div>
|
||||
<div class="detail-section">
|
||||
<div class="detail-title">操作内容</div>
|
||||
<a-textarea :value="currentLog.actionContent || '-'" :rows="4" readonly />
|
||||
</div>
|
||||
<div class="detail-section">
|
||||
<div class="detail-title">原始报文</div>
|
||||
<a-textarea :value="prettyPayload(currentLog.callbackPayload)" :rows="12" readonly />
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { h, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
|
||||
import { FileExcelOutlined, FilePdfOutlined, RedoOutlined, SearchOutlined } from "@ant-design/icons-vue";
|
||||
import { message } from "ant-design-vue";
|
||||
import { saveAs } from "file-saver";
|
||||
import { exportContractLogExcel, queryContractLogAll, queryContractLogList } from "@/api/launch.js";
|
||||
|
||||
const formRef = ref(null);
|
||||
const queryContainer = ref(null);
|
||||
const pageWrapperRef = ref(null);
|
||||
const tableRef = ref(null);
|
||||
const toolbarRef = ref(null);
|
||||
|
||||
const total = ref(0);
|
||||
const loading = ref(false);
|
||||
const tableHeight = ref(420);
|
||||
const tableData = ref([]);
|
||||
const detailVisible = ref(false);
|
||||
const currentLog = ref(null);
|
||||
|
||||
const actionTypeOptions = [
|
||||
{ label: "保存", value: "save" },
|
||||
{ label: "复制", value: "copy" },
|
||||
{ label: "变更", value: "change" },
|
||||
{ label: "提交法务", value: "submit" },
|
||||
{ label: "审批通过", value: "approve" },
|
||||
{ label: "审批驳回", value: "reject" },
|
||||
{ label: "开始签署", value: "seal_start" },
|
||||
{ label: "签署完成", value: "seal_finish" },
|
||||
{ label: "合同采集", value: "collect" },
|
||||
{ label: "合同归档", value: "archive" },
|
||||
{ label: "终止合同", value: "terminate" },
|
||||
{ label: "删除合同", value: "delete" },
|
||||
];
|
||||
|
||||
const actionStatusOptions = [
|
||||
{ label: "成功", value: "success" },
|
||||
{ label: "失败", value: "failed" },
|
||||
{ label: "处理中", value: "pending" },
|
||||
];
|
||||
|
||||
const statusLabelMap = {
|
||||
draft: "草稿",
|
||||
reviewing: "审批中",
|
||||
rejected: "审批驳回",
|
||||
pending_sign: "待签署",
|
||||
signing: "签署中",
|
||||
pending_collect: "待采集",
|
||||
pending_archive: "待归档",
|
||||
completed: "已完成",
|
||||
terminated: "已终止",
|
||||
};
|
||||
|
||||
const statusColorMap = {
|
||||
draft: "default",
|
||||
reviewing: "processing",
|
||||
rejected: "error",
|
||||
pending_sign: "warning",
|
||||
signing: "purple",
|
||||
pending_collect: "cyan",
|
||||
pending_archive: "orange",
|
||||
completed: "success",
|
||||
terminated: "default",
|
||||
};
|
||||
|
||||
const actionStatusLabelMap = {
|
||||
success: "成功",
|
||||
failed: "失败",
|
||||
pending: "处理中",
|
||||
};
|
||||
|
||||
const actionStatusColorMap = {
|
||||
success: "success",
|
||||
failed: "error",
|
||||
pending: "processing",
|
||||
};
|
||||
|
||||
const queryParam = ref({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
contractName: undefined,
|
||||
contractCode: undefined,
|
||||
operatorName: undefined,
|
||||
actionType: undefined,
|
||||
actionStatus: undefined,
|
||||
dateRange: [],
|
||||
});
|
||||
|
||||
const queryCardBodyStyle = {
|
||||
padding: "20px 24px 6px",
|
||||
};
|
||||
|
||||
const tableCardBodyStyle = {
|
||||
display: "flex",
|
||||
flexFlow: "column nowrap",
|
||||
minHeight: "0",
|
||||
padding: "8px 12px 6px",
|
||||
};
|
||||
|
||||
function isSuccessResponse(res) {
|
||||
return String(res?.code || "") === "0000";
|
||||
}
|
||||
|
||||
function getPageResult(res) {
|
||||
return res?.result || {};
|
||||
}
|
||||
|
||||
function getResponseMessage(res, fallback = "操作失败") {
|
||||
return res?.message || res?.msg || fallback;
|
||||
}
|
||||
|
||||
function connectToolbar() {
|
||||
const table = tableRef.value;
|
||||
const toolbar = toolbarRef.value;
|
||||
if (table && toolbar && table.connect) {
|
||||
table.connect(toolbar);
|
||||
}
|
||||
}
|
||||
|
||||
function buildQueryPayload() {
|
||||
return {
|
||||
pageNo: queryParam.value.pageNo,
|
||||
pageSize: queryParam.value.pageSize,
|
||||
contractName: queryParam.value.contractName,
|
||||
contractCode: queryParam.value.contractCode,
|
||||
operatorName: queryParam.value.operatorName,
|
||||
actionType: queryParam.value.actionType,
|
||||
actionStatus: queryParam.value.actionStatus,
|
||||
dateStart: queryParam.value.dateRange?.[0],
|
||||
dateEnd: queryParam.value.dateRange?.[1],
|
||||
};
|
||||
}
|
||||
|
||||
function getActionTypeLabel(value) {
|
||||
return actionTypeOptions.find((item) => item.value === value)?.label || value || "-";
|
||||
}
|
||||
|
||||
function getActionStatusLabel(value) {
|
||||
return actionStatusLabelMap[value] || value || "-";
|
||||
}
|
||||
|
||||
function getActionStatusColor(value) {
|
||||
return actionStatusColorMap[value] || "default";
|
||||
}
|
||||
|
||||
function getStatusLabel(value) {
|
||||
return statusLabelMap[value] || value || "-";
|
||||
}
|
||||
|
||||
function getStatusColor(value) {
|
||||
return statusColorMap[value] || "default";
|
||||
}
|
||||
|
||||
function prettyPayload(payload) {
|
||||
if (!payload) {
|
||||
return "-";
|
||||
}
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(payload), null, 2);
|
||||
} catch (error) {
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
queryContractLogList(buildQueryPayload())
|
||||
.then((res) => {
|
||||
if (!isSuccessResponse(res)) {
|
||||
tableData.value = [];
|
||||
total.value = 0;
|
||||
message.error(getResponseMessage(res, "日志查询失败"));
|
||||
return;
|
||||
}
|
||||
const pageResult = getPageResult(res);
|
||||
tableData.value = pageResult.data || [];
|
||||
total.value = pageResult.totalSize || 0;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
nextTick(() => {
|
||||
connectToolbar();
|
||||
syncTableHeight();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleQuery() {
|
||||
queryParam.value.pageNo = 1;
|
||||
getList();
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
formRef.value?.resetFields?.();
|
||||
Object.assign(queryParam.value, {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
contractName: undefined,
|
||||
contractCode: undefined,
|
||||
operatorName: undefined,
|
||||
actionType: undefined,
|
||||
actionStatus: undefined,
|
||||
dateRange: [],
|
||||
});
|
||||
getList();
|
||||
}
|
||||
|
||||
function onShowSizeChange(current, pageSize) {
|
||||
queryParam.value.pageNo = current;
|
||||
queryParam.value.pageSize = pageSize;
|
||||
getList();
|
||||
}
|
||||
|
||||
function handleViewDetail(row) {
|
||||
currentLog.value = row;
|
||||
detailVisible.value = true;
|
||||
}
|
||||
|
||||
function handleExportExcel() {
|
||||
exportContractLogExcel({
|
||||
...buildQueryPayload(),
|
||||
pageNo: 1,
|
||||
pageSize: 10000,
|
||||
}).then((data) => {
|
||||
const blob = new Blob([data], {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
saveAs(blob, `合同日志_${Date.now()}.xlsx`);
|
||||
message.success("日志 Excel 已导出");
|
||||
});
|
||||
}
|
||||
|
||||
function handleExportPdf() {
|
||||
queryContractLogAll({
|
||||
...buildQueryPayload(),
|
||||
pageNo: 1,
|
||||
pageSize: 10000,
|
||||
}).then((res) => {
|
||||
if (!isSuccessResponse(res)) {
|
||||
message.error(getResponseMessage(res, "日志 PDF 导出失败"));
|
||||
return;
|
||||
}
|
||||
const list = res?.result || [];
|
||||
const printWindow = window.open("", "_blank");
|
||||
if (!printWindow) {
|
||||
message.warning("浏览器拦截了新窗口,请允许弹窗后重试");
|
||||
return;
|
||||
}
|
||||
const rowsHtml = list.map((item, index) => `
|
||||
<tr>
|
||||
<td>${index + 1}</td>
|
||||
<td>${item.contractCode || ""}</td>
|
||||
<td>${item.contractName || ""}</td>
|
||||
<td>${getActionTypeLabel(item.actionType)}</td>
|
||||
<td>${item.actionName || ""}</td>
|
||||
<td>${getActionStatusLabel(item.actionStatus)}</td>
|
||||
<td>${item.operatorName || ""}</td>
|
||||
<td>${item.actionTime || ""}</td>
|
||||
<td>${item.actionContent || ""}</td>
|
||||
</tr>
|
||||
`).join("");
|
||||
printWindow.document.write(`
|
||||
<html>
|
||||
<head>
|
||||
<title>合同日志导出</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; padding: 24px; }
|
||||
h1 { font-size: 20px; margin-bottom: 16px; }
|
||||
table { width: 100%; border-collapse: collapse; table-layout: fixed; }
|
||||
th, td { border: 1px solid #d9d9d9; padding: 8px; font-size: 12px; word-break: break-all; vertical-align: top; }
|
||||
th { background: #f5f5f5; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>合同日志导出</h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>序号</th>
|
||||
<th>合同编码</th>
|
||||
<th>合同名称</th>
|
||||
<th>操作类型</th>
|
||||
<th>操作名称</th>
|
||||
<th>操作结果</th>
|
||||
<th>操作人</th>
|
||||
<th>操作时间</th>
|
||||
<th>操作内容</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>${rowsHtml}</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
printWindow.document.close();
|
||||
printWindow.focus();
|
||||
printWindow.print();
|
||||
});
|
||||
}
|
||||
|
||||
function syncTableHeight() {
|
||||
nextTick(() => {
|
||||
const viewportHeight = window.innerHeight;
|
||||
const queryHeight = queryContainer.value?.offsetHeight || 0;
|
||||
const pageHeight = pageWrapperRef.value?.$el?.offsetHeight || 56;
|
||||
tableHeight.value = Math.max(320, viewportHeight - queryHeight - pageHeight - 320);
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleQuery();
|
||||
nextTick(() => {
|
||||
connectToolbar();
|
||||
syncTableHeight();
|
||||
});
|
||||
window.addEventListener("resize", syncTableHeight);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("resize", syncTableHeight);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.section-container {
|
||||
height: calc(100vh - 116px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.query-card {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.section-query-container :deep(.ant-form-item) {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.section-query-container :deep(.ant-form-item .ant-form-item-control) {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.table-pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 12px 4px 2px;
|
||||
}
|
||||
|
||||
.log-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px 18px;
|
||||
}
|
||||
|
||||
.detail-grid span {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.detail-grid strong {
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.detail-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.section-container {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.detail-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user