修复核心流程并完成固定资产化改造
This commit is contained in:
parent
80cbf3fbf5
commit
c0ce7a6e3e
787
backend/package-lock.json
generated
787
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"description": "摄影设备借还系统后端",
|
||||
"description": "固定资产管理系统后端",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon --exec ts-node src/index.ts",
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
ALTER TABLE `asset_type`
|
||||
ADD COLUMN `short_name` VARCHAR(20) NULL;
|
||||
|
||||
CREATE UNIQUE INDEX `asset_type_short_name_key`
|
||||
ON `asset_type`(`short_name`);
|
||||
1
backend/prisma/migrations/migration_lock.toml
Normal file
1
backend/prisma/migrations/migration_lock.toml
Normal file
@ -0,0 +1 @@
|
||||
provider = "mysql"
|
||||
@ -13,6 +13,7 @@ datasource db {
|
||||
model AssetType {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique @db.VarChar(50)
|
||||
short_name String? @unique @db.VarChar(20)
|
||||
status String @default("active") @db.VarChar(20)
|
||||
can_be_parent Boolean @default(false) // 可作为父资产
|
||||
can_be_child Boolean @default(false) // 可作为子资产
|
||||
|
||||
@ -5,6 +5,7 @@ import { successResponse, errorResponse } from '../utils/response'
|
||||
const baseSelect = {
|
||||
id: true,
|
||||
name: true,
|
||||
short_name: true,
|
||||
status: true,
|
||||
can_be_parent: true,
|
||||
can_be_child: true,
|
||||
@ -12,6 +13,16 @@ const baseSelect = {
|
||||
updated_at: true
|
||||
}
|
||||
|
||||
const normalizeShortName = (shortName?: string | null) => {
|
||||
if (shortName === undefined) return undefined
|
||||
const value = shortName?.trim().toUpperCase() || ''
|
||||
if (!value) return null
|
||||
if (!/^[A-Z0-9]{2,20}$/.test(value)) {
|
||||
throw new Error('简称只能包含 2-20 位大写字母或数字')
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// 获取启用的资产类型列表(下拉选项)
|
||||
export const getAssetTypeOptions = async (_req: Request, res: Response) => {
|
||||
try {
|
||||
@ -37,7 +48,10 @@ export const getAssetTypeList = async (req: Request, res: Response) => {
|
||||
where.status = status
|
||||
}
|
||||
if (keyword) {
|
||||
where.name = { contains: keyword as string }
|
||||
where.OR = [
|
||||
{ name: { contains: keyword as string } },
|
||||
{ short_name: { contains: keyword as string } }
|
||||
]
|
||||
}
|
||||
|
||||
const skip = (Number(page) - 1) * Number(pageSize)
|
||||
@ -76,8 +90,9 @@ export const getAssetTypeList = async (req: Request, res: Response) => {
|
||||
// 创建资产类型
|
||||
export const createAssetType = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { name, can_be_parent, can_be_child } = req.body as {
|
||||
const { name, short_name, can_be_parent, can_be_child } = req.body as {
|
||||
name?: string
|
||||
short_name?: string
|
||||
can_be_parent?: boolean
|
||||
can_be_child?: boolean
|
||||
}
|
||||
@ -86,9 +101,12 @@ export const createAssetType = async (req: Request, res: Response) => {
|
||||
return errorResponse(res, '类型名称不能为空', 422)
|
||||
}
|
||||
|
||||
const normalizedShortName = normalizeShortName(short_name)
|
||||
|
||||
const assetType = await prisma.assetType.create({
|
||||
data: {
|
||||
name: name.trim(),
|
||||
short_name: normalizedShortName,
|
||||
can_be_parent: can_be_parent ?? false,
|
||||
can_be_child: can_be_child ?? false
|
||||
},
|
||||
@ -98,7 +116,7 @@ export const createAssetType = async (req: Request, res: Response) => {
|
||||
successResponse(res, assetType, '新增资产类型成功')
|
||||
} catch (error: any) {
|
||||
if (error.code === 'P2002') {
|
||||
return errorResponse(res, '类型名称已存在')
|
||||
return errorResponse(res, '类型名称或简称已存在')
|
||||
}
|
||||
errorResponse(res, error.message || '新增资产类型失败')
|
||||
}
|
||||
@ -108,8 +126,9 @@ export const createAssetType = async (req: Request, res: Response) => {
|
||||
export const updateAssetType = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const { name, status, can_be_parent, can_be_child } = req.body as {
|
||||
const { name, short_name, status, can_be_parent, can_be_child } = req.body as {
|
||||
name?: string
|
||||
short_name?: string
|
||||
status?: string
|
||||
can_be_parent?: boolean
|
||||
can_be_child?: boolean
|
||||
@ -127,10 +146,18 @@ export const updateAssetType = async (req: Request, res: Response) => {
|
||||
return errorResponse(res, '类型名称不能为空', 422)
|
||||
}
|
||||
|
||||
let normalizedShortName: string | null | undefined
|
||||
try {
|
||||
normalizedShortName = normalizeShortName(short_name)
|
||||
} catch (error: any) {
|
||||
return errorResponse(res, error.message || '简称格式不正确', 422)
|
||||
}
|
||||
|
||||
const updated = await prisma.assetType.update({
|
||||
where: { id: Number(id) },
|
||||
data: {
|
||||
name: name?.trim() ?? existing.name,
|
||||
short_name: normalizedShortName !== undefined ? normalizedShortName : existing.short_name,
|
||||
status: status ?? existing.status,
|
||||
can_be_parent: can_be_parent ?? existing.can_be_parent,
|
||||
can_be_child: can_be_child ?? existing.can_be_child
|
||||
@ -141,7 +168,7 @@ export const updateAssetType = async (req: Request, res: Response) => {
|
||||
successResponse(res, updated, '更新资产类型成功')
|
||||
} catch (error: any) {
|
||||
if (error.code === 'P2002') {
|
||||
return errorResponse(res, '类型名称已存在')
|
||||
return errorResponse(res, '类型名称或简称已存在')
|
||||
}
|
||||
errorResponse(res, error.message || '更新资产类型失败')
|
||||
}
|
||||
|
||||
@ -3,6 +3,122 @@ import prisma from '../utils/prisma'
|
||||
import { successResponse, errorResponse } from '../utils/response'
|
||||
import { logOperation } from './operationLogController'
|
||||
|
||||
const buildBorrowNotes = (notes?: string, isLongTerm?: boolean) => {
|
||||
const longTerm = Boolean(isLongTerm)
|
||||
return longTerm && notes
|
||||
? `${notes};在职长期`
|
||||
: longTerm
|
||||
? '在职长期'
|
||||
: notes || undefined
|
||||
}
|
||||
|
||||
const getChildrenSnapshot = async (equipmentId: number) => {
|
||||
const children = await prisma.equipment.findMany({
|
||||
where: { parent_id: equipmentId },
|
||||
select: {
|
||||
id: true,
|
||||
equipment_code: true,
|
||||
equipment_name: true,
|
||||
equipment_model: true,
|
||||
equipment_type: true,
|
||||
unit_price: true
|
||||
}
|
||||
})
|
||||
|
||||
return children.length > 0 ? JSON.stringify(children) : undefined
|
||||
}
|
||||
|
||||
const batchBorrowTransaction = async (
|
||||
equipment: any,
|
||||
person: any,
|
||||
expectedReturnTime?: string,
|
||||
isLongTerm?: boolean,
|
||||
notes?: string
|
||||
) => {
|
||||
const childrenSnapshot = await getChildrenSnapshot(equipment.id)
|
||||
const finalNotes = buildBorrowNotes(notes, isLongTerm)
|
||||
|
||||
return prisma.$transaction(async (tx) => {
|
||||
await tx.borrowRecord.create({
|
||||
data: {
|
||||
equipment_id: equipment.id,
|
||||
equipment_code: equipment.equipment_code,
|
||||
person_id: person.id,
|
||||
person_name: person.real_name,
|
||||
person_nickname: person.nickname,
|
||||
person_identifier: person.id_number,
|
||||
expected_return_time: isLongTerm ? null : (expectedReturnTime ? new Date(expectedReturnTime) : null),
|
||||
is_long_term: Boolean(isLongTerm),
|
||||
notes: finalNotes,
|
||||
status: '已借出',
|
||||
children_snapshot: childrenSnapshot
|
||||
}
|
||||
})
|
||||
|
||||
const nextStatus = equipment.remaining_quantity === 1 ? '已借出' : '可用'
|
||||
const updated = await tx.equipment.updateMany({
|
||||
where: {
|
||||
id: equipment.id,
|
||||
status: '可用',
|
||||
remaining_quantity: equipment.remaining_quantity
|
||||
},
|
||||
data: {
|
||||
remaining_quantity: { decrement: 1 },
|
||||
borrowed_value: { increment: Number(equipment.unit_price) },
|
||||
available_value: { decrement: Number(equipment.unit_price) },
|
||||
status: nextStatus
|
||||
}
|
||||
})
|
||||
|
||||
if (updated.count !== 1) {
|
||||
throw new Error('资产库存已变化,请刷新后重试')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const batchReturnTransaction = async (
|
||||
equipment: any,
|
||||
borrowRecord: any,
|
||||
returnCondition: string,
|
||||
notes?: string
|
||||
) => {
|
||||
return prisma.$transaction(async (tx) => {
|
||||
const updatedRecord = await tx.borrowRecord.updateMany({
|
||||
where: {
|
||||
id: borrowRecord.id,
|
||||
status: '已借出'
|
||||
},
|
||||
data: {
|
||||
return_time: new Date(),
|
||||
status: '已归还',
|
||||
return_condition: returnCondition,
|
||||
notes
|
||||
}
|
||||
})
|
||||
|
||||
if (updatedRecord.count !== 1) {
|
||||
throw new Error('该借用记录已归还,请刷新后重试')
|
||||
}
|
||||
|
||||
const updatedEquipment = await tx.equipment.updateMany({
|
||||
where: {
|
||||
id: equipment.id,
|
||||
remaining_quantity: { lt: equipment.total_quantity }
|
||||
},
|
||||
data: {
|
||||
remaining_quantity: { increment: 1 },
|
||||
borrowed_value: { decrement: Number(equipment.unit_price) },
|
||||
available_value: { increment: Number(equipment.unit_price) },
|
||||
status: '可用'
|
||||
}
|
||||
})
|
||||
|
||||
if (updatedEquipment.count !== 1) {
|
||||
throw new Error('资产库存已达到上限,请检查归还记录')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 批量借出
|
||||
export const batchBorrow = async (req: Request, res: Response) => {
|
||||
try {
|
||||
@ -37,32 +153,13 @@ export const batchBorrow = async (req: Request, res: Response) => {
|
||||
continue
|
||||
}
|
||||
|
||||
const longTerm = Boolean(is_long_term)
|
||||
await prisma.borrowRecord.create({
|
||||
data: {
|
||||
equipment_id: equipment.id,
|
||||
equipment_code: equipment.equipment_code,
|
||||
person_id: person.id,
|
||||
person_name: person.real_name,
|
||||
person_nickname: person.nickname,
|
||||
person_identifier: person.id_number,
|
||||
expected_return_time: longTerm ? null : (expected_return_time ? new Date(expected_return_time) : null),
|
||||
is_long_term: longTerm,
|
||||
notes: longTerm ? `${notes || ''};在职长期` : notes,
|
||||
status: '已借出'
|
||||
}
|
||||
})
|
||||
|
||||
const newRemaining = equipment.remaining_quantity - 1
|
||||
await prisma.equipment.update({
|
||||
where: { id: equipment.id },
|
||||
data: {
|
||||
remaining_quantity: newRemaining,
|
||||
status: newRemaining === 0 ? '已借出' : '可用',
|
||||
borrowed_value: Number(equipment.unit_price) * (equipment.total_quantity - newRemaining),
|
||||
available_value: Number(equipment.unit_price) * newRemaining
|
||||
}
|
||||
})
|
||||
await batchBorrowTransaction(
|
||||
equipment,
|
||||
person,
|
||||
expected_return_time,
|
||||
is_long_term,
|
||||
notes
|
||||
)
|
||||
|
||||
results.push({ equipment_id: equipmentId, equipment_code: equipment.equipment_code, status: 'success', message: '借出成功' })
|
||||
successCount++
|
||||
@ -117,10 +214,12 @@ export const batchReturn = async (req: Request, res: Response) => {
|
||||
continue
|
||||
}
|
||||
|
||||
const borrowRecord = await prisma.borrowRecord.findFirst({
|
||||
const activeBorrowRecords = await prisma.borrowRecord.findMany({
|
||||
where: { equipment_id: equipment.id, status: '已借出' },
|
||||
orderBy: { borrow_time: 'desc' }
|
||||
orderBy: { borrow_time: 'desc' },
|
||||
take: 2
|
||||
})
|
||||
const borrowRecord = activeBorrowRecords[0]
|
||||
|
||||
if (!borrowRecord) {
|
||||
results.push({ equipment_id: equipmentId, equipment_code: equipment.equipment_code, status: 'failed', message: '未找到借用记录' })
|
||||
@ -128,21 +227,13 @@ export const batchReturn = async (req: Request, res: Response) => {
|
||||
continue
|
||||
}
|
||||
|
||||
await prisma.borrowRecord.update({
|
||||
where: { id: borrowRecord.id },
|
||||
data: { return_time: new Date(), status: '已归还', return_condition, notes }
|
||||
})
|
||||
if (activeBorrowRecords.length > 1) {
|
||||
results.push({ equipment_id: equipmentId, equipment_code: equipment.equipment_code, status: 'failed', message: '存在多条未归还记录,请使用单个归还' })
|
||||
failedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
const newRemaining = equipment.remaining_quantity + 1
|
||||
await prisma.equipment.update({
|
||||
where: { id: equipment.id },
|
||||
data: {
|
||||
remaining_quantity: newRemaining,
|
||||
status: '可用',
|
||||
borrowed_value: Number(equipment.unit_price) * (equipment.total_quantity - newRemaining),
|
||||
available_value: Number(equipment.unit_price) * newRemaining
|
||||
}
|
||||
})
|
||||
await batchReturnTransaction(equipment, borrowRecord, return_condition, notes)
|
||||
|
||||
results.push({ equipment_id: equipmentId, equipment_code: equipment.equipment_code, status: 'success', message: '归还成功' })
|
||||
successCount++
|
||||
|
||||
@ -81,7 +81,7 @@ export const batchImportPersons = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
// 记录操作日志
|
||||
const user = (req as any).user
|
||||
const user = res.locals.user
|
||||
await prisma.operationLog.create({
|
||||
data: {
|
||||
user_id: user?.id || null,
|
||||
@ -201,7 +201,7 @@ export const batchImportEquipments = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
// 记录操作日志
|
||||
const user = (req as any).user
|
||||
const user = res.locals.user
|
||||
await prisma.operationLog.create({
|
||||
data: {
|
||||
user_id: user?.id || null,
|
||||
|
||||
@ -62,33 +62,206 @@ const ensureEquipmentBorrowable = (equipment: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
const updateEquipmentAfterBorrow = async (equipment: any) => {
|
||||
const newRemainingQuantity = equipment.remaining_quantity - 1
|
||||
if (newRemainingQuantity < 0) {
|
||||
throw new Error('资产库存不足')
|
||||
}
|
||||
const borrowedQuantity = equipment.total_quantity - newRemainingQuantity
|
||||
await prisma.equipment.update({
|
||||
where: { id: equipment.id },
|
||||
data: {
|
||||
remaining_quantity: newRemainingQuantity,
|
||||
status: newRemainingQuantity === 0 ? '已借出' : '可用',
|
||||
borrowed_value: Number(equipment.unit_price) * borrowedQuantity,
|
||||
available_value: Number(equipment.unit_price) * newRemainingQuantity
|
||||
const appendLongTermNotes = (notes?: string, isLongTerm?: boolean) => {
|
||||
const longTerm = Boolean(isLongTerm)
|
||||
return longTerm && notes
|
||||
? `${notes};在职长期`
|
||||
: longTerm
|
||||
? '在职长期'
|
||||
: notes || undefined
|
||||
}
|
||||
|
||||
const getChildrenSnapshot = async (equipmentId: number) => {
|
||||
const children = await prisma.equipment.findMany({
|
||||
where: { parent_id: equipmentId },
|
||||
select: {
|
||||
id: true,
|
||||
equipment_code: true,
|
||||
equipment_name: true,
|
||||
equipment_model: true,
|
||||
equipment_type: true,
|
||||
unit_price: true
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
children,
|
||||
snapshot: children.length > 0 ? JSON.stringify(children) : undefined
|
||||
}
|
||||
}
|
||||
|
||||
const borrowEquipmentInTransaction = async (
|
||||
equipment: any,
|
||||
person: any,
|
||||
expectedReturn: Date | null,
|
||||
combinedNotes?: string,
|
||||
isLongTerm?: boolean,
|
||||
childrenSnapshot?: string
|
||||
) => {
|
||||
return prisma.$transaction(async (tx) => {
|
||||
const record = await tx.borrowRecord.create({
|
||||
data: {
|
||||
equipment_id: equipment.id,
|
||||
equipment_code: equipment.equipment_code,
|
||||
person_id: person.id,
|
||||
person_name: person.real_name,
|
||||
person_nickname: person.nickname,
|
||||
person_identifier: person.id_number,
|
||||
expected_return_time: expectedReturn,
|
||||
is_long_term: Boolean(isLongTerm),
|
||||
notes: combinedNotes,
|
||||
status: '已借出',
|
||||
children_snapshot: childrenSnapshot
|
||||
}
|
||||
})
|
||||
|
||||
const nextStatus = equipment.remaining_quantity === 1 ? '已借出' : '可用'
|
||||
const updateResult = await tx.equipment.updateMany({
|
||||
where: {
|
||||
id: equipment.id,
|
||||
status: '可用',
|
||||
remaining_quantity: equipment.remaining_quantity
|
||||
},
|
||||
data: {
|
||||
remaining_quantity: { decrement: 1 },
|
||||
borrowed_value: { increment: Number(equipment.unit_price) },
|
||||
available_value: { decrement: Number(equipment.unit_price) },
|
||||
status: nextStatus
|
||||
}
|
||||
})
|
||||
|
||||
if (updateResult.count !== 1) {
|
||||
throw new Error('资产库存已变化,请刷新后重试')
|
||||
}
|
||||
|
||||
const updatedEquipment = await tx.equipment.findUnique({
|
||||
where: { id: equipment.id },
|
||||
include: equipmentInclude
|
||||
})
|
||||
|
||||
return { record, updatedEquipment }
|
||||
})
|
||||
}
|
||||
|
||||
const updateEquipmentAfterReturn = async (equipment: any) => {
|
||||
const newRemainingQuantity = equipment.remaining_quantity + 1
|
||||
const borrowedQuantity = equipment.total_quantity - newRemainingQuantity
|
||||
await prisma.equipment.update({
|
||||
where: { id: equipment.id },
|
||||
data: {
|
||||
remaining_quantity: newRemainingQuantity,
|
||||
status: '可用',
|
||||
borrowed_value: Number(equipment.unit_price) * borrowedQuantity,
|
||||
available_value: Number(equipment.unit_price) * newRemainingQuantity
|
||||
const findActiveBorrowRecord = async (
|
||||
equipmentId: number,
|
||||
borrowRecordId?: number,
|
||||
personId?: number
|
||||
) => {
|
||||
if (borrowRecordId) {
|
||||
return prisma.borrowRecord.findFirst({
|
||||
where: {
|
||||
id: borrowRecordId,
|
||||
equipment_id: equipmentId,
|
||||
status: '已借出'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (personId) {
|
||||
return prisma.borrowRecord.findFirst({
|
||||
where: {
|
||||
equipment_id: equipmentId,
|
||||
person_id: personId,
|
||||
status: '已借出'
|
||||
},
|
||||
orderBy: { borrow_time: 'desc' }
|
||||
})
|
||||
}
|
||||
|
||||
const activeRecords = await prisma.borrowRecord.findMany({
|
||||
where: {
|
||||
equipment_id: equipmentId,
|
||||
status: '已借出'
|
||||
},
|
||||
orderBy: { borrow_time: 'desc' },
|
||||
take: 2
|
||||
})
|
||||
|
||||
if (activeRecords.length > 1) {
|
||||
throw new Error('该资产存在多条未归还记录,请明确选择归还使用者')
|
||||
}
|
||||
|
||||
return activeRecords[0] || null
|
||||
}
|
||||
|
||||
const getChildrenDiff = async (equipmentId: number, borrowRecord: any) => {
|
||||
if (!borrowRecord?.children_snapshot) {
|
||||
return null
|
||||
}
|
||||
|
||||
const borrowedChildren = JSON.parse(borrowRecord.children_snapshot) as any[]
|
||||
const currentChildren = await prisma.equipment.findMany({
|
||||
where: { parent_id: equipmentId },
|
||||
select: {
|
||||
id: true,
|
||||
equipment_code: true,
|
||||
equipment_name: true,
|
||||
equipment_model: true,
|
||||
equipment_type: true,
|
||||
unit_price: true
|
||||
}
|
||||
})
|
||||
|
||||
const borrowedIds = new Set(borrowedChildren.map((child) => child.id))
|
||||
const currentIds = new Set(currentChildren.map((child) => child.id))
|
||||
const removed = borrowedChildren.filter((child) => !currentIds.has(child.id))
|
||||
const added = currentChildren.filter((child) => !borrowedIds.has(child.id))
|
||||
|
||||
if (removed.length === 0 && added.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return { removed, added }
|
||||
}
|
||||
|
||||
const returnEquipmentInTransaction = async (
|
||||
equipment: any,
|
||||
borrowRecord: any,
|
||||
returnCondition: string,
|
||||
notes?: string
|
||||
) => {
|
||||
return prisma.$transaction(async (tx) => {
|
||||
const returnTime = new Date()
|
||||
const updatedBorrowRecord = await tx.borrowRecord.updateMany({
|
||||
where: {
|
||||
id: borrowRecord.id,
|
||||
status: '已借出'
|
||||
},
|
||||
data: {
|
||||
return_time: returnTime,
|
||||
status: '已归还',
|
||||
return_condition: returnCondition,
|
||||
notes
|
||||
}
|
||||
})
|
||||
|
||||
if (updatedBorrowRecord.count !== 1) {
|
||||
throw new Error('该借用记录已归还,请刷新后重试')
|
||||
}
|
||||
|
||||
const equipmentUpdate = await tx.equipment.updateMany({
|
||||
where: {
|
||||
id: equipment.id,
|
||||
remaining_quantity: { lt: equipment.total_quantity }
|
||||
},
|
||||
data: {
|
||||
remaining_quantity: { increment: 1 },
|
||||
borrowed_value: { decrement: Number(equipment.unit_price) },
|
||||
available_value: { increment: Number(equipment.unit_price) },
|
||||
status: '可用'
|
||||
}
|
||||
})
|
||||
|
||||
if (equipmentUpdate.count !== 1) {
|
||||
throw new Error('资产库存已达到上限,请检查归还记录')
|
||||
}
|
||||
|
||||
return {
|
||||
id: borrowRecord.id,
|
||||
person_id: borrowRecord.person_id,
|
||||
return_time: returnTime,
|
||||
status: '已归还'
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -123,7 +296,8 @@ const mapBorrowRecord = (record: any) => ({
|
||||
return_time: record.return_time,
|
||||
status: record.status,
|
||||
return_condition: record.return_condition,
|
||||
notes: record.notes
|
||||
notes: record.notes,
|
||||
children_snapshot: record.children_snapshot
|
||||
})
|
||||
|
||||
// 借用资产
|
||||
@ -158,48 +332,16 @@ export const borrowEquipment = async (req: Request, res: Response) => {
|
||||
|
||||
const longTerm = Boolean(is_long_term)
|
||||
const expectedReturn = longTerm ? null : parseDate(expected_return_time)
|
||||
|
||||
const combinedNotes =
|
||||
longTerm && notes
|
||||
? `${notes};在职长期`
|
||||
: longTerm
|
||||
? '在职长期'
|
||||
: notes || undefined
|
||||
|
||||
// 获取子资产快照(如果是父资产)
|
||||
let childrenSnapshot: string | undefined
|
||||
const children = await prisma.equipment.findMany({
|
||||
where: { parent_id: equipment.id },
|
||||
select: {
|
||||
id: true,
|
||||
equipment_code: true,
|
||||
equipment_name: true,
|
||||
equipment_model: true,
|
||||
equipment_type: true,
|
||||
unit_price: true
|
||||
}
|
||||
})
|
||||
if (children.length > 0) {
|
||||
childrenSnapshot = JSON.stringify(children)
|
||||
}
|
||||
|
||||
const borrowRecord = await prisma.borrowRecord.create({
|
||||
data: {
|
||||
equipment_id: equipment.id,
|
||||
equipment_code: equipment.equipment_code,
|
||||
person_id: person.id,
|
||||
person_name: person.real_name,
|
||||
person_nickname: person.nickname,
|
||||
person_identifier: person.id_number,
|
||||
expected_return_time: expectedReturn,
|
||||
is_long_term: longTerm,
|
||||
notes: combinedNotes,
|
||||
status: '已借出',
|
||||
children_snapshot: childrenSnapshot
|
||||
}
|
||||
})
|
||||
|
||||
await updateEquipmentAfterBorrow(equipment)
|
||||
const combinedNotes = appendLongTermNotes(notes, longTerm)
|
||||
const { children, snapshot } = await getChildrenSnapshot(equipment.id)
|
||||
const { record: borrowRecord } = await borrowEquipmentInTransaction(
|
||||
equipment,
|
||||
person,
|
||||
expectedReturn,
|
||||
combinedNotes,
|
||||
longTerm,
|
||||
snapshot
|
||||
)
|
||||
|
||||
successResponse(
|
||||
res,
|
||||
@ -246,51 +388,16 @@ export const scanBorrow = async (req: Request, res: Response) => {
|
||||
|
||||
const longTerm = Boolean(is_long_term)
|
||||
const expectedReturn = longTerm ? null : parseDate(expected_return_time)
|
||||
const combinedNotes =
|
||||
longTerm && notes
|
||||
? `${notes};在职长期`
|
||||
: longTerm
|
||||
? '在职长期'
|
||||
: notes || undefined
|
||||
|
||||
// 获取子资产快照(如果是父资产)
|
||||
let childrenSnapshot: string | undefined
|
||||
const children = await prisma.equipment.findMany({
|
||||
where: { parent_id: equipment.id },
|
||||
select: {
|
||||
id: true,
|
||||
equipment_code: true,
|
||||
equipment_name: true,
|
||||
equipment_model: true,
|
||||
equipment_type: true,
|
||||
unit_price: true
|
||||
}
|
||||
})
|
||||
if (children.length > 0) {
|
||||
childrenSnapshot = JSON.stringify(children)
|
||||
}
|
||||
|
||||
const borrowRecord = await prisma.borrowRecord.create({
|
||||
data: {
|
||||
equipment_id: equipment.id,
|
||||
equipment_code: equipment.equipment_code,
|
||||
person_id: person.id,
|
||||
person_name: person.real_name,
|
||||
person_nickname: person.nickname,
|
||||
person_identifier: person.id_number,
|
||||
expected_return_time: expectedReturn,
|
||||
is_long_term: longTerm,
|
||||
notes: combinedNotes,
|
||||
status: '已借出',
|
||||
children_snapshot: childrenSnapshot
|
||||
}
|
||||
})
|
||||
|
||||
await updateEquipmentAfterBorrow(equipment)
|
||||
const updatedEquipment = await prisma.equipment.findUnique({
|
||||
where: { id: equipment.id },
|
||||
include: equipmentInclude
|
||||
})
|
||||
const combinedNotes = appendLongTermNotes(notes, longTerm)
|
||||
const { children, snapshot } = await getChildrenSnapshot(equipment.id)
|
||||
const { record: borrowRecord, updatedEquipment } = await borrowEquipmentInTransaction(
|
||||
equipment,
|
||||
person,
|
||||
expectedReturn,
|
||||
combinedNotes,
|
||||
longTerm,
|
||||
snapshot
|
||||
)
|
||||
|
||||
successResponse(
|
||||
res,
|
||||
@ -343,12 +450,7 @@ export const scanBorrowBatch = async (req: Request, res: Response) => {
|
||||
|
||||
const longTerm = Boolean(is_long_term)
|
||||
const expectedReturn = longTerm ? null : parseDate(expected_return_time)
|
||||
const combinedNotes =
|
||||
longTerm && notes
|
||||
? `${notes};在职长期`
|
||||
: longTerm
|
||||
? '在职长期'
|
||||
: notes || undefined
|
||||
const combinedNotes = appendLongTermNotes(notes, longTerm)
|
||||
const results: any[] = []
|
||||
let successCount = 0
|
||||
let failedCount = 0
|
||||
@ -370,22 +472,15 @@ export const scanBorrowBatch = async (req: Request, res: Response) => {
|
||||
continue
|
||||
}
|
||||
|
||||
await prisma.borrowRecord.create({
|
||||
data: {
|
||||
equipment_id: equipment.id,
|
||||
equipment_code: equipment.equipment_code,
|
||||
person_id: person.id,
|
||||
person_name: person.real_name,
|
||||
person_nickname: person.nickname,
|
||||
person_identifier: person.id_number,
|
||||
expected_return_time: expectedReturn,
|
||||
is_long_term: longTerm,
|
||||
notes: combinedNotes,
|
||||
status: '已借出'
|
||||
}
|
||||
})
|
||||
|
||||
await updateEquipmentAfterBorrow(equipment)
|
||||
const { snapshot } = await getChildrenSnapshot(equipment.id)
|
||||
await borrowEquipmentInTransaction(
|
||||
equipment,
|
||||
person,
|
||||
expectedReturn,
|
||||
combinedNotes,
|
||||
longTerm,
|
||||
snapshot
|
||||
)
|
||||
|
||||
results.push({ equipment_code: code, status: 'success', message: '出库成功' })
|
||||
successCount++
|
||||
@ -425,6 +520,8 @@ export const returnEquipment = async (req: Request, res: Response) => {
|
||||
const {
|
||||
equipment_id,
|
||||
equipment_code,
|
||||
borrow_record_id,
|
||||
person_id,
|
||||
return_condition = '正常',
|
||||
notes,
|
||||
confirm_children_diff
|
||||
@ -437,67 +534,37 @@ export const returnEquipment = async (req: Request, res: Response) => {
|
||||
return errorResponse(res, '资产不存在', 404)
|
||||
}
|
||||
|
||||
const borrowRecord = await prisma.borrowRecord.findFirst({
|
||||
where: {
|
||||
equipment_id: equipment.id,
|
||||
status: '已借出'
|
||||
},
|
||||
orderBy: { borrow_time: 'desc' }
|
||||
})
|
||||
let borrowRecord
|
||||
try {
|
||||
borrowRecord = await findActiveBorrowRecord(
|
||||
equipment.id,
|
||||
parseNumber(borrow_record_id),
|
||||
parseNumber(person_id)
|
||||
)
|
||||
} catch (error: any) {
|
||||
return errorResponse(res, error.message || '未找到对应的借用记录', 422)
|
||||
}
|
||||
|
||||
if (!borrowRecord) {
|
||||
return errorResponse(res, '未找到对应的借用记录')
|
||||
}
|
||||
|
||||
// 检查配件差异
|
||||
let childrenDiff: any = null
|
||||
if (borrowRecord.children_snapshot) {
|
||||
const borrowedChildren = JSON.parse(borrowRecord.children_snapshot) as any[]
|
||||
const currentChildren = await prisma.equipment.findMany({
|
||||
where: { parent_id: equipment.id },
|
||||
select: {
|
||||
id: true,
|
||||
equipment_code: true,
|
||||
equipment_name: true,
|
||||
equipment_model: true,
|
||||
equipment_type: true,
|
||||
unit_price: true
|
||||
}
|
||||
})
|
||||
|
||||
const borrowedIds = new Set(borrowedChildren.map(c => c.id))
|
||||
const currentIds = new Set(currentChildren.map(c => c.id))
|
||||
|
||||
const removed = borrowedChildren.filter(c => !currentIds.has(c.id))
|
||||
const added = currentChildren.filter(c => !borrowedIds.has(c.id))
|
||||
|
||||
if (removed.length > 0 || added.length > 0) {
|
||||
childrenDiff = { removed, added }
|
||||
|
||||
// 如果有差异且未确认,返回差异信息要求确认
|
||||
if (!confirm_children_diff) {
|
||||
return successResponse(res, {
|
||||
need_confirm: true,
|
||||
children_diff: childrenDiff,
|
||||
message: '配件与借出时不一致,请确认后继续'
|
||||
}, '配件差异提醒')
|
||||
}
|
||||
}
|
||||
const childrenDiff = await getChildrenDiff(equipment.id, borrowRecord)
|
||||
if (childrenDiff && !confirm_children_diff) {
|
||||
return successResponse(res, {
|
||||
need_confirm: true,
|
||||
children_diff: childrenDiff,
|
||||
message: '配件与借出时不一致,请确认后继续'
|
||||
}, '配件差异提醒')
|
||||
}
|
||||
|
||||
await prisma.borrowRecord.update({
|
||||
where: { id: borrowRecord.id },
|
||||
data: {
|
||||
return_time: new Date(),
|
||||
status: '已归还',
|
||||
return_condition,
|
||||
notes: childrenDiff
|
||||
? `${notes || ''};配件变更:移除${childrenDiff.removed.length}个,新增${childrenDiff.added.length}个`
|
||||
: notes
|
||||
}
|
||||
})
|
||||
|
||||
await updateEquipmentAfterReturn(equipment)
|
||||
|
||||
const finalNotes = childrenDiff
|
||||
? [notes, `配件变更:移除${childrenDiff.removed.length}个,新增${childrenDiff.added.length}个`]
|
||||
.filter(Boolean)
|
||||
.join(';')
|
||||
: notes
|
||||
|
||||
await returnEquipmentInTransaction(equipment, borrowRecord, return_condition, finalNotes)
|
||||
|
||||
successResponse(
|
||||
res,
|
||||
@ -527,7 +594,8 @@ export const scanReturn = async (req: Request, res: Response) => {
|
||||
return_condition = '正常',
|
||||
notes,
|
||||
person_id,
|
||||
borrow_record_id
|
||||
borrow_record_id,
|
||||
confirm_children_diff
|
||||
} = req.body as Record<string, any>
|
||||
|
||||
const equipment = await getEquipmentByIdentifier(undefined, equipment_code)
|
||||
@ -536,57 +604,42 @@ export const scanReturn = async (req: Request, res: Response) => {
|
||||
return errorResponse(res, '资产不存在', 404)
|
||||
}
|
||||
|
||||
const personIdNum = parseNumber(person_id)
|
||||
const borrowRecordIdNum = parseNumber(borrow_record_id)
|
||||
|
||||
let borrowRecord = null
|
||||
|
||||
if (borrowRecordIdNum) {
|
||||
borrowRecord = await prisma.borrowRecord.findFirst({
|
||||
where: {
|
||||
id: borrowRecordIdNum,
|
||||
equipment_id: equipment.id,
|
||||
status: '已借出'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (!borrowRecord && personIdNum) {
|
||||
borrowRecord = await prisma.borrowRecord.findFirst({
|
||||
where: {
|
||||
equipment_id: equipment.id,
|
||||
person_id: personIdNum,
|
||||
status: '已借出'
|
||||
},
|
||||
orderBy: { borrow_time: 'desc' }
|
||||
})
|
||||
}
|
||||
|
||||
if (!borrowRecord) {
|
||||
borrowRecord = await prisma.borrowRecord.findFirst({
|
||||
where: {
|
||||
equipment_id: equipment.id,
|
||||
status: '已借出'
|
||||
},
|
||||
orderBy: { borrow_time: 'desc' }
|
||||
})
|
||||
try {
|
||||
borrowRecord = await findActiveBorrowRecord(
|
||||
equipment.id,
|
||||
parseNumber(borrow_record_id),
|
||||
parseNumber(person_id)
|
||||
)
|
||||
} catch (error: any) {
|
||||
return errorResponse(res, error.message || '未找到对应的借用记录,请选择正确的使用者', 422)
|
||||
}
|
||||
|
||||
if (!borrowRecord) {
|
||||
return errorResponse(res, '未找到对应的借用记录,请选择正确的使用者', 404)
|
||||
}
|
||||
|
||||
const updatedRecord = await prisma.borrowRecord.update({
|
||||
where: { id: borrowRecord.id },
|
||||
data: {
|
||||
return_time: new Date(),
|
||||
status: '已归还',
|
||||
return_condition,
|
||||
notes
|
||||
}
|
||||
})
|
||||
|
||||
await updateEquipmentAfterReturn(equipment)
|
||||
const childrenDiff = await getChildrenDiff(equipment.id, borrowRecord)
|
||||
if (childrenDiff && !confirm_children_diff) {
|
||||
return successResponse(res, {
|
||||
need_confirm: true,
|
||||
children_diff: childrenDiff,
|
||||
message: '配件与借出时不一致,请确认后继续'
|
||||
}, '配件差异提醒')
|
||||
}
|
||||
|
||||
const finalNotes = childrenDiff
|
||||
? [notes, `配件变更:移除${childrenDiff.removed.length}个,新增${childrenDiff.added.length}个`]
|
||||
.filter(Boolean)
|
||||
.join(';')
|
||||
: notes
|
||||
|
||||
const updatedRecord = await returnEquipmentInTransaction(
|
||||
equipment,
|
||||
borrowRecord,
|
||||
return_condition,
|
||||
finalNotes
|
||||
)
|
||||
|
||||
successResponse(
|
||||
res,
|
||||
@ -638,31 +691,36 @@ export const scanReturnBatch = async (req: Request, res: Response) => {
|
||||
continue
|
||||
}
|
||||
|
||||
const borrowRecord = await prisma.borrowRecord.findFirst({
|
||||
const activeBorrowRecords = await prisma.borrowRecord.findMany({
|
||||
where: {
|
||||
equipment_id: equipment.id,
|
||||
status: '已借出'
|
||||
},
|
||||
orderBy: { borrow_time: 'desc' }
|
||||
orderBy: { borrow_time: 'desc' },
|
||||
take: 2
|
||||
})
|
||||
const borrowRecord = activeBorrowRecords[0]
|
||||
|
||||
if (!borrowRecord) {
|
||||
results.push({ equipment_code: code, status: 'failed', message: '未找到对应的借用记录' })
|
||||
failedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
await prisma.borrowRecord.update({
|
||||
where: { id: borrowRecord.id },
|
||||
data: {
|
||||
return_time: new Date(),
|
||||
status: '已归还',
|
||||
return_condition,
|
||||
notes
|
||||
}
|
||||
})
|
||||
|
||||
await updateEquipmentAfterReturn(equipment)
|
||||
|
||||
if (activeBorrowRecords.length > 1) {
|
||||
results.push({ equipment_code: code, status: 'failed', message: '存在多条未归还记录,请使用单个扫码归还' })
|
||||
failedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
const childrenDiff = await getChildrenDiff(equipment.id, borrowRecord)
|
||||
if (childrenDiff) {
|
||||
results.push({ equipment_code: code, status: 'failed', message: '配件与借出时不一致,请使用单个扫码归还确认' })
|
||||
failedCount++
|
||||
continue
|
||||
}
|
||||
|
||||
await returnEquipmentInTransaction(equipment, borrowRecord, return_condition, notes)
|
||||
|
||||
results.push({ equipment_code: code, status: 'success', message: '入库成功' })
|
||||
successCount++
|
||||
@ -827,4 +885,3 @@ export const getBorrowRecordList = async (req: Request, res: Response) => {
|
||||
errorResponse(res, error.message || '获取借用记录列表失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -37,6 +37,7 @@ const includeStorageLocation = {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
short_name: true,
|
||||
status: true
|
||||
}
|
||||
}
|
||||
@ -453,6 +454,25 @@ export const deleteEquipment = async (req: Request, res: Response) => {
|
||||
|
||||
const equipmentId = Number(id)
|
||||
|
||||
const equipment = await prisma.equipment.findUnique({
|
||||
where: { id: equipmentId },
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
borrow_records: true,
|
||||
children: true,
|
||||
mountHistories: true,
|
||||
disposalRecords: true,
|
||||
maintenanceRecords: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!equipment) {
|
||||
return errorResponse(res, '资产不存在', 404)
|
||||
}
|
||||
|
||||
// 检查是否有未归还的借用记录
|
||||
const activeBorrowCount = await prisma.borrowRecord.count({
|
||||
where: {
|
||||
@ -465,15 +485,28 @@ export const deleteEquipment = async (req: Request, res: Response) => {
|
||||
return errorResponse(res, '该资产存在未归还的借用记录,无法删除')
|
||||
}
|
||||
|
||||
// 清理历史借用记录,避免外键约束阻止删除
|
||||
await prisma.borrowRecord.deleteMany({
|
||||
where: {
|
||||
equipment_id: equipmentId
|
||||
}
|
||||
})
|
||||
|
||||
await prisma.equipment.delete({
|
||||
where: { id: equipmentId }
|
||||
if (equipment.parent_id) {
|
||||
return errorResponse(res, '该资产当前作为子资产挂载中,无法删除')
|
||||
}
|
||||
|
||||
if (equipment._count.children > 0) {
|
||||
return errorResponse(res, '该资产存在已挂载的子资产,无法删除')
|
||||
}
|
||||
|
||||
if (equipment._count.mountHistories > 0 || equipment._count.disposalRecords > 0 || equipment._count.maintenanceRecords > 0) {
|
||||
return errorResponse(res, '该资产已存在维修、报废或挂载历史,无法删除')
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.borrowRecord.deleteMany({
|
||||
where: {
|
||||
equipment_id: equipmentId
|
||||
}
|
||||
})
|
||||
|
||||
await tx.equipment.delete({
|
||||
where: { id: equipmentId }
|
||||
})
|
||||
})
|
||||
|
||||
successResponse(res, null, '删除成功')
|
||||
@ -495,7 +528,7 @@ export const generateCode = async (req: Request, res: Response) => {
|
||||
const code = await generateEquipmentCode(equipment_type, prisma, locationId)
|
||||
successResponse(res, {
|
||||
equipment_code: code,
|
||||
code_format: '{类型数字}{地址ID(4位)}{序号(5位)}'
|
||||
code_format: '{类型简称}-{地址ID(4位)}-{序号(5位)}'
|
||||
}, '生成成功')
|
||||
} else {
|
||||
errorResponse(res, '自定义录入模式不需要生成编码')
|
||||
@ -565,4 +598,3 @@ export const scanEquipment = async (req: Request, res: Response) => {
|
||||
errorResponse(res, error.message || '查询资产失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -38,7 +38,10 @@ export const mountChild = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { parentId } = req.params
|
||||
const { child_id, notes } = req.body as { child_id: number; notes?: string }
|
||||
const userId = (req as any).user?.id || 1
|
||||
const userId = Number(res.locals.user?.id)
|
||||
if (!userId) {
|
||||
return errorResponse(res, '用户未登录', 401)
|
||||
}
|
||||
|
||||
// 验证父资产存在
|
||||
const parent = await prisma.equipment.findUnique({
|
||||
@ -131,7 +134,10 @@ export const unmountChild = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { parentId } = req.params
|
||||
const { child_id, notes } = req.body as { child_id: number; notes?: string }
|
||||
const userId = (req as any).user?.id || 1
|
||||
const userId = Number(res.locals.user?.id)
|
||||
if (!userId) {
|
||||
return errorResponse(res, '用户未登录', 401)
|
||||
}
|
||||
|
||||
// 验证子资产存在且挂载在该父资产上
|
||||
const child = await prisma.equipment.findUnique({
|
||||
@ -185,7 +191,10 @@ export const replaceChild = async (req: Request, res: Response) => {
|
||||
new_child_id: number
|
||||
notes?: string
|
||||
}
|
||||
const userId = (req as any).user?.id || 1
|
||||
const userId = Number(res.locals.user?.id)
|
||||
if (!userId) {
|
||||
return errorResponse(res, '用户未登录', 401)
|
||||
}
|
||||
|
||||
// 验证旧配件挂载在该父资产上
|
||||
const oldChild = await prisma.equipment.findUnique({
|
||||
|
||||
@ -222,12 +222,13 @@ export const deletePerson = async (req: Request, res: Response) => {
|
||||
return errorResponse(res, '该使用者存在未归还的借用记录,无法删除')
|
||||
}
|
||||
|
||||
// 删除历史借用记录
|
||||
await prisma.borrowRecord.deleteMany({
|
||||
where: { person_id: personId }
|
||||
})
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.borrowRecord.deleteMany({
|
||||
where: { person_id: personId }
|
||||
})
|
||||
|
||||
await prisma.person.delete({ where: { id: personId } })
|
||||
await tx.person.delete({ where: { id: personId } })
|
||||
})
|
||||
|
||||
successResponse(res, null, '删除成功')
|
||||
} catch (error: any) {
|
||||
|
||||
@ -2,9 +2,9 @@ import type { Request, Response } from 'express'
|
||||
import prisma from '../utils/prisma'
|
||||
import { successResponse, errorResponse } from '../utils/response'
|
||||
|
||||
const DEFAULT_SITE_NAME = '摄影资产借还系统'
|
||||
const DEFAULT_SITE_NAME = '固定资产管理系统'
|
||||
const DEFAULT_THEME_COLOR = '#409EFF'
|
||||
const DEFAULT_LOGO_URL = 'https://s3.1080p.hk/shuzhou/2025/05/18/8ebadf0a7cc34eaea00e8619b8efb827.png'
|
||||
const DEFAULT_LOGO_URL = ''
|
||||
|
||||
const mapConfigResponse = (config: any) => {
|
||||
if (!config) {
|
||||
@ -20,8 +20,8 @@ const mapConfigResponse = (config: any) => {
|
||||
|
||||
return {
|
||||
site_name: config.site_name || DEFAULT_SITE_NAME,
|
||||
logo_url: DEFAULT_LOGO_URL,
|
||||
favicon_url: '',
|
||||
logo_url: config.logo_url || DEFAULT_LOGO_URL,
|
||||
favicon_url: config.favicon_url || '',
|
||||
theme_color: config.theme_color || DEFAULT_THEME_COLOR,
|
||||
updated_at: config.updated_at,
|
||||
updated_by: config.updatedBy
|
||||
@ -72,8 +72,8 @@ export const updateSiteConfig = async (req: Request, res: Response) => {
|
||||
theme_color: theme_color || DEFAULT_THEME_COLOR,
|
||||
updated_at: new Date(),
|
||||
updated_by: res.locals.user?.id ?? null,
|
||||
logo_url: DEFAULT_LOGO_URL,
|
||||
favicon_url: null
|
||||
logo_url: existing?.logo_url || DEFAULT_LOGO_URL,
|
||||
favicon_url: existing?.favicon_url || null
|
||||
}
|
||||
|
||||
const result = existing
|
||||
@ -105,4 +105,3 @@ export const updateSiteConfig = async (req: Request, res: Response) => {
|
||||
errorResponse(res, error.message || '更新站点配置失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -164,16 +164,32 @@ export const getValueStatistics = async (req: Request, res: Response) => {
|
||||
.filter(eq => eq.status === '维护中')
|
||||
.reduce((sum, eq) => sum + Number(eq.available_value), 0)
|
||||
|
||||
// 按资产类型统计
|
||||
const valueByType = ['相机', '镜头', '三脚架', '闪光灯', '摄影配饰品'].map(type => {
|
||||
const typeEquipment = equipmentList.filter(eq => eq.equipment_type === type)
|
||||
return {
|
||||
equipment_type: type,
|
||||
total_value: typeEquipment.reduce((sum, eq) => sum + Number(eq.total_value), 0),
|
||||
borrowed_value: typeEquipment.reduce((sum, eq) => sum + Number(eq.borrowed_value), 0),
|
||||
available_value: typeEquipment.reduce((sum, eq) => sum + Number(eq.available_value), 0)
|
||||
// 按资产类型统计,避免沿用旧项目中的固定类型名单
|
||||
const valueByTypeMap = equipmentList.reduce((acc, eq) => {
|
||||
const type = eq.equipment_type || '未分类'
|
||||
if (!acc[type]) {
|
||||
acc[type] = {
|
||||
equipment_type: type,
|
||||
total_value: 0,
|
||||
borrowed_value: 0,
|
||||
available_value: 0
|
||||
}
|
||||
}
|
||||
}).filter(item => item.total_value > 0)
|
||||
|
||||
acc[type].total_value += Number(eq.total_value)
|
||||
acc[type].borrowed_value += Number(eq.borrowed_value)
|
||||
acc[type].available_value += Number(eq.available_value)
|
||||
return acc
|
||||
}, {} as Record<string, {
|
||||
equipment_type: string
|
||||
total_value: number
|
||||
borrowed_value: number
|
||||
available_value: number
|
||||
}>)
|
||||
|
||||
const valueByType = Object.values(valueByTypeMap)
|
||||
.filter((item) => item.total_value > 0)
|
||||
.sort((a, b) => b.total_value - a.total_value)
|
||||
|
||||
// 价值最高的资产(Top 10)
|
||||
const topEquipment = equipmentList
|
||||
@ -199,4 +215,3 @@ export const getValueStatistics = async (req: Request, res: Response) => {
|
||||
errorResponse(res, error.message || '获取价值统计失败')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -201,7 +201,20 @@ export const deleteUser = async (req: Request, res: Response) => {
|
||||
const { id } = req.params
|
||||
const userId = Number(id)
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } })
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
include: {
|
||||
_count: {
|
||||
select: {
|
||||
site_configs: true,
|
||||
mount_histories: true,
|
||||
disposal_applied: true,
|
||||
disposal_approved: true,
|
||||
maintenance_records: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
if (!user) {
|
||||
return errorResponse(res, '用户不存在', 404)
|
||||
}
|
||||
@ -211,12 +224,23 @@ export const deleteUser = async (req: Request, res: Response) => {
|
||||
return errorResponse(res, '不能删除管理员账户')
|
||||
}
|
||||
|
||||
// 删除关联的操作日志
|
||||
await prisma.operationLog.deleteMany({
|
||||
where: { user_id: userId }
|
||||
})
|
||||
if (
|
||||
user._count.site_configs > 0 ||
|
||||
user._count.mount_histories > 0 ||
|
||||
user._count.disposal_applied > 0 ||
|
||||
user._count.disposal_approved > 0 ||
|
||||
user._count.maintenance_records > 0
|
||||
) {
|
||||
return errorResponse(res, '该用户已存在业务历史记录,无法删除')
|
||||
}
|
||||
|
||||
await prisma.user.delete({ where: { id: userId } })
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.operationLog.deleteMany({
|
||||
where: { user_id: userId }
|
||||
})
|
||||
|
||||
await tx.user.delete({ where: { id: userId } })
|
||||
})
|
||||
|
||||
successResponse(res, null, '删除成功')
|
||||
} catch (error: any) {
|
||||
|
||||
@ -62,7 +62,7 @@ app.use('/api/return', protect(), returnRouter)
|
||||
app.use('/api/statistics', protect(), statisticsRoutes)
|
||||
app.use('/api/barcode', protect(), barcodeRoutes)
|
||||
app.use('/api/auth', authRoutes)
|
||||
app.use('/api/settings', protect(), requireAdmin(), settingsRoutes)
|
||||
app.use('/api/settings', settingsRoutes)
|
||||
app.use('/api/users', protect(), requireAdmin(), usersRoutes)
|
||||
app.use('/api/persons', personRoutes)
|
||||
app.use('/api/storage-locations', storageLocationRoutes)
|
||||
@ -95,4 +95,3 @@ app.use((err: any, req: express.Request, res: express.Response, next: express.Ne
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on http://localhost:${PORT}`)
|
||||
})
|
||||
|
||||
|
||||
@ -7,9 +7,13 @@ import {
|
||||
completeDisposal,
|
||||
getEquipmentDisposalHistory
|
||||
} from '../controllers/disposalController'
|
||||
import { protect, requireAdmin } from '../middleware/auth'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.use(protect())
|
||||
router.use(requireAdmin())
|
||||
|
||||
router.post('/', createDisposal)
|
||||
router.get('/list', getDisposalList)
|
||||
router.get('/:id', getDisposalDetail)
|
||||
|
||||
@ -8,9 +8,13 @@ import {
|
||||
cancelMaintenance,
|
||||
getEquipmentMaintenanceHistory
|
||||
} from '../controllers/maintenanceController'
|
||||
import { protect, requireAdmin } from '../middleware/auth'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.use(protect())
|
||||
router.use(requireAdmin())
|
||||
|
||||
router.post('/', createMaintenance)
|
||||
router.get('/list', getMaintenanceList)
|
||||
router.get('/:id', getMaintenanceDetail)
|
||||
|
||||
@ -7,11 +7,14 @@ import {
|
||||
getMountHistory,
|
||||
getAvailableChildren
|
||||
} from '../controllers/mountController'
|
||||
import { protect, requireAdmin } from '../middleware/auth'
|
||||
|
||||
const router = Router()
|
||||
|
||||
router.use(protect())
|
||||
|
||||
// 获取可挂载的子资产列表
|
||||
router.get('/available-children', getAvailableChildren)
|
||||
router.get('/available-children', requireAdmin(), getAvailableChildren)
|
||||
|
||||
// 获取资产的挂载历史
|
||||
router.get('/:id/mount-history', getMountHistory)
|
||||
@ -20,12 +23,12 @@ router.get('/:id/mount-history', getMountHistory)
|
||||
router.get('/:parentId/children', getChildren)
|
||||
|
||||
// 挂载子资产到父资产
|
||||
router.post('/:parentId/mount', mountChild)
|
||||
router.post('/:parentId/mount', requireAdmin(), mountChild)
|
||||
|
||||
// 从父资产卸载子资产
|
||||
router.post('/:parentId/unmount', unmountChild)
|
||||
router.post('/:parentId/unmount', requireAdmin(), unmountChild)
|
||||
|
||||
// 更换配件
|
||||
router.post('/:parentId/replace', replaceChild)
|
||||
router.post('/:parentId/replace', requireAdmin(), replaceChild)
|
||||
|
||||
export default router
|
||||
|
||||
@ -1,44 +1,44 @@
|
||||
// 资产类型数字代码映射(纯数字编码)
|
||||
const EQUIPMENT_TYPE_NUMBERS: Record<string, number> = {
|
||||
'相机': 1,
|
||||
'镜头': 2,
|
||||
'三脚架': 3,
|
||||
'闪光灯': 4,
|
||||
'摄影配饰品': 5
|
||||
const EQUIPMENT_CODE_PATTERN = /^[A-Z0-9]{2,20}-\d{4}-\d{5}$/
|
||||
|
||||
const normalizeShortName = (value?: string | null) => {
|
||||
const normalized = value?.trim().toUpperCase() || ''
|
||||
if (!normalized) return null
|
||||
return normalized.replace(/[^A-Z0-9]/g, '')
|
||||
}
|
||||
|
||||
const resolveTypeShortName = async (equipmentType: string, prisma: any) => {
|
||||
const assetType = await prisma.assetType.findFirst({
|
||||
where: { name: equipmentType },
|
||||
select: {
|
||||
id: true,
|
||||
short_name: true
|
||||
}
|
||||
})
|
||||
|
||||
if (!assetType) {
|
||||
throw new Error(`资产类型 "${equipmentType}" 不存在`)
|
||||
}
|
||||
|
||||
const shortName = normalizeShortName(assetType.short_name)
|
||||
return shortName || `AT${assetType.id}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成资产编码(纯数字格式)
|
||||
* 格式: {类型数字}{地址ID(4位)}{序号(5位)}
|
||||
* 例如: 1000100001 (类型1,地址1,序号00001)
|
||||
* 如果没有地址,地址ID为0000
|
||||
*
|
||||
* 确保唯一性:
|
||||
* 1. 查找该类型和地址的最大序号,避免并发问题
|
||||
* 2. 检查生成的编码是否已存在,如果存在则递增序号重试
|
||||
* 3. 最多重试10次,防止无限循环
|
||||
* 生成资产编码
|
||||
* 格式: {类型简称}-{地址ID(4位)}-{序号(5位)}
|
||||
* 例如: PC-0001-00001
|
||||
*/
|
||||
export const generateEquipmentCode = async (
|
||||
equipmentType: string,
|
||||
prisma: any,
|
||||
storageLocationId?: number | null
|
||||
): Promise<string> => {
|
||||
const typeNumber = EQUIPMENT_TYPE_NUMBERS[equipmentType] || 0
|
||||
|
||||
// 获取地址ID,如果没有地址则为0
|
||||
const locationId = storageLocationId || 0
|
||||
|
||||
// 格式前缀:{类型数字}{地址ID(4位)}
|
||||
const typeCode = String(typeNumber)
|
||||
const locationCode = String(locationId).padStart(4, '0')
|
||||
const prefix = `${typeCode}${locationCode}`
|
||||
|
||||
// 查找该类型和地址的所有资产,提取最大序号
|
||||
// 使用这种方式比count更安全,可以处理并发情况
|
||||
const typeCode = await resolveTypeShortName(equipmentType, prisma)
|
||||
const locationCode = String(storageLocationId || 0).padStart(4, '0')
|
||||
const prefix = `${typeCode}-${locationCode}-`
|
||||
|
||||
const existingEquipment = await prisma.equipment.findMany({
|
||||
where: {
|
||||
equipment_type: equipmentType,
|
||||
storage_location_id: storageLocationId || null,
|
||||
equipment_code: {
|
||||
startsWith: prefix
|
||||
}
|
||||
@ -49,58 +49,42 @@ export const generateEquipmentCode = async (
|
||||
orderBy: {
|
||||
equipment_code: 'desc'
|
||||
},
|
||||
take: 1 // 只取最大的一条
|
||||
take: 1
|
||||
})
|
||||
|
||||
// 计算起始序号
|
||||
|
||||
let startSequence = 1
|
||||
if (existingEquipment.length > 0) {
|
||||
// 从最大编码中提取序号
|
||||
const maxCode = existingEquipment[0].equipment_code
|
||||
if (maxCode.startsWith(prefix)) {
|
||||
const sequencePart = maxCode.substring(prefix.length)
|
||||
const maxSequence = parseInt(sequencePart, 10)
|
||||
if (!isNaN(maxSequence)) {
|
||||
if (!Number.isNaN(maxSequence)) {
|
||||
startSequence = maxSequence + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查序号是否溢出(5位数字最大99999)
|
||||
|
||||
if (startSequence > 99999) {
|
||||
throw new Error(`资产编码序号已溢出,该类型和地址的资产数量已超过99999`)
|
||||
throw new Error(`资产编码序号已溢出,该类型和地址的资产数量已超过 99999`)
|
||||
}
|
||||
|
||||
// 生成编码并检查唯一性,最多重试10次
|
||||
const maxRetries = 10
|
||||
|
||||
const maxRetries = 20
|
||||
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
||||
const sequence = String(startSequence + attempt).padStart(5, '0')
|
||||
const code = `${prefix}${sequence}`
|
||||
|
||||
// 检查编码是否已存在
|
||||
|
||||
const existing = await prisma.equipment.findUnique({
|
||||
where: { equipment_code: code }
|
||||
})
|
||||
|
||||
|
||||
if (!existing) {
|
||||
// 编码唯一,返回
|
||||
return code
|
||||
}
|
||||
|
||||
// 如果编码已存在,继续尝试下一个序号
|
||||
console.warn(`资产编码 ${code} 已存在,尝试下一个序号`)
|
||||
}
|
||||
|
||||
// 如果重试10次后仍然冲突,抛出错误
|
||||
throw new Error(`无法生成唯一的资产编码,已重试${maxRetries}次`)
|
||||
|
||||
throw new Error(`无法生成唯一的资产编码,已重试 ${maxRetries} 次`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证资产编码格式(纯数字)
|
||||
*/
|
||||
export const validateEquipmentCode = (code: string): boolean => {
|
||||
// 纯数字格式,长度10位:{类型1位}{地址4位}{序号5位}
|
||||
const pattern = /^\d{10}$/
|
||||
return pattern.test(code)
|
||||
return EQUIPMENT_CODE_PATTERN.test(code.trim().toUpperCase())
|
||||
}
|
||||
|
||||
|
||||
754
frontend/package-lock.json
generated
754
frontend/package-lock.json
generated
@ -9,18 +9,22 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@heroicons/vue": "^2.2.0",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"axios": "^1.13.2",
|
||||
"echarts": "^6.0.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jsbarcode": "^3.12.1",
|
||||
"keycloak-js": "^23.0.7",
|
||||
"pinia": "^3.0.4",
|
||||
"qrcode": "^1.5.4",
|
||||
"vue": "^3.5.24",
|
||||
"vue-router": "^4.6.3"
|
||||
"vue-router": "^4.6.3",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@types/node": "^24.10.0",
|
||||
"@types/xlsx": "^0.0.35",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"autoprefixer": "^10.4.22",
|
||||
@ -985,12 +989,27 @@
|
||||
"version": "24.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
|
||||
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qrcode": {
|
||||
"version": "1.5.6",
|
||||
"resolved": "https://registry.npmmirror.com/@types/qrcode/-/qrcode-1.5.6.tgz",
|
||||
"integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/xlsx": {
|
||||
"version": "0.0.35",
|
||||
"resolved": "https://registry.npmmirror.com/@types/xlsx/-/xlsx-0.0.35.tgz",
|
||||
"integrity": "sha512-s0x3DYHZzOkxtjqOk/Nv1ezGzpbN7I8WX+lzlV/nFfTDOv7x4d8ZwGHcnaiB8UCx89omPsftQhS5II3jeWePxQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz",
|
||||
@ -1213,6 +1232,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/adler-32": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz",
|
||||
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/alien-signals": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.1.0.tgz",
|
||||
@ -1453,6 +1481,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.25",
|
||||
"caniuse-lite": "^1.0.30001754",
|
||||
@ -1480,6 +1509,15 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase-css": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||
@ -1511,6 +1549,19 @@
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/cfb": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz",
|
||||
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"adler-32": "~1.3.0",
|
||||
"crc-32": "~1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
@ -1549,11 +1600,100 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cliui/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/codepage": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz",
|
||||
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
@ -1566,7 +1706,6 @@
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
@ -1606,6 +1745,18 @@
|
||||
"url": "https://github.com/sponsors/mesqueeb"
|
||||
}
|
||||
},
|
||||
"node_modules/crc-32": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz",
|
||||
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"crc32": "bin/crc32.njs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@ -1649,6 +1800,15 @@
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@ -1658,18 +1818,6 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
@ -1677,6 +1825,12 @@
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/dijkstrajs": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
|
||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dlv": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||
@ -1915,6 +2069,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
@ -1968,6 +2135,15 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/frac": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz",
|
||||
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "5.3.4",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
|
||||
@ -2006,6 +2182,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
@ -2190,7 +2375,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@ -2254,18 +2438,6 @@
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jiti": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.6.1.tgz",
|
||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/js-sha256": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmmirror.com/js-sha256/-/js-sha256-0.10.1.tgz",
|
||||
@ -2298,280 +2470,6 @@
|
||||
"jwt-decode": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.30.2.tgz",
|
||||
"integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"lightningcss-android-arm64": "1.30.2",
|
||||
"lightningcss-darwin-arm64": "1.30.2",
|
||||
"lightningcss-darwin-x64": "1.30.2",
|
||||
"lightningcss-freebsd-x64": "1.30.2",
|
||||
"lightningcss-linux-arm-gnueabihf": "1.30.2",
|
||||
"lightningcss-linux-arm64-gnu": "1.30.2",
|
||||
"lightningcss-linux-arm64-musl": "1.30.2",
|
||||
"lightningcss-linux-x64-gnu": "1.30.2",
|
||||
"lightningcss-linux-x64-musl": "1.30.2",
|
||||
"lightningcss-win32-arm64-msvc": "1.30.2",
|
||||
"lightningcss-win32-x64-msvc": "1.30.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-android-arm64": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
|
||||
"integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-arm64": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
|
||||
"integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-x64": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
|
||||
"integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-freebsd-x64": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
|
||||
"integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
|
||||
"integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
|
||||
"integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-musl": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
|
||||
"integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-gnu": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
|
||||
"integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-musl": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
|
||||
"integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-arm64-msvc": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
|
||||
"integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-x64-msvc": {
|
||||
"version": "1.30.2",
|
||||
"resolved": "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
|
||||
"integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
@ -2592,6 +2490,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
@ -2801,6 +2711,42 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
@ -2815,6 +2761,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
@ -2867,6 +2822,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@ -2915,6 +2871,15 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz",
|
||||
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
@ -2934,6 +2899,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@ -3083,6 +3049,23 @@
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qrcode": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.4.tgz",
|
||||
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dijkstrajs": "^1.0.1",
|
||||
"pngjs": "^5.0.0",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"bin": {
|
||||
"qrcode": "bin/qrcode"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@ -3140,6 +3123,21 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.11",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||
@ -3244,6 +3242,12 @@
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@ -3298,6 +3302,18 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ssf": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz",
|
||||
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"frac": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
@ -3456,6 +3472,7 @@
|
||||
"integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"arg": "^5.0.2",
|
||||
@ -3579,6 +3596,7 @@
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@ -3591,7 +3609,6 @@
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
@ -3647,6 +3664,7 @@
|
||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
@ -3728,6 +3746,7 @@
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.24.tgz",
|
||||
"integrity": "sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.24",
|
||||
"@vue/compiler-sfc": "3.5.24",
|
||||
@ -3798,6 +3817,30 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/which-module": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz",
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/wmf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz",
|
||||
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/word": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/word/-/word-0.3.0.tgz",
|
||||
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
@ -3896,6 +3939,109 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/xlsx": {
|
||||
"version": "0.18.5",
|
||||
"resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz",
|
||||
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"adler-32": "~1.3.0",
|
||||
"cfb": "~1.2.1",
|
||||
"codepage": "~1.15.0",
|
||||
"crc-32": "~1.2.1",
|
||||
"ssf": "~0.11.2",
|
||||
"wmf": "~1.0.1",
|
||||
"word": "~0.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"xlsx": "bin/xlsx.njs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/yargs/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/zrender": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz",
|
||||
|
||||
@ -68,8 +68,11 @@ export const scanBorrowBatch = async (data: {
|
||||
export const returnEquipment = async (data: {
|
||||
equipment_id: number
|
||||
equipment_code: string
|
||||
person_id?: number
|
||||
borrow_record_id?: number
|
||||
return_condition?: string
|
||||
notes?: string
|
||||
confirm_children_diff?: boolean
|
||||
}) => {
|
||||
const response = await api.post<ApiResponse<BorrowRecord>>('/return', data)
|
||||
return response.data
|
||||
@ -82,6 +85,7 @@ export const scanReturn = async (data: {
|
||||
notes?: string
|
||||
person_id?: number
|
||||
borrow_record_id?: number
|
||||
confirm_children_diff?: boolean
|
||||
}) => {
|
||||
const response = await api.post<
|
||||
ApiResponse<{
|
||||
@ -91,6 +95,11 @@ export const scanReturn = async (data: {
|
||||
return_time: string
|
||||
status: string
|
||||
person_id?: number
|
||||
need_confirm?: boolean
|
||||
children_diff?: {
|
||||
removed: Array<Record<string, any>>
|
||||
added: Array<Record<string, any>>
|
||||
}
|
||||
}>
|
||||
>('/return/scan', data)
|
||||
return response.data
|
||||
@ -143,4 +152,3 @@ export const getBorrowRecordList = async (params: {
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
|
||||
@ -19,12 +19,6 @@ const bootstrap = async () => {
|
||||
const authMode = import.meta.env.VITE_AUTH_MODE || 'keycloak'
|
||||
|
||||
if (authMode === 'local') {
|
||||
localStorage.removeItem('token')
|
||||
authStore.$patch({
|
||||
token: null,
|
||||
user: null,
|
||||
isAuthenticated: false
|
||||
})
|
||||
try {
|
||||
const resp = await api.get('/auth/user')
|
||||
if (resp?.data?.data) {
|
||||
@ -39,9 +33,21 @@ const bootstrap = async () => {
|
||||
}
|
||||
authStore.setUser(normalizedUser)
|
||||
authStore.setToken('session')
|
||||
} else {
|
||||
authStore.$patch({
|
||||
token: null,
|
||||
user: null,
|
||||
isAuthenticated: false
|
||||
})
|
||||
localStorage.removeItem('token')
|
||||
}
|
||||
} catch {
|
||||
// ignore, user not logged in
|
||||
authStore.$patch({
|
||||
token: null,
|
||||
user: null,
|
||||
isAuthenticated: false
|
||||
})
|
||||
localStorage.removeItem('token')
|
||||
}
|
||||
|
||||
app.use(router)
|
||||
|
||||
@ -92,7 +92,8 @@ router.beforeEach((to, _from, next) => {
|
||||
if (authMode === 'local') {
|
||||
next('/login')
|
||||
} else {
|
||||
keycloak.login()
|
||||
keycloak.login()
|
||||
next(false)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -106,4 +107,3 @@ router.beforeEach((to, _from, next) => {
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import type { SiteConfigResponse } from '@/api/settings'
|
||||
|
||||
const DEFAULT_SITE_NAME = '固定资产管理系统'
|
||||
const DEFAULT_THEME_COLOR = '#409EFF'
|
||||
const DEFAULT_LOGO_URL = 'https://s3.1080p.hk/shuzhou/2025/05/18/8ebadf0a7cc34eaea00e8619b8efb827.png'
|
||||
const DEFAULT_LOGO_URL = ''
|
||||
|
||||
const normalizeHex = (color: string) => {
|
||||
if (!color) return null
|
||||
@ -139,4 +139,3 @@ export const useSiteStore = defineStore('site', {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -154,6 +154,7 @@ export interface BorrowRecord {
|
||||
status: '已借出' | '已归还' | '逾期'
|
||||
return_condition?: '正常' | '损坏' | '丢失'
|
||||
notes?: string
|
||||
children_snapshot?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
@ -228,4 +229,3 @@ export interface ValueStatistics {
|
||||
available_value: number
|
||||
}>
|
||||
}
|
||||
|
||||
|
||||
@ -228,6 +228,7 @@ const personKeyword = ref('')
|
||||
const selectedPersonId = ref<number | null>(null)
|
||||
const persons = ref<Person[]>([])
|
||||
let searchTimer: number | null = null
|
||||
let latestPersonRequestId = 0
|
||||
|
||||
const canSubmit = computed(() => {
|
||||
const hasEquipment = Boolean(equipmentInfo.value)
|
||||
@ -273,6 +274,13 @@ const handleScan = async () => {
|
||||
successInfo.value = null
|
||||
mountedChildren.value = []
|
||||
mountedChildrenValue.value = 0
|
||||
selectedPersonId.value = null
|
||||
personKeyword.value = ''
|
||||
borrowForm.value = {
|
||||
expected_return_time: '',
|
||||
notes: '',
|
||||
is_long_term: false
|
||||
}
|
||||
playBeepSound()
|
||||
|
||||
// 获取已挂载的子资产
|
||||
@ -336,11 +344,20 @@ const handleSubmit = async () => {
|
||||
} else if (equipmentInfo.value.remaining_quantity === 0) {
|
||||
equipmentInfo.value.status = '已借出'
|
||||
}
|
||||
equipmentInfo.value = null
|
||||
mountedChildren.value = []
|
||||
mountedChildrenValue.value = 0
|
||||
selectedPersonId.value = null
|
||||
personKeyword.value = ''
|
||||
equipmentConfirmed.value = false
|
||||
borrowForm.value.notes = ''
|
||||
if (!borrowForm.value.is_long_term) {
|
||||
borrowForm.value.expected_return_time = ''
|
||||
borrowForm.value = {
|
||||
expected_return_time: '',
|
||||
notes: '',
|
||||
is_long_term: false
|
||||
}
|
||||
nextTick(() => {
|
||||
scanInputRef.value?.focus()
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
alert(error.message || '出库失败')
|
||||
@ -350,6 +367,7 @@ const handleSubmit = async () => {
|
||||
}
|
||||
|
||||
const fetchPersons = async (keyword = '') => {
|
||||
const requestId = ++latestPersonRequestId
|
||||
try {
|
||||
const response = await getPersonList({
|
||||
page: 1,
|
||||
@ -357,7 +375,7 @@ const fetchPersons = async (keyword = '') => {
|
||||
keyword: keyword || undefined,
|
||||
status: 'active'
|
||||
})
|
||||
if (response.data) {
|
||||
if (response.data && requestId === latestPersonRequestId) {
|
||||
persons.value = response.data.list
|
||||
if (selectedPersonId.value) {
|
||||
const exists = persons.value.some((person) => person.id === selectedPersonId.value)
|
||||
@ -412,4 +430,3 @@ onUnmounted(() => {
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@ -348,13 +348,40 @@ const handleSubmit = async () => {
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
await scanReturn({
|
||||
let confirmChildrenDiff = false
|
||||
if (hasChildrenDiff.value) {
|
||||
confirmChildrenDiff = window.confirm('检测到配件与借出时不一致,确认继续入库吗?')
|
||||
if (!confirmChildrenDiff) {
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const response = await scanReturn({
|
||||
equipment_code: borrowRecordInfo.value.equipment_code,
|
||||
return_condition: returnInfo.value.return_condition,
|
||||
notes: returnInfo.value.notes || undefined,
|
||||
person_id: record.person_id,
|
||||
borrow_record_id: record.id
|
||||
borrow_record_id: record.id,
|
||||
confirm_children_diff: confirmChildrenDiff
|
||||
})
|
||||
|
||||
if (response.data?.need_confirm) {
|
||||
const confirmed = window.confirm('后端检测到配件与借出时不一致,确认继续入库吗?')
|
||||
if (!confirmed) {
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
await scanReturn({
|
||||
equipment_code: borrowRecordInfo.value.equipment_code,
|
||||
return_condition: returnInfo.value.return_condition,
|
||||
notes: returnInfo.value.notes || undefined,
|
||||
person_id: record.person_id,
|
||||
borrow_record_id: record.id,
|
||||
confirm_children_diff: true
|
||||
})
|
||||
}
|
||||
|
||||
alert('入库成功!')
|
||||
router.push('/borrow')
|
||||
@ -371,4 +398,3 @@ onMounted(() => {
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@ -212,11 +212,11 @@ const updateCategoryChart = () => {
|
||||
radius: ['45%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
data: categories.length
|
||||
? categories.map((item) => ({
|
||||
? categories.map((item, index) => ({
|
||||
value: item.total_quantity,
|
||||
name: item.equipment_type,
|
||||
itemStyle: {
|
||||
color: categoryColorPalette[item.equipment_type] || undefined
|
||||
color: getCategoryColor(index)
|
||||
}
|
||||
}))
|
||||
: [{ value: 1, name: '暂无数据', itemStyle: { color: '#d1d1d6' } }],
|
||||
@ -234,13 +234,18 @@ const updateCategoryChart = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const categoryColorPalette: Record<string, string> = {
|
||||
相机: '#0071e3',
|
||||
镜头: '#34c759',
|
||||
三脚架: '#ff9500',
|
||||
闪光灯: '#af52de',
|
||||
摄影配饰品: '#ff2d55'
|
||||
}
|
||||
const categoryColorPalette = [
|
||||
'#0071e3',
|
||||
'#34c759',
|
||||
'#ff9500',
|
||||
'#af52de',
|
||||
'#ff2d55',
|
||||
'#5ac8fa',
|
||||
'#ff3b30',
|
||||
'#30b0c7'
|
||||
]
|
||||
|
||||
const getCategoryColor = (index: number) => categoryColorPalette[index % categoryColorPalette.length]
|
||||
|
||||
const getStatusClass = (status: string) => {
|
||||
const classes: Record<string, string> = {
|
||||
@ -279,4 +284,3 @@ onUnmounted(() => {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@ -32,16 +32,16 @@
|
||||
v-model.trim="createForm.name"
|
||||
class="input-apple"
|
||||
type="text"
|
||||
placeholder="例如:相机、镜头"
|
||||
placeholder="例如:办公电脑、显示器"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-apple-body text-system-label-primary mb-2">简称(用于标签编码)</label>
|
||||
<label class="block text-apple-body text-system-label-primary mb-2">简称(用于自动编码) *</label>
|
||||
<input
|
||||
v-model.trim="createForm.short_name"
|
||||
class="input-apple"
|
||||
type="text"
|
||||
placeholder="例如:CAM、LENS"
|
||||
placeholder="例如:PC、MON"
|
||||
maxlength="20"
|
||||
/>
|
||||
</div>
|
||||
@ -57,6 +57,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-apple-footnote text-system-label-tertiary mt-2">
|
||||
简称将用于自动生成资产编码,例如 `PC-0001-00001`。
|
||||
</p>
|
||||
<p class="text-apple-footnote text-system-label-tertiary mt-1">
|
||||
父资产(如主机)可挂载子资产;子资产(如显卡、内存)可被挂载到父资产上
|
||||
</p>
|
||||
<button
|
||||
@ -161,7 +164,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-apple-body text-system-label-primary mb-2">简称(用于标签编码)</label>
|
||||
<input v-model.trim="editDraft.short_name" class="input-apple" type="text" placeholder="例如:CAM、LENS" maxlength="20" />
|
||||
<input v-model.trim="editDraft.short_name" class="input-apple" type="text" placeholder="例如:PC、MON" maxlength="20" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@ -208,6 +211,8 @@ const loading = ref(false)
|
||||
const createLoading = ref(false)
|
||||
const updateLoading = ref(false)
|
||||
|
||||
const normalizeShortName = (value: string) => value.trim().toUpperCase().replace(/[^A-Z0-9]/g, '')
|
||||
|
||||
const filters = reactive({
|
||||
keyword: '',
|
||||
status: ''
|
||||
@ -232,7 +237,11 @@ const editDraft = reactive({
|
||||
can_be_child: false
|
||||
})
|
||||
|
||||
const canCreate = computed(() => createForm.name.trim().length > 0)
|
||||
const canCreate = computed(() => {
|
||||
const nameOk = createForm.name.trim().length > 0
|
||||
const shortName = normalizeShortName(createForm.short_name)
|
||||
return nameOk && shortName.length >= 2 && shortName.length <= 20
|
||||
})
|
||||
|
||||
const resetCreateForm = () => {
|
||||
createForm.name = ''
|
||||
@ -264,9 +273,10 @@ const handleCreate = async () => {
|
||||
if (!canCreate.value) return
|
||||
createLoading.value = true
|
||||
try {
|
||||
const shortName = normalizeShortName(createForm.short_name)
|
||||
await createAssetType({
|
||||
name: createForm.name,
|
||||
short_name: createForm.short_name || undefined,
|
||||
short_name: shortName,
|
||||
can_be_parent: createForm.can_be_parent,
|
||||
can_be_child: createForm.can_be_child
|
||||
})
|
||||
@ -297,9 +307,10 @@ const submitEdit = async () => {
|
||||
if (!editingType.value) return
|
||||
updateLoading.value = true
|
||||
try {
|
||||
const shortName = normalizeShortName(editDraft.short_name)
|
||||
await updateAssetType(editDraft.id, {
|
||||
name: editDraft.name,
|
||||
short_name: editDraft.short_name || undefined,
|
||||
short_name: shortName || undefined,
|
||||
status: editDraft.status,
|
||||
can_be_parent: editDraft.can_be_parent,
|
||||
can_be_child: editDraft.can_be_child
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user