feat(frontend): 为前端添加完整移动端响应式适配

- 新增响应式工具函数与全局移动端样式,统一适配≤768px屏幕
- 添加移动端底部导航组件,替代桌面端侧边栏菜单
- 为经销商、客户、报备列表页新增移动端卡片视图替换表格布局
- 调整所有页面的表单、弹窗与布局适配移动端尺寸
- 优化移动端viewport配置,适配全面屏安全区域
- 修复密码哈希工具,在非安全上下文下回退使用js-sha256库
- 更新依赖包,新增@vueuse/core与js-sha256
This commit is contained in:
andy committed 2026-08-03 15:35:45 +08:00
1 parent 4c6970c32d
commit fd89c31640
16 files changed
+613 -62

No files matched your search

+28 -27
View File
@@ -1,39 +1,44 @@
/**
* 密码加密工具
* 使用 Web Crypto API 进行 SHA-256 哈希
* 密码加密工具(SHA-256 → 小写 hex)
*
* 安全上下文(HTTPS 或 localhost)下使用原生 Web Crypto API;
* 非安全上下文(HTTP + 局域网/IP 访问,此时 crypto.subtle 为 undefined)
* 下回退到纯 JS 实现 js-sha256,保证产物一致,登录不再卡死。
* 两条路径对同一字符串产出的 SHA-256 小写 hex 完全相同。
*/
import { sha256 } from 'js-sha256'
/**
* 将字符串转换为 ArrayBuffer
*/
function stringToArrayBuffer(str: string): ArrayBuffer {
const encoder = new TextEncoder()
return encoder.encode(str).buffer
/** 当前环境是否可用原生 Web Crypto(即处于安全上下文) */
function hasSubtleCrypto(): boolean {
return typeof crypto !== 'undefined' && typeof crypto.subtle?.digest === 'function'
}
/**
* 将 ArrayBuffer 转换为十六进制字符串
*/
function arrayBufferToHex(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer)
return Array.from(bytes)
.map(b => b.toString(16).padStart(2, '0'))
/** 原生 Web Crypto SHA-256 → 小写 hex */
async function subtleSha256Hex(input: string): Promise<string> {
const data = new TextEncoder().encode(input)
const hashBuffer = await crypto.subtle.digest('SHA-256', data)
return Array.from(new Uint8Array(hashBuffer))
.map((b) => b.toString(16).padStart(2, '0'))
.join('')
}
/** 统一入口:优先原生,回退纯 JS */
async function sha256Hex(input: string): Promise<string> {
if (hasSubtleCrypto()) {
return subtleSha256Hex(input)
}
return sha256(input)
}
/**
* 使用 SHA-256 算法哈希密码
* @param password 明文密码
* @returns 哈希后的十六进制字符串
* @returns 哈希后的十六进制字符串(小写)
*/
export async function hashPassword(password: string): Promise<string> {
if (!password) return ''
try {
// 使用 Web Crypto API 进行 SHA-256 哈希
const data = stringToArrayBuffer(password)
const hashBuffer = await crypto.subtle.digest('SHA-256', data)
return arrayBufferToHex(hashBuffer)
return await sha256Hex(password)
} catch (error) {
console.error('密码哈希失败:', error)
throw new Error('密码加密失败')
@@ -44,16 +49,12 @@ export async function hashPassword(password: string): Promise<string> {
* 简单的加盐哈希(可选,用于增强安全性)
* @param password 明文密码
* @param salt 盐值
* @returns 哈希后的十六进制字符串
* @returns 哈希后的十六进制字符串(小写)
*/
export async function hashPasswordWithSalt(password: string, salt: string): Promise<string> {
if (!password) return ''
try {
const saltedPassword = password + salt
const data = stringToArrayBuffer(saltedPassword)
const hashBuffer = await crypto.subtle.digest('SHA-256', data)
return arrayBufferToHex(hashBuffer)
return await sha256Hex(password + salt)
} catch (error) {
console.error('密码哈希失败:', error)
throw new Error('密码加密失败')