diff --git a/.gitignore b/.gitignore index c2a8a771..2d00338a 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ vite.config.ts.* *.sln *.sw? .history +/.cursor/ diff --git a/apps/web-antd/package.json b/apps/web-antd/package.json index 7bd776d2..40b4cfd5 100644 --- a/apps/web-antd/package.json +++ b/apps/web-antd/package.json @@ -44,6 +44,7 @@ "@vueuse/core": "catalog:", "ant-design-vue": "catalog:", "dayjs": "catalog:", + "exceljs": "^4.4.0", "markdown-it": "^14.1.0", "pinia": "catalog:", "sortablejs": "catalog:", diff --git a/apps/web-antd/src/views/business/order/product-order/api/index.ts b/apps/web-antd/src/views/business/order/product-order/api/index.ts index 5be257f2..fd99f037 100644 --- a/apps/web-antd/src/views/business/order/product-order/api/index.ts +++ b/apps/web-antd/src/views/business/order/product-order/api/index.ts @@ -80,12 +80,83 @@ export async function expressDetailByOrderId(data: Record) { } /** - * 导出订单数据 + * 导出订单数据(后端 Excel,旧接口保留) */ export async function exportOrderApi() { return requestClient.download(`${prefix}export`); } +export interface OrderExportDataParams { + search_time: [string, string]; + granularity: 'order' | 'item'; + order_no?: string; + store_id?: number; + status?: number; + delivery_method?: number; + prescription_type?: number; +} + +export interface OrderExportDataResult { + total: number; + rows: Record[]; + granularity: 'order' | 'item'; +} + +/** + * 导出订单 JSON 数据(供前端 ExcelJS) + */ +export async function getOrderExportDataApi(params: OrderExportDataParams) { + return requestClient.get(`${prefix}export-data`, { params }); +} + +export interface OrderExportSchemePayload { + name: string; + is_default?: boolean | number; + granularity: 'order' | 'item'; + config: { + order_no?: string; + store_id?: number | null; + status?: number | null; + delivery_method?: number | null; + prescription_type?: number | null; + selected_fields: string[]; + }; +} + +export interface OrderExportSchemeUpdatePayload { + id: number; + name?: string; + is_default?: boolean | number; + granularity?: 'order' | 'item'; + config?: OrderExportSchemePayload['config']; +} + +export interface OrderExportSchemeItem { + id: number; + name: string; + is_default: number; + granularity: 'order' | 'item'; + config: OrderExportSchemePayload['config']; + created_at?: string; + updated_at?: string; +} + +export async function getOrderExportSchemeList() { + return requestClient.get(`${prefix}export-scheme-list`); +} + +export async function createOrderExportScheme(data: OrderExportSchemePayload) { + return requestClient.post(`${prefix}export-scheme-create`, data); +} + +export async function updateOrderExportScheme(data: OrderExportSchemeUpdatePayload) { + return requestClient.post(`${prefix}export-scheme-update`, data); +} + +export async function deleteOrderExportScheme(data: { id: number }) { + return requestClient.post(`${prefix}export-scheme-delete`, data); +} + /** * 更新包邮状态 * @param data diff --git a/apps/web-antd/src/views/business/order/product-order/components/ProductOrderExportFieldSortModal.vue b/apps/web-antd/src/views/business/order/product-order/components/ProductOrderExportFieldSortModal.vue new file mode 100644 index 00000000..50b5d89d --- /dev/null +++ b/apps/web-antd/src/views/business/order/product-order/components/ProductOrderExportFieldSortModal.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/apps/web-antd/src/views/business/order/product-order/components/ProductOrderExportModal.vue b/apps/web-antd/src/views/business/order/product-order/components/ProductOrderExportModal.vue new file mode 100644 index 00000000..fcab1521 --- /dev/null +++ b/apps/web-antd/src/views/business/order/product-order/components/ProductOrderExportModal.vue @@ -0,0 +1,851 @@ + + + diff --git a/apps/web-antd/src/views/business/order/product-order/config/exportFields.ts b/apps/web-antd/src/views/business/order/product-order/config/exportFields.ts new file mode 100644 index 00000000..c1047043 --- /dev/null +++ b/apps/web-antd/src/views/business/order/product-order/config/exportFields.ts @@ -0,0 +1,98 @@ +export type ExportFieldFormat = 'text' | 'currency' | 'datetime' | 'integer'; +export type ExportFieldAlign = 'left' | 'center' | 'right'; +export type ExportGranularity = 'order' | 'item'; + +export interface ExportFieldMeta { + key: string; + label: string; + default: boolean; + width: number; + align: ExportFieldAlign; + group: string; + format: ExportFieldFormat; + /** 汇总行是否对该列求和 */ + summary?: boolean; +} + +export const ORDER_EXPORT_FIELDS: ExportFieldMeta[] = [ + { key: 'id', label: 'ID', default: false, width: 10, align: 'center', group: '基础信息', format: 'integer' }, + { key: 'order_no', label: '订单号', default: true, width: 22, align: 'left', group: '基础信息', format: 'text' }, + { key: 'store_name', label: '诊所名称', default: true, width: 18, align: 'left', group: '基础信息', format: 'text' }, + { key: 'warehouse_name', label: '仓库', default: false, width: 14, align: 'left', group: '基础信息', format: 'text' }, + { key: 'doctor_name', label: '医生', default: true, width: 12, align: 'left', group: '基础信息', format: 'text' }, + { key: 'patient_name', label: '就诊人', default: false, width: 12, align: 'left', group: '基础信息', format: 'text' }, + { key: 'order_type_text', label: '订单类型', default: true, width: 14, align: 'center', group: '基础信息', format: 'text' }, + { key: 'prescription_type_text', label: '处方类型', default: true, width: 14, align: 'center', group: '基础信息', format: 'text' }, + { key: 'pay_type_text', label: '支付类型', default: false, width: 12, align: 'center', group: '基础信息', format: 'text' }, + { key: 'dosage', label: '中药剂数', default: false, width: 10, align: 'center', group: '基础信息', format: 'integer' }, + { key: 'is_free_shipping_text', label: '是否包邮', default: false, width: 10, align: 'center', group: '基础信息', format: 'text' }, + { key: 'items_price', label: '商品总价', default: true, width: 12, align: 'right', group: '金额', format: 'currency', summary: true }, + { key: 'market_price', label: '采购价', default: false, width: 12, align: 'right', group: '金额', format: 'currency', summary: true }, + { key: 'decoct_price', label: '代煎费用', default: false, width: 12, align: 'right', group: '金额', format: 'currency', summary: true }, + { key: 'process_price', label: '加工费用', default: false, width: 12, align: 'right', group: '金额', format: 'currency', summary: true }, + { key: 'treatement_price', label: '诊疗费用', default: false, width: 12, align: 'right', group: '金额', format: 'currency', summary: true }, + { key: 'total_pay_price', label: '支付总价', default: true, width: 12, align: 'right', group: '金额', format: 'currency', summary: true }, + { key: 'trans_expenses', label: '运费', default: false, width: 10, align: 'right', group: '金额', format: 'currency', summary: true }, + { key: 'pay_time', label: '支付时间', default: true, width: 20, align: 'center', group: '时间', format: 'datetime' }, + { key: 'status_text', label: '订单状态', default: true, width: 12, align: 'center', group: '状态', format: 'text' }, + { key: 'pay_method_text', label: '订单来源', default: false, width: 16, align: 'center', group: '状态', format: 'text' }, + { key: 'delivery_method_text', label: '配送方式', default: true, width: 10, align: 'center', group: '状态', format: 'text' }, + { key: 'remark', label: '留言备注', default: false, width: 20, align: 'left', group: '收货信息', format: 'text' }, + { key: 'express_name', label: '收货人', default: true, width: 12, align: 'left', group: '收货信息', format: 'text' }, + { key: 'express_mobile', label: '收货电话', default: true, width: 14, align: 'left', group: '收货信息', format: 'text' }, + { key: 'express_region', label: '收货省市区', default: false, width: 18, align: 'left', group: '收货信息', format: 'text' }, + { key: 'express_address', label: '收货地址', default: true, width: 24, align: 'left', group: '收货信息', format: 'text' }, + { key: 'is_send_text', label: '是否发货', default: false, width: 10, align: 'center', group: '发货', format: 'text' }, + { key: 'send_time', label: '发货时间', default: false, width: 20, align: 'center', group: '发货', format: 'datetime' }, + { key: 'introduce', label: '发货备注', default: false, width: 20, align: 'left', group: '发货', format: 'text' }, + { key: 'created_at', label: '创建时间', default: true, width: 20, align: 'center', group: '时间', format: 'datetime' }, +]; + +export const ITEM_EXPORT_FIELDS: ExportFieldMeta[] = [ + { key: 'epl_order_no', label: '易票联订单号', default: false, width: 22, align: 'left', group: '基础信息', format: 'text' }, + { key: 'order_no', label: '订单号', default: true, width: 22, align: 'left', group: '基础信息', format: 'text' }, + { key: 'store_name', label: '诊所名称', default: true, width: 18, align: 'left', group: '基础信息', format: 'text' }, + { key: 'drug_name', label: '商品名称', default: true, width: 20, align: 'left', group: '商品', format: 'text' }, + { key: 'specification', label: '规格', default: true, width: 14, align: 'left', group: '商品', format: 'text' }, + { key: 'manufacturer', label: '厂家', default: true, width: 16, align: 'left', group: '商品', format: 'text' }, + { key: 'unit_price', label: '单价', default: true, width: 12, align: 'right', group: '金额', format: 'currency' }, + { key: 'number', label: '数量', default: true, width: 10, align: 'center', group: '商品', format: 'integer' }, + { key: 'line_amount', label: '行金额', default: true, width: 12, align: 'right', group: '金额', format: 'currency', summary: true }, + { key: 'process_price', label: '加工费', default: false, width: 12, align: 'right', group: '金额', format: 'currency' }, + { key: 'treatement_price', label: '诊疗费', default: false, width: 12, align: 'right', group: '金额', format: 'currency' }, + { key: 'total_pay_price', label: '订单总价', default: true, width: 12, align: 'right', group: '金额', format: 'currency' }, + { key: 'trans_expenses', label: '运费', default: false, width: 10, align: 'right', group: '金额', format: 'currency' }, + { key: 'doctor_name', label: '医生', default: true, width: 12, align: 'left', group: '基础信息', format: 'text' }, + { key: 'status_text', label: '订单状态', default: true, width: 12, align: 'center', group: '状态', format: 'text' }, + { key: 'refund_status_text', label: '售后状态', default: false, width: 12, align: 'center', group: '状态', format: 'text' }, + { key: 'created_at', label: '下单时间', default: true, width: 20, align: 'center', group: '时间', format: 'datetime' }, + { key: 'express_name', label: '收件人', default: true, width: 12, align: 'left', group: '收货信息', format: 'text' }, + { key: 'express_mobile', label: '电话', default: true, width: 14, align: 'left', group: '收货信息', format: 'text' }, + { key: 'express_address', label: '地址', default: true, width: 28, align: 'left', group: '收货信息', format: 'text' }, + { key: 'pay_time', label: '支付时间', default: true, width: 20, align: 'center', group: '时间', format: 'datetime' }, +]; + +export function getExportFields(granularity: ExportGranularity): ExportFieldMeta[] { + return granularity === 'item' ? ITEM_EXPORT_FIELDS : ORDER_EXPORT_FIELDS; +} + +export function getDefaultSelectedKeys(granularity: ExportGranularity): string[] { + return getExportFields(granularity) + .filter((f) => f.default) + .map((f) => f.key); +} + +export function groupExportFields(fields: ExportFieldMeta[]): Record { + return fields.reduce>((acc, field) => { + const group = field.group; + if (!acc[group]) { + acc[group] = []; + } + acc[group]!.push(field); + return acc; + }, {}); +} + +export function granularityLabel(granularity: ExportGranularity): string { + return granularity === 'item' ? '商品明细维度' : '订单维度'; +} diff --git a/apps/web-antd/src/views/business/order/product-order/index.vue b/apps/web-antd/src/views/business/order/product-order/index.vue index f4b5315d..02b7b5f2 100644 --- a/apps/web-antd/src/views/business/order/product-order/index.vue +++ b/apps/web-antd/src/views/business/order/product-order/index.vue @@ -17,9 +17,7 @@ import { Button, Image, message, Modal as AntdModal, Popover, Space, Switch, Tab import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { TableAction } from '#/components/table-action'; -import { downloadByData } from '#/util/tool'; import { - exportOrderApi, getOrderInfo, getOrderList, getVerifyRecentOrderAmounts, @@ -37,6 +35,7 @@ import PrescriptionDetail from '#/views/doctor/doctor-reception/components/Presc import DetailModal from './components/detail.vue'; import FormModalDemo from './components/modal.vue'; +import ProductOrderExportModal from './components/ProductOrderExportModal.vue'; import Refund from './components/refund.vue'; import { formOptions } from './config/search'; import { gridOptions } from './config/table'; @@ -130,6 +129,10 @@ const [RefundModal, RefundModalApi] = useVbenModal({ connectedComponent: Refund, }); +const [ExportModal, exportModalApi] = useVbenModal({ + connectedComponent: ProductOrderExportModal, +}); + const infoModal = (data = {}) => { modalApi.setData({ // 表单值 @@ -327,15 +330,10 @@ const openPrescriptionDetail = (values) => { }; /** - * 西药导出 + * 打开导出弹窗 */ -const passApplication = () => { - // 新标签跳转到 exportWesternMedicineApi - exportOrderApi().then((res) => { - // 创建新的URL表示指定的File对象或者Blob对象。 - downloadByData(res.data, `萧康云医-商品订单导出${new Date()}.xlsx`); - message.success('导出成功!'); - }); +const openExportModal = () => { + exportModalApi.open(); }; const openRefundModal = (id) => { @@ -457,6 +455,7 @@ const openOrderAmountVerify = () => { + @@ -477,7 +476,7 @@ const openOrderAmountVerify = () => { type: 'primary', icon: 'ix:export-check', // auth: ['超级西(中成)药', 'sys:user:save'], - onClick: passApplication.bind(null), + onClick: openExportModal.bind(null), }, { label: '展开全部', diff --git a/apps/web-antd/src/views/business/order/product-order/utils/exportFieldOrder.ts b/apps/web-antd/src/views/business/order/product-order/utils/exportFieldOrder.ts new file mode 100644 index 00000000..526eb050 --- /dev/null +++ b/apps/web-antd/src/views/business/order/product-order/utils/exportFieldOrder.ts @@ -0,0 +1,116 @@ +import type { + ExportFieldMeta, + ExportGranularity, +} from '#/views/business/order/product-order/config/exportFields'; +import { getExportFields } from '#/views/business/order/product-order/config/exportFields'; + +/** 「金额」分组字段 key,保持 exportFields 定义顺序 */ +export function getAmountFieldKeys(granularity: ExportGranularity): string[] { + return getExportFields(granularity) + .filter((f) => f.group === '金额') + .map((f) => f.key); +} + +/** 全量字段 canonical 顺序 */ +export function getCanonicalFieldOrder(granularity: ExportGranularity): string[] { + return getExportFields(granularity).map((f) => f.key); +} + +/** + * 将已选金额字段聚合为连续块,块内按金额组定义序排列。 + * 块插入位置:原序中第一个已选金额字段的位置。 + */ +export function clusterAmountFields( + keys: string[], + granularity: ExportGranularity, +): string[] { + const amountKeys = getAmountFieldKeys(granularity); + const amountSet = new Set(amountKeys); + const selectedAmount = amountKeys.filter((k) => keys.includes(k)); + if (selectedAmount.length <= 1) { + return [...keys]; + } + + const nonAmount = keys.filter((k) => !amountSet.has(k)); + + let firstAmountIdx = keys.length; + for (let i = 0; i < keys.length; i++) { + if (amountSet.has(keys[i]!)) { + firstAmountIdx = i; + break; + } + } + + let nonAmountBefore = 0; + for (let i = 0; i < firstAmountIdx; i++) { + if (!amountSet.has(keys[i]!)) { + nonAmountBefore++; + } + } + + const result = [...nonAmount]; + result.splice(nonAmountBefore, 0, ...selectedAmount); + return result; +} + +/** canonical 过滤 + 费用聚合 */ +export function buildDefaultOrderedKeys( + selectedKeys: string[], + granularity: ExportGranularity, +): string[] { + const selectedSet = new Set(selectedKeys); + const ordered = getCanonicalFieldOrder(granularity).filter((k) => + selectedSet.has(k), + ); + return clusterAmountFields(ordered, granularity); +} + +/** + * 勾选变更时同步有序列表。 + * customized=false:重建默认序(含费用聚合) + * customized=true:仅增删,新增字段按 canonical 位置插入 + */ +export function syncOrderedSelectedKeys( + selectedKeys: string[], + prevOrdered: string[], + granularity: ExportGranularity, + customized: boolean, +): string[] { + const selectedSet = new Set(selectedKeys); + + if (!customized) { + return buildDefaultOrderedKeys(selectedKeys, granularity); + } + + let ordered = prevOrdered.filter((k) => selectedSet.has(k)); + const orderedSet = new Set(ordered); + const added = selectedKeys.filter((k) => !orderedSet.has(k)); + + if (added.length === 0) { + return ordered; + } + + const canonical = getCanonicalFieldOrder(granularity); + for (const key of added) { + const canonicalIdx = canonical.indexOf(key); + let insertAt = ordered.length; + for (let i = 0; i < ordered.length; i++) { + const existingIdx = canonical.indexOf(ordered[i]!); + if (existingIdx > canonicalIdx) { + insertAt = i; + break; + } + } + ordered = [...ordered.slice(0, insertAt), key, ...ordered.slice(insertAt)]; + } + + return ordered; +} + +export function formatFieldOrderPreview( + keys: string[], + fieldMeta: ExportFieldMeta[], +): string { + const labelMap = new Map(fieldMeta.map((f) => [f.key, f.label])); + return keys.map((k) => labelMap.get(k) ?? k).join(' → '); +} diff --git a/apps/web-antd/src/views/business/order/product-order/utils/exportProductOrderExcel.ts b/apps/web-antd/src/views/business/order/product-order/utils/exportProductOrderExcel.ts new file mode 100644 index 00000000..fd15ac62 --- /dev/null +++ b/apps/web-antd/src/views/business/order/product-order/utils/exportProductOrderExcel.ts @@ -0,0 +1,403 @@ +import ExcelJS from 'exceljs'; + +import type { + ExportFieldMeta, + ExportGranularity, +} from '#/views/business/order/product-order/config/exportFields'; +import { granularityLabel } from '#/views/business/order/product-order/config/exportFields'; + +export interface OrderExportFilterInfo { + search_time: [string, string]; + order_no?: string; + store_name?: string; + status_text?: string; + delivery_method_text?: string; + prescription_type_text?: string; + export_fields_text?: string; + row_count?: number; +} + +export interface ExportProductOrderExcelOptions { + rows: Record[]; + selectedFields: string[]; + fieldMeta: ExportFieldMeta[]; + filters: OrderExportFilterInfo; + granularity: ExportGranularity; + exportedAt?: Date; +} + +const THIN_BORDER: Partial = { + top: { style: 'thin', color: { argb: 'FF000000' } }, + left: { style: 'thin', color: { argb: 'FF000000' } }, + bottom: { style: 'thin', color: { argb: 'FF000000' } }, + right: { style: 'thin', color: { argb: 'FF000000' } }, +}; + +const TEXT_KEYS = new Set(['order_no', 'express_mobile', 'epl_order_no']); + +function alignOf(meta: ExportFieldMeta): Partial { + const horizontal = + meta.align === 'right' + ? 'right' + : meta.align === 'left' + ? 'left' + : 'center'; + return { horizontal, vertical: 'middle', wrapText: meta.format === 'text' }; +} + +function cellValue(row: Record, key: string, format: ExportFieldMeta['format']) { + const raw = row[key]; + if (raw === null || raw === undefined || raw === '') { + return ''; + } + if (format === 'currency' || format === 'integer') { + const num = Number(raw); + return Number.isFinite(num) ? num : raw; + } + return String(raw); +} + +function applyCellFormat(cell: ExcelJS.Cell, meta: ExportFieldMeta) { + cell.alignment = alignOf(meta); + cell.border = THIN_BORDER; + if (TEXT_KEYS.has(meta.key)) { + cell.numFmt = '@'; + } else if (meta.format === 'currency') { + cell.numFmt = '#,##0.00'; + } else if (meta.format === 'datetime') { + cell.numFmt = 'yyyy-mm-dd hh:mm:ss'; + } else if (meta.format === 'integer') { + cell.numFmt = '0'; + } +} + +function filterRows(filters: OrderExportFilterInfo): { + pairs: [[string, string], [string, string]][]; + exportFields: [string, string]; + rowCount: [string, string]; +} { + const [start, end] = filters.search_time; + return { + pairs: [ + [ + ['时间范围', `${start} ~ ${end}`], + ['订单号', filters.order_no?.trim() || '全部'], + ], + [ + ['门店', filters.store_name?.trim() || '全部'], + ['订单状态', filters.status_text?.trim() || '全部'], + ], + [ + ['发货方式', filters.delivery_method_text?.trim() || '全部'], + ['订单类型', filters.prescription_type_text?.trim() || '全部'], + ], + ], + exportFields: ['导出字段', filters.export_fields_text || '—'], + rowCount: [ + '数据条数', + filters.row_count != null ? `共 ${filters.row_count} 条` : '—', + ], + }; +} + +function styleFilterLabel(cell: ExcelJS.Cell, label: string) { + cell.value = label; + cell.font = { bold: true, size: 10 }; + cell.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFE8F0FE' }, + }; + cell.alignment = { horizontal: 'right', vertical: 'middle' }; + cell.border = THIN_BORDER; +} + +function styleFilterValue(cell: ExcelJS.Cell, value: string, wrapText = false) { + cell.value = value; + cell.font = { size: 10 }; + cell.alignment = { horizontal: 'left', vertical: 'middle', wrapText }; + cell.border = THIN_BORDER; +} + +/** 一行两个条件:左/右各一组 label|value;right 省略时右侧合并留空 */ +function renderFilterPairRow( + sheet: ExcelJS.Worksheet, + row: number, + layoutCols: number, + left: [string, string], + right?: [string, string], +) { + const mid = Math.floor(layoutCols / 2); + const leftValueEnd = mid; + const rightLabelCol = mid + 1; + const rightValueStart = mid + 2; + + styleFilterLabel(sheet.getCell(row, 1), left[0]); + if (leftValueEnd >= 2) { + sheet.mergeCells(row, 2, row, leftValueEnd); + } + styleFilterValue(sheet.getCell(row, 2), left[1]); + + if (right?.[0]) { + styleFilterLabel(sheet.getCell(row, rightLabelCol), right[0]); + if (rightValueStart <= layoutCols) { + if (rightValueStart < layoutCols) { + sheet.mergeCells(row, rightValueStart, row, layoutCols); + } + styleFilterValue(sheet.getCell(row, rightValueStart), right[1] ?? ''); + } + } else if (rightLabelCol <= layoutCols) { + sheet.mergeCells(row, rightLabelCol, row, layoutCols); + const emptyCell = sheet.getCell(row, rightLabelCol); + emptyCell.border = THIN_BORDER; + } + sheet.getRow(row).height = 20; +} + +/** 单行条件:标签 + 值占满剩余列 */ +function renderFilterFullRow( + sheet: ExcelJS.Worksheet, + row: number, + layoutCols: number, + label: string, + value: string, + rowHeight = 20, +) { + styleFilterLabel(sheet.getCell(row, 1), label); + if (layoutCols >= 2) { + sheet.mergeCells(row, 2, row, layoutCols); + } + styleFilterValue(sheet.getCell(row, 2), value, true); + sheet.getRow(row).height = rowHeight; +} + +function renderFilterBlock( + sheet: ExcelJS.Worksheet, + filters: OrderExportFilterInfo, + startRow: number, + layoutCols: number, +): number { + const { pairs, exportFields, rowCount } = filterRows(filters); + let r = startRow; + + pairs.forEach(([left, right]) => { + renderFilterPairRow(sheet, r, layoutCols, left, right); + r += 1; + }); + + renderFilterFullRow(sheet, r, layoutCols, exportFields[0], exportFields[1], 28); + r += 1; + + renderFilterPairRow(sheet, r, layoutCols, rowCount); + r += 1; + + return r; +} + +function autoFitColumns( + sheet: ExcelJS.Worksheet, + colCount: number, + fieldMeta: ExportFieldMeta[], + dataStartRow: number, + dataEndRow: number, +) { + for (let c = 1; c <= colCount; c++) { + const meta = fieldMeta[c - 1]; + let maxLen = meta?.label.length ?? 8; + for (let r = dataStartRow; r <= dataEndRow; r++) { + const cell = sheet.getCell(r, c); + const v = cell.value?.toString() ?? ''; + maxLen = Math.max(maxLen, v.length); + } + const width = Math.min(Math.max(maxLen * 1.15 + 2, meta?.width ?? 10), 55); + sheet.getColumn(c).width = width; + } +} + +export async function exportProductOrderExcel( + options: ExportProductOrderExcelOptions, +): Promise { + const { + rows, + selectedFields, + fieldMeta, + filters, + granularity, + exportedAt = new Date(), + } = options; + + const columns = selectedFields + .map((key) => fieldMeta.find((f) => f.key === key)) + .filter((f): f is ExportFieldMeta => !!f); + + if (columns.length === 0) { + throw new Error('请至少选择一个导出字段'); + } + + const workbook = new ExcelJS.Workbook(); + workbook.creator = '萧康云医'; + const sheetName = granularity === 'item' ? '商品明细' : '商品订单'; + const sheet = workbook.addWorksheet(sheetName, { + views: [{ showGridLines: true }], + }); + + const colCount = columns.length; + const layoutCols = Math.max(colCount, 4); + + // Row 1: title + sheet.mergeCells(1, 1, 1, layoutCols); + const titleCell = sheet.getCell(1, 1); + titleCell.value = `萧康云医 · 商品订单导出报表(${granularityLabel(granularity)})`; + titleCell.font = { bold: true, size: 16, color: { argb: 'FF1F4E79' } }; + titleCell.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFF5F7FA' }, + }; + titleCell.alignment = { horizontal: 'center', vertical: 'middle' }; + titleCell.border = THIN_BORDER; + sheet.getRow(1).height = 36; + + // Row 2: meta + const metaSplit = Math.max(1, Math.floor(layoutCols / 2)); + sheet.mergeCells(2, 1, 2, metaSplit); + sheet.mergeCells(2, metaSplit + 1, 2, layoutCols); + const leftMeta = sheet.getCell(2, 1); + leftMeta.value = `导出时间:${formatDateTime(exportedAt)}`; + leftMeta.font = { size: 10 }; + leftMeta.alignment = { horizontal: 'left', vertical: 'middle' }; + const rightMeta = sheet.getCell(2, metaSplit + 1); + rightMeta.value = `导出粒度:${granularityLabel(granularity)}`; + rightMeta.font = { size: 10 }; + rightMeta.alignment = { horizontal: 'right', vertical: 'middle' }; + sheet.getRow(2).height = 22; + + // Filter block: 2 conditions per row (4-col layout minimum) + const filterStartRow = 3; + const afterFilterRow = renderFilterBlock(sheet, filters, filterStartRow, layoutCols); + + const headerRowNum = afterFilterRow + 1; + sheet.getRow(afterFilterRow).height = 8; + + // Header row + columns.forEach((meta, i) => { + const cell = sheet.getCell(headerRowNum, i + 1); + cell.value = meta.label; + cell.font = { bold: true, color: { argb: 'FF000000' } }; + cell.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFFFFF00' }, + }; + cell.alignment = { horizontal: 'center', vertical: 'middle' }; + cell.border = THIN_BORDER; + }); + sheet.getRow(headerRowNum).height = 30; + + const dataStartRow = headerRowNum + 1; + rows.forEach((row, rowIdx) => { + const excelRow = sheet.getRow(dataStartRow + rowIdx); + columns.forEach((meta, colIdx) => { + const cell = excelRow.getCell(colIdx + 1); + cell.value = cellValue(row, meta.key, meta.format) as ExcelJS.CellValue; + applyCellFormat(cell, meta); + if (rowIdx % 2 === 1) { + cell.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFF2F2F2' }, + }; + } + }); + excelRow.height = 22; + }); + + const dataEndRow = dataStartRow + Math.max(rows.length, 1) - 1; + + // Summary row (order granularity) + let summaryRowNum = dataEndRow + 1; + if (granularity === 'order' && rows.length > 0) { + const summaryFields = columns.filter((c) => c.summary); + if (summaryFields.length > 0) { + summaryRowNum = dataEndRow + 2; + sheet.mergeCells(summaryRowNum, 1, summaryRowNum, Math.min(2, colCount)); + const summaryTitle = sheet.getCell(summaryRowNum, 1); + summaryTitle.value = '汇总'; + summaryTitle.font = { bold: true, size: 11 }; + summaryTitle.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFFFF9E6' }, + }; + summaryTitle.border = { + ...THIN_BORDER, + top: { style: 'double', color: { argb: 'FF000000' } }, + }; + + columns.forEach((meta, colIdx) => { + const cell = sheet.getCell(summaryRowNum, colIdx + 1); + cell.border = { + ...THIN_BORDER, + top: { style: 'double', color: { argb: 'FF000000' } }, + }; + cell.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFFFF9E6' }, + }; + cell.font = { bold: true }; + + if (colIdx === 0) { + cell.value = '汇总'; + } else if (meta.summary) { + const sum = rows.reduce((acc, row) => { + const n = Number(row[meta.key]); + return acc + (Number.isFinite(n) ? n : 0); + }, 0); + cell.value = sum; + applyCellFormat(cell, meta); + } + }); + sheet.getRow(summaryRowNum).height = 24; + } + } + + autoFitColumns(sheet, colCount, columns, headerRowNum, dataEndRow); + + sheet.autoFilter = { + from: { row: headerRowNum, column: 1 }, + to: { row: dataEndRow, column: colCount }, + }; + + sheet.views = [ + { + state: 'frozen', + xSplit: 0, + ySplit: headerRowNum, + topLeftCell: `A${headerRowNum + 1}`, + activeCell: `A${headerRowNum + 1}`, + }, + ]; + + sheet.pageSetup = { + orientation: 'landscape', + fitToPage: true, + fitToWidth: 1, + fitToHeight: 0, + }; + + return workbook.xlsx.writeBuffer(); +} + +function formatDateTime(d: Date): string { + const pad = (n: number) => String(n).padStart(2, '0'); + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; +} + +export function exportFilename( + granularity: ExportGranularity, + searchTime: [string, string], +): string { + const g = granularity === 'item' ? '明细' : '订单'; + return `萧康云医-商品订单_${g}_${searchTime[0]}_${searchTime[1]}.xlsx`; +} diff --git a/apps/web-antd/src/views/business/order/product-order/utils/exportScheme.ts b/apps/web-antd/src/views/business/order/product-order/utils/exportScheme.ts new file mode 100644 index 00000000..f57dc73f --- /dev/null +++ b/apps/web-antd/src/views/business/order/product-order/utils/exportScheme.ts @@ -0,0 +1,85 @@ +import type { ExportGranularity } from '#/views/business/order/product-order/config/exportFields'; +import { getExportFields } from '#/views/business/order/product-order/config/exportFields'; +import { buildDefaultOrderedKeys } from '#/views/business/order/product-order/utils/exportFieldOrder'; + +export interface ExportSchemeConfig { + order_no?: string; + store_id?: number | null; + status?: number | null; + delivery_method?: number | null; + prescription_type?: number | null; + /** 有序导出字段 key 列表 */ + selected_fields: string[]; +} + +export interface ExportSchemeItem { + id: number; + name: string; + is_default: number; + granularity: ExportGranularity; + config: ExportSchemeConfig; + created_at?: string; + updated_at?: string; +} + +export interface ExportFormState { + orderNo: string; + storeId: number | undefined; + status: number | undefined; + deliveryMethod: number | undefined; + prescriptionType: number | undefined; + granularity: ExportGranularity; + selectedKeys: string[]; + orderedSelectedKeys: string[]; +} + +export function buildSchemeConfigFromForm(form: ExportFormState): ExportSchemeConfig { + return { + order_no: form.orderNo.trim(), + store_id: form.storeId ?? null, + status: form.status ?? null, + delivery_method: form.deliveryMethod ?? null, + prescription_type: form.prescriptionType ?? null, + selected_fields: [...form.orderedSelectedKeys], + }; +} + +export function sanitizeSelectedFields( + granularity: ExportGranularity, + keys: string[], +): string[] { + const allowed = new Set(getExportFields(granularity).map((f) => f.key)); + return keys.filter((k) => allowed.has(k)); +} + +export function applySchemeToForm( + scheme: ExportSchemeItem, + form: ExportFormState, +): void { + const cfg = scheme.config ?? ({} as ExportSchemeConfig); + form.granularity = scheme.granularity; + form.orderNo = cfg.order_no?.trim() ?? ''; + form.storeId = cfg.store_id ?? undefined; + form.status = cfg.status ?? undefined; + form.deliveryMethod = cfg.delivery_method ?? undefined; + form.prescriptionType = cfg.prescription_type ?? undefined; + + const fields = sanitizeSelectedFields( + scheme.granularity, + cfg.selected_fields ?? [], + ); + if (fields.length > 0) { + form.orderedSelectedKeys = fields; + form.selectedKeys = [...fields]; + } else { + const defaults = getExportFields(scheme.granularity) + .filter((f) => f.default) + .map((f) => f.key); + form.selectedKeys = defaults; + form.orderedSelectedKeys = buildDefaultOrderedKeys(defaults, scheme.granularity); + } +} + +export function findDefaultScheme(schemes: ExportSchemeItem[]): ExportSchemeItem | undefined { + return schemes.find((s) => s.is_default === 1); +} diff --git a/apps/web-antd/src/views/system/pharmacy/index.vue b/apps/web-antd/src/views/system/pharmacy/index.vue index 65dc7dae..7a913a02 100644 --- a/apps/web-antd/src/views/system/pharmacy/index.vue +++ b/apps/web-antd/src/views/system/pharmacy/index.vue @@ -133,10 +133,13 @@ const openBankCardReport = async (row: any, update = false) => { } catch { report = null; } + const reportStatus = Number(report?.report_status ?? 0); + const useUpdate = + (update || [3, 4].includes(reportStatus)) && Boolean(report?.id); bankCardReportModalApi.setData({ storeId: row.id, storeName: row.name, - mode: update && report?.id ? 'update' : 'report', + mode: useUpdate ? 'update' : 'report', report, gridApi, }); diff --git a/apps/web-antd/src/views/system/process/components/inline-field-editor.vue b/apps/web-antd/src/views/system/process/components/inline-field-editor.vue index 5400fb60..b95243e5 100644 --- a/apps/web-antd/src/views/system/process/components/inline-field-editor.vue +++ b/apps/web-antd/src/views/system/process/components/inline-field-editor.vue @@ -1,186 +1,182 @@ - - - - - + + + + + diff --git a/apps/web-antd/src/views/system/process/components/method-info-bar.vue b/apps/web-antd/src/views/system/process/components/method-info-bar.vue index 071c3483..c55b2904 100644 --- a/apps/web-antd/src/views/system/process/components/method-info-bar.vue +++ b/apps/web-antd/src/views/system/process/components/method-info-bar.vue @@ -1,118 +1,220 @@ + + + + + diff --git a/apps/web-antd/src/views/system/process/composables/useProcessTree.ts b/apps/web-antd/src/views/system/process/composables/useProcessTree.ts index 6d394e44..ea64ff8c 100644 --- a/apps/web-antd/src/views/system/process/composables/useProcessTree.ts +++ b/apps/web-antd/src/views/system/process/composables/useProcessTree.ts @@ -3,6 +3,10 @@ import { computed, ref } from 'vue'; import { getProcessList } from '#/views/system/process/api'; import type { ProcessNoteRow, ProcessRuleRow } from '../constants'; +import { + readProcessSelection, + writeProcessSelection, +} from '../utils/processSelectionStorage'; export function parseProcessListItems(items: Record[]) { const level1: ProcessRuleRow[] = []; @@ -47,14 +51,35 @@ export function parseProcessListItems(items: Record[]) { return { level1, level2Map, notesMap }; } +function restoreSelection( + level1: ProcessRuleRow[], + level2Map: Record, + savedLevel1Id: number | null, + savedMethodId: number | null, +) { + let level1Id = savedLevel1Id; + if (!level1Id || !level1.some((item) => item.id === level1Id)) { + level1Id = level1[0]?.id ?? null; + } + + const methods = level1Id ? (level2Map[level1Id] ?? []) : []; + let methodId = savedMethodId; + if (!methodId || !methods.some((item) => item.id === methodId)) { + methodId = methods[0]?.id ?? null; + } + + return { level1Id, methodId }; +} + export function useProcessTree() { + const saved = readProcessSelection(); const loading = ref(false); const keyword = ref(''); const level1List = ref([]); const level2Map = ref>({}); const notesMap = ref>({}); - const selectedLevel1Id = ref(null); - const selectedMethodId = ref(null); + const selectedLevel1Id = ref(saved.level1Id); + const selectedMethodId = ref(saved.methodId); const filteredLevel1 = computed(() => { const kw = keyword.value.trim().toLowerCase(); @@ -103,6 +128,13 @@ export function useProcessTree() { level1List.value.find((item) => item.id === selectedLevel1Id.value), ); + function persistSelection() { + writeProcessSelection({ + level1Id: selectedLevel1Id.value, + methodId: selectedMethodId.value, + }); + } + async function loadData() { loading.value = true; try { @@ -116,22 +148,15 @@ export function useProcessTree() { level2Map.value = parsed.level2Map; notesMap.value = parsed.notesMap; - if ( - !selectedLevel1Id.value || - !level1List.value.some((item) => item.id === selectedLevel1Id.value) - ) { - selectedLevel1Id.value = level1List.value[0]?.id ?? null; - } - - const methods = selectedLevel1Id.value - ? (level2Map.value[selectedLevel1Id.value] ?? []) - : []; - if ( - !selectedMethodId.value || - !methods.some((item) => item.id === selectedMethodId.value) - ) { - selectedMethodId.value = methods[0]?.id ?? null; - } + const restored = restoreSelection( + level1List.value, + level2Map.value, + selectedLevel1Id.value, + selectedMethodId.value, + ); + selectedLevel1Id.value = restored.level1Id; + selectedMethodId.value = restored.methodId; + persistSelection(); } finally { loading.value = false; } @@ -141,10 +166,12 @@ export function useProcessTree() { selectedLevel1Id.value = id; const methods = level2Map.value[id] ?? []; selectedMethodId.value = methods[0]?.id ?? null; + persistSelection(); } function selectMethod(id: number) { selectedMethodId.value = id; + persistSelection(); } function patchRule(id: number, patch: Partial) { diff --git a/apps/web-antd/src/views/system/process/config/note-table.ts b/apps/web-antd/src/views/system/process/config/note-table.ts index 3d26a3dc..bb07acca 100644 --- a/apps/web-antd/src/views/system/process/config/note-table.ts +++ b/apps/web-antd/src/views/system/process/config/note-table.ts @@ -1,5 +1,7 @@ import type { VxeGridProps } from '#/adapter/vxe-table'; +import { SUB_LIST_GRID_HEIGHT } from '#/views/system/shared/sub-list-grid'; + export const noteGridOptions: VxeGridProps = { columnConfig: { useKey: true, @@ -33,7 +35,7 @@ export const noteGridOptions: VxeGridProps = { minWidth: 160, }, ], - height: 'auto', + height: SUB_LIST_GRID_HEIGHT, border: true, showOverflow: true, pagerConfig: { diff --git a/apps/web-antd/src/views/system/process/index.vue b/apps/web-antd/src/views/system/process/index.vue index 7d3859a9..845e276f 100644 --- a/apps/web-antd/src/views/system/process/index.vue +++ b/apps/web-antd/src/views/system/process/index.vue @@ -1,16 +1,9 @@ diff --git a/apps/web-antd/src/views/system/process/utils/processSelectionStorage.ts b/apps/web-antd/src/views/system/process/utils/processSelectionStorage.ts new file mode 100644 index 00000000..bb6b2ad3 --- /dev/null +++ b/apps/web-antd/src/views/system/process/utils/processSelectionStorage.ts @@ -0,0 +1,38 @@ +const STORAGE_KEY = 'process-management-selection'; + +export interface ProcessSelectionState { + level1Id: number | null; + methodId: number | null; +} + +export function readProcessSelection(): ProcessSelectionState { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) { + return { level1Id: null, methodId: null }; + } + const parsed = JSON.parse(raw) as Partial; + return { + level1Id: + parsed.level1Id != null && Number.isFinite(Number(parsed.level1Id)) + ? Number(parsed.level1Id) + : null, + methodId: + parsed.methodId != null && Number.isFinite(Number(parsed.methodId)) + ? Number(parsed.methodId) + : null, + }; + } catch { + return { level1Id: null, methodId: null }; + } +} + +export function writeProcessSelection(state: ProcessSelectionState) { + localStorage.setItem( + STORAGE_KEY, + JSON.stringify({ + level1Id: state.level1Id, + methodId: state.methodId, + }), + ); +} diff --git a/apps/web-antd/src/views/system/region/components/modal.vue b/apps/web-antd/src/views/system/region/components/modal.vue index c37afecb..d161ef9c 100644 --- a/apps/web-antd/src/views/system/region/components/modal.vue +++ b/apps/web-antd/src/views/system/region/components/modal.vue @@ -9,13 +9,44 @@ import { useVbenForm } from '#/adapter/form'; import { createRegion, updateRegion } from '#/views/system/region/api'; import { modalFormProps } from '#/views/system/region/config/form'; - - const isUpdate = ref(false); -const gridApi = ref(); +const onSuccessRef = ref<(() => void) | null>(null); const [Form, formApi] = useVbenForm(modalFormProps); +function buildSubmitPayload(values: Record) { + const formLevel = Number(values.formLevel); + return { + id: values.id, + name: values.name, + pid: formLevel === 1 ? 0 : values.pid, + express_fee: values.express_fee ?? 0, + }; +} + +function validateFormValues(values: Record) { + const formLevel = Number(values.formLevel); + if (!String(values.name ?? '').trim()) { + message.error('请输入名称'); + return false; + } + if (formLevel === 2 && !values.pid) { + message.error('请选择父级地区'); + return false; + } + if ( + values.express_fee === null || + values.express_fee === undefined || + values.express_fee === '' + ) { + message.error('请输入快递费'); + return false; + } + return true; +} + +const modalTitle = ref('新增快递费'); + const [Modal, modalApi] = useVbenModal({ fullscreenButton: false, draggable: true, @@ -23,38 +54,47 @@ const [Modal, modalApi] = useVbenModal({ modalApi.close(); }, onConfirm: async () => { - formApi.validate().then(async (e: any) => { - if (e.valid) { - const values = await formApi.getValues(); - modalApi.setState({ loading: true, confirmLoading: true }); - const submitApi = isUpdate.value ? updateRegion : createRegion; - submitApi(values) - .then(() => { - message.success('保存成功'); - gridApi.value?.reload(); - modalApi.close(); - }) - .finally(() => { - modalApi.setState({ loading: false, confirmLoading: false }); - }); - } - }); - await formApi.validateAndSubmitForm(); + const values = await formApi.getValues(); + if (!validateFormValues(values)) { + return; + } + const payload = buildSubmitPayload(values); + modalApi.setState({ loading: true, confirmLoading: true }); + const submitApi = isUpdate.value ? updateRegion : createRegion; + submitApi(payload) + .then(() => { + message.success('保存成功'); + onSuccessRef.value?.(); + modalApi.close(); + }) + .finally(() => { + modalApi.setState({ loading: false, confirmLoading: false }); + }); }, onOpenChange(isOpen: boolean) { - gridApi.value = isOpen ? modalApi.getData()?.gridApi : null; if (isOpen) { - const { values, update } = modalApi.getData>(); + const { values, update, formLevel, onSuccess } = modalApi.getData< + Record + >(); + isUpdate.value = update; + onSuccessRef.value = onSuccess ?? null; + const levelLabels: Record = { + 1: '地区', + 2: '子地区', + }; + modalTitle.value = `${update ? '编辑' : '新增'}${levelLabels[formLevel] ?? '快递费'}`; + formApi.resetForm(); if (values) { - isUpdate.value = update; - formApi.setValues(values); + formApi.setValues({ ...values, formLevel }); } + } else { + onSuccessRef.value = null; } }, }); diff --git a/apps/web-antd/src/views/system/region/components/move-pid-modal.vue b/apps/web-antd/src/views/system/region/components/move-pid-modal.vue new file mode 100644 index 00000000..abaf5b05 --- /dev/null +++ b/apps/web-antd/src/views/system/region/components/move-pid-modal.vue @@ -0,0 +1,87 @@ + + + diff --git a/apps/web-antd/src/views/system/region/components/region-info-bar.vue b/apps/web-antd/src/views/system/region/components/region-info-bar.vue new file mode 100644 index 00000000..f9469af0 --- /dev/null +++ b/apps/web-antd/src/views/system/region/components/region-info-bar.vue @@ -0,0 +1,91 @@ + + + + + diff --git a/apps/web-antd/src/views/system/region/composables/useRegionTree.ts b/apps/web-antd/src/views/system/region/composables/useRegionTree.ts new file mode 100644 index 00000000..194f1f82 --- /dev/null +++ b/apps/web-antd/src/views/system/region/composables/useRegionTree.ts @@ -0,0 +1,150 @@ +import { computed, ref } from 'vue'; + +import { getRegionList } from '#/views/system/region/api'; + +import type { RegionRow } from '../constants'; +import { + readRegionSelection, + writeRegionSelection, +} from '../utils/regionSelectionStorage'; + +export function parseRegionListItems(items: Record[]) { + const level1: RegionRow[] = []; + const level2Map: Record = {}; + + for (const item of items) { + const row: RegionRow = { + id: Number(item.id), + name: String(item.name ?? ''), + pid: Number(item.pid ?? 0), + code: item.code, + level: item.level, + express_fee: item.express_fee, + created_at: item.created_at, + }; + + if (Number(row.pid) === 0) { + level1.push(row); + continue; + } + + const parentId = Number(row.pid); + if (!level2Map[parentId]) { + level2Map[parentId] = []; + } + level2Map[parentId].push(row); + } + + level1.sort((a, b) => a.id - b.id); + Object.values(level2Map).forEach((list) => list.sort((a, b) => a.id - b.id)); + + return { level1, level2Map }; +} + +function restoreSelection(level1: RegionRow[], savedLevel1Id: number | null) { + let level1Id = savedLevel1Id; + if (!level1Id || !level1.some((item) => item.id === level1Id)) { + level1Id = level1[0]?.id ?? null; + } + return { level1Id }; +} + +export function useRegionTree() { + const saved = readRegionSelection(); + const loading = ref(false); + const keyword = ref(''); + const level1List = ref([]); + const level2Map = ref>({}); + const selectedLevel1Id = ref(saved.level1Id); + + const filteredLevel1 = computed(() => { + const kw = keyword.value.trim().toLowerCase(); + if (!kw) { + return level1List.value; + } + return level1List.value.filter((item) => { + const children = level2Map.value[item.id] ?? []; + if (item.name.toLowerCase().includes(kw)) { + return true; + } + return children.some((child) => child.name.toLowerCase().includes(kw)); + }); + }); + + const currentChildren = computed(() => { + if (!selectedLevel1Id.value) { + return []; + } + return level2Map.value[selectedLevel1Id.value] ?? []; + }); + + const selectedLevel1 = computed(() => + level1List.value.find((item) => item.id === selectedLevel1Id.value), + ); + + function persistSelection() { + writeRegionSelection({ + level1Id: selectedLevel1Id.value, + }); + } + + async function loadData() { + loading.value = true; + try { + const res = await getRegionList({ + page: 1, + pageSize: 500, + }); + const items = res?.items ?? []; + const parsed = parseRegionListItems(items); + level1List.value = parsed.level1; + level2Map.value = parsed.level2Map; + + const restored = restoreSelection( + level1List.value, + selectedLevel1Id.value, + ); + selectedLevel1Id.value = restored.level1Id; + persistSelection(); + } finally { + loading.value = false; + } + } + + function selectLevel1(id: number) { + selectedLevel1Id.value = id; + persistSelection(); + } + + function patchRegion(id: number, patch: Partial) { + const inLevel1 = level1List.value.find((item) => item.id === id); + if (inLevel1) { + Object.assign(inLevel1, patch); + return; + } + for (const list of Object.values(level2Map.value)) { + const found = list.find((item) => item.id === id); + if (found) { + Object.assign(found, patch); + if (patch.pid !== undefined && Number(patch.pid) !== Number(found.pid)) { + loadData(); + } + return; + } + } + } + + return { + currentChildren, + filteredLevel1, + keyword, + level1List, + level2Map, + loadData, + loading, + patchRegion, + selectLevel1, + selectedLevel1, + selectedLevel1Id, + }; +} diff --git a/apps/web-antd/src/views/system/region/config/child-table.ts b/apps/web-antd/src/views/system/region/config/child-table.ts new file mode 100644 index 00000000..af52e0be --- /dev/null +++ b/apps/web-antd/src/views/system/region/config/child-table.ts @@ -0,0 +1,47 @@ +import type { VxeGridProps } from '#/adapter/vxe-table'; + +import { SUB_LIST_GRID_HEIGHT } from '#/views/system/shared/sub-list-grid'; + +export const childGridOptions: VxeGridProps = { + columnConfig: { + useKey: true, + }, + rowConfig: { + useKey: true, + keyField: 'id', + }, + columns: [ + { + field: 'level', + title: '级别', + width: 80, + formatter: () => '子地区', + }, + { + field: 'name', + title: '名称', + minWidth: 160, + slots: { default: 'name' }, + }, + { + field: 'code', + title: '地区编码', + minWidth: 120, + }, + { + field: 'express_fee', + title: '快递费', + width: 140, + slots: { default: 'express_fee' }, + }, + ], + height: SUB_LIST_GRID_HEIGHT, + border: true, + showOverflow: true, + pagerConfig: { + enabled: false, + }, + toolbarConfig: { + enabled: false, + }, +}; diff --git a/apps/web-antd/src/views/system/region/config/form.ts b/apps/web-antd/src/views/system/region/config/form.ts index 6f187b8e..98aedeab 100644 --- a/apps/web-antd/src/views/system/region/config/form.ts +++ b/apps/web-antd/src/views/system/region/config/form.ts @@ -1,50 +1,93 @@ import type { VbenFormProps } from '#/adapter/form'; -import {getRegionOptionPidApi} from "#/views/system/region/api"; + +import { getRegionOptionPidApi } from '#/views/system/region/api'; export const modalFormProps: VbenFormProps = { - wrapperClass: 'grid-cols-12', // 24栅格, + wrapperClass: 'grid-cols-12', commonConfig: { formItemClass: 'col-span-12', - // 所有表单项 componentProps: { class: 'w-full', }, }, - // handleSubmit: onSubmit, layout: 'horizontal', schema: [ - // { - // fieldName: 'baseinfo', - // component: 'Divider', - // label: '基础信息', - // formItemClass: 'col-span-12', - // componentProps: {}, - // hideLabel: true, - // renderComponentContent: () => { - // return { - // default: () => { - // return '基础信息'; - // }, - // }; - // }, - // }, { component: 'VbenInput', fieldName: 'id', label: 'ID', - formItemClass: 'col-span-6', dependencies: { show: false, triggerFields: ['id'], }, }, + { + component: 'VbenInput', + fieldName: 'formLevel', + label: '表单层级', + dependencies: { + show: false, + triggerFields: ['formLevel'], + }, + }, + { + component: 'VbenInput', + fieldName: 'level_label', + label: '级别', + componentProps: { + disabled: true, + }, + dependencies: { + show(values) { + return Number(values.formLevel) === 2; + }, + trigger(_values, form) { + form.setFieldValue('level_label', '子地区'); + }, + triggerFields: ['formLevel'], + }, + }, + { + component: 'VbenInput', + componentProps: { + placeholder: '请输入名称', + }, + fieldName: 'name', + label: '名称', + }, + { + component: 'ApiSelect', + componentProps: { + placeholder: '请选择父级地区', + api: getRegionOptionPidApi, + filterOption: (input: string, option: any) => + option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0, + showSearch: true, + afterFetch: (data: { id: number; name: string }[]) => { + return data.map((item: any) => ({ + label: item.name, + value: item.id, + })); + }, + }, + fieldName: 'pid', + label: '父级地区', + dependencies: { + show(values) { + return Number(values.formLevel) === 2; + }, + triggerFields: ['formLevel'], + }, + }, { component: 'InputNumber', componentProps: { placeholder: '请输入价格', + class: 'w-full', + min: 0, }, fieldName: 'express_fee', - label: '价格', + label: '快递费', }, ], showDefaultActions: false, diff --git a/apps/web-antd/src/views/system/region/constants.ts b/apps/web-antd/src/views/system/region/constants.ts new file mode 100644 index 00000000..ac853b2c --- /dev/null +++ b/apps/web-antd/src/views/system/region/constants.ts @@ -0,0 +1,9 @@ +export interface RegionRow { + id: number; + name: string; + pid: number; + code?: string; + level?: number; + express_fee?: number | string; + created_at?: number | string; +} diff --git a/apps/web-antd/src/views/system/region/index.vue b/apps/web-antd/src/views/system/region/index.vue index 833925dc..43c526f0 100644 --- a/apps/web-antd/src/views/system/region/index.vue +++ b/apps/web-antd/src/views/system/region/index.vue @@ -1,140 +1,301 @@ diff --git a/apps/web-antd/src/views/system/region/utils/regionSelectionStorage.ts b/apps/web-antd/src/views/system/region/utils/regionSelectionStorage.ts new file mode 100644 index 00000000..882c556a --- /dev/null +++ b/apps/web-antd/src/views/system/region/utils/regionSelectionStorage.ts @@ -0,0 +1,32 @@ +const STORAGE_KEY = 'region-management-selection'; + +export interface RegionSelectionState { + level1Id: number | null; +} + +export function readRegionSelection(): RegionSelectionState { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) { + return { level1Id: null }; + } + const parsed = JSON.parse(raw) as Partial; + return { + level1Id: + parsed.level1Id != null && Number.isFinite(Number(parsed.level1Id)) + ? Number(parsed.level1Id) + : null, + }; + } catch { + return { level1Id: null }; + } +} + +export function writeRegionSelection(state: RegionSelectionState) { + localStorage.setItem( + STORAGE_KEY, + JSON.stringify({ + level1Id: state.level1Id, + }), + ); +} diff --git a/apps/web-antd/src/views/system/shared/config-section-header.vue b/apps/web-antd/src/views/system/shared/config-section-header.vue new file mode 100644 index 00000000..1c9e263e --- /dev/null +++ b/apps/web-antd/src/views/system/shared/config-section-header.vue @@ -0,0 +1,20 @@ + + + diff --git a/apps/web-antd/src/views/system/shared/config-tree-sidebar.vue b/apps/web-antd/src/views/system/shared/config-tree-sidebar.vue new file mode 100644 index 00000000..03ff3492 --- /dev/null +++ b/apps/web-antd/src/views/system/shared/config-tree-sidebar.vue @@ -0,0 +1,100 @@ + + + diff --git a/apps/web-antd/src/views/system/shared/sub-list-grid.ts b/apps/web-antd/src/views/system/shared/sub-list-grid.ts new file mode 100644 index 00000000..8f11e029 --- /dev/null +++ b/apps/web-antd/src/views/system/shared/sub-list-grid.ts @@ -0,0 +1 @@ +export const SUB_LIST_GRID_HEIGHT = 400; diff --git a/apps/web-antd/src/views/system/shared/types.ts b/apps/web-antd/src/views/system/shared/types.ts new file mode 100644 index 00000000..5bb115ee --- /dev/null +++ b/apps/web-antd/src/views/system/shared/types.ts @@ -0,0 +1,4 @@ +export interface TreeSidebarRow { + id: number; + name: string; +} diff --git a/apps/web-antd/src/views/system/store-bank-card/components/BankCardReportModal.vue b/apps/web-antd/src/views/system/store-bank-card/components/BankCardReportModal.vue index 8862c548..30b6bf30 100644 --- a/apps/web-antd/src/views/system/store-bank-card/components/BankCardReportModal.vue +++ b/apps/web-antd/src/views/system/store-bank-card/components/BankCardReportModal.vue @@ -42,6 +42,17 @@ const showImportAlert = computed( () => mode.value === 'report' && importReportId.value > 0, ); +const isRejectedResubmit = computed(() => { + const status = Number(reportInfo.value?.report_status ?? 0); + return Boolean(reportInfo.value?.id) && [3, 4].includes(status); +}); + +const showPendingSyncHint = computed( + () => + Boolean(reportInfo.value?.id) && + Number(reportInfo.value?.report_status) === 1, +); + const [Form, formApi] = useVbenForm(bankCardReportFormProps); async function loadSelectableCards() { @@ -95,7 +106,18 @@ const [Modal, modalApi] = useVbenModal({ const values = await formApi.getValues(); modalApi.setState({ loading: true, confirmLoading: true }); try { - if (mode.value === 'report') { + const shouldUpdate = + mode.value === 'update' || isRejectedResubmit.value; + if (shouldUpdate) { + await updateStoreBankCard({ + ...values, + id: values.id, + use_type: USE_TYPE_TRANSFER, + }); + message.success( + isRejectedResubmit.value ? '银行卡报备已重新提交' : '银行卡修改已提交', + ); + } else { await reportStoreBankCard({ ...values, store_id: values.store_id, @@ -105,13 +127,6 @@ const [Modal, modalApi] = useVbenModal({ message.success( importReportId.value > 0 ? '银行卡报备已导入' : '银行卡报备已提交', ); - } else { - await updateStoreBankCard({ - ...values, - id: values.id, - use_type: USE_TYPE_TRANSFER, - }); - message.success('银行卡修改已提交'); } gridApi.value?.reload(); modalApi.close(); @@ -134,6 +149,10 @@ const [Modal, modalApi] = useVbenModal({ reportInfo.value = data?.report ?? null; importReportId.value = 0; + const rejectedResubmit = [3, 4].includes( + Number(reportInfo.value?.report_status ?? 0), + ); + formApi.resetForm(); formApi.setValues({ store_id: storeId.value, @@ -141,10 +160,16 @@ const [Modal, modalApi] = useVbenModal({ use_type: USE_TYPE_TRANSFER, use_type_display: '转账卡(useType=2)', account_type: reportInfo.value?.account_type ?? '2', + report_status: reportInfo.value?.report_status ?? 0, ...(reportInfo.value ?? {}), id: reportInfo.value?.id, + attachment: rejectedResubmit ? '' : (reportInfo.value?.attachment ?? ''), }); loadSelectableCards(); + + if (reportInfo.value?.id && Number(reportInfo.value?.report_status) === 1) { + refreshStatus({ silent: true }); + } }, }); @@ -153,7 +178,7 @@ const modalTitle = computed(() => { return storeName.value ? `${prefix} - ${storeName.value}` : prefix; }); -async function refreshStatus() { +async function refreshStatus(options: { silent?: boolean } = {}) { if (!reportInfo.value?.id) { return; } @@ -161,14 +186,19 @@ async function refreshStatus() { try { const res = await queryStoreBankCardStatus({ id: reportInfo.value.id }); reportInfo.value = res; + const rejectedResubmit = [3, 4].includes(Number(res.report_status ?? 0)); formApi.setValues({ ...res, store_id: res.store_id, use_type: USE_TYPE_TRANSFER, use_type_display: '转账卡(useType=2)', + report_status: res.report_status ?? 0, only_remark: res.can_full_update === false ? 1 : 0, + attachment: rejectedResubmit ? '' : (res.attachment ?? ''), }); - message.success('状态已刷新'); + if (!options.silent) { + message.success('状态已刷新'); + } gridApi.value?.reload(); } finally { modalApi.setState({ loading: false }); @@ -192,6 +222,20 @@ async function refreshStatus() { show-icon type="warning" /> + + !values.only_remark && !values.id, - triggerFields: ['only_remark', 'id'], + show: (values) => { + if (values.only_remark) { + return false; + } + if (!values.id) { + return true; + } + return [3, 4].includes(Number(values.report_status ?? 0)); + }, + triggerFields: ['only_remark', 'id', 'report_status'], }, }, { diff --git a/apps/web-antd/src/views/system/store/index.vue b/apps/web-antd/src/views/system/store/index.vue index b9096e46..d2f650b9 100644 --- a/apps/web-antd/src/views/system/store/index.vue +++ b/apps/web-antd/src/views/system/store/index.vue @@ -121,10 +121,13 @@ const openBankCardReport = async (row: any, update = false) => { } catch { report = null; } + const reportStatus = Number(report?.report_status ?? 0); + const useUpdate = + (update || [3, 4].includes(reportStatus)) && Boolean(report?.id); bankCardReportModalApi.setData({ storeId: row.id, storeName: row.name, - mode: update && report?.id ? 'update' : 'report', + mode: useUpdate ? 'update' : 'report', report, gridApi, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6d383e58..42359c43 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -650,6 +650,9 @@ importers: dayjs: specifier: 'catalog:' version: 1.11.13 + exceljs: + specifier: ^4.4.0 + version: 4.4.0 markdown-it: specifier: ^14.1.0 version: 14.1.0 @@ -3189,6 +3192,12 @@ packages: resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fast-csv/format@4.3.5': + resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} + + '@fast-csv/parse@4.3.6': + resolution: {integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==} + '@floating-ui/core@1.6.8': resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} @@ -3276,8 +3285,8 @@ packages: resolution: {integrity: sha512-bmsP4L2HqBF6i6uaMqJMcFBONVjKt+siGluRq4Ca4C0q7W2eMaVZr8iCgF9dKbcVXutftkC7D6z2SaSMmLiDyA==} engines: {node: '>= 16'} - '@intlify/shared@11.4.4': - resolution: {integrity: sha512-QRUCHqda1U6aR14FR0vvXD4+4gj6+fm0AhAozvSuRCw0fCvrmCugWpgiR4xH2NI6s8am6N9p5OhirplsX8ZS3g==} + '@intlify/shared@11.4.6': + resolution: {integrity: sha512-m1p1HHAMLhqSpTRH7VnXdrN0CQ4y+9vunFkpLkbD8soIuBsnQdawZXqMCgvwI2UVF9Ww7sVaw7g9tV2VO7shoA==} engines: {node: '>= 22'} '@intlify/shared@12.0.0-alpha.4': @@ -4001,6 +4010,9 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + '@types/node@14.18.63': + resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + '@types/node@22.10.2': resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==} @@ -4445,10 +4457,22 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + archiver-utils@2.1.0: + resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} + engines: {node: '>= 6'} + + archiver-utils@3.0.4: + resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==} + engines: {node: '>= 10'} + archiver-utils@5.0.2: resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} engines: {node: '>= 14'} + archiver@5.3.2: + resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} + engines: {node: '>= 10'} + archiver@7.0.1: resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} engines: {node: '>= 14'} @@ -4586,10 +4610,17 @@ packages: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + binary@0.3.0: + resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==} + bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -4599,6 +4630,12 @@ packages: birpc@0.2.19: resolution: {integrity: sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==} + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + bluebird@3.4.7: + resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -4621,6 +4658,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-crc32@1.0.0: resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} engines: {node: '>=8.0.0'} @@ -4628,9 +4668,20 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer-indexof-polyfill@1.0.2: + resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==} + engines: {node: '>=0.10'} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + buffers@0.1.1: + resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} + engines: {node: '>=0.2.0'} + builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} @@ -4707,6 +4758,9 @@ packages: resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} engines: {node: '>=12'} + chainsaw@0.1.0: + resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} + chalk-template@1.1.0: resolution: {integrity: sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==} engines: {node: '>=14.16'} @@ -4912,6 +4966,10 @@ packages: compatx@0.1.8: resolution: {integrity: sha512-jcbsEAR81Bt5s1qOFymBufmCbXCXbk0Ql+K5ouj6gCyx2yHlu6AgmGIi9HxfKixpUDO5bCFJUHQ5uM6ecbTebw==} + compress-commons@4.1.2: + resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} + engines: {node: '>= 10'} + compress-commons@6.0.2: resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} engines: {node: '>= 14'} @@ -5007,6 +5065,10 @@ packages: engines: {node: '>=0.8'} hasBin: true + crc32-stream@4.0.3: + resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} + engines: {node: '>= 10'} + crc32-stream@6.0.0: resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} engines: {node: '>= 14'} @@ -5418,6 +5480,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -5466,6 +5531,9 @@ packages: encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enhanced-resolve@5.18.0: resolution: {integrity: sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==} engines: {node: '>=10.13.0'} @@ -5802,6 +5870,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + exceljs@4.4.0: + resolution: {integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==} + engines: {node: '>=8.3.0'} + execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -5828,6 +5900,10 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-csv@4.3.6: + resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} + engines: {node: '>=10.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -5966,6 +6042,9 @@ packages: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} @@ -6003,6 +6082,11 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + fstream@1.0.12: + resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} + engines: {node: '>=0.6'} + deprecated: This package is no longer supported. + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -6324,6 +6408,9 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + immutable@4.3.7: resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} @@ -6761,6 +6848,9 @@ packages: resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} engines: {node: '>=0.10.0'} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -6806,6 +6896,9 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -6821,6 +6914,9 @@ packages: engines: {node: '>=18.12.0'} hasBin: true + listenercount@1.0.1: + resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==} + listhen@1.9.0: resolution: {integrity: sha512-I8oW2+QL5KJo8zXNWX046M134WchxsXC7SawLPvRQpogCbkyQIaFxPE89A2HiwR7vAK2Dm2ERBAmyjTYGYEpBg==} hasBin: true @@ -6863,20 +6959,44 @@ packages: lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + lodash.difference@4.5.0: + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + + lodash.flatten@4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. + lodash.groupby@4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + lodash.isfunction@3.0.9: + resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} + + lodash.isnil@4.0.0: + resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + lodash.isundefined@3.0.1: + resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} + lodash.kebabcase@4.1.1: resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} @@ -6901,6 +7021,9 @@ packages: lodash.truncate@4.4.2: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + lodash.union@4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} @@ -7146,6 +7269,10 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} @@ -7447,6 +7574,9 @@ packages: package-manager-detector@0.2.7: resolution: {integrity: sha512-g4+387DXDKlZzHkP+9FLt8yKj8+/3tOkPv7DVTJGGRm00RkEWgqbFstX1mXJ4M0VDYhUqsTOiISqNOJnhAu3PQ==} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + param-case@3.0.4: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} @@ -8250,6 +8380,10 @@ packages: readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + readable-stream@4.6.0: resolution: {integrity: sha512-cbAdYt0VcnpN2Bekq7PU+k363ZRsPwJoEEJOEtSJQlJXzwaxt3FIo/uL+KeDSGIjJqtkwyge4KQgD2S2kd+CQw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -8396,6 +8530,11 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -8477,6 +8616,10 @@ packages: sax@1.4.1: resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + saxes@5.0.1: + resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} + engines: {node: '>=10'} + scroll-into-view-if-needed@2.2.31: resolution: {integrity: sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==} @@ -8533,6 +8676,9 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -8952,6 +9098,10 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} @@ -9033,6 +9183,10 @@ packages: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} + engines: {node: '>=14.14'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -9051,6 +9205,9 @@ packages: tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + traverse@0.3.9: + resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} + ts-api-utils@1.4.3: resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} engines: {node: '>=16'} @@ -9313,6 +9470,9 @@ packages: unwasm@0.3.9: resolution: {integrity: sha512-LDxTx/2DkFURUd+BU1vUsF/moj0JsoTvl+2tcg2AUOiEzVturhGGx17/IMgGvKUYdZwr33EJHtChCJuhu9Ouvg==} + unzipper@0.10.14: + resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==} + upath@1.2.0: resolution: {integrity: sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==} engines: {node: '>=4'} @@ -9345,6 +9505,10 @@ packages: utrie@1.0.2: resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -9759,6 +9923,9 @@ packages: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xss@1.0.15: resolution: {integrity: sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==} engines: {node: '>= 0.10.0'} @@ -9833,6 +10000,10 @@ packages: resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} engines: {node: '>=18'} + zip-stream@4.1.1: + resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} + engines: {node: '>= 10'} + zip-stream@6.0.1: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} @@ -11720,6 +11891,25 @@ snapshots: dependencies: levn: 0.4.1 + '@fast-csv/format@4.3.5': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.isboolean: 3.0.3 + lodash.isequal: 4.5.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + + '@fast-csv/parse@4.3.6': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.groupby: 4.6.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + lodash.isundefined: 3.0.1 + lodash.uniq: 4.5.0 + '@floating-ui/core@1.6.8': dependencies: '@floating-ui/utils': 0.2.8 @@ -11812,7 +12002,7 @@ snapshots: '@intlify/shared@10.0.5': {} - '@intlify/shared@11.4.4': {} + '@intlify/shared@11.4.6': {} '@intlify/shared@12.0.0-alpha.4': {} @@ -11820,8 +12010,8 @@ snapshots: dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) '@intlify/bundle-utils': 10.0.0(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2))) - '@intlify/shared': 11.4.4 - '@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.4.4)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2)) + '@intlify/shared': 11.4.6 + '@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.4.6)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2)) '@rollup/pluginutils': 5.1.4(rollup@4.28.1) '@typescript-eslint/scope-manager': 8.18.1 '@typescript-eslint/typescript-estree': 8.18.1(typescript@5.7.2) @@ -11843,11 +12033,11 @@ snapshots: - supports-color - typescript - '@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.4.4)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))': + '@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.4.6)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))': dependencies: '@babel/parser': 7.26.3 optionalDependencies: - '@intlify/shared': 11.4.4 + '@intlify/shared': 11.4.6 '@vue/compiler-dom': 3.5.13 vue: 3.5.13(typescript@5.7.2) vue-i18n: 10.0.5(vue@3.5.13(typescript@5.7.2)) @@ -12640,6 +12830,8 @@ snapshots: '@types/node@12.20.55': {} + '@types/node@14.18.63': {} + '@types/node@22.10.2': dependencies: undici-types: 6.20.0 @@ -13251,6 +13443,32 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + archiver-utils@2.1.0: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 2.3.8 + + archiver-utils@3.0.4: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + archiver-utils@5.0.2: dependencies: glob: 10.4.5 @@ -13261,6 +13479,16 @@ snapshots: normalize-path: 3.0.0 readable-stream: 4.6.0 + archiver@5.3.2: + dependencies: + archiver-utils: 2.1.0 + async: 3.2.6 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.1 + archiver@7.0.1: dependencies: archiver-utils: 5.0.2 @@ -13408,8 +13636,15 @@ snapshots: dependencies: is-windows: 1.0.2 + big-integer@1.6.52: {} + binary-extensions@2.3.0: {} + binary@0.3.0: + dependencies: + buffers: 0.1.1 + chainsaw: 0.1.0 + bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 @@ -13418,6 +13653,14 @@ snapshots: birpc@0.2.19: {} + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + bluebird@3.4.7: {} + boolbase@1.0.0: {} boxen@8.0.1: @@ -13451,15 +13694,26 @@ snapshots: node-releases: 2.0.19 update-browserslist-db: 1.1.1(browserslist@4.24.3) + buffer-crc32@0.2.13: {} + buffer-crc32@1.0.0: {} buffer-from@1.1.2: {} + buffer-indexof-polyfill@1.0.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + buffer@6.0.3: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + buffers@0.1.1: {} + builtin-modules@3.3.0: {} bundle-name@4.1.0: @@ -13564,6 +13818,10 @@ snapshots: loupe: 3.1.2 pathval: 2.0.0 + chainsaw@0.1.0: + dependencies: + traverse: 0.3.9 + chalk-template@1.1.0: dependencies: chalk: 5.4.0 @@ -13777,6 +14035,13 @@ snapshots: compatx@0.1.8: {} + compress-commons@4.1.2: + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.3 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + compress-commons@6.0.2: dependencies: crc-32: 1.2.2 @@ -13873,6 +14138,11 @@ snapshots: crc-32@1.2.2: {} + crc32-stream@4.0.3: + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + crc32-stream@6.0.0: dependencies: crc-32: 1.2.2 @@ -14323,6 +14593,10 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + duplexer@0.1.2: {} eastasianwidth@0.2.0: {} @@ -14367,6 +14641,10 @@ snapshots: iconv-lite: 0.6.3 optional: true + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + enhanced-resolve@5.18.0: dependencies: graceful-fs: 4.2.11 @@ -14820,6 +15098,18 @@ snapshots: events@3.3.0: {} + exceljs@4.4.0: + dependencies: + archiver: 5.3.2 + dayjs: 1.11.13 + fast-csv: 4.3.6 + jszip: 3.10.1 + readable-stream: 3.6.2 + saxes: 5.0.1 + tmp: 0.2.7 + unzipper: 0.10.14 + uuid: 8.3.2 + execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -14863,6 +15153,11 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + fast-csv@4.3.6: + dependencies: + '@fast-csv/format': 4.3.5 + '@fast-csv/parse': 4.3.6 + fast-deep-equal@3.1.3: {} fast-diff@1.1.2: {} @@ -14988,6 +15283,8 @@ snapshots: fresh@0.5.2: {} + fs-constants@1.0.0: {} + fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 @@ -15031,6 +15328,13 @@ snapshots: fsevents@2.3.3: optional: true + fstream@1.0.12: + dependencies: + graceful-fs: 4.2.11 + inherits: 2.0.4 + mkdirp: 0.5.6 + rimraf: 2.7.1 + function-bind@1.1.2: {} function.prototype.name@1.1.8: @@ -15398,6 +15702,8 @@ snapshots: image-size@0.5.5: optional: true + immediate@3.0.6: {} + immutable@4.3.7: {} import-fresh@3.3.0: @@ -15763,6 +16069,13 @@ snapshots: jsonpointer@5.0.1: {} + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -15809,6 +16122,10 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -15832,6 +16149,8 @@ snapshots: transitivePeerDependencies: - supports-color + listenercount@1.0.1: {} + listhen@1.9.0: dependencies: '@parcel/watcher': 2.5.0 @@ -15891,14 +16210,30 @@ snapshots: lodash.defaults@4.2.0: {} + lodash.difference@4.5.0: {} + + lodash.escaperegexp@4.1.2: {} + + lodash.flatten@4.4.0: {} + lodash.get@4.4.2: {} + lodash.groupby@4.6.0: {} + lodash.isarguments@3.1.0: {} + lodash.isboolean@3.0.3: {} + lodash.isequal@4.5.0: {} + lodash.isfunction@3.0.9: {} + + lodash.isnil@4.0.0: {} + lodash.isplainobject@4.0.6: {} + lodash.isundefined@3.0.1: {} + lodash.kebabcase@4.1.1: {} lodash.memoize@4.1.2: {} @@ -15915,6 +16250,8 @@ snapshots: lodash.truncate@4.4.2: {} + lodash.union@4.6.0: {} + lodash.uniq@4.5.0: {} lodash.upperfirst@4.3.1: {} @@ -16165,6 +16502,10 @@ snapshots: mitt@3.0.1: {} + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + mkdirp@1.0.4: {} mkdirp@3.0.1: {} @@ -16559,6 +16900,8 @@ snapshots: package-manager-detector@0.2.7: {} + pako@1.0.11: {} + param-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -17331,6 +17674,12 @@ snapshots: string_decoder: 1.1.1 util-deprecate: 1.0.2 + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + readable-stream@4.6.0: dependencies: abort-controller: 3.0.0 @@ -17469,6 +17818,10 @@ snapshots: rfdc@1.4.1: {} + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + rimraf@3.0.2: dependencies: glob: 7.2.3 @@ -17571,6 +17924,10 @@ snapshots: sax@1.4.1: optional: true + saxes@5.0.1: + dependencies: + xmlchars: 2.2.0 + scroll-into-view-if-needed@2.2.31: dependencies: compute-scroll-into-view: 1.0.20 @@ -17648,6 +18005,8 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + setimmediate@1.0.5: {} + setprototypeof@1.2.0: {} shallow-equal@1.2.1: {} @@ -18146,6 +18505,14 @@ snapshots: tapable@2.2.1: {} + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + tar-stream@3.1.7: dependencies: b4a: 1.6.7 @@ -18231,6 +18598,8 @@ snapshots: dependencies: os-tmpdir: 1.0.2 + tmp@0.2.7: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -18245,6 +18614,8 @@ snapshots: dependencies: punycode: 2.3.1 + traverse@0.3.9: {} + ts-api-utils@1.4.3(typescript@5.7.2): dependencies: typescript: 5.7.2 @@ -18505,6 +18876,19 @@ snapshots: pkg-types: 1.2.1 unplugin: 1.16.0 + unzipper@0.10.14: + dependencies: + big-integer: 1.6.52 + binary: 0.3.0 + bluebird: 3.4.7 + buffer-indexof-polyfill: 1.0.2 + duplexer2: 0.1.4 + fstream: 1.0.12 + graceful-fs: 4.2.11 + listenercount: 1.0.1 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + upath@1.2.0: {} update-browserslist-db@1.1.1(browserslist@4.24.3): @@ -18542,6 +18926,8 @@ snapshots: dependencies: base64-arraybuffer: 1.0.2 + uuid@8.3.2: {} + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 @@ -19087,6 +19473,8 @@ snapshots: xml-name-validator@4.0.0: {} + xmlchars@2.2.0: {} + xss@1.0.15: dependencies: commander: 2.20.3 @@ -19163,6 +19551,12 @@ snapshots: yoctocolors@2.1.1: {} + zip-stream@4.1.1: + dependencies: + archiver-utils: 3.0.4 + compress-commons: 4.1.2 + readable-stream: 3.6.2 + zip-stream@6.0.1: dependencies: archiver-utils: 5.0.2