<template>
|
<div class="appointment-page">
|
<div class="search-card">
|
<div class="panel-title">预约记录</div>
|
<el-form class="search-grid" @submit.prevent="doSearch">
|
<el-form-item label="被访人">
|
<el-input v-model="filters.visitedUserName" placeholder="请输入" clearable />
|
</el-form-item>
|
<el-form-item label="被访人部门">
|
<el-select v-model="filters.visitedDeptId" placeholder="请选择部门" clearable filterable>
|
<el-option v-for="dept in deptOptions" :key="dept.id" :label="dept.deptName" :value="dept.id" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="访问区域">
|
<el-select v-model="filters.accessArea" placeholder="请选择" clearable filterable>
|
<el-option v-for="area in passAreaOptions" :key="area.id" :label="area.areaName" :value="area.areaName" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="属地单位">
|
<el-select v-model="filters.localUnit" placeholder="请选择" clearable filterable>
|
<el-option v-for="unit in localUnitOptions" :key="unit.id" :label="unit.unitName" :value="unit.unitName" />
|
</el-select>
|
</el-form-item>
|
<div class="search-actions top-actions">
|
<el-button @click="resetSearch">重置</el-button>
|
<el-button type="primary" native-type="submit">搜索</el-button>
|
</div>
|
|
<el-form-item label="来访时间" class="date-span-item">
|
<el-date-picker
|
v-model="visitDateRange"
|
type="daterange"
|
unlink-panels
|
range-separator="至"
|
start-placeholder="开始时间"
|
end-placeholder="结束时间"
|
value-format="YYYY-MM-DD"
|
/>
|
</el-form-item>
|
<el-form-item label="预约状态">
|
<el-select v-model="filters.displayStatus" placeholder="请选择" clearable>
|
<el-option label="待拜访" value="PENDING_VISIT" />
|
<el-option label="生效中" value="EFFECTIVE" />
|
<el-option label="已失效" value="INVALID" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="审核状态">
|
<el-select v-model="filters.approvalStatus" placeholder="请选择" clearable>
|
<el-option label="经办人" value="LEVEL1_HANDLER" />
|
<el-option label="部门" value="LEVEL2_DEPT_LEADER" />
|
<el-option label="属地部门" value="LEVEL3_LOCAL_DEPT_LEADER" />
|
<el-option label="主管部门" value="LEVEL4_VISIT_DEPT" />
|
<el-option label="主管部门领导" value="LEVEL5_VISIT_DEPT_LEADER" />
|
<el-option label="已通过" value="APPROVED" />
|
<el-option label="已拒绝" value="REJECTED" />
|
<el-option label="已取消" value="CANCELLED" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="访客">
|
<el-input v-model="filters.visitorKeyword" placeholder="请输入" clearable />
|
</el-form-item>
|
<el-form-item label="来访事由">
|
<el-select v-model="filters.visitReasonCode" placeholder="请选择" clearable filterable>
|
<el-option v-for="reason in visitReasonOptions" :key="reason.reasonCode" :label="reason.reasonName" :value="reason.reasonCode" />
|
</el-select>
|
</el-form-item>
|
</el-form>
|
</div>
|
|
<div class="table-card">
|
<div class="table-toolbar">
|
<el-button v-permission="'appointments:export'" type="primary" plain :loading="exporting" @click="exportExcel">导出</el-button>
|
</div>
|
|
<el-table :data="rows" v-loading="loading" stripe border>
|
<el-table-column label="序号" width="68" type="index" :index="indexMethod" />
|
<el-table-column prop="visitTypeText" label="来访类型" width="110" />
|
<el-table-column prop="visitedUserName" label="被访人" width="110" show-overflow-tooltip />
|
<el-table-column prop="visitedDeptName" label="被访人部门" width="130" show-overflow-tooltip />
|
<el-table-column prop="accessAreaText" label="访问区域" min-width="140" show-overflow-tooltip />
|
<el-table-column prop="localUnitText" label="涉及属地单位" min-width="150" show-overflow-tooltip />
|
<el-table-column prop="plateNoText" label="车牌号" width="120" show-overflow-tooltip />
|
<el-table-column prop="visitReasonText" label="来访事由" min-width="140" show-overflow-tooltip />
|
<el-table-column prop="visitTimeText" label="来访时间" width="180" show-overflow-tooltip />
|
<el-table-column label="预约状态" width="100" align="center">
|
<template #default="{ row }">
|
<el-tag :type="displayStatusTagType(row.displayStatusCode)" effect="plain">
|
{{ row.displayStatusText }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column label="审核状态" width="120" align="center">
|
<template #default="{ row }">
|
<el-tag :type="approvalStatusTagType(row.approvalStatusCode)" effect="plain">
|
{{ row.approvalStatusText }}
|
</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="visitorNamesText" label="访客" min-width="140" show-overflow-tooltip />
|
<el-table-column label="操作" width="90" fixed="right">
|
<template #default="{ row }">
|
<el-button v-permission="'appointments:detail'" type="primary" link @click="openDetail(row)">详情</el-button>
|
</template>
|
</el-table-column>
|
</el-table>
|
|
<div class="pager-wrap">
|
<div class="pager-total">共 {{ total }} 条</div>
|
<el-pagination
|
background
|
layout="sizes, prev, pager, next"
|
:total="total"
|
:page-size="size"
|
:page-sizes="[10, 20, 50, 100]"
|
v-model:current-page="page"
|
@current-change="load"
|
@size-change="onSizeChange"
|
/>
|
</div>
|
</div>
|
|
<el-dialog v-model="detailVisible" title="预约详情" width="960px" destroy-on-close>
|
<template v-if="detailData">
|
<section class="detail-section">
|
<div class="detail-title-row">
|
<div class="detail-title">预约信息</div>
|
<div class="detail-statuses">
|
<el-tag :type="approvalStatusTagType(detailData.approvalStatusCode)" effect="plain">
|
{{ detailData.approvalStatusText }}
|
</el-tag>
|
<el-tag :type="displayStatusTagType(detailData.displayStatusCode)" effect="plain">
|
{{ detailData.displayStatusText }}
|
</el-tag>
|
</div>
|
</div>
|
|
<div class="detail-grid">
|
<div class="detail-item">
|
<div class="detail-label">来访类型</div>
|
<div class="detail-value">{{ detailData.visitTypeText || "-" }}</div>
|
</div>
|
<div class="detail-item">
|
<div class="detail-label">被访人</div>
|
<div class="detail-value">{{ detailData.visitedUserName || "-" }}</div>
|
</div>
|
<div class="detail-item">
|
<div class="detail-label">受访区域</div>
|
<div class="detail-value">{{ detailData.accessAreaText || "-" }}</div>
|
</div>
|
<div class="detail-item">
|
<div class="detail-label">涉及属地单位</div>
|
<div class="detail-value">{{ detailData.localUnitText || "-" }}</div>
|
</div>
|
<div class="detail-item">
|
<div class="detail-label">车牌号码</div>
|
<div class="detail-value">{{ detailData.plateNoText || "-" }}</div>
|
</div>
|
<div class="detail-item">
|
<div class="detail-label">来访事由</div>
|
<div class="detail-value">{{ detailData.visitReasonText || "-" }}</div>
|
</div>
|
<div class="detail-item">
|
<div class="detail-label">来访时间</div>
|
<div class="detail-value">{{ detailData.visitStartText || "-" }}</div>
|
</div>
|
<div class="detail-item">
|
<div class="detail-label">结束时间</div>
|
<div class="detail-value">{{ detailData.visitEndText || "-" }}</div>
|
</div>
|
</div>
|
</section>
|
|
<section class="detail-section">
|
<div class="detail-title">访客</div>
|
<el-table v-if="detailData.visitors.length" :data="detailData.visitors" border class="detail-table">
|
<el-table-column label="序号" width="68" type="index" />
|
<el-table-column prop="name" label="姓名" min-width="100" />
|
<el-table-column prop="gender" label="性别" width="80" align="center" />
|
<el-table-column prop="idCardMasked" label="身份证号" min-width="160" />
|
<el-table-column prop="mobileMasked" label="联系电话" min-width="130" />
|
<el-table-column prop="companyName" label="公司" min-width="160" show-overflow-tooltip />
|
</el-table>
|
<div v-else class="empty-text">暂无访客信息</div>
|
</section>
|
|
<section class="detail-section">
|
<div class="detail-title">审核流程</div>
|
<div v-if="flowNodes.length" class="flow-list">
|
<div v-for="(node, nodeIndex) in flowNodes" :key="`${node.nodeCode}-${nodeIndex}`" class="flow-node">
|
<div class="flow-marker-col">
|
<div class="flow-marker" :class="flowStatusClass(node.nodeStatus)"></div>
|
<div v-if="nodeIndex !== flowNodes.length - 1" class="flow-line"></div>
|
</div>
|
<div class="flow-content">
|
<div class="flow-head">
|
<span class="flow-name">{{ node.nodeName }}</span>
|
<el-tag size="small" :type="flowNodeTagType(node.nodeStatus)" effect="plain">
|
{{ flowNodeStatusText(node.nodeStatus) }}
|
</el-tag>
|
</div>
|
<div v-for="(group, groupIndex) in node.groups" :key="`${group.groupKey}-${groupIndex}`" class="flow-group">
|
<div class="flow-group-name">{{ group.groupName || "审批组" }}</div>
|
<div class="flow-users">
|
<div v-for="user in group.users" :key="user.taskId" class="flow-user">
|
<span class="flow-user-name">{{ user.userName || "-" }}</span>
|
<span class="flow-user-status">{{ flowNodeStatusText(user.taskStatus) }}</span>
|
<span class="flow-user-time">{{ formatActionTime(user.actionTime) }}</span>
|
<span v-if="user.comment" class="flow-user-comment">{{ user.comment }}</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
<div v-else class="empty-text">暂无审批流程</div>
|
</section>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup lang="ts">
|
import { onMounted, reactive, ref } from "vue";
|
import { ElMessage } from "element-plus";
|
import http from "../api/http";
|
|
type DeptOption = { id: number; deptName: string };
|
type PassAreaOption = { id: number; areaName: string };
|
type LocalUnitOption = { id: number; unitName: string };
|
type VisitReasonOption = { reasonCode: string; reasonName: string };
|
|
type AppointmentRow = {
|
id: number;
|
uuid: string;
|
orderNo: string;
|
visitTypeText: string;
|
visitedUserName: string;
|
visitedDeptName: string;
|
accessAreaText: string;
|
localUnitText: string;
|
plateNoText: string;
|
visitReasonText: string;
|
visitTimeText: string;
|
displayStatusCode: string;
|
displayStatusText: string;
|
approvalStatusCode: string;
|
approvalStatusText: string;
|
visitorNamesText: string;
|
status: string;
|
};
|
|
type AppointmentDetailVisitor = {
|
visitorId?: number;
|
name?: string;
|
gender?: string;
|
idCardMasked?: string;
|
mobileMasked?: string;
|
companyName?: string;
|
plateNo?: string;
|
driverName?: string;
|
};
|
|
type AppointmentDetail = {
|
id: number;
|
uuid: string;
|
orderNo: string;
|
visitTypeText: string;
|
visitedUserName: string;
|
visitedDeptName: string;
|
accessAreaText: string;
|
localUnitText: string;
|
plateNoText: string;
|
visitReasonText: string;
|
visitStartText: string;
|
visitEndText: string;
|
displayStatusCode: string;
|
displayStatusText: string;
|
approvalStatusCode: string;
|
approvalStatusText: string;
|
visitors: AppointmentDetailVisitor[];
|
};
|
|
type FlowUser = {
|
taskId: number;
|
userName?: string;
|
taskStatus?: string;
|
comment?: string;
|
actionTime?: string;
|
};
|
|
type FlowGroup = {
|
groupKey: string;
|
groupName?: string;
|
groupStatus?: string;
|
users: FlowUser[];
|
};
|
|
type FlowNode = {
|
nodeCode: string;
|
nodeName: string;
|
nodeStatus?: string;
|
groups: FlowGroup[];
|
};
|
|
const loading = ref(false);
|
const exporting = ref(false);
|
const page = ref(1);
|
const size = ref(10);
|
const total = ref(0);
|
const rows = ref<AppointmentRow[]>([]);
|
const visitDateRange = ref<string[]>([]);
|
|
const deptOptions = ref<DeptOption[]>([]);
|
const passAreaOptions = ref<PassAreaOption[]>([]);
|
const localUnitOptions = ref<LocalUnitOption[]>([]);
|
const visitReasonOptions = ref<VisitReasonOption[]>([]);
|
|
const detailVisible = ref(false);
|
const detailData = ref<AppointmentDetail | null>(null);
|
const flowNodes = ref<FlowNode[]>([]);
|
|
const filters = reactive({
|
visitedUserName: "",
|
visitedDeptId: undefined as number | undefined,
|
accessArea: "",
|
localUnit: "",
|
displayStatus: "",
|
approvalStatus: "",
|
visitorKeyword: "",
|
visitReasonCode: "",
|
});
|
|
function indexMethod(index: number) {
|
return (page.value - 1) * size.value + index + 1;
|
}
|
|
function buildParams() {
|
return {
|
page: page.value,
|
size: size.value,
|
visitedUserName: filters.visitedUserName.trim() || undefined,
|
visitedDeptId: filters.visitedDeptId,
|
accessArea: filters.accessArea || undefined,
|
localUnit: filters.localUnit || undefined,
|
visitDateStart: visitDateRange.value?.[0] || undefined,
|
visitDateEnd: visitDateRange.value?.[1] || undefined,
|
displayStatus: filters.displayStatus || undefined,
|
approvalStatus: filters.approvalStatus || undefined,
|
visitorKeyword: filters.visitorKeyword.trim() || undefined,
|
visitReasonCode: filters.visitReasonCode || undefined,
|
};
|
}
|
|
async function load() {
|
loading.value = true;
|
try {
|
const { data } = await http.get("/appointments", { params: buildParams() });
|
rows.value = (data.result || []) as AppointmentRow[];
|
total.value = Number(data.total) || 0;
|
} catch (e: unknown) {
|
ElMessage.error(e instanceof Error ? e.message : "加载失败");
|
} finally {
|
loading.value = false;
|
}
|
}
|
|
async function loadLookups() {
|
const [deptRes, reasonRes, areaRes, unitRes] = await Promise.all([
|
http.get("/sys/depts"),
|
http.get("/config/visit-reasons"),
|
http.get("/sys/pass-access-areas", { params: { page: 1, size: 500, pid: 0 } }),
|
http.get("/sys/local-units", { params: { page: 1, size: 500 } }),
|
]);
|
deptOptions.value = (deptRes.data.result || []) as DeptOption[];
|
visitReasonOptions.value = ((reasonRes.data.result || []) as VisitReasonOption[]).filter((item) => item.reasonCode && item.reasonName);
|
passAreaOptions.value = (areaRes.data.result || []) as PassAreaOption[];
|
localUnitOptions.value = (unitRes.data.result || []) as LocalUnitOption[];
|
}
|
|
function doSearch() {
|
page.value = 1;
|
load();
|
}
|
|
function resetSearch() {
|
filters.visitedUserName = "";
|
filters.visitedDeptId = undefined;
|
filters.accessArea = "";
|
filters.localUnit = "";
|
filters.displayStatus = "";
|
filters.approvalStatus = "";
|
filters.visitorKeyword = "";
|
filters.visitReasonCode = "";
|
visitDateRange.value = [];
|
page.value = 1;
|
load();
|
}
|
|
function onSizeChange(value: number) {
|
size.value = value;
|
page.value = 1;
|
load();
|
}
|
|
function displayStatusTagType(code?: string) {
|
if (code === "EFFECTIVE") return "success";
|
if (code === "PENDING_VISIT") return "warning";
|
return "info";
|
}
|
|
function approvalStatusTagType(code?: string) {
|
if (code === "APPROVED") return "success";
|
if (code === "REJECTED") return "danger";
|
if (code === "CANCELLED") return "info";
|
return "warning";
|
}
|
|
function resolveExportFileName(contentDisposition?: string) {
|
if (!contentDisposition) return "appointments.xlsx";
|
const utf8Match = contentDisposition.match(/filename\*=UTF-8''([^;]+)/i);
|
if (utf8Match?.[1]) {
|
return decodeURIComponent(utf8Match[1]);
|
}
|
const plainMatch = contentDisposition.match(/filename="?([^"]+)"?/i);
|
return plainMatch?.[1] || "appointments.xlsx";
|
}
|
|
async function exportExcel() {
|
exporting.value = true;
|
try {
|
const response = await http.get("/appointments/export", {
|
params: {
|
visitedUserName: filters.visitedUserName.trim() || undefined,
|
visitedDeptId: filters.visitedDeptId,
|
accessArea: filters.accessArea || undefined,
|
localUnit: filters.localUnit || undefined,
|
visitDateStart: visitDateRange.value?.[0] || undefined,
|
visitDateEnd: visitDateRange.value?.[1] || undefined,
|
displayStatus: filters.displayStatus || undefined,
|
approvalStatus: filters.approvalStatus || undefined,
|
visitorKeyword: filters.visitorKeyword.trim() || undefined,
|
visitReasonCode: filters.visitReasonCode || undefined,
|
},
|
responseType: "blob",
|
});
|
const blob = response.data as Blob;
|
const contentType = response.headers["content-type"];
|
if (typeof contentType === "string" && contentType.includes("application/json")) {
|
const text = await blob.text();
|
const body = JSON.parse(text) as { message?: string };
|
throw new Error(body.message || "导出失败");
|
}
|
const url = window.URL.createObjectURL(blob);
|
const link = document.createElement("a");
|
link.href = url;
|
link.download = resolveExportFileName(response.headers["content-disposition"]);
|
document.body.appendChild(link);
|
link.click();
|
document.body.removeChild(link);
|
window.URL.revokeObjectURL(url);
|
ElMessage.success("导出成功");
|
} catch (e: unknown) {
|
ElMessage.error(e instanceof Error ? e.message : "导出失败");
|
} finally {
|
exporting.value = false;
|
}
|
}
|
|
async function openDetail(row: AppointmentRow) {
|
try {
|
const [detailRes, flowRes] = await Promise.all([
|
http.get(`/appointments/${row.id}`, { params: { uuid: row.uuid } }),
|
http.get("/approvals/flow-tree", { params: { orderId: row.id, orderUuid: row.uuid } }),
|
]);
|
detailData.value = (detailRes.data.result || null) as AppointmentDetail | null;
|
flowNodes.value = (flowRes.data.result || []) as FlowNode[];
|
detailVisible.value = true;
|
} catch (e: unknown) {
|
ElMessage.error(e instanceof Error ? e.message : "加载详情失败");
|
}
|
}
|
|
function flowNodeTagType(status?: string) {
|
if (status === "APPROVED") return "success";
|
if (status === "REJECTED") return "danger";
|
if (status === "PENDING") return "warning";
|
return "info";
|
}
|
|
function flowStatusClass(status?: string) {
|
if (status === "APPROVED") return "is-approved";
|
if (status === "REJECTED") return "is-rejected";
|
if (status === "PENDING") return "is-pending";
|
return "is-waiting";
|
}
|
|
function flowNodeStatusText(status?: string) {
|
if (status === "APPROVED") return "已通过";
|
if (status === "REJECTED") return "已拒绝";
|
if (status === "PENDING") return "审批中";
|
if (status === "WAITING") return "待处理";
|
return status || "-";
|
}
|
|
function formatActionTime(value?: string) {
|
if (!value) return "-";
|
return String(value).replace("T", " ").slice(0, 16);
|
}
|
|
onMounted(async () => {
|
try {
|
await loadLookups();
|
} catch (e: unknown) {
|
ElMessage.error(e instanceof Error ? e.message : "加载筛选项失败");
|
}
|
load();
|
});
|
</script>
|
|
<style scoped>
|
.appointment-page {
|
display: flex;
|
flex-direction: column;
|
gap: 12px;
|
}
|
|
.search-card,
|
.table-card {
|
background: #ffffff;
|
border: 1px solid #e5ecfb;
|
border-radius: 10px;
|
box-shadow: 0 8px 24px rgba(77, 111, 197, 0.06);
|
}
|
|
.search-card {
|
padding: 16px 18px 10px;
|
}
|
|
.panel-title {
|
color: #2e446e;
|
font-size: 18px;
|
font-weight: 700;
|
margin-bottom: 14px;
|
}
|
|
.search-grid {
|
display: grid;
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
gap: 8px 18px;
|
align-items: start;
|
}
|
|
.search-grid :deep(.el-form-item) {
|
margin-bottom: 12px;
|
}
|
|
.search-grid :deep(.el-form-item__label) {
|
color: #42567f;
|
font-weight: 600;
|
}
|
|
.search-grid :deep(.el-input),
|
.search-grid :deep(.el-select),
|
.search-grid :deep(.el-date-editor) {
|
width: 100%;
|
}
|
|
.search-actions {
|
display: flex;
|
justify-content: flex-end;
|
gap: 10px;
|
}
|
|
.top-actions {
|
align-items: flex-start;
|
padding-top: 2px;
|
}
|
|
.date-span-item {
|
grid-column: span 2;
|
}
|
|
.table-card {
|
padding: 14px 16px 12px;
|
}
|
|
.table-toolbar {
|
display: flex;
|
justify-content: flex-start;
|
margin-bottom: 12px;
|
}
|
|
.pager-wrap {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-top: 14px;
|
}
|
|
.pager-total {
|
color: #7484a6;
|
font-size: 13px;
|
}
|
|
.detail-table {
|
margin-top: 16px;
|
}
|
|
.detail-section + .detail-section {
|
margin-top: 22px;
|
}
|
|
.detail-title-row {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
margin-bottom: 16px;
|
}
|
|
.detail-title {
|
color: #2e446e;
|
font-size: 16px;
|
font-weight: 700;
|
}
|
|
.detail-statuses {
|
display: flex;
|
gap: 8px;
|
}
|
|
.detail-grid {
|
display: grid;
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
gap: 14px 32px;
|
}
|
|
.detail-item {
|
display: flex;
|
align-items: flex-start;
|
gap: 12px;
|
min-height: 24px;
|
}
|
|
.detail-label {
|
width: 88px;
|
color: #6b7a99;
|
flex: none;
|
}
|
|
.detail-value {
|
color: #2f3d57;
|
line-height: 1.6;
|
word-break: break-word;
|
}
|
|
.flow-list {
|
display: flex;
|
flex-direction: column;
|
gap: 2px;
|
}
|
|
.flow-node {
|
display: flex;
|
gap: 14px;
|
}
|
|
.flow-marker-col {
|
width: 20px;
|
display: flex;
|
flex-direction: column;
|
align-items: center;
|
flex: none;
|
}
|
|
.flow-marker {
|
width: 10px;
|
height: 10px;
|
border-radius: 50%;
|
margin-top: 8px;
|
background: #c0c4cc;
|
}
|
|
.flow-marker.is-approved {
|
background: #67c23a;
|
}
|
|
.flow-marker.is-rejected {
|
background: #f56c6c;
|
}
|
|
.flow-marker.is-pending {
|
background: #e6a23c;
|
}
|
|
.flow-line {
|
width: 2px;
|
flex: 1;
|
background: #dcdfe6;
|
margin-top: 4px;
|
}
|
|
.flow-content {
|
flex: 1;
|
padding-bottom: 16px;
|
}
|
|
.flow-head {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
margin-bottom: 8px;
|
}
|
|
.flow-name {
|
color: #2f3d57;
|
font-weight: 600;
|
}
|
|
.flow-group {
|
padding: 10px 12px;
|
border: 1px solid #ebeef5;
|
border-radius: 8px;
|
background: #fafcff;
|
}
|
|
.flow-group + .flow-group {
|
margin-top: 8px;
|
}
|
|
.flow-group-name {
|
color: #606266;
|
font-size: 13px;
|
margin-bottom: 8px;
|
}
|
|
.flow-users {
|
display: flex;
|
flex-direction: column;
|
gap: 6px;
|
}
|
|
.flow-user {
|
display: grid;
|
grid-template-columns: 120px 80px 140px 1fr;
|
gap: 10px;
|
color: #2f3d57;
|
font-size: 13px;
|
}
|
|
.flow-user-status {
|
color: #909399;
|
}
|
|
.flow-user-time {
|
color: #909399;
|
}
|
|
.flow-user-comment {
|
color: #606266;
|
}
|
|
.empty-text {
|
color: #909399;
|
text-align: center;
|
padding: 24px 0 8px;
|
}
|
|
@media (max-width: 1400px) {
|
.search-grid {
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
}
|
|
.date-span-item {
|
grid-column: span 2;
|
}
|
}
|
|
@media (max-width: 900px) {
|
.search-grid {
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
}
|
|
.date-span-item {
|
grid-column: span 2;
|
}
|
|
.detail-grid {
|
grid-template-columns: 1fr;
|
}
|
|
.flow-user {
|
grid-template-columns: 1fr;
|
}
|
}
|
</style>
|