Files
xmc-Assets/web/src/components/alarm/AccessAlarmRecordContent.vue
caopeng a254aae503 feat(web): 引入 Vite 前端应用并扩展仓库忽略规则
将整套 web 源码纳入仓库,并为 web/node_modules、构建产物及本地环境文件配置 .gitignore,同时移除占位用的 assets/.gitkeep。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-17 15:22:29 +08:00

2027 lines
66 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>
<div class="UserManage">
<!-- 全局头部栏 -->
<GlobalHeader :on-search="handleSearch" />
<DataTable :value="tableAccessAlarmData" :loading="loading" v-model:selection="selectedRows" dataKey="id"
@sort="onSort" @row-click="handleRowClick" paginator :rows="queryData.size" :totalRecords="totalItems"
@page="onPage" :lazy="true" :rowsPerPageOptions="[5, 10, 20, 50]" class="table" scrollable
:sortField="sortField" :sortOrder="sortOrder"
paginatorTemplate="PrevPageLink PageLinks NextPageLink RowsPerPageDropdown"
:pageLinkSize="10">
<template #paginatorprevpagelinkicon>
<span class="paginator-btn-text"><i class="pi pi-chevron-left"></i> {{ $t('common.prevPage') }}</span>
</template>
<template #paginatornextpagelinkicon>
<span class="paginator-btn-text">{{ $t('common.nextPage') }} <i class="pi pi-chevron-right"></i></span>
</template>
<template #header>
<div class="table-actions">
<div class="table-left-wrapper">
<div class="select-all-wrapper">
<Checkbox :inputId="'selectAll'" v-model="isAllSelected" :binary="true" />
<label :for="'selectAll'" class="select-all-label">
{{ $t('common.selectAll', { count: selectedRows.length }) }}
</label>
</div>
<div class="table-left" @click="handleBatchDelete">
<!-- <Button label="批量删除" icon="pi pi-trash" class="p-button-danger" @click="handleBatchDelete"
:disabled="!selectedRows || selectedRows.length === 0
" /> -->
<div class="delIcon">
<img src="../../images/login/del.png" alt="">
</div>
<div class="delItems">{{ $t('common.batchDelete') }}</div>
</div>
</div>
<div class="table-right">
<ImportExportButton export-only @export="handleExport" />
</div>
</div>
</template>
<Column selectionMode="multiple" headerStyle="width: 3rem; white-space: nowrap;"></Column>
<Column v-if="columnVisible('al_asset_number')" field="al_asset_number" headerStyle="white-space: nowrap;">
<template #header>
<div class="column-header-with-search">
<span>{{ $t('assetList.assetNumber') }}</span>
<div class="header-icons">
<img :src="getSortIcon('al_asset_number')" class="sort-icon" alt=""
@click.stop="handleSortIconClick('al_asset_number', $event)" />
<i class="pi pi-search search-icon"
@click.stop="openSearchPopup('al_asset_number', $t('assetList.assetNumber'), $event)"></i>
</div>
</div>
</template>
</Column>
<Column v-if="columnVisible('al_asset_name')" field="al_asset_name" headerStyle="white-space: nowrap;">
<template #header>
<div class="column-header-with-search">
<span>{{ $t('assetList.assetName') }}</span>
<div class="header-icons">
<img :src="getSortIcon('al_asset_name')" class="sort-icon" alt=""
@click.stop="handleSortIconClick('al_asset_name', $event)" />
<i class="pi pi-search search-icon"
@click.stop="openSearchPopup('al_asset_name', $t('assetList.assetName'), $event)"></i>
</div>
</div>
</template>
</Column>
<Column v-if="columnVisible('al_asset_pic')" field="al_asset_pic" headerStyle="white-space: nowrap;">
<template #header>
<div class="column-header-with-search">
<span>{{ $t('assetList.responsiblePerson') }}</span>
<div class="header-icons">
<img :src="getSortIcon('al_asset_pic')" class="sort-icon" alt=""
@click.stop="handleSortIconClick('al_asset_pic', $event)" />
<i class="pi pi-search search-icon"
@click.stop="openSearchPopup('al_asset_pic', $t('assetList.responsiblePerson'), $event)"></i>
</div>
</div>
</template>
</Column>
<Column v-if="columnVisible('al_asset_department')" field="al_asset_department" headerStyle="white-space: nowrap;">
<template #header>
<div class="column-header-with-search">
<span>{{ $t('assetList.assetDepartment') }}</span>
<div class="header-icons">
<img :src="getSortIcon('al_asset_department')" class="sort-icon" alt=""
@click.stop="handleSortIconClick('al_asset_department', $event)" />
<i class="pi pi-search search-icon"
@click.stop="openSearchPopup('al_asset_department', $t('assetList.assetDepartment'), $event)"></i>
</div>
</div>
</template>
</Column>
<Column v-if="columnVisible('al_factory_area_name')" field="al_factory_area_name" headerStyle="white-space: nowrap;">
<template #header>
<div class="column-header-with-search">
<span>{{ $t('alarmRecord.belongFactoryArea') }}</span>
<div class="header-icons">
<img :src="getSortIcon('al_factory_area_name')" class="sort-icon" alt=""
@click.stop="handleSortIconClick('al_factory_area_name', $event)" />
<i class="pi pi-search search-icon"
@click.stop="openSearchPopup('al_factory_area_name', $t('alarmRecord.belongFactoryArea'), $event)"></i>
</div>
</div>
</template>
</Column>
<Column v-if="columnVisible('im_factory_area_name')" field="im_factory_area_name" headerStyle="white-space: nowrap;">
<template #header>
<div class="column-header-with-search">
<span>{{ $t('alarmRecord.currentFactoryArea') }}</span>
<div class="header-icons">
<img :src="getSortIcon('im_factory_area_name')" class="sort-icon" alt=""
@click.stop="handleSortIconClick('im_factory_area_name', $event)" />
<i class="pi pi-search search-icon"
@click.stop="openSearchPopup('im_factory_area_name', $t('alarmRecord.currentFactoryArea'), $event)"></i>
</div>
</div>
</template>
</Column>
<Column v-if="columnVisible('alarm_time')" field="alarm_time" headerStyle="white-space: nowrap;">
<template #header>
<div class="column-header-with-search">
<span>{{ $t('alarmRecord.alarmTime') }}</span>
<div class="header-icons">
<img :src="getSortIcon('alarm_time')" class="sort-icon" alt=""
@click.stop="handleSortIconClick('alarm_time', $event)" />
<i class="pi pi-search search-icon"
@click.stop="openSearchPopup('alarm_time', $t('alarmRecord.alarmTime'), $event)"></i>
</div>
</div>
</template>
<template #body="{ data }">
{{ formatDateTime(data.alarm_time) }}
</template>
</Column>
<Column v-if="columnVisible('im_name')" field="im_name" headerStyle="white-space: nowrap;">
<template #header>
<div class="column-header-with-search">
<span>{{ $t('alarmRecord.infoMachineName') }}</span>
<div class="header-icons">
<img :src="getSortIcon('im_name')" class="sort-icon" alt=""
@click.stop="handleSortIconClick('im_name', $event)" />
<i class="pi pi-search search-icon"
@click.stop="openSearchPopup('im_name', $t('alarmRecord.infoMachineName'), $event)"></i>
</div>
</div>
</template>
</Column>
<Column v-if="columnVisible('signal_machine')" field="al_asset_name" headerStyle="white-space: nowrap;">
<template #header>
<div class="column-header-with-search">
<span>{{ $t('alarmRecord.signalMachine') }}</span>
<div class="header-icons">
<img :src="getSortIcon('al_asset_name')" class="sort-icon" alt=""
@click.stop="handleSortIconClick('al_asset_name', $event)" />
<i class="pi pi-search search-icon"
@click.stop="openSearchPopup('al_asset_name', $t('alarmRecord.signalMachine'), $event)"></i>
</div>
</div>
</template>
</Column>
<Column v-if="columnVisible('epc')" field="epc" headerStyle="white-space: nowrap;">
<template #header>
<div class="column-header-with-search">
<span>EPC</span>
<div class="header-icons">
<img :src="getSortIcon('epc')" class="sort-icon" alt=""
@click.stop="handleSortIconClick('epc', $event)" />
<i class="pi pi-search search-icon"
@click.stop="openSearchPopup('epc', 'EPC', $event)"></i>
</div>
</div>
</template>
</Column>
<Column v-if="columnVisible('im_information_machine_type')" field="im_information_machine_type" headerStyle="white-space: nowrap;">
<template #header>
<div class="column-header-with-search">
<span>{{ $t('alarmRecord.alarmType') }}</span>
<div class="header-icons">
<img :src="getSortIcon('im_information_machine_type')" class="sort-icon" alt=""
@click.stop="handleSortIconClick('im_information_machine_type', $event)" />
<i class="pi pi-search search-icon"
@click.stop="openSearchPopup('im_information_machine_type', $t('alarmRecord.alarmType'), $event)"></i>
</div>
</div>
</template>
</Column>
<Column v-if="columnVisible('al_type_specification')" field="al_type_specification" headerStyle="white-space: nowrap;">
<template #header>
<div class="column-header-with-search">
<span>{{ $t('assetList.typeSpecification') }}</span>
<div class="header-icons">
<img :src="getSortIcon('al_type_specification')" class="sort-icon" alt=""
@click.stop="handleSortIconClick('al_type_specification', $event)" />
<i class="pi pi-search search-icon"
@click.stop="openSearchPopup('al_type_specification', $t('assetList.typeSpecification'), $event)"></i>
</div>
</div>
</template>
</Column>
<Column v-if="columnVisible('create_at')" field="create_at" headerStyle="white-space: nowrap;">
<template #header>
<div class="column-header-with-search">
<span>{{ $t('common.createTime') }}</span>
<div class="header-icons">
<img :src="getSortIcon('create_at')" class="sort-icon" alt=""
@click.stop="handleSortIconClick('create_at', $event)" />
<i class="pi pi-search search-icon"
@click.stop="openSearchPopup('create_at', $t('common.createTime'), $event)"></i>
</div>
</div>
</template>
<template #body="{ data }">
{{ formatDateTime(data.create_at) }}
</template>
</Column>
<template #paginatorstart>
<span class="select-all-label-paginator">{{ $t('common.selectedTotal', { selected: selectedRows.length, total: totalItems }) }}</span>
</template>
</DataTable>
<!-- 删除确认弹窗 -->
<!-- <Dialog v-model:visible="deleteDialogVisible" header="确认删除" modal :style="{ width: '300px' }">
<p>
{{
deleteDialogType === "batch"
? `确定要删除选中的 ${selectedRows.length} 条数据吗?`
: "确定要删除这条数据吗?"
}}
</p>
<template #footer>
<Button label="取消" icon="pi pi-times" @click="deleteDialogVisible = false" class="p-button-text" />
<Button label="确定" icon="pi pi-check" :loading="loading" @click="confirmDelete" class="p-button-danger"
autofocus />
</template>
</Dialog> -->
<!-- 删除确认弹窗原生 div好改样式 -->
<div v-show="deleteDialogVisible" class="delete-dialog-overlay" @click.self="deleteDialogVisible = false">
<div class="del_box" @click.stop>
<div class="header">
<div class="left">
<div class="img_box">
<img src="../../images/login/confirm.png" alt="">
</div>
<div class="del_name">{{ $t('common.confirmDelete') }}</div>
</div>
<div class="del_icon" @click="deleteDialogVisible = false">
<img src="../../images/login/delClose.png" alt="">
</div>
</div>
<div class="delText">
{{
deleteDialogType === "batch"
? $t('common.deleteBatchConfirm', { count: selectedRows.length })
: $t('common.deleteSingleConfirm')
}}
</div>
<div class="del-btn">
<div class="btnAncle" @click="deleteDialogVisible = false">{{ $t('common.cancel') }}</div>
<div class="btnOk" :class="{ 'btnOk--loading': loading }"
:style="{ pointerEvents: loading ? 'none' : 'auto' }" @click="confirmDelete">{{ $t('common.confirm') }}</div>
</div>
</div>
</div>
<!-- 列搜索弹窗 -->
<div v-if="searchPopupVisible" class="column-search-popup" :style="searchPopupStyle" @click.stop>
<!-- 日期范围选择器用于创建时间列 -->
<Calendar v-if="currentSearchColumn === 'create_at'" v-model="currentDateRange" selectionMode="range"
:placeholder="$t('common.selectTimeRange', { label: currentSearchLabel })" class="search-popup-input" dateFormat="yy-mm-dd" showIcon
:manualInput="false" showTime showSeconds hourFormat="24" panelClass="datepicker-panel-scale-07" />
<!-- 文本输入框用于其他列 -->
<InputText v-else v-model="currentSearchValue" :placeholder="$t('common.searchColumnPlaceholder', { label: currentSearchLabel })"
class="search-popup-input" @keyup.enter="handleColumnSearch" />
<div class="search-popup-buttons">
<div class="search_reset" @click="handleColumnReset">{{ $t('common.reset') }}</div>
<div class="search-btn" @click="handleColumnSearch">{{ $t('common.searchBtn') }}</div>
</div>
</div>
<!-- 遮罩层用于点击外部关闭弹窗 -->
<div v-if="searchPopupVisible" class="search-popup-overlay" @click="closeSearchPopup"></div>
<!-- 失败/错误提示与资产台账一致右上角原生样式 -->
<div v-if="showFailToast" class="edit-fail-toast">
<div class="totast_wrapper">
<div class="left_box">
<div class="edit-fail-toast-icon">
<img src="../../images/commen/fail.png" alt="失败" />
</div>
<div class="edit-fail-toast-title">{{ failToastContent.title }}</div>
</div>
<div class="edit-fail-toast-close" @click="closeFailToast">
<img src="../../images/commen/close.png" alt="关闭" />
</div>
</div>
<div class="edit-fail-toast-content">
<div class="edit-fail-toast-detail">{{ failToastContent.detail }}</div>
</div>
</div>
<!-- 成功提示与失败提示统一右上角左对齐布局 -->
<div v-if="showSuccessToast" class="edit-success-toast">
<div class="totast_wrapper">
<div class="left_box">
<div class="edit-success-toast-icon">
<img src="../../images/commen/success.png" alt="成功" />
</div>
<div class="edit-success-toast-title">{{ successToastContent.title }}</div>
</div>
<div class="edit-success-toast-close" @click="closeSuccessToast">
<img src="../../images/commen/close.png" alt="关闭" />
</div>
</div>
<div class="edit-success-toast-content">
<div class="edit-success-toast-detail">{{ successToastContent.detail }}</div>
</div>
</div>
</div>
</template>
<script setup>
import {
getTablist,
delTablist,
exportAccess,
} from "../../api/asset/accessalarm";
import ImportExportButton from "../ImportExportButton.vue";
import { ref, reactive, onMounted, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useSystemStore } from "../../store/system";
import GlobalHeader from "../GlobalHeader.vue";
import DataTable from "primevue/datatable";
import Column from "primevue/column";
import Button from "primevue/button";
import InputText from "primevue/inputtext";
import Dialog from "primevue/dialog";
import Calendar from "primevue/calendar";
import Checkbox from "primevue/checkbox";
import topIcon from "../../images/login/top.png";
import bottomIcon from "../../images/login/bottom.png";
import noneIcon from "../../images/login/none.png";
const systemStore = useSystemStore();
const { t } = useI18n();
// 表头列 field 与字段权限 key与 fieldPermissionGroups AssetAlarmRecord 一致)的映射,用于按 advanced_permissions 控制列显示
const ASSET_ALARM_RECORD_COLUMN_PERMISSION_KEY = {
al_asset_number: "asset_no_assetAlarmRecord",
al_asset_name: "asset_name_assetAlarmRecord",
al_asset_pic: "responsible_assetAlarmRecord",
al_asset_department: "asset_dept_assetAlarmRecord",
al_factory_area_name: "factory_area_assetAlarmRecord",
im_factory_area_name: "current_factory_assetAlarmRecord",
alarm_time: "alarm_time_assetAlarmRecord",
im_name: "info_machine_name_assetAlarmRecord",
signal_machine: "signal_machine_assetAlarmRecord", // 信号机列(与资产名称同 field用逻辑 key 区分)
epc: "epc_assetAlarmRecord",
im_information_machine_type: "alarm_type_assetAlarmRecord",
al_type_specification: "spec_model_assetAlarmRecord",
};
const ASSET_ALARM_RECORD_PERMISSION_KEYS = new Set(Object.values(ASSET_ALARM_RECORD_COLUMN_PERMISSION_KEY));
/** 当前用户是否有某列的字段权限。为空或与本模块无交集时显示全部列;有本模块权限时才按权限过滤列 */
const columnVisible = (field) => {
const codes = systemStore.advancedPermissionCodes;
if (!codes || codes.length === 0) return true;
const hasAnyAlarmRecord = codes.some((k) => ASSET_ALARM_RECORD_PERMISSION_KEYS.has(k));
if (!hasAnyAlarmRecord) return true;
const permissionKey = ASSET_ALARM_RECORD_COLUMN_PERMISSION_KEY[field];
if (!permissionKey) return true;
return codes.includes(permissionKey);
};
// 原生提示(与资产台账一致)
const showFailToast = ref(false);
const failToastContent = reactive({ title: "", detail: "" });
let failToastTimer = null;
const showSuccessToast = ref(false);
const successToastContent = reactive({ title: "", detail: "" });
let successToastTimer = null;
const FAIL_TOAST_AUTO_CLOSE_MS = 3000;
const showFailToastMsg = (title, detail) => {
failToastContent.title = title || t("alarmRecord.error");
failToastContent.detail = detail || "";
showFailToast.value = true;
if (failToastTimer) clearTimeout(failToastTimer);
failToastTimer = setTimeout(() => { showFailToast.value = false; }, FAIL_TOAST_AUTO_CLOSE_MS);
};
const showSuccessToastMsg = (title, detail) => {
successToastContent.title = title || t("common.success");
successToastContent.detail = detail || "";
showSuccessToast.value = true;
if (successToastTimer) clearTimeout(successToastTimer);
successToastTimer = setTimeout(() => { showSuccessToast.value = false; }, FAIL_TOAST_AUTO_CLOSE_MS);
};
const closeFailToast = () => {
showFailToast.value = false;
if (failToastTimer) clearTimeout(failToastTimer);
failToastTimer = null;
};
const closeSuccessToast = () => {
showSuccessToast.value = false;
if (successToastTimer) clearTimeout(successToastTimer);
successToastTimer = null;
};
const queryData = ref({
page: 1,
size: 10,
});
// 状态管理
const loading = ref(false);
const tableAccessAlarmData = ref([]);
const totalItems = ref(0);
const dialogVisible = ref(false);
const deleteDialogVisible = ref(false);
const isReadonly = ref(false); // 新增:控制表单是否只读
const dialogType = ref("add"); // add, edit, info
const deleteDialogType = ref("single");
const currentId = ref(null);
const selectedRows = ref([]); // 存储选中的行
const formInline = reactive({
user: "",
sort: {
column: "create_at",
num: "desc",
},
});
// 门禁报警记录表单数据
const formUserManageData = reactive({
comment: "",
name: "",
password: "",
checked_password: "",
employee_id: null,
roles: [],
});
// 员工列表和角色列表从 store 获取
const employeeOptions = computed(() => systemStore.employeeOptions);
const roleOptions = computed(() => systemStore.roleOptions);
// 计算角色显示文本当选择的角色超过2个时显示
const selectedRolesLabel = computed(() => {
if (!formUserManageData.roles || formUserManageData.roles.length === 0) {
return "";
}
const count = formUserManageData.roles.length;
return `已选择${count}个角色`;
});
// 计算&nbsp;&nbsp;描述信息字符数
const commentCharCount = computed(() => {
return formUserManageData.comment ? formUserManageData.comment.length : 0;
});
const maxCommentLength = 500;
// 全选状态
const isAllSelected = computed({
get: () => {
return selectedRows.value.length > 0 && selectedRows.value.length === tableAccessAlarmData.value.length;
},
set: (value) => {
if (value) {
selectedRows.value = [...tableAccessAlarmData.value];
} else {
selectedRows.value = [];
}
}
});
// 列搜索相关状态
const searchPopupVisible = ref(false);
const currentSearchColumn = ref('');
const currentSearchLabel = ref('');
const currentSearchValue = ref('');
const currentDateRange = ref(null); // 日期范围选择器的值
const searchPopupStyle = ref({});
const columnFilters = ref({}); // 存储每列的筛选值 { column: value }
const formatDateTime = (timestamp) => {
if (!timestamp && timestamp !== 0) return "-";
try {
// 处理时间戳(可能是数字或字符串)
let date;
if (typeof timestamp === 'number' || (typeof timestamp === 'string' && /^\d+$/.test(timestamp))) {
// 如果是时间戳转换为毫秒如果小于13位认为是秒级时间戳
const ts = Number(timestamp);
if (ts === 0) return "-";
date = new Date(ts < 10000000000 ? ts * 1000 : ts);
} else {
// 如果是ISO字符串或其他格式
date = new Date(timestamp);
}
if (isNaN(date.getTime())) return "-";
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hours = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
} catch (error) {
console.error("日期格式化错误:", error);
return "-";
}
};
// 计算排序字段(用于 DataTable 的 sortField 属性)
const sortField = computed(() => formInline.sort.column || "create_at");
// 计算排序顺序(用于 DataTable 的 sortOrder 属性1=升序,-1=降序)
const sortOrder = computed(() => formInline.sort.num === "asc" ? 1 : -1);
// 表头排序图标:未排序 none升序 top降序 bottom对应 images/login
const getSortIcon = (field) => {
if (sortField.value !== field) return noneIcon;
return sortOrder.value === 1 ? topIcon : bottomIcon;
};
// 处理排序图标点击
const handleSortIconClick = (field, event) => {
event.stopPropagation(); // 阻止事件冒泡
if (formInline.sort.column === field) {
// 如果当前列已排序,切换升序/降序
formInline.sort.num = formInline.sort.num === "asc" ? "desc" : "asc";
} else {
// 如果点击的是其他列,设置为该列升序
formInline.sort.column = field;
formInline.sort.num = "asc";
}
fetchUserManageDataList();
};
// 获取门禁报警记录数据
const fetchUserManageDataList = async () => {
// 构建filter对象包含所有表头字段
const filter = {
al_asset_number: columnFilters.value.al_asset_number || "",
im_number: columnFilters.value.im_number || "",
al_asset_name: columnFilters.value.al_asset_name || "",
al_asset_pic: columnFilters.value.al_asset_pic || "",
al_factory_area_name: columnFilters.value.al_factory_area_name || "",
al_asset_department: columnFilters.value.al_asset_department || "",
im_name: columnFilters.value.im_name || "",
al_type_specification: columnFilters.value.al_type_specification || "",
};
// 处理日期范围筛选,使用 create_at 作为字段名
if (columnFilters.value.create_at) {
filter.create_at = {
start_time: columnFilters.value.create_at.start_time || "",
end_time: columnFilters.value.create_at.end_time || ""
};
} else {
filter.create_at = {
start_time: "",
end_time: ""
};
}
const params = {
page: {
page_num: queryData.value.page,
page_size: queryData.value.size,
},
search: formInline.user || "",
sort: [{
column: formInline.sort.column,
order: formInline.sort.num,
}],
filter: filter,
};
// loading.value = true;
try {
const res = await getTablist(params);
tableAccessAlarmData.value = res.data || [];
totalItems.value = res.total || 0;
} catch (error) {
// 从错误对象中提取 msg 字段优先顺序response.data.msg > response.data.message > data.msg > data.message > message
let errorMsg = t("alarmRecord.getDataFail");
if (error?.response?.data?.msg) {
errorMsg = `${t("alarmRecord.accessAlarmListErrorPrefix")}${error.response.data.msg}`;
} else if (error?.response?.data?.message) {
errorMsg = `${t("alarmRecord.accessAlarmListErrorPrefix")}${error.response.data.message}`;
} else if (error?.data?.msg) {
errorMsg = `${t("alarmRecord.accessAlarmListErrorPrefix")}${error.data.msg}`;
} else if (error?.data?.message) {
errorMsg = `${t("alarmRecord.accessAlarmListErrorPrefix")}${error.data.message}`;
} else if (error?.message && error.message !== "Error") {
errorMsg = `${t("alarmRecord.accessAlarmListErrorPrefix")}${error.message}`;
}
showFailToastMsg(t("alarmRecord.error"), errorMsg);
console.error(error);
} finally {
setTimeout(() => {
loading.value = false;
}, 500);
}
};
// 处理搜索
const handleSearch = (searchText) => {
formInline.user = searchText || "";
selectedRows.value = [];
fetchUserManageDataList();
};
const onPage = (event) => {
queryData.value.page = event.page + 1;
queryData.value.size = event.rows;
fetchUserManageDataList();
};
// 导出门禁报警记录
const handleExport = async () => {
const filter = {
al_asset_number: columnFilters.value.al_asset_number || "",
im_number: columnFilters.value.im_number || "",
al_asset_name: columnFilters.value.al_asset_name || "",
al_asset_pic: columnFilters.value.al_asset_pic || "",
al_factory_area_name: columnFilters.value.al_factory_area_name || "",
al_asset_department: columnFilters.value.al_asset_department || "",
im_name: columnFilters.value.im_name || "",
al_type_specification: columnFilters.value.al_type_specification || "",
};
if (columnFilters.value.create_at) {
filter.create_at = {
start_time: columnFilters.value.create_at.start_time || "",
end_time: columnFilters.value.create_at.end_time || "",
};
} else {
filter.create_at = { start_time: "", end_time: "" };
}
const payload = { search: formInline.user || "", filter };
if (selectedRows.value.length > 0) {
payload.ids = selectedRows.value.map((r) => r.id);
}
try {
const res = await exportAccess(payload);
const blob = res?.data instanceof Blob ? res.data : res;
if (!blob) throw new Error(t("alarmRecord.exportFail"));
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `${t("alarmRecord.accessAlarmExportFileName")}_${Date.now()}.xlsx`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
showSuccessToastMsg(t("common.success"), t("alarmRecord.exportSuccess"));
} catch (err) {
const msg = err?.response?.data?.msg ?? err?.data?.msg ?? err?.message ?? t("alarmRecord.exportFail");
showFailToastMsg(t("common.operationFail"), msg);
}
};
// 新增
const handleAdd = () => {
dialogType.value = "add";
currentId.value = null;
isReadonly.value = false;
formUserManageData.name = "";
formUserManageData.comment = "";
formUserManageData.password = "";
formUserManageData.checked_password = "";
formUserManageData.employee_id = null;
formUserManageData.roles = [];
dialogVisible.value = true;
};
// 行点击事件
const handleRowClick = (event) => {
// 如果点击的是复选框区域,不触发编辑
if (event.originalEvent && event.originalEvent.target.closest('.p-checkbox')) {
return;
}
handleEdit(event.data);
};
// 编辑
const handleEdit = (row) => {
dialogType.value = "edit";
currentId.value = row.id;
isReadonly.value = false;
formUserManageData.name = row.name || "";
formUserManageData.comment = row.comment || "";
formUserManageData.password = row.password || "";
formUserManageData.checked_password = row.password || "";
// 根据 al_asset_name 找到对应的 employee_id
if (row.al_asset_name) {
const employee = employeeOptions.value.find(emp => emp.name === row.al_asset_name);
formUserManageData.employee_id = employee ? Number(employee.id) : null;
} else {
// 如果API返回了employee_id直接使用
formUserManageData.employee_id = row.employee_id ? Number(row.employee_id) : null;
}
// 根据 im_factory_area_name 找到对应的角色ID数组
if (row.im_factory_area_name) {
// im_factory_area_name 可能是逗号分隔的字符串,如 "机器工程师,plc工程师"
const roleNames = row.im_factory_area_name.split(',').map(name => name.trim());
const roleIds = roleNames
.map(roleName => {
const role = roleOptions.value.find(r => r.name === roleName);
return role ? Number(role.id) : null;
})
.filter(id => id !== null);
formUserManageData.roles = roleIds;
} else if (row.roles) {
// 如果API返回了roles字段使用它
if (Array.isArray(row.roles)) {
formUserManageData.roles = row.roles.map(id => Number(id));
} else if (row.roles !== null && row.roles !== undefined && row.roles !== '') {
formUserManageData.roles = [Number(row.roles)];
} else {
formUserManageData.roles = [];
}
} else {
formUserManageData.roles = [];
}
dialogVisible.value = true;
};
// 批量删除
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) return;
deleteDialogType.value = "batch";
deleteDialogVisible.value = true;
};
// 删除确定
const confirmDelete = async () => {
loading.value = true;
try {
const ids =
deleteDialogType.value === "single"
? [currentId.value]
: selectedRows.value.map((r) => r.id);
console.log(ids);
await delTablist({ ids });
showSuccessToastMsg(t("common.success"), deleteDialogType.value === "single" ? t("common.deleteSuccess") : t("common.deleteBatchSuccessDetail", { count: ids.length }));
fetchUserManageDataList();
deleteDialogVisible.value = false;
selectedRows.value = [];
} catch (e) {
// 尝试从错误响应中提取消息
let errorMessage = t("common.deleteFail");
// 优先从多个位置获取错误消息
if (e.data?.msg) {
// 从错误对象附加的 data 中获取
errorMessage = e.data.msg;
} else if (e.response?.data?.msg) {
// 从 response.data.msg 获取(后端返回的错误消息)
errorMessage = e.response.data.msg;
} else if (e.data?.message) {
errorMessage = e.data.message;
} else if (e.response?.data?.message) {
errorMessage = e.response.data.message;
} else if (e.message && e.message !== "Error") {
// 如果响应拦截器已经将错误消息设置到 message 中,使用它
errorMessage = e.message;
}
showFailToastMsg("错误", errorMessage);
} finally {
loading.value = false;
}
};
const onSort = (event) => {
formInline.sort.column = event.sortField;
formInline.sort.num = event.sortOrder === 1 ? "asc" : "desc";
fetchUserManageDataList();
};
// 打开列搜索弹窗
const openSearchPopup = (column, label, event) => {
currentSearchColumn.value = column;
currentSearchLabel.value = label;
// 如果是日期列,初始化日期范围
if (column === 'create_at') {
const filterValue = columnFilters.value[column];
if (filterValue && filterValue.start_time && filterValue.end_time) {
currentDateRange.value = [
new Date(filterValue.start_time),
new Date(filterValue.end_time)
];
} else {
currentDateRange.value = null;
}
currentSearchValue.value = '';
} else {
currentSearchValue.value = columnFilters.value[column] || '';
currentDateRange.value = null;
}
// 计算弹窗位置,使其在对应表头下方居中
if (event && event.target) {
// 找到表头单元格元素
let headerCell = event.target.closest('th');
// 如果没找到,尝试从图标向上查找
if (!headerCell) {
let parent = event.target.parentElement;
while (parent && parent.tagName !== 'TH') {
parent = parent.parentElement;
}
headerCell = parent;
}
if (headerCell) {
const rect = headerCell.getBoundingClientRect();
// 如果是日期列,使用更宽的弹窗(包含时间选择器需要更多空间)
const popupWidth = column === 'create_at' ? 450 : 320;
const centerX = rect.left + rect.width / 2;
const leftPosition = centerX - popupWidth / 2;
// 确保弹窗不超出屏幕左右边界
const minLeft = 10;
const maxLeft = window.innerWidth - popupWidth - 10;
const finalLeft = Math.max(minLeft, Math.min(leftPosition, maxLeft));
searchPopupStyle.value = {
position: 'fixed',
top: `${rect.bottom + 8}px`,
left: `${finalLeft}px`,
zIndex: 1000,
width: `${popupWidth}px`
};
} else {
// 如果找不到表头单元格,使用图标位置
const rect = event.target.getBoundingClientRect();
const popupWidth = column === 'create_at' ? 450 : 320;
const centerX = rect.left + rect.width / 2;
const leftPosition = centerX - popupWidth / 2;
const minLeft = 10;
const maxLeft = window.innerWidth - popupWidth - 10;
const finalLeft = Math.max(minLeft, Math.min(leftPosition, maxLeft));
searchPopupStyle.value = {
position: 'fixed',
top: `${rect.bottom + 8}px`,
left: `${finalLeft}px`,
zIndex: 1000,
width: `${popupWidth}px`
};
}
} else {
// 如果没有事件对象,使用默认位置(屏幕中央)
searchPopupStyle.value = {
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
zIndex: 1000
};
}
searchPopupVisible.value = true;
};
// 关闭列搜索弹窗
const closeSearchPopup = () => {
searchPopupVisible.value = false;
currentSearchColumn.value = '';
currentSearchLabel.value = '';
currentSearchValue.value = '';
currentDateRange.value = null;
};
// 执行列搜索
const handleColumnSearch = () => {
if (currentSearchColumn.value) {
// 如果是日期列,处理日期范围
if (currentSearchColumn.value === 'create_at') {
if (currentDateRange.value && Array.isArray(currentDateRange.value) && currentDateRange.value.length === 2) {
const startDate = currentDateRange.value[0];
const endDate = currentDateRange.value[1];
// 格式化日期时间为 YYYY-MM-DD HH:mm:ss 格式
const formatDateTime = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
columnFilters.value[currentSearchColumn.value] = {
start_time: formatDateTime(startDate),
end_time: formatDateTime(endDate)
};
} else {
// 如果日期范围为空,移除该列的筛选
delete columnFilters.value[currentSearchColumn.value];
}
} else {
// 其他列使用文本搜索
const value = currentSearchValue.value?.trim() || '';
if (value) {
columnFilters.value[currentSearchColumn.value] = value;
} else {
// 如果值为空,移除该列的筛选
delete columnFilters.value[currentSearchColumn.value];
}
}
// 重置到第一页
queryData.value.page = 1;
fetchUserManageDataList();
}
closeSearchPopup();
};
// 重置列搜索
const handleColumnReset = () => {
if (currentSearchColumn.value) {
delete columnFilters.value[currentSearchColumn.value];
// 重置到第一页
queryData.value.page = 1;
fetchUserManageDataList();
}
closeSearchPopup();
};
// 初始化时加载用户数据和员工、角色列表
onMounted(async () => {
fetchUserManageDataList();
// 如果store中没有数据则加载一次
if (systemStore.employeeList.length === 0) {
try {
await systemStore.fetchEmployeeList();
} catch (error) {
console.error("加载员工列表失败:", error);
}
}
if (systemStore.roleList.length === 0) {
try {
await systemStore.fetchRoleList();
} catch (error) {
console.error("加载角色列表失败:", error);
}
}
});
</script>
<style scoped>
:deep(.p-checkbox-input) {
width: 18px;
height: 18px;
}
:deep(.p-checkbox-box) {
width: 18px;
height: 18px;
}
:deep(.p-datatable-header) {
background-color: #F3F6F8;
}
:deep(.pi .pi-search .search-icon) {
scale: .86 !important;
}
.textarea {
display: flex;
flex-direction: row;
align-items: flex-start;
margin-top: 20px;
}
.textarea-wrapper {
position: relative;
flex: 1;
width: 100%;
}
.char-count {
position: absolute;
bottom: 8px;
right: 12px;
font-size: 12px;
color: #999999;
pointer-events: none;
background-color: rgba(255, 255, 255, 0.9);
padding: 2px 4px;
}
.w-full {
width: 270px;
}
.UserManage :deep(.p-select-option.p-select-option-selected.p-focus) {
background-color: #3067E5;
}
/* 隐藏 PrimeVue 默认排序图标,表头使用 images/login 的 none/top/bottom 图标 */
.UserManage :deep(.p-datatable-thead > tr > th.p--column .p--column-icon) {
display: none !important;
}
/* 表头右侧:搜索图标 + 排序图标,紧凑排列 */
.header-icons {
margin-top: 5px;
display: flex;
align-items: center;
gap: 0.125rem;
flex-shrink: 0;
}
.sort-icon {
width: 14px;
height: 14px;
object-fit: contain;
cursor: pointer;
transition: opacity 0.2s;
}
.sort-icon:hover {
opacity: 0.7;
}
/* .p-inputtext {
height: 32px;
} */
.p-inputtext,
.p-select,
.p-multiselect,
.p-textarea {
height: 32px;
font-weight: 400;
font-size: 14px;
color: rgba(0, 0, 0, 0.4);
line-height: 14px;
}
.UserManage {
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
background-color: #F3F6F8;
}
.UserManage :deep(.p-datatable) {
margin: 5px 16px 16px;
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
background: #FFFFFF;
border-radius: 0px 0px 0px 0px;
}
.UserManage :deep(.p-datatable-wrapper) {
flex: 1;
overflow: auto;
}
.UserManage :deep(.p-datatable-tbody > tr) {
cursor: pointer;
}
.UserManage :deep(.p-datatable-tbody > tr:hover) {
background-color: rgba(0, 0, 0, 0.04);
}
.table-actions {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1.25rem;
}
.table-left-wrapper {
display: flex;
align-items: center;
gap: 24px;
}
.select-all-wrapper {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
}
.select-all-label {
margin-top: 4px;
height: 20px;
font-family: Source Han Sans SC, Source Han Sans SC;
font-weight: 500;
font-size: 14px;
color: #333333;
line-height: 20px;
text-align: left;
font-style: normal;
text-transform: none;
}
.table-left {
margin-top: 4px;
flex: 0 0 auto;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
.delItems {
height: 20px;
font-family: Source Han Sans SC, Source Han Sans SC;
font-weight: 500;
font-size: 14px;
color: #3067E5;
line-height: 20px;
text-align: left;
font-style: normal;
text-transform: none;
}
.delIcon {
margin-right: 3px;
width: 16px;
height: 16px;
img {
width: 100%;
height: 100%;
}
}
}
.table-right {
margin-right: 14px;
display: flex;
align-items: center;
gap: 16px;
}
.table-center {
flex: 1;
max-width: 25rem;
}
.demo-form-inline {
display: flex;
gap: 0.625rem;
align-items: center;
}
.demo-form-inline InputText {
flex: 1;
}
:deep(.p-datatable-column-sorted) {
background-color: transparent !important;
color: inherit !important;
}
.options {
display: flex;
gap: 0.5rem;
align-items: center;
justify-content: center;
flex-wrap: nowrap;
}
.options :deep(.p-button) {
min-width: auto;
padding: 0.5rem 1rem;
white-space: nowrap;
}
.options :deep(.p-button-rounded) {
border-radius: 0.375rem;
}
.p-mr-2 {
margin-right: 0;
}
.dialog-form {
/* padding: 1.25rem 0; */
margin-top: 30px;
}
.form-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem 1.5rem;
max-width: 100%;
}
.form-field {
display: flex;
flex-direction: row;
align-items: center;
/* gap: 0.75rem; */
}
.form-field.textarea {
align-items: flex-start;
}
.form-field label {
flex-shrink: 0;
white-space: nowrap;
color: #333333;
margin-bottom: 0;
text-align: left;
width: 100px;
font-weight: 400;
font-size: 14px;
line-height: 22px;
font-style: normal;
text-transform: none;
}
.form-field :deep(.p-inputtext),
.form-field :deep(.p-dropdown),
.form-field :deep(.p-multiselect),
.form-field :deep(.p-inputtextarea) {
flex: 1;
width: 100%;
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 4px;
}
.form-field :deep(.p-inputtext:focus),
.form-field :deep(.p-dropdown:not(.p-disabled).p-focus),
.form-field :deep(.p-multiselect:not(.p-disabled).p-focus) {
border-color: #3067E5;
box-shadow: 0 0 0 0.2rem rgba(48, 103, 229, 0.25);
}
.form-field :deep(.p-inputtext::placeholder) {
color: #999999;
font-size: 14px;
}
@media (max-width: 768px) {
.form-grid {
grid-template-columns: 1fr;
}
}
.department-selector {
display: flex;
gap: 0.5rem;
align-items: center;
width: 100%;
}
.department-selector :deep(.p-button) {
width: 100%;
flex: 1;
}
.department-input {
flex: 1;
cursor: pointer;
}
.department-btn {
flex-shrink: 0;
}
.department-selector-dialog {
padding: 0;
}
.search-bar {
margin-bottom: 1rem;
}
.search-bar .search-input {
width: 100%;
}
.department-tree-container {
max-height: 450px;
overflow-y: auto;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 0.5rem;
}
.department-tree :deep(.ops) {
display: none !important;
}
.department-tree :deep(.node-content.selected) {
background-color: #3067E5 !important;
color: #ffffff !important;
}
.department-tree :deep(.node-content.selected:hover) {
background-color: #3067E5 !important;
}
.UserManage :deep(.p-datatable-scrollable > .p-datatable-table-container) {
background-color: #ffffff;
height: 100%;
}
.UserManage :deep(.p-datatable-gridlines .p-datatable-paginator-bottom) {
border: none;
}
/* 分页器样式 - 与厂区管理一致 */
.UserManage :deep(.p-paginator) {
margin: 0 10px;
display: flex;
align-items: center;
justify-content: space-between;
border: none;
gap: 16px;
}
.UserManage :deep(.p-paginator .p-paginator-content) {
display: flex;
align-items: center;
gap: 8px;
}
.UserManage :deep(.p-paginator .p-paginator-prev),
.UserManage :deep(.p-paginator .p-paginator-next) {
background: none;
border: none;
padding: 4px 8px;
color: #3067E5;
font-size: 14px;
font-weight: 400;
box-shadow: none;
}
.UserManage :deep(.p-paginator .p-paginator-prev:disabled),
.UserManage :deep(.p-paginator .p-paginator-next:disabled) {
color: #adb5bd;
cursor: not-allowed;
}
.paginator-btn-text {
display: inline-flex;
align-items: center;
gap: 4px;
font-family: Source Han Sans SC, Source Han Sans SC;
}
.UserManage :deep(.p-paginator .p-paginator-pages) {
display: flex;
align-items: center;
gap: 4px;
}
.UserManage :deep(.p-paginator .p-paginator-page) {
min-width: 32px;
height: 32px;
padding: 0 8px;
background: transparent;
border: none;
border-radius: 6px;
color: #333333;
font-size: 14px;
font-weight: 400;
}
.UserManage :deep(.p-paginator .p-paginator-page[data-p-active="true"]),
.UserManage :deep(.p-paginator .p-paginator-page.p-paginator-page-selected) {
background: #3067E5;
color: #FFFFFF;
}
.UserManage :deep(.p-paginator .p-paginator-page:not([data-p-active="true"]):hover) {
background: rgba(0, 0, 0, 0.04);
color: #333333;
}
.UserManage :deep(.p-paginator .p-paginator-rpp-dropdown),
.UserManage :deep(.p-paginator .p-paginator-rpp-dropdown .p-select) {
min-width: 60px;
border: 1px solid #dee2e6;
border-radius: 4px;
background: #ffffff;
font-size: 14px;
}
/* 每页条数下拉展开后,选中项为淡蓝色(下拉层 teleport 到 body需 :global */
:global(.p-select-overlay .p-select-option.p-select-option-selected),
:global(.p-select-overlay .p-select-option[data-p-selected="true"]) {
background: #E6F0FF;
color: #3067E5;
}
:deep(.p-checkbox .p-checkbox-box) {
border-color: #3067E5 !important;
}
:deep(.p-checkbox-checked .p-checkbox-box) {
background-color: #3067E5 !important;
border-color: #3067E5 !important;
background: #3067E5 !important;
}
:deep(.p-checkbox .p-checkbox-box.p-highlight:hover) {
background-color: #2556d1 !important;
border-color: #2556d1 !important;
}
/* 表头搜索样式 - 紧凑布局:文字与图标、图标之间间距收紧 */
.column-header-with-search {
display: flex;
align-items: center;
gap: 0.25rem;
/* justify-content: space-between; */
width: 100%;
}
/* 确保表头文字在所有状态下都可见 */
.column-header-with-search span {
color: #333333 !important;
/* flex: 1; */
}
/* 确保排序状态下表头文字也可见 */
.UserManage :deep(.p-datatable-thead > tr > th.p--column .column-header-with-search span),
.UserManage :deep(.p-datatable-thead > tr > th.p--column.p-highlight .column-header-with-search span),
.UserManage :deep(.p-datatable-thead > tr > th.p--column-active .column-header-with-search span) {
color: #333333 !important;
}
.search-icon {
cursor: pointer;
color: #6c757d;
font-size: 0.875rem;
transition: color 0.2s;
padding: 0.0625rem;
}
.search-icon:hover {
color: #3067E5;
}
/* 列搜索弹窗样式 */
.column-search-popup {
background: white;
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
padding: 1rem;
min-width: 320px;
max-width: 450px;
z-index: 1000;
}
.search-popup-input {
width: 100%;
margin-bottom: 0.75rem;
width: 288px;
border: 1px solid #0052D9;
border-radius: 3px 3px 3px 3px;
}
.search-popup-input :deep(.p-inputtext) {
border-color: #3067E5;
}
.search-popup-input :deep(.p-inputtext:focus) {
border-color: #3067E5;
box-shadow: 0 0 0 0.2rem rgba(48, 103, 229, 0.25);
}
.search-popup-input :deep(.p-calendar) {
width: 100%;
}
.search-popup-input :deep(.p-calendar .p-inputtext) {
border-color: #3067E5;
width: 100%;
}
.search-popup-input :deep(.p-calendar .p-inputtext:focus) {
border-color: #3067E5;
box-shadow: 0 0 0 0.2rem rgba(48, 103, 229, 0.25);
}
.search-popup-input :deep(.p-calendar .p-datepicker-trigger) {
background-color: #3067E5;
border-color: #3067E5;
}
.search-popup-input :deep(.p-calendar .p-datepicker-trigger:hover) {
background-color: #2556d1;
border-color: #2556d1;
}
.search-popup-buttons {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
}
.search-btn {
background-color: #3067E5;
border-color: #3067E5;
}
.search-btn:hover {
background-color: #2556d1;
border-color: #2556d1;
}
.reset-btn {
background-color: white;
border-color: #dee2e6;
color: #495057;
}
.reset-btn:hover {
background-color: #f8f9fa;
border-color: #adb5bd;
}
/* 搜索弹窗遮罩层 */
.search-popup-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
background: transparent;
}
.w-full {
width: 100%;
}
/* 对话框样式:门禁报警记录弹窗使用原生 div 表头,隐藏自带的 p-dialog-header */
.UserManage :deep(.user-manage-dialog .p-dialog-header),
:global(.user-manage-dialog .p-dialog-header) {
display: none !important;
}
.UserManage :deep(.p-dialog-header) {
background-color: #ffffff;
border-bottom: 1px solid green;
padding: 1rem 1.5rem;
}
.dialog_header {
height: 55px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #E7E7E7;
margin: 0 -1.5rem 20px -1.5rem;
padding: 0 1.5rem;
/* 原生 div 表头(替代 p-dialog-header */
.dialog-header-custom {
margin-left: -5px;
height: 24px;
font-family: Source Han Sans SC, Source Han Sans SC;
font-weight: 500;
font-size: 16px;
color: rgba(0, 0, 0, 0.9);
line-height: 24px;
text-align: left;
font-style: normal;
text-transform: none;
}
}
:deep(.p-dialog-header .p-dialog-title) {
color: red !important;
}
.UserManage :deep(.p-dialog-header-icon) {
display: none !important;
}
.UserManage :deep(.p-dialog-header-icon:hover) {
background-color: #2556d1;
}
.UserManage :deep(.p-dialog-header-icon .p-dialog-header-icon-icon) {
color: #ffffff;
font-size: 14px;
}
.UserManage :deep(.p-dialog-content) {
background-color: #f8f9fa;
padding: 1.5rem;
border-top: 1px solid green !important;
}
/* 仅隐藏新增/编辑用户弹窗底部的横向滚动条(弹窗 portaled 到 body需用 :global */
:global(.user-manage-dialog .p-dialog-content) {
overflow-x: hidden !important;
}
:global(.user-manage-dialog.p-dialog) {
overflow-x: hidden !important;
}
.UserManage :deep(.p-dialog-footer) {
background-color: #ffffff;
border-top: 1px solid red !important;
padding: 1rem 1.5rem;
display: flex;
justify-content: flex-end;
gap: 0.75rem;
}
.dialog-close-btn,
.dialog-confirm-btn {
background-color: #3067E5;
border-color: #3067E5;
color: #ffffff;
border-radius: 4px;
padding: 0.5rem 1.25rem;
font-weight: 400;
transition: background-color 0.2s, border-color 0.2s;
}
.dialog-close-btn:hover,
.dialog-confirm-btn:hover {
background-color: #2556d1;
border-color: #2556d1;
}
.dialog-close-btn :deep(.p-button-icon),
.dialog-confirm-btn :deep(.p-button-icon) {
color: #ffffff;
margin-right: 0.5rem;
}
:deep(.p-textarea) {
height: 170px;
}
.select-all-label-paginator {
height: 19px;
font-family: Source Han Sans SC, Source Han Sans SC;
font-weight: 400;
font-size: 14px;
color: rgba(0, 0, 0, 0.6);
line-height: 22px;
text-align: left;
font-style: normal;
text-transform: none;
}
:deep(.p-datatable-gridlines .p-datatable-paginator-bottom) {
border: none;
}
.footer_box {
margin-top: 32px;
position: absolute;
bottom: 0;
left: 0;
right: 0;
width: 100%;
display: flex;
flex-direction: row-reverse;
border-top: 1px solid #E7E7E7;
padding-top: 12px;
height: 55px;
/* background: #f8f9fa; */
z-index: 1;
}
/* .footer_box::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: -20px;
height: 20px;
background: #f8f9fa;
z-index: 1;
} */
:global(.datepicker-panel-scale-07.p-datepicker-panel) {
transform: scale(0.7);
transform-origin: top left;
}
:global(.datepicker-panel-scale-07.p-datepicker-panel .p-datepicker-day.p-datepicker-day-selected),
:global(.datepicker-panel-scale-07.p-datepicker-panel .p-datepicker-today > .p-datepicker-day.p-datepicker-day-selected) {
background: #3067E5 !important;
color: #fff !important;
}
.footer_box .btn {
display: flex;
margin-right: 16px;
.ancle {
cursor: pointer;
width: 60px;
height: 32px;
background: #E7E7E7;
border-radius: 3px 3px 3px 3px;
text-align: center;
line-height: 32px;
font-weight: 400;
font-size: 14px;
color: rgba(0, 0, 0, 0.9);
text-align: center;
font-style: normal;
text-transform: none;
margin-right: 8px;
}
.confirm {
cursor: pointer;
width: 60px;
height: 32px;
background: #3067E5;
border-radius: 3px 3px 3px 3px;
text-align: center;
line-height: 32px;
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
text-align: center;
font-style: normal;
text-transform: none;
}
}
.delete-dialog-overlay {
position: fixed;
inset: 0;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.5);
}
.del_box {
cursor: pointer;
width: 480px;
height: 172px;
background: #FFFFFF;
border-radius: 4px 4px 4px 4px;
.header {
margin-top: 28px;
margin-left: 32px;
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex;
}
.img_box {
width: 24px;
height: 24px;
img {
width: 100%;
height: 100%;
}
}
.del_name {
margin-left: 8px;
height: 24px;
font-family: Source Han Sans SC, Source Han Sans SC;
font-weight: 500;
font-size: 16px;
color: rgba(0, 0, 0, 0.9);
line-height: 24px;
text-align: left;
font-style: normal;
text-transform: none;
}
.del_icon {
width: 16px;
height: 16px;
margin-right: 24px;
img {
width: 100%;
height: 100%;
}
}
}
.delText {
margin-top: 16px;
margin-left: 64px;
height: 22px;
font-family: Source Han Sans SC, Source Han Sans SC;
font-weight: 400;
font-size: 14px;
color: rgba(0, 0, 0, 0.6);
line-height: 22px;
text-align: left;
font-style: normal;
text-transform: none;
}
.del-btn {
margin-top: 26px;
margin-left: 320px;
display: flex;
.btnAncle {
width: 60px;
height: 32px;
line-height: 32px;
text-align: center;
background: #E7E7E7;
border-radius: 3px 3px 3px 3px;
font-weight: 400;
font-size: 14px;
color: rgba(0, 0, 0, 0.9);
margin-right: 8px;
}
.btnOk {
width: 60px;
height: 32px;
text-align: center;
background: #3067E5;
border-radius: 3px 3px 3px 3px;
font-weight: 400;
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
line-height: 32px;
}
}
}
:deep(.p-checkbox-input) {
margin-top: 4px;
width: 16px;
height: 16px;
}
:deep(.p-checkbox-box) {
margin-top: 4px;
width: 16px;
height: 16px;
}
:deep(.p-datatable-tbody > tr > td) {
height: 22px;
font-family: Source Han Sans SC, Source Han Sans SC;
font-weight: 400;
font-size: 14px;
color: #666666;
line-height: 22px;
text-align: left;
font-style: normal;
text-transform: none;
}
:deep(.column-header-with-search span) {
font-family: Source Han Sans SC, Source Han Sans SC;
font-weight: 500;
font-size: 14px;
color: #333333 !important;
line-height: 22px;
text-align: left;
font-style: normal;
text-transform: none;
}
:deep(.p-checkbox-input) {
margin-top: 4px;
width: 16px;
height: 16px;
}
:deep(.p-checkbox-box) {
margin-top: 4px;
width: 16px;
height: 16px;
}
.search-popup-buttons {
cursor: pointer;
display: flex;
gap: 0.5rem;
justify-content: flex-end;
.search_reset {
display: flex;
align-items: center;
justify-content: center;
width: 60px;
height: 32px;
background: #E7E7E7;
border-radius: 3px 3px 3px 3px;
font-family: Source Han Sans SC, Source Han Sans SC;
font-weight: 400;
font-size: 14px;
color: rgba(0,0,0,0.9);
line-height: 22px;
text-align: center;
font-style: normal;
text-transform: none;
}
.search-btn {
display: flex;
align-items: center;
justify-content: center;
width: 60px;
height: 32px;
background: #0052D9;
border-radius: 3px 3px 3px 3px;
font-family: Source Han Sans SC, Source Han Sans SC;
font-weight: 400;
font-size: 14px;
color: rgba(255,255,255,0.9);
line-height: 22px;
text-align: center;
font-style: normal;
text-transform: none;
}
}
.search-btn {
background-color: #3067E5;
border-color: #3067E5;
}
.search-btn:hover {
background-color: #2556d1;
border-color: #2556d1;
}
.reset-btn {
background-color: white;
border-color: #dee2e6;
color: #495057;
}
.search-popup-input :deep(.p-inputtext) {
width: 288px;
height: 36px;
border-radius: 3px 3px 3px 3px;
border-color: #3067E5;
}
.search-popup-input :deep(.p-inputtext:focus) {
/* border-color: #3067E5; */
border: 1px solid #0052D9;
}
/* 失败提示 - 与资产台账一致:右上角、原生样式 */
.edit-fail-toast {
position: fixed;
top: 24px;
right: 24px;
z-index: 9999;
gap: 12px;
padding: 16px 24px 24px 24px;
width: 400px;
height: 98px;
border-radius: 4px;
background: #ffffff;
box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, 0.08), 0px 16px 24px 2px rgba(0, 0, 0, 0.04), 0px 6px 30px 5px rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0, 0, 0, 0.06);
}
.totast_wrapper { display: flex; justify-content: space-between; }
.left_box { display: flex; }
.edit-fail-toast-icon { flex-shrink: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; }
.edit-fail-toast-icon img { width: 24px; height: 24px; object-fit: contain; }
.edit-fail-toast-content { flex: 1; padding-right: 8px; position: absolute; left: 57px; margin-top: 8px; }
.edit-fail-toast-title { font-size: 16px; font-weight: 600; color: rgba(0, 0, 0, 0.85); line-height: 1.4; margin-left: 8px; margin-top: 2px; }
.edit-fail-toast-detail { margin-top: 4px; font-size: 14px; font-weight: 400; color: rgba(0, 0, 0, 0.45); line-height: 1.5; }
.edit-fail-toast-close { flex-shrink: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: rgba(0, 0, 0, 0.45); }
.edit-fail-toast-close:hover { color: rgba(0, 0, 0, 0.65); }
.edit-fail-toast-close img { width: 14px; height: 14px; object-fit: contain; }
/* 成功提示 - 与失败提示统一:右上角、左对齐布局 */
.edit-success-toast {
position: fixed;
top: 24px;
right: 24px;
z-index: 9999;
width: 400px;
border-radius: 4px;
padding: 16px 24px 24px 24px;
background: #ffffff;
box-shadow: 0px 8px 10px -5px rgba(0, 0, 0, 0.08), 0px 16px 24px 2px rgba(0, 0, 0, 0.04), 0px 6px 30px 5px rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0, 0, 0, 0.06);
text-align: left;
}
.edit-success-toast .totast_wrapper { display: flex; justify-content: space-between; align-items: flex-start; }
.edit-success-toast .left_box { display: flex; align-items: flex-start; gap: 8px; }
.edit-success-toast-icon { flex-shrink: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; }
.edit-success-toast-icon img { width: 24px; height: 24px; object-fit: contain; }
.edit-success-toast-title { font-size: 16px; font-weight: 600; color: rgba(0, 0, 0, 0.85); line-height: 1.4; margin-top: 2px; }
.edit-success-toast-content { padding-right: 8px; margin-top: 8px; margin-left: 32px; }
.edit-success-toast-detail { margin-top: 4px; font-size: 14px; font-weight: 400; color: rgba(0, 0, 0, 0.45); line-height: 1.5; }
.edit-success-toast-close { flex-shrink: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: rgba(0, 0, 0, 0.45); }
.edit-success-toast-close:hover { color: rgba(0, 0, 0, 0.65); }
.edit-success-toast-close img { width: 14px; height: 14px; object-fit: contain; }
</style>