feat: 登录认证 + 用户/角色/组织架构/审计日志管理页

- 新增登录页、认证 store、路由守卫(未登录跳转 + meta.permissions 校验 + 401 统一处理)
- 新增 v-permission 按钮权限指令,Sidebar 按权限过滤菜单,Header 绑定真实用户+改密+退出
- 用户管理:增删改查、启停、重置密码、分配角色、组织树选择、岗位、技能标签
- 角色管理:预定义角色矩阵、权限分配、数据范围(全部/本组织及下级)
- 组织架构:固定层级 公司/实验室/部门/班组(白夜班) 树形管理 + 数据权限摘要展示
- 审计日志页:操作人/类型/对象/时间段筛选 + 前后值查看
- 补齐同事模块缺失文件:api/alert.js、utils/format.js(修复构建)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 17:30:44 +08:00
co-authored by Claude Sonnet 4.6
parent 240ede9769
commit 97ce665aec
21 changed files with 1471 additions and 79 deletions
+22 -40
View File
@@ -1,60 +1,42 @@
/**
* 通用格式化工具(后端返回的日期为 ISO 字符串,如 "2026-08-28T10:30:00"
* 通用格式化工具(asset 台账视图与告警页共用
*/
// 补零:5 → "05"
const pad = n => String(n).padStart(2, '0')
/**
* 格式化为日期:2026-08-28
* @param {string|Date} value 日期/时间值
* @returns {string} 空值返回 ''
*/
// 日期:YYYY-MM-DD(兼容 ISO 字符串 / Date
export function fmtDate(value) {
if (!value) return ''
if (!value) return '-'
const d = new Date(value)
if (isNaN(d.getTime())) return String(value).slice(0, 10)
if (Number.isNaN(d.getTime())) return String(value)
const pad = (n) => String(n).padStart(2, '0')
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
}
/**
* 格式化为日期时间:2026-08-28 10:30:00
* @param {string|Date} value 日期/时间值
* @returns {string} 空值返回 ''
*/
// 日期时间:YYYY-MM-DD HH:mm:ss
export function fmtDateTime(value) {
if (!value) return ''
if (!value) return '-'
const d = new Date(value)
if (isNaN(d.getTime())) return String(value).replace('T', ' ').slice(0, 19)
if (Number.isNaN(d.getTime())) return String(value)
const pad = (n) => String(n).padStart(2, '0')
return `${fmtDate(d)} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
}
/**
* 格式化文件大小:1024 → "1.00 KB"
* @param {number} bytes 字节数
* @returns {string} 空值返回 ''
*/
// 文件大小(字节 → 可读单位)
export function fmtFileSize(bytes) {
if (bytes === null || bytes === undefined || isNaN(bytes)) return ''
if (bytes < 1024) return `${bytes} B`
const units = ['KB', 'MB', 'GB', 'TB']
let size = bytes
let i = -1
do {
size /= 1024
i++
} while (size >= 1024 && i < units.length - 1)
return `${size.toFixed(2)} ${units[i]}`
if (bytes == null || bytes === '' || Number.isNaN(Number(bytes))) return '-'
const n = Number(bytes)
if (n < 1024) return `${n} B`
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`
if (n < 1024 * 1024 * 1024) return `${(n / 1024 / 1024).toFixed(1)} MB`
return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`
}
/**
* 判断日期是否已超期(小于当前日期)
* @param {string|Date} value 日期值
* @returns {boolean}
*/
// 是否已逾期(日期早于今天且非空)
export function isOverdue(value) {
if (!value) return false
const d = new Date(value)
if (isNaN(d.getTime())) return false
return d < new Date()
if (Number.isNaN(d.getTime())) return false
const today = new Date()
today.setHours(0, 0, 0, 0)
d.setHours(0, 0, 0, 0)
return d.getTime() < today.getTime()
}