Files
stamp-ant/src/views/contract/launch/index.vue
T
2026-06-08 14:32:00 +08:00

855 lines
26 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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.contractType" allow-clear placeholder="请输入合同类型编码" />
</a-form-item>
</a-col>
<a-col :md="6" :sm="24">
<a-form-item label="当前状态">
<a-select
v-model:value="queryParam.status"
allow-clear
placeholder="请选择状态"
:options="contractRecordStatusOptions"
/>
</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 type="primary" ghost :icon="h(PlusOutlined)" @click="handleCreate">发起合同</a-button>
<a-button danger :icon="h(DeleteOutlined)" :disabled="!selectedRowKeys.length" @click="handleBatchDelete">删除</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' }"
:checkbox-config="{ range: true }"
:data="tableData"
:loading="loading"
:loading-config="{ icon: 'vxe-icon-indicator roll', text: '正在拼命加载中...' }"
@checkbox-range-change="selectChangeEvent"
@checkbox-change="selectChangeEvent"
@checkbox-all="selectChangeEvent"
>
<vxe-column type="checkbox" width="56" />
<vxe-column type="seq" title="序号" width="68" />
<vxe-column field="contractCode" title="合同编码" width="160" show-overflow="title" />
<vxe-column field="lawContractCode" title="法务合同编号" width="160" show-overflow="title" />
<vxe-column field="contractName" title="合同名称" width="220" show-overflow="title" />
<vxe-column field="contractType" title="合同类型" width="160" show-overflow="title">
<template #default="{ row }">
{{ getContractTypeSimpleLabel(row.contractTypeCode || row.contractType) }}
</template>
</vxe-column>
<vxe-column field="templateName" title="所选模板" width="180" show-overflow="title" />
<vxe-column field="counterpartySummary" title="合同相对方" width="260" show-overflow="title" />
<vxe-column field="status" title="状态" width="120">
<template #default="{ row }">
<a-tag :color="getStatusColor(row.status)">{{ getStatusLabel(row.status) }}</a-tag>
</template>
</vxe-column>
<vxe-column field="approveMessage" title="审批说明" width="220" show-overflow="title" />
<vxe-column field="initiator" title="发起人" width="100" 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="updateTime" title="最后更新时间" width="170" show-overflow="title" />
<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 v-if="canChange(row)" @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>
<a v-if="canMockApprove(row)" @click="handleMockApprove(row)">测试通过</a>
<a v-if="canOnlineSign(row)" @click="handleOnlineSign(row)">在线签订</a>
<a v-if="row.status === 'completed' && row.signedFilePath" @click="handleDownloadSigned(row)">下载签署文件</a>
<a v-if="canUploadSigned(row)" @click="handleUploadSigned(row)">上传已签署版本</a>
<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>
</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>
<ContractPartyPlaceholderModal ref="contractModalRef" @save="handleModalSave" />
<MixedSignModal ref="mixedSignModalRef" @business-updated="handleSignBusinessUpdated" />
<UploadSignedModal ref="uploadSignedModalRef" @done="handleSignBusinessUpdated" />
</section>
</template>
<script setup>
import { h, nextTick, onBeforeUnmount, onMounted, ref } from "vue";
import {
DeleteOutlined,
PlusOutlined,
RedoOutlined,
SearchOutlined,
} from "@ant-design/icons-vue";
import { Input, message, Modal } from "ant-design-vue";
import ContractPartyPlaceholderModal from "./components/ContractPartyPlaceholderModal.vue";
import MixedSignModal from "./components/mixedSignModal.vue";
import UploadSignedModal from "./components/UploadSignedModal.vue";
import { getContractTypeSimpleLabel } from "./contractLaunchOptions.js";
import {
archiveLaunch,
copyLaunchDraft,
createLaunchChangeDraft,
createLaunchDraft,
deleteLaunchRecord,
getLaunchDetail,
mockApproveLaunch,
mockCollectLaunch,
mockRejectLaunch,
finishLaunchSeal,
queryApproveViewUrl,
queryLaunchList,
submitLaunchRecord,
downloadLaunchFile,
terminateLaunchRecord,
updateLaunchDraft,
} from "@/api/launch.js";
const contractRecordStatusOptions = [
{ label: "草稿", value: "draft" },
{ label: "审批中", value: "reviewing" },
{ label: "审批驳回", value: "rejected" },
{ label: "待签署", value: "pending_sign" },
{ label: "签署中", value: "signing" },
{ label: "待采集", value: "pending_collect" },
{ label: "待归档", value: "pending_archive" },
{ label: "已完成", value: "completed" },
{ label: "已终止", value: "terminated" },
];
const formRef = ref(null);
const queryContainer = ref(null);
const pageWrapperRef = ref(null);
const tableRef = ref(null);
const toolbarRef = ref(null);
const contractModalRef = ref(null);
const mixedSignModalRef = ref(null);
const uploadSignedModalRef = ref(null);
const total = ref(0);
const loading = ref(false);
const tableHeight = ref(420);
const tableData = ref([]);
const selectedRows = ref([]);
const selectedRowKeys = ref([]);
const queryParam = ref({
pageNo: 1,
pageSize: 10,
contractName: undefined,
contractCode: undefined,
contractType: undefined,
status: undefined,
dateRange: [],
});
const queryCardBodyStyle = {
padding: "20px 24px 6px",
};
const tableCardBodyStyle = {
display: "flex",
flexFlow: "column nowrap",
minHeight: "0",
padding: "8px 12px 6px",
};
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",
};
function isSuccessResponse(res) {
return String(res?.code || "") === "0000";
}
function getPageResult(res) {
return res?.result || {};
}
function getDataResult(res) {
return res?.result || null;
}
function getResponseMessage(res, fallback = "操作失败") {
return res?.message || res?.msg || fallback;
}
function sliceDateValue(value) {
return value ? String(value).slice(0, 10) : "";
}
function todayString() {
const now = new Date();
const month = String(now.getMonth() + 1).padStart(2, "0");
const day = String(now.getDate()).padStart(2, "0");
return `${now.getFullYear()}-${month}-${day}`;
}
function formatDateTimeValue(value) {
return value ? `${value} 00:00:00` : null;
}
function connectToolbar() {
const table = tableRef.value;
const toolbar = toolbarRef.value;
if (table && toolbar && table.connect) {
table.connect(toolbar);
}
}
function getStatusLabel(status) {
return statusLabelMap[status] || status;
}
function getStatusColor(status) {
return statusColorMap[status] || "default";
}
function canEdit(row) {
return ["draft", "rejected"].includes(row.status);
}
function canDelete(row) {
return ["draft", "rejected"].includes(row.status);
}
function canSubmit(row) {
return ["draft", "rejected"].includes(row.status);
}
function canChange(row) {
// 变更/补充协议:仅已完成/待采集/待归档的合同允许基于它发起变更
return ["completed", "pending_archive", "pending_collect"].includes(row.status);
}
function canMockApprove(row) {
return row.status === "reviewing";
}
function canMockReject(row) {
return row.status === "reviewing";
}
function canOnlineSign(row) {
// 签署中也可以再次调起签署(续签/补签),已完成则不能再签
return ["pending_sign", "signing"].includes(row.status);
}
function canMockFinishSign(row) {
return ["pending_sign", "signing"].includes(row.status);
}
function canUploadSigned(row) {
return ["pending_sign", "signing"].includes(row.status);
}
function canCollect(row) {
return row.status === "pending_collect";
}
function canArchive(row) {
return row.status === "pending_archive";
}
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);
}
function buildQueryPayload() {
return {
pageNo: queryParam.value.pageNo,
pageSize: queryParam.value.pageSize,
contractName: queryParam.value.contractName,
contractCode: queryParam.value.contractCode,
contractType: queryParam.value.contractType,
status: queryParam.value.status,
dateStart: queryParam.value.dateRange?.[0],
dateEnd: queryParam.value.dateRange?.[1],
};
}
function getList() {
loading.value = true;
queryLaunchList(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,
contractType: undefined,
status: undefined,
dateRange: [],
});
getList();
}
function onShowSizeChange(current, pageSize) {
queryParam.value.pageNo = current;
queryParam.value.pageSize = pageSize;
getList();
}
function handleCreate() {
contractModalRef.value?.showModal({}, "create");
}
function handleView(row) {
getLaunchDetail(row.id).then((res) => {
if (!isSuccessResponse(res) || !getDataResult(res)) {
message.error(getResponseMessage(res, "获取合同详情失败"));
return;
}
contractModalRef.value?.showModal(getDataResult(res), "view");
});
}
function handleEdit(row) {
getLaunchDetail(row.id).then((res) => {
if (!isSuccessResponse(res) || !getDataResult(res)) {
message.error(getResponseMessage(res, "获取合同详情失败"));
return;
}
contractModalRef.value?.showModal(getDataResult(res), "edit");
});
}
function handleCopy(row) {
Modal.confirm({
title: "确认复制合同",
content: `将复制合同【${row.contractName}】生成一份新草稿,复制后自动进入编辑状态。`,
okText: "确认复制",
cancelText: "取消",
onOk: () =>
copyLaunchDraft(row.id).then((res) => {
if (!isSuccessResponse(res) || !getDataResult(res)) {
message.error(getResponseMessage(res, "复制合同失败"));
return;
}
const copiedRecord = getDataResult(res);
message.success("合同已复制为新草稿请继续完善后保存");
getList();
contractModalRef.value?.showModal(copiedRecord, "edit");
}),
icon: null,
});
}
function handleChange(row) {
Modal.confirm({
title: "确认发起变更合同",
content: h("div", [
h("p", `将基于【${row.contractName}】发起一份变更/补充协议,`),
h("p", "自动关联主合同编码生成新草稿原合同不受影响"),
]),
okText: "确认变更",
cancelText: "取消",
onOk: () =>
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");
}),
icon: null,
});
}
function handleSubmit(row) {
Modal.confirm({
title: "确认提交法务审批",
content: h("div", [
h("p", `确定将合同【${row.contractName}】提交法务系统审批吗?`),
h("p", { style: { color: "#999", fontSize: "12px" } }, "提交后合同进入审批中状态审批完成前不可编辑"),
]),
okText: "确认提交",
cancelText: "取消",
onOk: () =>
submitLaunchRecord(row.id).then((res) => {
if (!isSuccessResponse(res)) {
message.error(getResponseMessage(res, "提交法务失败"));
return;
}
message.success("已提交法务等待审批");
getList();
}),
icon: null,
});
}
function handleViewApprove(row) {
if (!row.lawContractId) {
message.warning("当前暂无法务合同ID审批记录地址尚未生成");
return;
}
queryApproveViewUrl({
contractId: String(row.lawContractId),
ivUser: row.initiator || "admin",
}).then((res) => {
if (!isSuccessResponse(res)) {
message.error(getResponseMessage(res, "获取审批记录查看地址失败"));
return;
}
const approveViewUrl = res?.result?.approveViewUrl;
if (!approveViewUrl) {
message.warning("审批记录地址为空");
return;
}
window.open(approveViewUrl, "_blank");
});
}
function handleMockApprove(row) {
Modal.confirm({
title: "测试操作模拟审批通过",
content: `模拟法务审批通过合同【${row.contractName}】,状态将变为「待签署」。`,
okText: "确认模拟",
cancelText: "取消",
onOk: () =>
mockApproveLaunch(row.id, {}).then((res) => {
if (!isSuccessResponse(res)) {
message.error(getResponseMessage(res, "模拟审批通过失败"));
return;
}
message.success("测试审批通过完成合同已流转到待签署");
getList();
}),
icon: null,
});
}
function handleMockReject(row) {
Modal.confirm({
title: "测试操作模拟审批驳回",
content: h("div", [
h("p", `模拟法务审批驳回合同【${row.contractName}】,状态将回到「已驳回」。`),
h("p", { style: { color: "#999", fontSize: "12px" } }, "驳回后可重新编辑再提交"),
]),
okText: "确认驳回",
cancelText: "取消",
onOk: () =>
mockRejectLaunch(row.id, {}).then((res) => {
if (!isSuccessResponse(res)) {
message.error(getResponseMessage(res, "模拟审批驳回失败"));
return;
}
message.success("测试审批驳回完成合同已流转到审批驳回");
getList();
}),
icon: null,
});
}
function handleMockFinishSign(row) {
Modal.confirm({
title: "测试操作模拟签署完成",
content: `模拟合同【${row.contractName}】签署完成,状态将变为「待采集」。`,
okText: "确认模拟",
cancelText: "取消",
onOk: () => {
const signDate = sliceDateValue(row.signDate) || todayString();
const sealDate = sliceDateValue(row.sealDate) || signDate;
const activeDate = sliceDateValue(row.activeDate) || sliceDateValue(row.effectiveStart) || signDate;
return finishLaunchSeal(row.id, {
sealContractId: row.sealContractId || `TEST-SEAL-${row.id}`,
sealContractStatus: 2000,
signedFileName: `${row.contractName || "合同"}-测试已签署.pdf`,
signedFilePath: row.contractTextFilePath || `mock-signed/${row.id}.pdf`,
signedFileType: "pdf",
signedFileSize: row.contractTextFileSize || null,
signedOfflineReason: "测试按钮模拟签署完成真实签章接入后可删除此按钮",
signDate: formatDateTimeValue(signDate),
sealDate: formatDateTimeValue(sealDate),
activeDate: formatDateTimeValue(activeDate),
}).then((res) => {
if (!isSuccessResponse(res)) {
message.error(getResponseMessage(res, "测试签署完成失败"));
return;
}
message.success("测试签署完成合同已流转到待采集");
getList();
});
},
icon: null,
});
}
function handleDownloadSigned(row) {
const filePath = row.signedFilePath;
const fileName = row.signedFileName || `${row.contractName || "合同"}-已签署版.pdf`;
if (!filePath) {
message.warning("暂无签署文件可下载");
return;
}
downloadLaunchFile(filePath).then((res) => {
const blob = new Blob([res.data]);
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
}).catch(() => {
message.error("签署文件下载失败");
});
}
function handleUploadSigned(row) {
uploadSignedModalRef.value?.showModal(row);
}
function handleOnlineSign(row) {
getLaunchDetail(row.id).then((res) => {
if (!isSuccessResponse(res) || !getDataResult(res)) {
message.error(getResponseMessage(res, "获取合同详情失败"));
return;
}
const detail = getDataResult(res);
mixedSignModalRef.value?.showModal({
...detail,
contractId: detail.sealContractId,
});
});
}
function handleSignBusinessUpdated() {
getList();
}
function handleModalSave(payload) {
const requestFn = payload.mode === "edit" ? updateLaunchDraft : createLaunchDraft;
requestFn(payload.record).then((res) => {
if (!isSuccessResponse(res)) {
message.error(getResponseMessage(res, "保存合同失败"));
return;
}
message.success(
payload.successMessage ||
(payload.mode === "edit" ? "合同修改成功" : "合同草稿保存成功"),
);
getList();
});
}
function handleMockCollect(row) {
Modal.confirm({
title: "测试操作模拟合同采集",
content: `模拟将合同【${row.contractName}】信息采集同步至法务系统,状态将变为「待归档」。`,
okText: "确认模拟",
cancelText: "取消",
onOk: () =>
mockCollectLaunch(row.id).then((res) => {
if (!isSuccessResponse(res)) {
message.error(getResponseMessage(res, "模拟合同采集失败"));
return;
}
message.success("测试合同采集完成合同已流转到待归档");
getList();
}),
icon: null,
});
}
function handleArchive(row) {
Modal.confirm({
title: "确认归档合同",
content: h("div", [
h("p", `确认将合同【${row.contractName}】归档?`),
h("p", { style: { color: "#999", fontSize: "12px" } }, "归档后合同进入已完成状态全生命周期结束"),
]),
okText: "确认归档",
cancelText: "取消",
onOk: () =>
archiveLaunch(row.id, {}).then((res) => {
if (!isSuccessResponse(res)) {
message.error(getResponseMessage(res, "测试归档失败"));
return;
}
message.success("测试归档完成合同已流转到已完成");
getList();
}),
icon: null,
});
}
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}】吗?`,
content: "仅草稿或审批驳回状态支持删除",
okText: "删除",
cancelText: "取消",
okType: "danger",
onOk: () => {
deleteLaunchRecord(row.id).then((res) => {
if (!isSuccessResponse(res)) {
message.error(getResponseMessage(res, "删除失败"));
return;
}
message.success("删除成功");
getList();
});
},
});
}
function handleBatchDelete() {
const undeletable = selectedRows.value.filter((item) => !canDelete(item));
if (undeletable.length) {
message.warning("仅草稿或审批驳回状态支持删除请重新选择");
return;
}
Modal.confirm({
title: `确认删除选中的 ${selectedRowKeys.value.length} 条记录吗?`,
content: "仅草稿或审批驳回状态支持删除",
okText: "删除",
cancelText: "取消",
okType: "danger",
onOk: async () => {
for (const id of selectedRowKeys.value) {
await deleteLaunchRecord(id);
}
message.success("批量删除成功");
selectedRows.value = [];
selectedRowKeys.value = [];
getList();
},
});
}
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-actions {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.table-pagination {
display: flex;
justify-content: flex-end;
padding: 12px 4px 2px;
}
@media (max-width: 960px) {
.section-container {
height: auto;
}
}
</style>