日志
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) {
|
export function submitLaunchRecord(id) {
|
||||||
return request({
|
return request({
|
||||||
url: `/contract/launch/${id}/submit`,
|
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) {
|
export function deleteLaunchRecord(id) {
|
||||||
return request({
|
return request({
|
||||||
url: `/contract/launch/${id}`,
|
url: `/contract/launch/${id}`,
|
||||||
@@ -160,3 +175,28 @@ export function downloadLaunchFile(fileName) {
|
|||||||
responseType: "arraybuffer",
|
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 />
|
<team-outlined />
|
||||||
<span>相关方管理</span>
|
<span>相关方管理</span>
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
<a-menu-item key="/contract-console/log">
|
||||||
|
<profile-outlined />
|
||||||
|
<span>日志管理</span>
|
||||||
|
</a-menu-item>
|
||||||
</a-menu>
|
</a-menu>
|
||||||
|
|
||||||
<div class="sider-footer">
|
<div class="sider-footer">
|
||||||
@@ -50,7 +54,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
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 route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -64,6 +68,10 @@ const selectedKey = computed(() => {
|
|||||||
return '/contract-console/partner'
|
return '/contract-console/partner'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (route.path.startsWith('/contract-console/log')) {
|
||||||
|
return '/contract-console/log'
|
||||||
|
}
|
||||||
|
|
||||||
return '/contract-console/launch'
|
return '/contract-console/launch'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,14 @@ const routes = [
|
|||||||
title: '相关方管理',
|
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="initiatorDept" title="所属部门" width="140" show-overflow="title" />
|
||||||
<vxe-column field="createTime" title="发起时间" width="170" 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 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 }">
|
<template #default="{ row }">
|
||||||
<div class="table-actions">
|
<div class="table-actions">
|
||||||
<a @click="handleView(row)">查看</a>
|
<a @click="handleView(row)">查看</a>
|
||||||
<a @click="handleCopy(row)">复制</a>
|
<a @click="handleCopy(row)">复制</a>
|
||||||
|
<a @click="handleChange(row)">变更</a>
|
||||||
<a v-if="canEdit(row)" @click="handleEdit(row)">编辑</a>
|
<a v-if="canEdit(row)" @click="handleEdit(row)">编辑</a>
|
||||||
<a v-if="canSubmit(row)" @click="handleSubmit(row)">提交法务</a>
|
<a v-if="canSubmit(row)" @click="handleSubmit(row)">提交法务</a>
|
||||||
<a v-if="canViewApprove(row)" @click="handleViewApprove(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="canMockFinishSign(row)" @click="handleMockFinishSign(row)">测试签署完成</a>
|
||||||
<a v-if="canCollect(row)" @click="handleMockCollect(row)">测试采集</a>
|
<a v-if="canCollect(row)" @click="handleMockCollect(row)">测试采集</a>
|
||||||
<a v-if="canArchive(row)" @click="handleArchive(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>
|
<a v-if="canDelete(row)" @click="handleDelete(row)">删除</a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -151,13 +153,14 @@ import {
|
|||||||
RedoOutlined,
|
RedoOutlined,
|
||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
} from "@ant-design/icons-vue";
|
} 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 ContractPartyPlaceholderModal from "./components/ContractPartyPlaceholderModal.vue";
|
||||||
import MixedSignModal from "./components/mixedSignModal.vue";
|
import MixedSignModal from "./components/mixedSignModal.vue";
|
||||||
import { getContractTypeSimpleLabel } from "./contractLaunchOptions.js";
|
import { getContractTypeSimpleLabel } from "./contractLaunchOptions.js";
|
||||||
import {
|
import {
|
||||||
archiveLaunch,
|
archiveLaunch,
|
||||||
copyLaunchDraft,
|
copyLaunchDraft,
|
||||||
|
createLaunchChangeDraft,
|
||||||
createLaunchDraft,
|
createLaunchDraft,
|
||||||
deleteLaunchRecord,
|
deleteLaunchRecord,
|
||||||
getLaunchDetail,
|
getLaunchDetail,
|
||||||
@@ -168,6 +171,7 @@ import {
|
|||||||
queryApproveViewUrl,
|
queryApproveViewUrl,
|
||||||
queryLaunchList,
|
queryLaunchList,
|
||||||
submitLaunchRecord,
|
submitLaunchRecord,
|
||||||
|
terminateLaunchRecord,
|
||||||
updateLaunchDraft,
|
updateLaunchDraft,
|
||||||
} from "@/api/launch.js";
|
} from "@/api/launch.js";
|
||||||
|
|
||||||
@@ -315,6 +319,10 @@ function canViewApprove(row) {
|
|||||||
return ["reviewing", "pending_sign", "signing", "pending_collect", "pending_archive", "completed"].includes(row.status);
|
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 }) {
|
function selectChangeEvent({ records }) {
|
||||||
selectedRows.value = records;
|
selectedRows.value = records;
|
||||||
selectedRowKeys.value = records.map((item) => item.id);
|
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) {
|
function handleSubmit(row) {
|
||||||
submitLaunchRecord(row.id).then((res) => {
|
submitLaunchRecord(row.id).then((res) => {
|
||||||
if (!isSuccessResponse(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) {
|
function handleDelete(row) {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: `确认删除合同【${row.contractName}】吗?`,
|
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