完成仓库模块
This commit is contained in:
10
src/utils/map.ts
Normal file
10
src/utils/map.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* 合并两个对象数组并去重(基于 id,后面的覆盖前面的)
|
||||
*/
|
||||
export function mergeObjectsAndDedupe<T extends { id?: string }>(arr1: T[], arr2: T[]): T[] {
|
||||
const map = new Map(arr1.map((item) => [item.id, item]));
|
||||
arr2.forEach((item) => {
|
||||
if (item.id) map.set(item.id, item);
|
||||
});
|
||||
return [...map.values()];
|
||||
}
|
||||
82
src/utils/money.ts
Normal file
82
src/utils/money.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 数字金额转中文大写(支持整数、小数)
|
||||
* @param num - 待转换的金额(支持数字或字符串格式)
|
||||
* @returns 中文大写金额字符串
|
||||
*/
|
||||
export function convertToChineseCapital(num: number | string): string {
|
||||
// 中文数字映射
|
||||
const digits: readonly string[] = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
|
||||
// 基本单位(个、拾、佰、仟)
|
||||
const units: readonly string[] = ['', '拾', '佰', '仟'];
|
||||
// 大单位(万、亿、万亿)
|
||||
const bigUnits: readonly string[] = ['', '万', '亿', '万亿'];
|
||||
|
||||
// 1. 标准化输入:保留4位小数,分割整数与小数部分
|
||||
const [intPartRaw, decPartRaw] = Number(num).toFixed(4).split('.');
|
||||
// 去除整数前导零,若全零则保留一个 '0'
|
||||
const intPart: string = intPartRaw.replace(/^0+/, '') || '0';
|
||||
|
||||
let result: string = '';
|
||||
|
||||
// 2. 处理整数部分
|
||||
if (intPart === '0') {
|
||||
result = '零元';
|
||||
} else {
|
||||
// 从右向左,每4位分为一组(处理万、亿等大单位)
|
||||
const groups: string[] = [];
|
||||
let tempInt: string = intPart;
|
||||
while (tempInt.length > 0) {
|
||||
groups.unshift(tempInt.slice(-4));
|
||||
tempInt = tempInt.slice(0, -4);
|
||||
}
|
||||
|
||||
// 遍历每一组进行转换
|
||||
groups.forEach((group: string, gIndex: number) => {
|
||||
let groupStr: string = '';
|
||||
let needZero: boolean = false; // 标记是否需要补零
|
||||
|
||||
for (let i = 0; i < group.length; i++) {
|
||||
const digit: number = parseInt(group[i], 10);
|
||||
const unitPos: number = group.length - 1 - i; // 当前数字对应的单位位置
|
||||
|
||||
if (digit === 0) {
|
||||
needZero = true;
|
||||
} else {
|
||||
// 补零(如果前面有0)
|
||||
if (needZero) {
|
||||
groupStr += '零';
|
||||
needZero = false;
|
||||
}
|
||||
groupStr += digits[digit] + units[unitPos];
|
||||
}
|
||||
}
|
||||
|
||||
// 拼接大单位(万、亿)
|
||||
if (groupStr) {
|
||||
result += groupStr + bigUnits[groups.length - 1 - gIndex];
|
||||
}
|
||||
});
|
||||
result += '元';
|
||||
}
|
||||
|
||||
// 3. 处理小数部分(仅保留角、分,忽略毫、厘)
|
||||
const jiao: number = parseInt(decPartRaw[0], 10); // 角(第1位小数)
|
||||
const fen: number = parseInt(decPartRaw[1], 10); // 分(第2位小数)
|
||||
|
||||
if (jiao === 0 && fen === 0) {
|
||||
result += '整';
|
||||
} else {
|
||||
// 处理“角”
|
||||
if (jiao > 0) {
|
||||
result += digits[jiao] + '角';
|
||||
} else if (jiao === 0 && fen > 0) {
|
||||
result += '零';
|
||||
}
|
||||
// 处理“分”
|
||||
if (fen > 0) {
|
||||
result += digits[fen] + '分';
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
264
src/utils/print.ts
Normal file
264
src/utils/print.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* 🔥 Vue后台专用 - 打印指定DIV(样式完全还原 + 无侧边栏/导航)
|
||||
* 适配 Element Plus / scoped样式 / 后台管理系统
|
||||
*/
|
||||
export function printVueDiv(elementId: string, title: string = '打印') {
|
||||
const el = document.getElementById(elementId);
|
||||
if (!el) {
|
||||
console.error('未找到打印元素');
|
||||
return;
|
||||
}
|
||||
|
||||
const win = window.open('', '_blank', 'width=1000,height=800');
|
||||
if (!win) return;
|
||||
|
||||
// ==============================================
|
||||
// 【核心1】复制 全局样式 + Element Plus 样式
|
||||
// ==============================================
|
||||
const styles = Array.from(document.querySelectorAll('link[rel="stylesheet"], style:not([scoped])'))
|
||||
.map((item) => item.outerHTML)
|
||||
.join('');
|
||||
|
||||
// ==============================================
|
||||
// 【核心2】手动注入打印区域的 scoped 样式(关键!)
|
||||
// ==============================================
|
||||
const printScopeStyle = `
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin:0;
|
||||
padding:0;
|
||||
}
|
||||
body {
|
||||
margin: 10px;
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
font-size: 12px; /* 全局缩小字体(核心) */
|
||||
font-family: "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
/* 强制不显示后台侧边栏/导航 */
|
||||
.el-container, .el-aside, .el-header, .el-footer {
|
||||
display: none !important;
|
||||
}
|
||||
/* 打印区域基础样式 */
|
||||
#print-content {
|
||||
width: 100% !important;
|
||||
}
|
||||
/* 标题样式(缩小) */
|
||||
#print-content h3 {
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
margin: 10px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
#print-content p {
|
||||
font-size: 12px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
/* ====================== 表格核心优化 ====================== */
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100% !important;
|
||||
table-layout: fixed; /* 固定表格布局,防止列变形 */
|
||||
}
|
||||
table td, table th {
|
||||
border: 1px solid #333;
|
||||
font-size: 9px; /* 表格字体最小化 */
|
||||
text-align: center;
|
||||
white-space: nowrap; /* ✅ 禁止文字自动换行(核心) */
|
||||
overflow: hidden;
|
||||
}
|
||||
/* 适配合并列 */
|
||||
td[colspan], th[colspan] {
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
/* 打印纸张设置 */
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 8mm; /* 缩小页边距 */
|
||||
}
|
||||
|
||||
.navtitle {
|
||||
text-align: center;
|
||||
height: 41px;
|
||||
line-height: 14px;
|
||||
color: rgba(48, 49, 51, 1);
|
||||
font-size: 20px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
p {
|
||||
line-height: 14px;
|
||||
color: rgba(48, 49, 51, 1);
|
||||
font-size: 9px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
table {
|
||||
margin-top: 15px;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
color: rgba(48, 49, 51, 1);
|
||||
}
|
||||
tr {
|
||||
border: 1px solid #dddddd;
|
||||
}
|
||||
td {
|
||||
border: 1px solid #dddddd;
|
||||
height: 50px;
|
||||
line-height: 45px;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
text-align: center;
|
||||
font-weight: 450;
|
||||
}
|
||||
|
||||
.footertr td {
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.foorter {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
width: 85%;
|
||||
margin: 0 auto;
|
||||
gap: 30px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
// ==============================================
|
||||
// 【核心3】写入新窗口
|
||||
// ==============================================
|
||||
win.document.write(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>${title}</title>
|
||||
<meta charset="UTF-8">
|
||||
${styles}
|
||||
${printScopeStyle}
|
||||
</head>
|
||||
<body>
|
||||
${el.outerHTML} <!-- 直接用innerHTML复制完整结构 -->
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
|
||||
win.document.close();
|
||||
win.onload = () => {
|
||||
setTimeout(() => {
|
||||
win.print();
|
||||
win.close();
|
||||
}, 300);
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 打印指定 DOM 元素(绝对不会打印其他内容)
|
||||
* @param elementId 目标 div 的 id
|
||||
* @param title 打印标题(可选)
|
||||
*/
|
||||
export function printDivPure(elementId: string, title: string = '打印') {
|
||||
// 1. 获取目标元素
|
||||
const targetElement = document.getElementById(elementId);
|
||||
if (!targetElement) {
|
||||
console.error(`未找到 id 为 "${elementId}" 的元素`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 保存原页面的所有内容
|
||||
const originalBodyContent = document.body.innerHTML;
|
||||
|
||||
// 3. 克隆目标元素
|
||||
const cloneElement = targetElement.cloneNode(true) as HTMLElement;
|
||||
|
||||
// 4. 【核心】把 body 替换成只有目标元素的内容
|
||||
document.body.innerHTML = '';
|
||||
|
||||
// 添加打印专用样式
|
||||
const printStyle = document.createElement('style');
|
||||
printStyle.innerHTML = `
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 10mm;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(printStyle);
|
||||
|
||||
// 插入目标元素
|
||||
document.body.appendChild(cloneElement);
|
||||
|
||||
// 5. 打印
|
||||
window.print();
|
||||
|
||||
// 6. 【核心】打印完恢复原页面内容
|
||||
setTimeout(() => {
|
||||
document.body.innerHTML = originalBodyContent;
|
||||
document.head.removeChild(printStyle);
|
||||
// 强制刷新一下 Vue 组件(避免恢复后事件丢失)
|
||||
window.location.reload();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印指定 DOM 元素(新窗口法加强版,不刷新页面)
|
||||
* @param elementId 目标 div 的 id
|
||||
* @param title 打印标题(可选)
|
||||
*/
|
||||
export function printDivNewWindow(elementId: string, title: string = '打印') {
|
||||
const targetElement = document.getElementById(elementId);
|
||||
if (!targetElement) {
|
||||
console.error(`未找到 id 为 "${elementId}" 的元素`);
|
||||
return;
|
||||
}
|
||||
|
||||
const printWindow = window.open('', '_blank', 'width=800,height=1000');
|
||||
if (!printWindow) {
|
||||
console.error('无法打开新窗口,请检查浏览器弹窗拦截设置');
|
||||
return;
|
||||
}
|
||||
|
||||
// 【关键】深度克隆目标元素,包括所有子节点
|
||||
const clone = targetElement.cloneNode(true) as HTMLElement;
|
||||
|
||||
printWindow.document.write(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>${title}</title>
|
||||
<meta charset="UTF-8">
|
||||
<!-- 复制所有外部 CSS -->
|
||||
${Array.from(document.querySelectorAll('link[rel="stylesheet"]'))
|
||||
.map((l) => l.outerHTML)
|
||||
.join('')}
|
||||
<!-- 复制所有 style 标签 -->
|
||||
${Array.from(document.querySelectorAll('style'))
|
||||
.map((s) => s.outerHTML)
|
||||
.join('')}
|
||||
<style>
|
||||
body { margin: 20px; padding: 0; }
|
||||
@page { size: A4; margin: 10mm; }
|
||||
</style>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
`);
|
||||
|
||||
// 把克隆的元素插入到新窗口
|
||||
printWindow.document.body.appendChild(clone);
|
||||
printWindow.document.close();
|
||||
|
||||
// 等待加载完成后打印
|
||||
printWindow.onload = () => {
|
||||
printWindow.focus();
|
||||
setTimeout(() => {
|
||||
printWindow.print();
|
||||
printWindow.close();
|
||||
}, 300);
|
||||
};
|
||||
}
|
||||
@@ -51,13 +51,20 @@ export function needVillageId(url: string): boolean {
|
||||
// 去掉 ? 后面的参数
|
||||
const path = url.split('?')[0];
|
||||
|
||||
// 遍历白名单,正则匹配
|
||||
return !whiteVillageId.some((pattern) => {
|
||||
// 把 * 转成正则的 .* 通配
|
||||
const regPattern = pattern.replace(/\*/g, '[^\\/]*');
|
||||
const regex = new RegExp(`^${regPattern}$`);
|
||||
return regex.test(path);
|
||||
// 遍历白名单匹配
|
||||
const isWhite = whiteVillageId.some((pattern) => {
|
||||
// 处理 /* 结尾的通配(匹配该路径及其所有子路径)
|
||||
if (pattern.endsWith('/*')) {
|
||||
const basePath = pattern.slice(0, -2); // 比如 /warehouse/* → /warehouse
|
||||
// 只要 path 是 /warehouse 或 /warehouse/ 或 /warehouse/xxx 就匹配
|
||||
return path === basePath || path.startsWith(basePath + '/');
|
||||
} else {
|
||||
// 精确匹配
|
||||
return path === pattern;
|
||||
}
|
||||
});
|
||||
|
||||
return !isWhite;
|
||||
}
|
||||
// 请求拦截器
|
||||
service.interceptors.request.use(
|
||||
@@ -105,6 +112,7 @@ service.interceptors.request.use(
|
||||
} else {
|
||||
obj = config.data;
|
||||
}
|
||||
|
||||
if (!config.data) config.data = {};
|
||||
Object.assign(config.data, obj);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user