Compare commits

..

No commits in common. 'main' and 'main' have entirely different histories.
main ... main

@ -35,21 +35,17 @@ restaurant_system/
│ ├── uploads/ # 上传文件目录
│ ├── server.js # 应用入口
│ ├── package.json # 后端依赖
│ ├── .env # 环境变量配置
| ├── package-lock.json # 后端依赖的具体版本
| ├── server.js # 后端核心接口
| └── test-db.js # 数据库连接测试脚本
│ └── .env # 环境变量配置
├── frontend/ # 前端代码
│ ├── src/
│ │ ├── components/ # Vue组件
│ │ ├── views/ # 页面视图
│ │ ├── router/ # 路由配置
│ │ ├── stores/ # 状态管理
│ │ ├── store/ # 状态管理
│ │ ├── api/ # API接口
│ │ └── utils/ # 工具函数
│ ├── public/ # 公共资源
│ └── package.json # 前端依赖
├── node_modules # 前端依赖
└── docs/ # 项目文档
├── database_design.md # 数据库设计文档
└── api_docs.md # API接口文档
@ -172,19 +168,19 @@ npm run dev
## 开发进度
- [x] 步骤1项目初始化与环境配置
- [ ] 步骤2数据库设计
- [ ] 步骤3基础架构搭建
- [ ] 步骤4用户认证系统
- [ ] 步骤5菜品管理功能
- [ ] 步骤6菜单管理功能
- [ ] 步骤7订单管理功能
- [ ] 步骤8库存管理功能
- [ ] 步骤9预订管理功能
- [ ] 步骤10会员管理功能
- [ ] 步骤11支付结算功能
- [ ] 步骤12数据分析与报表
- [ ] 步骤13前端界面开发
- [ ] 步骤14测试与优化
- [x] 步骤2数据库设计
- [x] 步骤3基础架构搭建
- [x] 步骤4用户认证系统
- [x] 步骤5菜品管理功能
- [x] 步骤6菜单管理功能
- [x] 步骤7订单管理功能
- [x] 步骤8库存管理功能
- [x] 步骤9预订管理功能
- [x] 步骤10会员管理功能
- [x] 步骤11支付结算功能
- [x] 步骤12数据分析与报表
- [x] 步骤13前端界面开发
- [x] 步骤14测试与优化
## 默认账户
@ -195,7 +191,7 @@ npm run dev
- **顾客**: customer / customer123
## 作者
课程作业项目
李俊烨 冯昊 李鸿越 孙亮
## 许可证
MIT License

@ -35,23 +35,17 @@ const getSalesTrend = async (req, res, next) => {
try {
const { start_date, end_date } = req.query;
console.log('📊 getSalesTrend 请求参数:', { start_date, end_date });
if (!start_date || !end_date) {
throw new ValidationError('请提供开始日期和结束日期');
}
const trend = await Analytics.getSalesTrend(start_date, end_date);
console.log('📊 Model返回数据条数:', trend ? trend.length : 0);
console.log('📊 数据样例:', trend ? trend.slice(0, 2) : 'null');
res.json(success({
period: { start_date, end_date },
data: trend
}, '获取销售趋势成功'));
} catch (err) {
console.error('❌ getSalesTrend错误:', err);
next(err);
}
};

@ -39,7 +39,7 @@ const register = async (req, res, next) => {
}
// 验证邮箱格式(如果提供了)
if (email && !validateEmail(email).valid) {
if (email && !validateEmail(email)) {
throw new ValidationError('邮箱格式不正确');
}
@ -170,7 +170,7 @@ const updateProfile = async (req, res, next) => {
const { real_name, phone, email, avatar } = req.body;
// 验证邮箱格式(如果提供了)
if (email && !validateEmail(email).valid) {
if (email && !validateEmail(email)) {
throw new ValidationError('邮箱格式不正确');
}

@ -145,9 +145,9 @@ const getDishes = async (req, res, next) => {
const result = await Dish.findAll({
page: parseInt(page),
pageSize: parseInt(pageSize),
category_id: category_id && category_id !== '' ? parseInt(category_id) : undefined,
is_available: is_available !== undefined && is_available !== '' ? parseInt(is_available) : undefined,
status: status !== undefined && status !== '' ? status : undefined,
category_id: category_id ? parseInt(category_id) : undefined,
is_available: is_available !== undefined ? parseInt(is_available) : undefined,
status: status !== undefined ? parseInt(status) : undefined,
search
});

@ -18,7 +18,7 @@ const getInventory = async (req, res, next) => {
const {
page = 1,
pageSize = 10,
item_name,
category,
search,
low_stock_only
} = req.query;
@ -26,18 +26,13 @@ const getInventory = async (req, res, next) => {
const result = await Inventory.findAll({
page: parseInt(page),
pageSize: parseInt(pageSize),
search: item_name || search,
category,
search,
low_stock_only: low_stock_only === 'true' || low_stock_only === '1'
});
// Map database fields to frontend fields
const mappedList = result.list.map(item => ({
...item,
item_name: item.name
}));
res.json(success({
list: mappedList,
list: result.list,
pagination: {
total: result.total,
page: result.page,
@ -63,13 +58,7 @@ const getInventoryById = async (req, res, next) => {
throw new BusinessError('库存项不存在', 404);
}
// Map database fields to frontend fields
const mappedItem = {
...item,
item_name: item.name
};
res.json(success(mappedItem, '获取库存详情成功'));
res.json(success(item, '获取库存详情成功'));
} catch (err) {
next(err);
}
@ -83,25 +72,24 @@ const getInventoryById = async (req, res, next) => {
const createInventory = async (req, res, next) => {
try {
const {
item_name,
name,
category,
quantity,
unit,
unit_price,
purchase_price,
supplier,
expiry_date,
min_quantity
alert_quantity
} = req.body;
// Use item_name if provided, otherwise use name
const itemName = item_name || name;
// 数据验证
const validation = validateBatch([
validateRequired(itemName, '商品名称'),
validateRequired(name, '原材料名称'),
validateRequired(category, '分类'),
validateRequired(quantity, '数量'),
validateRequired(unit, '单位'),
validateRequired(unit_price, '单价'),
validateRequired(min_quantity, '最低库存')
validateRequired(purchase_price, '进货价格'),
validateRequired(alert_quantity, '预警数量')
]);
if (!validation.valid) {
@ -112,36 +100,32 @@ const createInventory = async (req, res, next) => {
if (parseFloat(quantity) < 0) {
throw new ValidationError('数量不能为负数');
}
if (parseFloat(unit_price) <= 0) {
throw new ValidationError('单价必须大于0');
if (parseFloat(purchase_price) <= 0) {
throw new ValidationError('进货价格必须大于0');
}
if (parseFloat(min_quantity) < 0) {
throw new ValidationError('最低库存不能为负数');
if (parseFloat(alert_quantity) < 0) {
throw new ValidationError('预警数量不能为负数');
}
// 检查名称是否已存在
const exists = await Inventory.isNameExists(itemName);
const exists = await Inventory.isNameExists(name);
if (exists) {
throw new ValidationError('该商品名称已存在');
throw new ValidationError('该原材料名称已存在');
}
const item = await Inventory.create({
name: itemName,
name,
category,
quantity: parseFloat(quantity),
unit,
unit_price: parseFloat(unit_price),
purchase_price: parseFloat(purchase_price),
supplier: supplier || null,
expiry_date: expiry_date || null,
min_quantity: parseFloat(min_quantity),
alert_quantity: parseFloat(alert_quantity),
operator_id: req.user.id
});
// Map response fields
const mappedItem = {
...item,
item_name: item.name
};
res.status(201).json(success(mappedItem, '创建库存项成功'));
res.status(201).json(success(item, '创建库存项成功'));
} catch (err) {
next(err);
}
@ -156,18 +140,15 @@ const updateInventory = async (req, res, next) => {
try {
const { id } = req.params;
const {
item_name,
name,
category,
unit,
unit_price,
purchase_price,
supplier,
expiry_date,
min_quantity,
quantity
alert_quantity
} = req.body;
// Use item_name if provided, otherwise use name
const itemName = item_name || name;
// 检查库存项是否存在
const item = await Inventory.findById(id);
if (!item) {
@ -175,40 +156,32 @@ const updateInventory = async (req, res, next) => {
}
// 验证数据
if (unit_price !== undefined && parseFloat(unit_price) <= 0) {
throw new ValidationError('单价必须大于0');
if (purchase_price !== undefined && parseFloat(purchase_price) <= 0) {
throw new ValidationError('进货价格必须大于0');
}
if (min_quantity !== undefined && parseFloat(min_quantity) < 0) {
throw new ValidationError('最低库存不能为负数');
}
if (quantity !== undefined && parseFloat(quantity) < 0) {
throw new ValidationError('库存数量不能为负数');
if (alert_quantity !== undefined && parseFloat(alert_quantity) < 0) {
throw new ValidationError('预警数量不能为负数');
}
// 检查名称是否已存在
if (itemName && itemName !== item.name) {
const exists = await Inventory.isNameExists(itemName, id);
if (name && name !== item.name) {
const exists = await Inventory.isNameExists(name, id);
if (exists) {
throw new ValidationError('该商品名称已存在');
throw new ValidationError('该原材料名称已存在');
}
}
const updatedItem = await Inventory.update(id, {
name: itemName,
name,
category,
unit,
unit_price: unit_price !== undefined ? parseFloat(unit_price) : undefined,
purchase_price: purchase_price !== undefined ? parseFloat(purchase_price) : undefined,
supplier,
expiry_date: expiry_date !== undefined ? expiry_date : undefined,
min_quantity: min_quantity !== undefined ? parseFloat(min_quantity) : undefined,
quantity: quantity !== undefined ? parseFloat(quantity) : undefined
alert_quantity: alert_quantity !== undefined ? parseFloat(alert_quantity) : undefined
});
// Map response fields
const mappedItem = {
...updatedItem,
item_name: updatedItem.name
};
res.json(success(mappedItem, '更新库存项成功'));
res.json(success(updatedItem, '更新库存项成功'));
} catch (err) {
next(err);
}
@ -294,56 +267,6 @@ const adjustInventory = async (req, res, next) => {
}
};
/**
* 库存补货
* POST /api/inventory/:id/restock
* 需要管理员或服务员权限
*/
const restockInventory = async (req, res, next) => {
try {
const { id } = req.params;
const { quantity } = req.body;
// 数据验证
const validation = validateBatch([
validateRequired(quantity, '补货数量')
]);
if (!validation.valid) {
throw new ValidationError('数据验证失败', validation.errors);
}
// 验证数量
if (parseFloat(quantity) <= 0) {
throw new ValidationError('补货数量必须大于0');
}
// 检查库存项是否存在
const item = await Inventory.findById(id);
if (!item) {
throw new BusinessError('库存项不存在', 404);
}
const updatedItem = await Inventory.adjustQuantity(
id,
'in',
parseFloat(quantity),
req.user.id,
'库存补货'
);
// Map response fields
const mappedItem = {
...updatedItem,
item_name: updatedItem.name
};
res.json(success(mappedItem, '补货成功'));
} catch (err) {
next(err);
}
};
/**
* 获取低库存预警列表
* GET /api/inventory/alerts/low-stock
@ -431,6 +354,20 @@ const getInventoryLogs = async (req, res, next) => {
}
};
/**
* 获取库存分类列表
* GET /api/inventory/categories
*/
const getCategories = async (req, res, next) => {
try {
const categories = await Inventory.getCategories();
res.json(success(categories, '获取分类列表成功'));
} catch (err) {
next(err);
}
};
/**
* 获取库存统计信息
* GET /api/inventory/statistics
@ -453,10 +390,10 @@ module.exports = {
updateInventory,
deleteInventory,
adjustInventory,
restockInventory,
getLowStockAlerts,
getExpiringAlerts,
getExpiredAlerts,
getInventoryLogs,
getCategories,
getStatistics
};

@ -70,15 +70,12 @@ const getMenuById = async (req, res, next) => {
*/
const createMenu = async (req, res, next) => {
try {
const { name, type, menu_type, start_date, end_date, is_active } = req.body;
// Use menu_type if provided, otherwise use type
const menuType = menu_type || type;
const { name, type, start_date, end_date, is_active } = req.body;
// 数据验证
const validation = validateBatch([
validateRequired(name, '菜单名称'),
validateRequired(menuType, '菜单类型')
validateRequired(type, '菜单类型')
]);
if (!validation.valid) {
@ -87,7 +84,7 @@ const createMenu = async (req, res, next) => {
// 验证菜单类型
const validTypes = ['lunch', 'dinner', 'holiday'];
if (!validTypes.includes(menuType)) {
if (!validTypes.includes(type)) {
throw new ValidationError('菜单类型必须是 lunch、dinner 或 holiday');
}
@ -98,10 +95,10 @@ const createMenu = async (req, res, next) => {
const menu = await Menu.create({
name,
type: menuType,
type,
start_date: start_date || null,
end_date: end_date || null,
is_active: is_active !== undefined ? (is_active ? 1 : 0) : 1
is_active: is_active !== undefined ? parseInt(is_active) : 1
});
res.status(201).json(success(menu, '创建菜单成功'));
@ -118,10 +115,7 @@ const createMenu = async (req, res, next) => {
const updateMenu = async (req, res, next) => {
try {
const { id } = req.params;
const { name, type, menu_type, start_date, end_date, is_active } = req.body;
// Use menu_type if provided, otherwise use type
const menuType = menu_type || type;
const { name, type, start_date, end_date, is_active } = req.body;
// 检查菜单是否存在
const menu = await Menu.findById(id);
@ -130,9 +124,9 @@ const updateMenu = async (req, res, next) => {
}
// 验证菜单类型
if (menuType) {
if (type) {
const validTypes = ['lunch', 'dinner', 'holiday'];
if (!validTypes.includes(menuType)) {
if (!validTypes.includes(type)) {
throw new ValidationError('菜单类型必须是 lunch、dinner 或 holiday');
}
}
@ -146,10 +140,10 @@ const updateMenu = async (req, res, next) => {
const updatedMenu = await Menu.update(id, {
name,
type: menuType,
type,
start_date: start_date !== undefined ? start_date : undefined,
end_date: end_date !== undefined ? end_date : undefined,
is_active: is_active !== undefined ? (is_active ? 1 : 0) : undefined
is_active: is_active !== undefined ? parseInt(is_active) : undefined
});
res.json(success(updatedMenu, '更新菜单成功'));

@ -83,70 +83,53 @@ const createReservation = async (req, res, next) => {
try {
const {
type,
reservation_type,
reservation_time,
reservation_date,
guest_count,
party_size,
table_number,
contact_name,
customer_name,
contact_phone,
phone,
special_request,
notes
special_request
} = req.body;
// Map frontend fields to backend fields
const reservationType = reservation_type || type;
const peopleCount = party_size || guest_count;
const contactPhone = phone || contact_phone;
const specialRequest = notes || special_request;
// Handle date and time - keep them separate for the database
const resDate = reservation_date;
const resTime = req.body.reservation_time;
// 数据验证
const validationFields = [
validateRequired(reservationType, '预订类型'),
validateRequired(resDate, '预订日期'),
validateRequired(resTime, '预订时间'),
validateRequired(contactPhone, '联系电话'),
validatePhone(contactPhone, '联系电话')
];
// Only require people_count for table reservations
if (reservationType === 'table' && peopleCount) {
validationFields.push(validateRequired(peopleCount, '客人数量'));
}
const validation = validateBatch(validationFields);
const validation = validateBatch([
validateRequired(type, '预订类型'),
validateRequired(reservation_time, '预订时间'),
validateRequired(guest_count, '客人数量'),
validateRequired(contact_name, '联系人姓名'),
validateRequired(contact_phone, '联系电话'),
validatePhone(contact_phone, '联系电话')
]);
if (!validation.valid) {
throw new ValidationError('数据验证失败', validation.errors);
}
// Validate reservation type matches database enum
const validTypes = ['table', 'takeaway'];
if (!validTypes.includes(reservationType)) {
throw new ValidationError('预订类型必须是 table 或 takeaway');
// 验证预订类型
const validTypes = ['dine_in', 'takeaway'];
if (!validTypes.includes(type)) {
throw new ValidationError('预订类型必须是 dine_in 或 takeaway');
}
// 验证客人数量(仅当提供时)
if (peopleCount && parseInt(peopleCount) <= 0) {
// 验证客人数量
if (parseInt(guest_count) <= 0) {
throw new ValidationError('客人数量必须大于0');
}
// 验证预订时间不能是过去时间
const reservationDateTime = new Date(`${resDate} ${resTime}`);
if (reservationDateTime < new Date()) {
const reservationDate = new Date(reservation_time);
if (reservationDate < new Date()) {
throw new ValidationError('预订时间不能早于当前时间');
}
// 如果是堂食预订且提供了桌号,检查桌位是否可用
if (reservationType === 'table' && table_number) {
const isAvailable = await Reservation.isTableAvailable(table_number, `${resDate} ${resTime}`);
// 堂食预订必须有桌号
if (type === 'dine_in') {
if (!table_number) {
throw new ValidationError('堂食预订必须指定桌号');
}
// 检查桌位是否可用
const isAvailable = await Reservation.isTableAvailable(table_number, reservation_time);
if (!isAvailable) {
throw new BusinessError('该桌位在所选时间段内已被预订,请选择其他桌位或时间');
}
@ -154,13 +137,13 @@ const createReservation = async (req, res, next) => {
const reservation = await Reservation.create({
customer_id: req.user.id,
type: reservationType,
reservation_date: resDate,
reservation_time: resTime,
people_count: peopleCount ? parseInt(peopleCount) : null,
table_number: table_number || null,
contact_phone: contactPhone,
special_request: specialRequest || null
type,
reservation_time,
guest_count: parseInt(guest_count),
table_number: type === 'dine_in' ? parseInt(table_number) : null,
contact_name,
contact_phone,
special_request: special_request || null
});
res.status(201).json(success(reservation, '创建预订成功'));

@ -49,7 +49,7 @@ CREATE TABLE dishes (
ingredients TEXT COMMENT '原材料清单',
cooking_time INT COMMENT '预计制作时间(分钟)',
is_available TINYINT DEFAULT 1 COMMENT '是否可售1是/0否',
status VARCHAR(20) DEFAULT 'available' COMMENT '状态available在售/sold_out售罄/discontinued下架',
status TINYINT DEFAULT 1 COMMENT '状态1启用/0禁用',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
FOREIGN KEY (category_id) REFERENCES dish_categories(id) ON DELETE SET NULL,

@ -28,8 +28,7 @@ class Analytics {
COUNT(*) as total_orders,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_orders,
SUM(CASE WHEN status = 'confirmed' THEN 1 ELSE 0 END) as confirmed_orders,
SUM(CASE WHEN status = 'cooking' THEN 1 ELSE 0 END) as cooking_orders,
SUM(CASE WHEN status = 'served' THEN 1 ELSE 0 END) as served_orders,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_orders,
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) as paid_orders,
SUM(final_amount) as total_revenue,
AVG(final_amount) as avg_order_amount
@ -38,16 +37,17 @@ class Analytics {
[start, end]
);
// 订单金额统计 (用于替代支付统计)
const [amountStats] = await pool.query(
// 支付统计
const [paymentStats] = await pool.query(
`SELECT
COUNT(*) as total_paid_orders,
SUM(final_amount) as total_amount,
SUM(CASE WHEN order_type = 'dine_in' THEN final_amount ELSE 0 END) as dine_in_amount,
SUM(CASE WHEN order_type = 'takeaway' THEN final_amount ELSE 0 END) as takeaway_amount
FROM orders
WHERE status NOT IN ('cancelled')
AND DATE(created_at) BETWEEN ? AND ?`,
COUNT(*) as total_payments,
SUM(amount) as total_payment_amount,
SUM(CASE WHEN payment_method = 'cash' THEN amount ELSE 0 END) as cash_amount,
SUM(CASE WHEN payment_method = 'card' THEN amount ELSE 0 END) as card_amount,
SUM(CASE WHEN payment_method = 'wechat' THEN amount ELSE 0 END) as wechat_amount,
SUM(CASE WHEN payment_method = 'alipay' THEN amount ELSE 0 END) as alipay_amount
FROM payments
WHERE status = 'paid' AND DATE(paid_at) BETWEEN ? AND ?`,
[start, end]
);
@ -66,11 +66,11 @@ class Analytics {
const [reservationStats] = await pool.query(
`SELECT
COUNT(*) as total_reservations,
SUM(CASE WHEN type = 'table' THEN 1 ELSE 0 END) as table_count,
SUM(CASE WHEN type = 'dine_in' THEN 1 ELSE 0 END) as dine_in_count,
SUM(CASE WHEN type = 'takeaway' THEN 1 ELSE 0 END) as takeaway_count,
SUM(CASE WHEN status = 'confirmed' THEN 1 ELSE 0 END) as confirmed_count
FROM reservations
WHERE DATE(reservation_date) BETWEEN ? AND ?`,
WHERE DATE(reservation_time) BETWEEN ? AND ?`,
[start, end]
);
@ -78,13 +78,13 @@ class Analytics {
const [inventoryAlerts] = await pool.query(
`SELECT COUNT(*) as low_stock_count
FROM inventory
WHERE quantity <= min_quantity`
WHERE quantity <= alert_quantity`
);
return {
period: { start_date: start, end_date: end },
orders: orderStats[0],
payments: amountStats[0],
payments: paymentStats[0],
members: memberStats[0],
reservations: reservationStats[0],
inventory: inventoryAlerts[0]
@ -104,14 +104,14 @@ class Analytics {
try {
const [rows] = await pool.query(
`SELECT
DATE(created_at) as date,
DATE(paid_at) as date,
COUNT(*) as order_count,
SUM(final_amount) as revenue,
AVG(final_amount) as avg_amount
FROM orders
WHERE status NOT IN ('cancelled')
AND DATE(created_at) BETWEEN ? AND ?
GROUP BY DATE(created_at)
SUM(amount) as revenue,
AVG(amount) as avg_amount
FROM payments
WHERE status = 'paid'
AND DATE(paid_at) BETWEEN ? AND ?
GROUP BY DATE(paid_at)
ORDER BY date ASC`,
[startDate, endDate]
);
@ -141,7 +141,7 @@ class Analytics {
AVG(oi.price) as avg_price
FROM order_items oi
JOIN orders o ON oi.order_id = o.id
WHERE o.status NOT IN ('cancelled')
WHERE o.status IN ('completed', 'paid')
`;
const params = [];
@ -311,7 +311,7 @@ class Analytics {
SUM(final_amount) as revenue,
AVG(final_amount) as avg_amount
FROM orders
WHERE status NOT IN ('cancelled')
WHERE status IN ('completed', 'paid')
`;
const params = [];
@ -403,8 +403,8 @@ class Analytics {
`SELECT
category,
COUNT(*) as item_count,
SUM(quantity * unit_price) as total_value,
SUM(CASE WHEN quantity <= min_quantity THEN 1 ELSE 0 END) as low_stock_count
SUM(quantity * purchase_price) as total_value,
SUM(CASE WHEN quantity <= alert_quantity THEN 1 ELSE 0 END) as low_stock_count
FROM inventory
GROUP BY category
ORDER BY total_value DESC`
@ -413,8 +413,8 @@ class Analytics {
const [totalStats] = await pool.query(
`SELECT
COUNT(*) as total_items,
SUM(quantity * unit_price) as total_value,
SUM(CASE WHEN quantity <= min_quantity THEN 1 ELSE 0 END) as total_low_stock
SUM(quantity * purchase_price) as total_value,
SUM(CASE WHEN quantity <= alert_quantity THEN 1 ELSE 0 END) as total_low_stock
FROM inventory`
);

@ -54,6 +54,7 @@ class Inventory {
const {
page = 1,
pageSize = 10,
category,
search,
low_stock_only = false
} = options;
@ -64,14 +65,19 @@ class Inventory {
const conditions = [];
const params = [];
if (category) {
conditions.push('category = ?');
params.push(category);
}
if (search) {
conditions.push('name LIKE ?');
params.push(`%${search}%`);
conditions.push('(name LIKE ? OR supplier LIKE ?)');
params.push(`%${search}%`, `%${search}%`);
}
// 低库存筛选
if (low_stock_only) {
conditions.push('quantity <= min_quantity');
conditions.push('quantity <= alert_quantity');
}
const whereClause = conditions.length > 0
@ -113,18 +119,20 @@ class Inventory {
try {
const {
name,
category,
quantity,
unit,
unit_price,
purchase_price,
supplier,
expiry_date,
min_quantity
alert_quantity
} = inventoryData;
const [result] = await pool.query(
`INSERT INTO inventory
(name, quantity, unit, unit_price, expiry_date, min_quantity)
VALUES (?, ?, ?, ?, ?, ?)`,
[name, quantity, unit, unit_price, expiry_date || null, min_quantity]
(name, category, quantity, unit, purchase_price, supplier, expiry_date, alert_quantity)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[name, category, quantity, unit, purchase_price, supplier, expiry_date || null, alert_quantity]
);
// 记录库存日志
@ -153,11 +161,12 @@ class Inventory {
try {
const {
name,
category,
unit,
unit_price,
purchase_price,
supplier,
expiry_date,
min_quantity,
quantity
alert_quantity
} = inventoryData;
const updates = [];
@ -167,25 +176,29 @@ class Inventory {
updates.push('name = ?');
values.push(name);
}
if (category !== undefined) {
updates.push('category = ?');
values.push(category);
}
if (unit !== undefined) {
updates.push('unit = ?');
values.push(unit);
}
if (unit_price !== undefined) {
updates.push('unit_price = ?');
values.push(unit_price);
if (purchase_price !== undefined) {
updates.push('purchase_price = ?');
values.push(purchase_price);
}
if (supplier !== undefined) {
updates.push('supplier = ?');
values.push(supplier);
}
if (expiry_date !== undefined) {
updates.push('expiry_date = ?');
values.push(expiry_date);
}
if (min_quantity !== undefined) {
updates.push('min_quantity = ?');
values.push(min_quantity);
}
if (quantity !== undefined) {
updates.push('quantity = ?');
values.push(quantity);
if (alert_quantity !== undefined) {
updates.push('alert_quantity = ?');
values.push(alert_quantity);
}
if (updates.length === 0) {
@ -275,9 +288,9 @@ class Inventory {
await connection.query(
`INSERT INTO inventory_logs
(inventory_id, type, quantity, operator_id, remark)
VALUES (?, ?, ?, ?, ?)`,
[id, changeType, actualChange, operatorId, remark]
(inventory_id, change_type, quantity_change, quantity_after, operator_id, remark)
VALUES (?, ?, ?, ?, ?, ?)`,
[id, changeType, actualChange, newQuantity, operatorId, remark]
);
await connection.commit();
@ -299,8 +312,8 @@ class Inventory {
try {
const [rows] = await pool.query(
`SELECT * FROM inventory
WHERE quantity <= min_quantity
ORDER BY (quantity / min_quantity) ASC`
WHERE quantity <= alert_quantity
ORDER BY (quantity / alert_quantity) ASC`
);
return rows;
@ -376,7 +389,7 @@ class Inventory {
}
if (change_type) {
conditions.push('il.type = ?');
conditions.push('il.change_type = ?');
params.push(change_type);
}
@ -429,15 +442,16 @@ class Inventory {
inventory_id,
change_type,
quantity_change,
quantity_after,
operator_id,
remark
} = logData;
const [result] = await pool.query(
`INSERT INTO inventory_logs
(inventory_id, type, quantity, operator_id, remark)
VALUES (?, ?, ?, ?, ?)`,
[inventory_id, change_type, quantity_change, operator_id, remark || null]
(inventory_id, change_type, quantity_change, quantity_after, operator_id, remark)
VALUES (?, ?, ?, ?, ?, ?)`,
[inventory_id, change_type, quantity_change, quantity_after, operator_id, remark || null]
);
return result.insertId;
@ -467,6 +481,21 @@ class Inventory {
}
}
/**
* 获取库存分类列表
* @returns {Promise<array>} 分类列表
*/
static async getCategories() {
try {
const [rows] = await pool.query(
'SELECT DISTINCT category FROM inventory ORDER BY category'
);
return rows.map(row => row.category);
} catch (error) {
throw new Error('查询分类列表失败: ' + error.message);
}
}
/**
* 获取库存统计信息
* @returns {Promise<object>} 统计信息
@ -474,11 +503,11 @@ class Inventory {
static async getStatistics() {
try {
const [totalResult] = await pool.query(
'SELECT COUNT(*) as total_items, SUM(quantity * unit_price) as total_value FROM inventory'
'SELECT COUNT(*) as total_items, SUM(quantity * purchase_price) as total_value FROM inventory'
);
const [lowStockResult] = await pool.query(
'SELECT COUNT(*) as low_stock_count FROM inventory WHERE quantity <= min_quantity'
'SELECT COUNT(*) as low_stock_count FROM inventory WHERE quantity <= alert_quantity'
);
const [expiringResult] = await pool.query(

@ -112,7 +112,7 @@ class Member {
FROM members m
JOIN users u ON m.user_id = u.id
${whereClause}
ORDER BY m.points DESC
ORDER BY m.total_points DESC
LIMIT ? OFFSET ?`,
[...params, pageSize, offset]
);

@ -30,7 +30,7 @@ class Order {
static async findById(id) {
try {
const [rows] = await pool.query(
`SELECT o.*, o.order_no AS order_number,
`SELECT o.*,
u1.username as customer_name,
u2.username as waiter_name
FROM orders o
@ -53,7 +53,7 @@ class Order {
static async findByOrderNo(orderNo) {
try {
const [rows] = await pool.query(
`SELECT o.*, o.order_no AS order_number,
`SELECT o.*,
u1.username as customer_name,
u2.username as waiter_name
FROM orders o
@ -130,7 +130,7 @@ class Order {
// 查询列表
const [rows] = await pool.query(
`SELECT o.*, o.order_no AS order_number,
`SELECT o.*,
u1.username as customer_name,
u2.username as waiter_name
FROM orders o

@ -127,26 +127,26 @@ class Reservation {
const {
customer_id,
type,
reservation_date,
reservation_time,
people_count,
guest_count,
table_number,
contact_name,
contact_phone,
special_request
} = reservationData;
const [result] = await pool.query(
`INSERT INTO reservations
(customer_id, type, reservation_date, reservation_time, people_count, table_number,
contact_phone, special_request, status)
(customer_id, type, reservation_time, guest_count, table_number,
contact_name, contact_phone, special_request, status)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending')`,
[
customer_id,
type,
reservation_date,
reservation_time,
people_count,
guest_count,
table_number || null,
contact_name,
contact_phone,
special_request || null
]

@ -9,7 +9,6 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"axios": "^1.13.2",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
@ -62,12 +61,6 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/aws-ssl-profiles": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
@ -77,17 +70,6 @@
"node": ">= 6.0.0"
}
},
"node_modules/axios": {
"version": "1.13.2",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.2.tgz",
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
@ -248,18 +230,6 @@
"fsevents": "~2.3.2"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
@ -346,15 +316,6 @@
"ms": "2.0.0"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz",
@ -463,21 +424,6 @@
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
@ -583,42 +529,6 @@
"node": ">= 0.8"
}
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz",
@ -754,21 +664,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
@ -1340,12 +1235,6 @@
"node": ">= 0.10"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmmirror.com/pstree.remy/-/pstree.remy-1.1.8.tgz",

@ -15,7 +15,6 @@
"author": "",
"license": "MIT",
"dependencies": {
"axios": "^1.13.2",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.3.1",

@ -48,13 +48,13 @@ router.get(
/**
* @route GET /api/analytics/sales-trend
* @desc 获取销售趋势按日期
* @access Private (Admin, Waiter)
* @access Private (Admin only)
* @query start_date, end_date (必填)
*/
router.get(
'/sales-trend',
authenticate,
checkRole(['admin', 'waiter']),
checkRole(['admin']),
asyncHandler(getSalesTrend)
);

@ -15,11 +15,11 @@ const {
updateInventory,
deleteInventory,
adjustInventory,
restockInventory,
getLowStockAlerts,
getExpiringAlerts,
getExpiredAlerts,
getInventoryLogs,
getCategories,
getStatistics
} = require('../controllers/inventoryController');
@ -43,6 +43,18 @@ router.get(
asyncHandler(getStatistics)
);
/**
* @route GET /api/inventory/categories
* @desc 获取库存分类列表
* @access Private (Admin, Waiter)
*/
router.get(
'/categories',
authenticate,
checkRole(['admin', 'waiter']),
asyncHandler(getCategories)
);
/**
* @route GET /api/inventory/alerts/low-stock
* @desc 获取低库存预警列表
@ -176,23 +188,6 @@ router.post(
asyncHandler(adjustInventory)
);
/**
* @route POST /api/inventory/:id/restock
* @desc 库存补货
* @access Private (Admin, Waiter)
*
* 请求体
* {
* "quantity": 补货数量
* }
*/
router.post(
'/:id/restock',
authenticate,
checkRole(['admin', 'waiter']),
asyncHandler(restockInventory)
);
/**
* @route GET /api/inventory/:id/logs
* @desc 获取指定库存项的日志

@ -1,68 +0,0 @@
const { pool } = require('../config/database');
async function addSampleOrders() {
try {
// 检查时区
const [tz] = await pool.query('SELECT @@session.time_zone, NOW() as now');
console.log('MySQL时区设置:', tz[0]);
// 获取用户和菜品
const [users] = await pool.query('SELECT id FROM users LIMIT 2');
const [dishes] = await pool.query('SELECT id, name, price FROM dishes LIMIT 3');
if (!users.length || !dishes.length) {
console.log('缺少用户或菜品数据');
process.exit(1);
}
// 直接插入1月7日和1月10日的订单
const orders = [
{ no: 'ORD20260107001', datetime: '2026-01-07 12:00:00', amount: 320 },
{ no: 'ORD20260107002', datetime: '2026-01-07 19:30:00', amount: 280 },
{ no: 'ORD20260110001', datetime: '2026-01-10 13:00:00', amount: 195 },
{ no: 'ORD20260110002', datetime: '2026-01-10 20:00:00', amount: 385 }
];
for (const o of orders) {
const [result] = await pool.query(
`INSERT INTO orders (order_no, customer_id, waiter_id, order_type, table_number, total_amount, final_amount, status, created_at, updated_at)
VALUES (?, ?, ?, 'dine_in', 5, ?, ?, 'confirmed', ?, ?)`,
[o.no, users[0].id, users[1]?.id || users[0].id, o.amount, o.amount, o.datetime, o.datetime]
);
const dish = dishes[0];
const qty = Math.ceil(o.amount / parseFloat(dish.price));
await pool.query(
`INSERT INTO order_items (order_id, dish_id, dish_name, quantity, price, subtotal, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[result.insertId, dish.id, dish.name, qty, dish.price, o.amount, o.datetime]
);
console.log(`✅ 插入: ${o.no} at ${o.datetime}, 金额: ¥${o.amount}`);
}
// 验证结果 - 查看所有日期
const [all] = await pool.query(
`SELECT
DATE_FORMAT(created_at, '%Y-%m-%d') as date,
COUNT(*) as count,
SUM(final_amount) as revenue
FROM orders
WHERE status NOT IN ('cancelled')
GROUP BY DATE_FORMAT(created_at, '%Y-%m-%d')
ORDER BY date ASC`
);
console.log('\n所有订单按日期统计:');
console.table(all);
await pool.end();
process.exit(0);
} catch (error) {
console.error('错误:', error.message);
process.exit(1);
}
}
addSampleOrders();

@ -1,87 +0,0 @@
const axios = require('axios');
async function testAnalyticsAPI() {
try {
// 1. 登录获取token
console.log('1⃣ 登录为admin用户...');
const loginRes = await axios.post('http://localhost:5000/api/auth/login', {
username: 'admin',
password: 'admin123'
});
if (!loginRes.data.success) {
console.error('❌ 登录失败:', loginRes.data.message);
process.exit(1);
}
const token = loginRes.data.data.token;
console.log('✅ 登录成功, Token:', token.substring(0, 20) + '...');
// 2. 测试 sales-trend API
console.log('\n2⃣ 测试营业额趋势API...');
const today = new Date().toISOString().split('T')[0];
const start = new Date();
start.setDate(start.getDate() - 7);
const startDate = start.toISOString().split('T')[0];
console.log(`查询范围: ${startDate}${today}`);
const trendRes = await axios.get('http://localhost:5000/api/analytics/sales-trend', {
params: {
start_date: startDate,
end_date: today
},
headers: {
Authorization: `Bearer ${token}`
}
});
if (trendRes.data.success) {
console.log('✅ API调用成功!');
console.log('完整响应:', JSON.stringify(trendRes.data, null, 2));
const dataArray = Array.isArray(trendRes.data.data) ? trendRes.data.data :
Array.isArray(trendRes.data) ? trendRes.data : [];
console.log('返回数据条数:', dataArray.length);
if (dataArray.length > 0) {
console.log('\n数据样例:');
console.table(dataArray.slice(0, 5));
}
} else {
console.error('❌ API返回失败:', trendRes.data.message);
}
// 3. 测试 dashboard API
console.log('\n3⃣ 测试仪表盘API...');
const dashboardRes = await axios.get('http://localhost:5000/api/analytics/dashboard', {
params: {
start_date: today,
end_date: today
},
headers: {
Authorization: `Bearer ${token}`
}
});
if (dashboardRes.data.success) {
console.log('✅ 仪表盘API成功!');
console.log('订单统计:', dashboardRes.data.data.orders);
}
console.log('\n🎉 所有测试通过!');
process.exit(0);
} catch (error) {
console.error('\n❌ 测试失败:');
if (error.response) {
console.error('状态码:', error.response.status);
console.error('错误信息:', error.response.data);
} else {
console.error('错误:', error.message);
}
process.exit(1);
}
}
testAnalyticsAPI();

@ -7,47 +7,23 @@
/**
* 验证邮箱格式
* @param {string} email - 邮箱地址
* @param {string} fieldName - 字段名称
* @returns {object} { valid: boolean, message: string }
* @returns {boolean} 是否有效
*/
const validateEmail = (email, fieldName = '邮箱') => {
const validateEmail = (email) => {
// 正则表达式:匹配标准邮箱格式
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return {
valid: false,
message: `${fieldName}格式不正确`
};
}
return {
valid: true,
message: ''
};
return emailRegex.test(email);
};
/**
* 验证中国大陆手机号
* @param {string} phone - 手机号
* @param {string} fieldName - 字段名称
* @returns {object} { valid: boolean, message: string }
* @returns {boolean} 是否有效
*/
const validatePhone = (phone, fieldName = '手机号') => {
const validatePhone = (phone) => {
// 正则表达式1开头第二位是3-9后面9位数字
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(phone)) {
return {
valid: false,
message: `${fieldName}格式不正确`
};
}
return {
valid: true,
message: ''
};
return phoneRegex.test(phone);
};
/**

@ -1,71 +1,71 @@
{
"hash": "fd97874e",
"configHash": "4340e46e",
"hash": "2315ec54",
"configHash": "4d6904ee",
"lockfileHash": "2bb9459d",
"browserHash": "d0380a4d",
"browserHash": "747f1100",
"optimized": {
"@element-plus/icons-vue": {
"src": "../../@element-plus/icons-vue/dist/index.js",
"file": "@element-plus_icons-vue.js",
"fileHash": "1ffbcc2c",
"fileHash": "999b2a4f",
"needsInterop": false
},
"axios": {
"src": "../../axios/index.js",
"file": "axios.js",
"fileHash": "02f3d41b",
"fileHash": "3a389781",
"needsInterop": false
},
"dayjs": {
"src": "../../dayjs/dayjs.min.js",
"file": "dayjs.js",
"fileHash": "cd81fa25",
"fileHash": "cb738123",
"needsInterop": true
},
"echarts": {
"src": "../../echarts/index.js",
"file": "echarts.js",
"fileHash": "95c9db8f",
"fileHash": "bb7a0f77",
"needsInterop": false
},
"element-plus": {
"src": "../../element-plus/es/index.mjs",
"file": "element-plus.js",
"fileHash": "c8fdff47",
"fileHash": "80f048d3",
"needsInterop": false
},
"element-plus/es/locale/lang/zh-cn": {
"src": "../../element-plus/es/locale/lang/zh-cn.mjs",
"file": "element-plus_es_locale_lang_zh-cn.js",
"fileHash": "85b73e91",
"fileHash": "68bd2d2d",
"needsInterop": false
},
"pinia": {
"src": "../../pinia/dist/pinia.mjs",
"file": "pinia.js",
"fileHash": "c608d9e9",
"fileHash": "5c5505f4",
"needsInterop": false
},
"vue": {
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "d1c2522f",
"fileHash": "f7fa7178",
"needsInterop": false
},
"vue-router": {
"src": "../../vue-router/dist/vue-router.mjs",
"file": "vue-router.js",
"fileHash": "0e4b87a7",
"fileHash": "047e19ce",
"needsInterop": false
}
},
"chunks": {
"chunk-HYZ2CRGS": {
"file": "chunk-HYZ2CRGS.js"
},
"chunk-HZ72IWKL": {
"file": "chunk-HZ72IWKL.js"
},
"chunk-HYZ2CRGS": {
"file": "chunk-HYZ2CRGS.js"
},
"chunk-QZC7O2C6": {
"file": "chunk-QZC7O2C6.js"
},

@ -1,6 +1,3 @@
import {
isVue2
} from "./chunk-HYZ2CRGS.js";
import {
arrow_down_default,
arrow_left_default,
@ -45,6 +42,9 @@ import {
zoom_in_default,
zoom_out_default
} from "./chunk-HZ72IWKL.js";
import {
isVue2
} from "./chunk-HYZ2CRGS.js";
import {
require_dayjs_min
} from "./chunk-QZC7O2C6.js";

@ -1,593 +0,0 @@
import {
add_location_default,
aim_default,
alarm_clock_default,
apple_default,
arrow_down_bold_default,
arrow_down_default,
arrow_left_bold_default,
arrow_left_default,
arrow_right_bold_default,
arrow_right_default,
arrow_up_bold_default,
arrow_up_default,
avatar_default,
back_default,
baseball_default,
basketball_default,
bell_default,
bell_filled_default,
bicycle_default,
bottom_default,
bottom_left_default,
bottom_right_default,
bowl_default,
box_default,
briefcase_default,
brush_default,
brush_filled_default,
burger_default,
calendar_default,
camera_default,
camera_filled_default,
caret_bottom_default,
caret_left_default,
caret_right_default,
caret_top_default,
cellphone_default,
chat_dot_round_default,
chat_dot_square_default,
chat_line_round_default,
chat_line_square_default,
chat_round_default,
chat_square_default,
check_default,
checked_default,
cherry_default,
chicken_default,
chrome_filled_default,
circle_check_default,
circle_check_filled_default,
circle_close_default,
circle_close_filled_default,
circle_plus_default,
circle_plus_filled_default,
clock_default,
close_bold_default,
close_default,
cloudy_default,
coffee_cup_default,
coffee_default,
coin_default,
cold_drink_default,
collection_default,
collection_tag_default,
comment_default,
compass_default,
connection_default,
coordinate_default,
copy_document_default,
cpu_default,
credit_card_default,
crop_default,
d_arrow_left_default,
d_arrow_right_default,
d_caret_default,
data_analysis_default,
data_board_default,
data_line_default,
delete_default,
delete_filled_default,
delete_location_default,
dessert_default,
discount_default,
dish_default,
dish_dot_default,
document_add_default,
document_checked_default,
document_copy_default,
document_default,
document_delete_default,
document_remove_default,
download_default,
drizzling_default,
edit_default,
edit_pen_default,
eleme_default,
eleme_filled_default,
element_plus_default,
expand_default,
failed_default,
female_default,
files_default,
film_default,
filter_default,
finished_default,
first_aid_kit_default,
flag_default,
fold_default,
folder_add_default,
folder_checked_default,
folder_default,
folder_delete_default,
folder_opened_default,
folder_remove_default,
food_default,
football_default,
fork_spoon_default,
fries_default,
full_screen_default,
goblet_default,
goblet_full_default,
goblet_square_default,
goblet_square_full_default,
gold_medal_default,
goods_default,
goods_filled_default,
grape_default,
grid_default,
guide_default,
handbag_default,
headset_default,
help_default,
help_filled_default,
hide_default,
histogram_default,
home_filled_default,
hot_water_default,
house_default,
ice_cream_default,
ice_cream_round_default,
ice_cream_square_default,
ice_drink_default,
ice_tea_default,
info_filled_default,
iphone_default,
key_default,
knife_fork_default,
lightning_default,
link_default,
list_default,
loading_default,
location_default,
location_filled_default,
location_information_default,
lock_default,
lollipop_default,
magic_stick_default,
magnet_default,
male_default,
management_default,
map_location_default,
medal_default,
memo_default,
menu_default,
message_box_default,
message_default,
mic_default,
microphone_default,
milk_tea_default,
minus_default,
money_default,
monitor_default,
moon_default,
moon_night_default,
more_default,
more_filled_default,
mostly_cloudy_default,
mouse_default,
mug_default,
mute_default,
mute_notification_default,
no_smoking_default,
notebook_default,
notification_default,
odometer_default,
office_building_default,
open_default,
operation_default,
opportunity_default,
orange_default,
paperclip_default,
partly_cloudy_default,
pear_default,
phone_default,
phone_filled_default,
picture_default,
picture_filled_default,
picture_rounded_default,
pie_chart_default,
place_default,
platform_default,
plus_default,
pointer_default,
position_default,
postcard_default,
pouring_default,
present_default,
price_tag_default,
printer_default,
promotion_default,
quartz_watch_default,
question_filled_default,
rank_default,
reading_default,
reading_lamp_default,
refresh_default,
refresh_left_default,
refresh_right_default,
refrigerator_default,
remove_default,
remove_filled_default,
right_default,
scale_to_original_default,
school_default,
scissor_default,
search_default,
select_default,
sell_default,
semi_select_default,
service_default,
set_up_default,
setting_default,
share_default,
ship_default,
shop_default,
shopping_bag_default,
shopping_cart_default,
shopping_cart_full_default,
shopping_trolley_default,
smoking_default,
soccer_default,
sold_out_default,
sort_default,
sort_down_default,
sort_up_default,
stamp_default,
star_default,
star_filled_default,
stopwatch_default,
success_filled_default,
sugar_default,
suitcase_default,
suitcase_line_default,
sunny_default,
sunrise_default,
sunset_default,
switch_button_default,
switch_default,
switch_filled_default,
takeaway_box_default,
ticket_default,
tickets_default,
timer_default,
toilet_paper_default,
tools_default,
top_default,
top_left_default,
top_right_default,
trend_charts_default,
trophy_base_default,
trophy_default,
turn_off_default,
umbrella_default,
unlock_default,
upload_default,
upload_filled_default,
user_default,
user_filled_default,
van_default,
video_camera_default,
video_camera_filled_default,
video_pause_default,
video_play_default,
view_default,
wallet_default,
wallet_filled_default,
warn_triangle_filled_default,
warning_default,
warning_filled_default,
watch_default,
watermelon_default,
wind_power_default,
zoom_in_default,
zoom_out_default
} from "./chunk-HZ72IWKL.js";
import "./chunk-QJ6DQKGF.js";
import "./chunk-G3PMV62Z.js";
export {
add_location_default as AddLocation,
aim_default as Aim,
alarm_clock_default as AlarmClock,
apple_default as Apple,
arrow_down_default as ArrowDown,
arrow_down_bold_default as ArrowDownBold,
arrow_left_default as ArrowLeft,
arrow_left_bold_default as ArrowLeftBold,
arrow_right_default as ArrowRight,
arrow_right_bold_default as ArrowRightBold,
arrow_up_default as ArrowUp,
arrow_up_bold_default as ArrowUpBold,
avatar_default as Avatar,
back_default as Back,
baseball_default as Baseball,
basketball_default as Basketball,
bell_default as Bell,
bell_filled_default as BellFilled,
bicycle_default as Bicycle,
bottom_default as Bottom,
bottom_left_default as BottomLeft,
bottom_right_default as BottomRight,
bowl_default as Bowl,
box_default as Box,
briefcase_default as Briefcase,
brush_default as Brush,
brush_filled_default as BrushFilled,
burger_default as Burger,
calendar_default as Calendar,
camera_default as Camera,
camera_filled_default as CameraFilled,
caret_bottom_default as CaretBottom,
caret_left_default as CaretLeft,
caret_right_default as CaretRight,
caret_top_default as CaretTop,
cellphone_default as Cellphone,
chat_dot_round_default as ChatDotRound,
chat_dot_square_default as ChatDotSquare,
chat_line_round_default as ChatLineRound,
chat_line_square_default as ChatLineSquare,
chat_round_default as ChatRound,
chat_square_default as ChatSquare,
check_default as Check,
checked_default as Checked,
cherry_default as Cherry,
chicken_default as Chicken,
chrome_filled_default as ChromeFilled,
circle_check_default as CircleCheck,
circle_check_filled_default as CircleCheckFilled,
circle_close_default as CircleClose,
circle_close_filled_default as CircleCloseFilled,
circle_plus_default as CirclePlus,
circle_plus_filled_default as CirclePlusFilled,
clock_default as Clock,
close_default as Close,
close_bold_default as CloseBold,
cloudy_default as Cloudy,
coffee_default as Coffee,
coffee_cup_default as CoffeeCup,
coin_default as Coin,
cold_drink_default as ColdDrink,
collection_default as Collection,
collection_tag_default as CollectionTag,
comment_default as Comment,
compass_default as Compass,
connection_default as Connection,
coordinate_default as Coordinate,
copy_document_default as CopyDocument,
cpu_default as Cpu,
credit_card_default as CreditCard,
crop_default as Crop,
d_arrow_left_default as DArrowLeft,
d_arrow_right_default as DArrowRight,
d_caret_default as DCaret,
data_analysis_default as DataAnalysis,
data_board_default as DataBoard,
data_line_default as DataLine,
delete_default as Delete,
delete_filled_default as DeleteFilled,
delete_location_default as DeleteLocation,
dessert_default as Dessert,
discount_default as Discount,
dish_default as Dish,
dish_dot_default as DishDot,
document_default as Document,
document_add_default as DocumentAdd,
document_checked_default as DocumentChecked,
document_copy_default as DocumentCopy,
document_delete_default as DocumentDelete,
document_remove_default as DocumentRemove,
download_default as Download,
drizzling_default as Drizzling,
edit_default as Edit,
edit_pen_default as EditPen,
eleme_default as Eleme,
eleme_filled_default as ElemeFilled,
element_plus_default as ElementPlus,
expand_default as Expand,
failed_default as Failed,
female_default as Female,
files_default as Files,
film_default as Film,
filter_default as Filter,
finished_default as Finished,
first_aid_kit_default as FirstAidKit,
flag_default as Flag,
fold_default as Fold,
folder_default as Folder,
folder_add_default as FolderAdd,
folder_checked_default as FolderChecked,
folder_delete_default as FolderDelete,
folder_opened_default as FolderOpened,
folder_remove_default as FolderRemove,
food_default as Food,
football_default as Football,
fork_spoon_default as ForkSpoon,
fries_default as Fries,
full_screen_default as FullScreen,
goblet_default as Goblet,
goblet_full_default as GobletFull,
goblet_square_default as GobletSquare,
goblet_square_full_default as GobletSquareFull,
gold_medal_default as GoldMedal,
goods_default as Goods,
goods_filled_default as GoodsFilled,
grape_default as Grape,
grid_default as Grid,
guide_default as Guide,
handbag_default as Handbag,
headset_default as Headset,
help_default as Help,
help_filled_default as HelpFilled,
hide_default as Hide,
histogram_default as Histogram,
home_filled_default as HomeFilled,
hot_water_default as HotWater,
house_default as House,
ice_cream_default as IceCream,
ice_cream_round_default as IceCreamRound,
ice_cream_square_default as IceCreamSquare,
ice_drink_default as IceDrink,
ice_tea_default as IceTea,
info_filled_default as InfoFilled,
iphone_default as Iphone,
key_default as Key,
knife_fork_default as KnifeFork,
lightning_default as Lightning,
link_default as Link,
list_default as List,
loading_default as Loading,
location_default as Location,
location_filled_default as LocationFilled,
location_information_default as LocationInformation,
lock_default as Lock,
lollipop_default as Lollipop,
magic_stick_default as MagicStick,
magnet_default as Magnet,
male_default as Male,
management_default as Management,
map_location_default as MapLocation,
medal_default as Medal,
memo_default as Memo,
menu_default as Menu,
message_default as Message,
message_box_default as MessageBox,
mic_default as Mic,
microphone_default as Microphone,
milk_tea_default as MilkTea,
minus_default as Minus,
money_default as Money,
monitor_default as Monitor,
moon_default as Moon,
moon_night_default as MoonNight,
more_default as More,
more_filled_default as MoreFilled,
mostly_cloudy_default as MostlyCloudy,
mouse_default as Mouse,
mug_default as Mug,
mute_default as Mute,
mute_notification_default as MuteNotification,
no_smoking_default as NoSmoking,
notebook_default as Notebook,
notification_default as Notification,
odometer_default as Odometer,
office_building_default as OfficeBuilding,
open_default as Open,
operation_default as Operation,
opportunity_default as Opportunity,
orange_default as Orange,
paperclip_default as Paperclip,
partly_cloudy_default as PartlyCloudy,
pear_default as Pear,
phone_default as Phone,
phone_filled_default as PhoneFilled,
picture_default as Picture,
picture_filled_default as PictureFilled,
picture_rounded_default as PictureRounded,
pie_chart_default as PieChart,
place_default as Place,
platform_default as Platform,
plus_default as Plus,
pointer_default as Pointer,
position_default as Position,
postcard_default as Postcard,
pouring_default as Pouring,
present_default as Present,
price_tag_default as PriceTag,
printer_default as Printer,
promotion_default as Promotion,
quartz_watch_default as QuartzWatch,
question_filled_default as QuestionFilled,
rank_default as Rank,
reading_default as Reading,
reading_lamp_default as ReadingLamp,
refresh_default as Refresh,
refresh_left_default as RefreshLeft,
refresh_right_default as RefreshRight,
refrigerator_default as Refrigerator,
remove_default as Remove,
remove_filled_default as RemoveFilled,
right_default as Right,
scale_to_original_default as ScaleToOriginal,
school_default as School,
scissor_default as Scissor,
search_default as Search,
select_default as Select,
sell_default as Sell,
semi_select_default as SemiSelect,
service_default as Service,
set_up_default as SetUp,
setting_default as Setting,
share_default as Share,
ship_default as Ship,
shop_default as Shop,
shopping_bag_default as ShoppingBag,
shopping_cart_default as ShoppingCart,
shopping_cart_full_default as ShoppingCartFull,
shopping_trolley_default as ShoppingTrolley,
smoking_default as Smoking,
soccer_default as Soccer,
sold_out_default as SoldOut,
sort_default as Sort,
sort_down_default as SortDown,
sort_up_default as SortUp,
stamp_default as Stamp,
star_default as Star,
star_filled_default as StarFilled,
stopwatch_default as Stopwatch,
success_filled_default as SuccessFilled,
sugar_default as Sugar,
suitcase_default as Suitcase,
suitcase_line_default as SuitcaseLine,
sunny_default as Sunny,
sunrise_default as Sunrise,
sunset_default as Sunset,
switch_default as Switch,
switch_button_default as SwitchButton,
switch_filled_default as SwitchFilled,
takeaway_box_default as TakeawayBox,
ticket_default as Ticket,
tickets_default as Tickets,
timer_default as Timer,
toilet_paper_default as ToiletPaper,
tools_default as Tools,
top_default as Top,
top_left_default as TopLeft,
top_right_default as TopRight,
trend_charts_default as TrendCharts,
trophy_default as Trophy,
trophy_base_default as TrophyBase,
turn_off_default as TurnOff,
umbrella_default as Umbrella,
unlock_default as Unlock,
upload_default as Upload,
upload_filled_default as UploadFilled,
user_default as User,
user_filled_default as UserFilled,
van_default as Van,
video_camera_default as VideoCamera,
video_camera_filled_default as VideoCameraFilled,
video_pause_default as VideoPause,
video_play_default as VideoPlay,
view_default as View,
wallet_default as Wallet,
wallet_filled_default as WalletFilled,
warn_triangle_filled_default as WarnTriangleFilled,
warning_default as Warning,
warning_filled_default as WarningFilled,
watch_default as Watch,
watermelon_default as Watermelon,
wind_power_default as WindPower,
zoom_in_default as ZoomIn,
zoom_out_default as ZoomOut
};
//# sourceMappingURL=@element-plus_icons-vue.js.map

@ -1,7 +0,0 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -1,36 +0,0 @@
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
export {
__commonJS,
__export,
__toESM
};
//# sourceMappingURL=chunk-G3PMV62Z.js.map

@ -1,7 +0,0 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

@ -1,25 +0,0 @@
// node_modules/vue-demi/lib/index.mjs
var isVue2 = false;
function set(target, key, val) {
if (Array.isArray(target)) {
target.length = Math.max(target.length, key);
target.splice(key, 1, val);
return val;
}
target[key] = val;
return val;
}
function del(target, key) {
if (Array.isArray(target)) {
target.splice(key, 1);
return;
}
delete target[key];
}
export {
isVue2,
set,
del
};
//# sourceMappingURL=chunk-HYZ2CRGS.js.map

@ -1,7 +0,0 @@
{
"version": 3,
"sources": ["../../vue-demi/lib/index.mjs"],
"sourcesContent": ["import * as Vue from 'vue'\n\nvar isVue2 = false\nvar isVue3 = true\nvar Vue2 = undefined\n\nfunction install() {}\n\nexport function set(target, key, val) {\n if (Array.isArray(target)) {\n target.length = Math.max(target.length, key)\n target.splice(key, 1, val)\n return val\n }\n target[key] = val\n return val\n}\n\nexport function del(target, key) {\n if (Array.isArray(target)) {\n target.splice(key, 1)\n return\n }\n delete target[key]\n}\n\nexport * from 'vue'\nexport {\n Vue,\n Vue2,\n isVue2,\n isVue3,\n install,\n}\n"],
"mappings": ";AAEA,IAAI,SAAS;AAMN,SAAS,IAAI,QAAQ,KAAK,KAAK;AACpC,MAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,WAAO,SAAS,KAAK,IAAI,OAAO,QAAQ,GAAG;AAC3C,WAAO,OAAO,KAAK,GAAG,GAAG;AACzB,WAAO;AAAA,EACT;AACA,SAAO,GAAG,IAAI;AACd,SAAO;AACT;AAEO,SAAS,IAAI,QAAQ,KAAK;AAC/B,MAAI,MAAM,QAAQ,MAAM,GAAG;AACzB,WAAO,OAAO,KAAK,CAAC;AACpB;AAAA,EACF;AACA,SAAO,OAAO,GAAG;AACnB;",
"names": []
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -1,288 +0,0 @@
import {
__commonJS
} from "./chunk-G3PMV62Z.js";
// node_modules/dayjs/dayjs.min.js
var require_dayjs_min = __commonJS({
"node_modules/dayjs/dayjs.min.js"(exports, module) {
!function(t, e) {
"object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs = e();
}(exports, function() {
"use strict";
var t = 1e3, e = 6e4, n = 36e5, r = "millisecond", i = "second", s = "minute", u = "hour", a = "day", o = "week", c = "month", f = "quarter", h = "year", d = "date", l = "Invalid Date", $ = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/, y = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, M = { name: "en", weekdays: "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), months: "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), ordinal: function(t2) {
var e2 = ["th", "st", "nd", "rd"], n2 = t2 % 100;
return "[" + t2 + (e2[(n2 - 20) % 10] || e2[n2] || e2[0]) + "]";
} }, m = function(t2, e2, n2) {
var r2 = String(t2);
return !r2 || r2.length >= e2 ? t2 : "" + Array(e2 + 1 - r2.length).join(n2) + t2;
}, v = { s: m, z: function(t2) {
var e2 = -t2.utcOffset(), n2 = Math.abs(e2), r2 = Math.floor(n2 / 60), i2 = n2 % 60;
return (e2 <= 0 ? "+" : "-") + m(r2, 2, "0") + ":" + m(i2, 2, "0");
}, m: function t2(e2, n2) {
if (e2.date() < n2.date()) return -t2(n2, e2);
var r2 = 12 * (n2.year() - e2.year()) + (n2.month() - e2.month()), i2 = e2.clone().add(r2, c), s2 = n2 - i2 < 0, u2 = e2.clone().add(r2 + (s2 ? -1 : 1), c);
return +(-(r2 + (n2 - i2) / (s2 ? i2 - u2 : u2 - i2)) || 0);
}, a: function(t2) {
return t2 < 0 ? Math.ceil(t2) || 0 : Math.floor(t2);
}, p: function(t2) {
return { M: c, y: h, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: f }[t2] || String(t2 || "").toLowerCase().replace(/s$/, "");
}, u: function(t2) {
return void 0 === t2;
} }, g = "en", D = {};
D[g] = M;
var p = "$isDayjsObject", S = function(t2) {
return t2 instanceof _ || !(!t2 || !t2[p]);
}, w = function t2(e2, n2, r2) {
var i2;
if (!e2) return g;
if ("string" == typeof e2) {
var s2 = e2.toLowerCase();
D[s2] && (i2 = s2), n2 && (D[s2] = n2, i2 = s2);
var u2 = e2.split("-");
if (!i2 && u2.length > 1) return t2(u2[0]);
} else {
var a2 = e2.name;
D[a2] = e2, i2 = a2;
}
return !r2 && i2 && (g = i2), i2 || !r2 && g;
}, O = function(t2, e2) {
if (S(t2)) return t2.clone();
var n2 = "object" == typeof e2 ? e2 : {};
return n2.date = t2, n2.args = arguments, new _(n2);
}, b = v;
b.l = w, b.i = S, b.w = function(t2, e2) {
return O(t2, { locale: e2.$L, utc: e2.$u, x: e2.$x, $offset: e2.$offset });
};
var _ = function() {
function M2(t2) {
this.$L = w(t2.locale, null, true), this.parse(t2), this.$x = this.$x || t2.x || {}, this[p] = true;
}
var m2 = M2.prototype;
return m2.parse = function(t2) {
this.$d = function(t3) {
var e2 = t3.date, n2 = t3.utc;
if (null === e2) return /* @__PURE__ */ new Date(NaN);
if (b.u(e2)) return /* @__PURE__ */ new Date();
if (e2 instanceof Date) return new Date(e2);
if ("string" == typeof e2 && !/Z$/i.test(e2)) {
var r2 = e2.match($);
if (r2) {
var i2 = r2[2] - 1 || 0, s2 = (r2[7] || "0").substring(0, 3);
return n2 ? new Date(Date.UTC(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2)) : new Date(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2);
}
}
return new Date(e2);
}(t2), this.init();
}, m2.init = function() {
var t2 = this.$d;
this.$y = t2.getFullYear(), this.$M = t2.getMonth(), this.$D = t2.getDate(), this.$W = t2.getDay(), this.$H = t2.getHours(), this.$m = t2.getMinutes(), this.$s = t2.getSeconds(), this.$ms = t2.getMilliseconds();
}, m2.$utils = function() {
return b;
}, m2.isValid = function() {
return !(this.$d.toString() === l);
}, m2.isSame = function(t2, e2) {
var n2 = O(t2);
return this.startOf(e2) <= n2 && n2 <= this.endOf(e2);
}, m2.isAfter = function(t2, e2) {
return O(t2) < this.startOf(e2);
}, m2.isBefore = function(t2, e2) {
return this.endOf(e2) < O(t2);
}, m2.$g = function(t2, e2, n2) {
return b.u(t2) ? this[e2] : this.set(n2, t2);
}, m2.unix = function() {
return Math.floor(this.valueOf() / 1e3);
}, m2.valueOf = function() {
return this.$d.getTime();
}, m2.startOf = function(t2, e2) {
var n2 = this, r2 = !!b.u(e2) || e2, f2 = b.p(t2), l2 = function(t3, e3) {
var i2 = b.w(n2.$u ? Date.UTC(n2.$y, e3, t3) : new Date(n2.$y, e3, t3), n2);
return r2 ? i2 : i2.endOf(a);
}, $2 = function(t3, e3) {
return b.w(n2.toDate()[t3].apply(n2.toDate("s"), (r2 ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e3)), n2);
}, y2 = this.$W, M3 = this.$M, m3 = this.$D, v2 = "set" + (this.$u ? "UTC" : "");
switch (f2) {
case h:
return r2 ? l2(1, 0) : l2(31, 11);
case c:
return r2 ? l2(1, M3) : l2(0, M3 + 1);
case o:
var g2 = this.$locale().weekStart || 0, D2 = (y2 < g2 ? y2 + 7 : y2) - g2;
return l2(r2 ? m3 - D2 : m3 + (6 - D2), M3);
case a:
case d:
return $2(v2 + "Hours", 0);
case u:
return $2(v2 + "Minutes", 1);
case s:
return $2(v2 + "Seconds", 2);
case i:
return $2(v2 + "Milliseconds", 3);
default:
return this.clone();
}
}, m2.endOf = function(t2) {
return this.startOf(t2, false);
}, m2.$set = function(t2, e2) {
var n2, o2 = b.p(t2), f2 = "set" + (this.$u ? "UTC" : ""), l2 = (n2 = {}, n2[a] = f2 + "Date", n2[d] = f2 + "Date", n2[c] = f2 + "Month", n2[h] = f2 + "FullYear", n2[u] = f2 + "Hours", n2[s] = f2 + "Minutes", n2[i] = f2 + "Seconds", n2[r] = f2 + "Milliseconds", n2)[o2], $2 = o2 === a ? this.$D + (e2 - this.$W) : e2;
if (o2 === c || o2 === h) {
var y2 = this.clone().set(d, 1);
y2.$d[l2]($2), y2.init(), this.$d = y2.set(d, Math.min(this.$D, y2.daysInMonth())).$d;
} else l2 && this.$d[l2]($2);
return this.init(), this;
}, m2.set = function(t2, e2) {
return this.clone().$set(t2, e2);
}, m2.get = function(t2) {
return this[b.p(t2)]();
}, m2.add = function(r2, f2) {
var d2, l2 = this;
r2 = Number(r2);
var $2 = b.p(f2), y2 = function(t2) {
var e2 = O(l2);
return b.w(e2.date(e2.date() + Math.round(t2 * r2)), l2);
};
if ($2 === c) return this.set(c, this.$M + r2);
if ($2 === h) return this.set(h, this.$y + r2);
if ($2 === a) return y2(1);
if ($2 === o) return y2(7);
var M3 = (d2 = {}, d2[s] = e, d2[u] = n, d2[i] = t, d2)[$2] || 1, m3 = this.$d.getTime() + r2 * M3;
return b.w(m3, this);
}, m2.subtract = function(t2, e2) {
return this.add(-1 * t2, e2);
}, m2.format = function(t2) {
var e2 = this, n2 = this.$locale();
if (!this.isValid()) return n2.invalidDate || l;
var r2 = t2 || "YYYY-MM-DDTHH:mm:ssZ", i2 = b.z(this), s2 = this.$H, u2 = this.$m, a2 = this.$M, o2 = n2.weekdays, c2 = n2.months, f2 = n2.meridiem, h2 = function(t3, n3, i3, s3) {
return t3 && (t3[n3] || t3(e2, r2)) || i3[n3].slice(0, s3);
}, d2 = function(t3) {
return b.s(s2 % 12 || 12, t3, "0");
}, $2 = f2 || function(t3, e3, n3) {
var r3 = t3 < 12 ? "AM" : "PM";
return n3 ? r3.toLowerCase() : r3;
};
return r2.replace(y, function(t3, r3) {
return r3 || function(t4) {
switch (t4) {
case "YY":
return String(e2.$y).slice(-2);
case "YYYY":
return b.s(e2.$y, 4, "0");
case "M":
return a2 + 1;
case "MM":
return b.s(a2 + 1, 2, "0");
case "MMM":
return h2(n2.monthsShort, a2, c2, 3);
case "MMMM":
return h2(c2, a2);
case "D":
return e2.$D;
case "DD":
return b.s(e2.$D, 2, "0");
case "d":
return String(e2.$W);
case "dd":
return h2(n2.weekdaysMin, e2.$W, o2, 2);
case "ddd":
return h2(n2.weekdaysShort, e2.$W, o2, 3);
case "dddd":
return o2[e2.$W];
case "H":
return String(s2);
case "HH":
return b.s(s2, 2, "0");
case "h":
return d2(1);
case "hh":
return d2(2);
case "a":
return $2(s2, u2, true);
case "A":
return $2(s2, u2, false);
case "m":
return String(u2);
case "mm":
return b.s(u2, 2, "0");
case "s":
return String(e2.$s);
case "ss":
return b.s(e2.$s, 2, "0");
case "SSS":
return b.s(e2.$ms, 3, "0");
case "Z":
return i2;
}
return null;
}(t3) || i2.replace(":", "");
});
}, m2.utcOffset = function() {
return 15 * -Math.round(this.$d.getTimezoneOffset() / 15);
}, m2.diff = function(r2, d2, l2) {
var $2, y2 = this, M3 = b.p(d2), m3 = O(r2), v2 = (m3.utcOffset() - this.utcOffset()) * e, g2 = this - m3, D2 = function() {
return b.m(y2, m3);
};
switch (M3) {
case h:
$2 = D2() / 12;
break;
case c:
$2 = D2();
break;
case f:
$2 = D2() / 3;
break;
case o:
$2 = (g2 - v2) / 6048e5;
break;
case a:
$2 = (g2 - v2) / 864e5;
break;
case u:
$2 = g2 / n;
break;
case s:
$2 = g2 / e;
break;
case i:
$2 = g2 / t;
break;
default:
$2 = g2;
}
return l2 ? $2 : b.a($2);
}, m2.daysInMonth = function() {
return this.endOf(c).$D;
}, m2.$locale = function() {
return D[this.$L];
}, m2.locale = function(t2, e2) {
if (!t2) return this.$L;
var n2 = this.clone(), r2 = w(t2, e2, true);
return r2 && (n2.$L = r2), n2;
}, m2.clone = function() {
return b.w(this.$d, this);
}, m2.toDate = function() {
return new Date(this.valueOf());
}, m2.toJSON = function() {
return this.isValid() ? this.toISOString() : null;
}, m2.toISOString = function() {
return this.$d.toISOString();
}, m2.toString = function() {
return this.$d.toUTCString();
}, M2;
}(), k = _.prototype;
return O.prototype = k, [["$ms", r], ["$s", i], ["$m", s], ["$H", u], ["$W", a], ["$M", c], ["$y", h], ["$D", d]].forEach(function(t2) {
k[t2[1]] = function(e2) {
return this.$g(e2, t2[0], t2[1]);
};
}), O.extend = function(t2, e2) {
return t2.$i || (t2(e2, _, O), t2.$i = true), O;
}, O.locale = w, O.isDayjs = S, O.unix = function(t2) {
return O(1e3 * t2);
}, O.en = D[g], O.Ls = D, O.p = {}, O;
});
}
});
export {
require_dayjs_min
};
//# sourceMappingURL=chunk-QZC7O2C6.js.map

File diff suppressed because one or more lines are too long

@ -1,162 +0,0 @@
// node_modules/@vue/devtools-api/lib/esm/env.js
function getDevtoolsGlobalHook() {
return getTarget().__VUE_DEVTOOLS_GLOBAL_HOOK__;
}
function getTarget() {
return typeof navigator !== "undefined" && typeof window !== "undefined" ? window : typeof globalThis !== "undefined" ? globalThis : {};
}
var isProxyAvailable = typeof Proxy === "function";
// node_modules/@vue/devtools-api/lib/esm/const.js
var HOOK_SETUP = "devtools-plugin:setup";
var HOOK_PLUGIN_SETTINGS_SET = "plugin:settings:set";
// node_modules/@vue/devtools-api/lib/esm/time.js
var supported;
var perf;
function isPerformanceSupported() {
var _a;
if (supported !== void 0) {
return supported;
}
if (typeof window !== "undefined" && window.performance) {
supported = true;
perf = window.performance;
} else if (typeof globalThis !== "undefined" && ((_a = globalThis.perf_hooks) === null || _a === void 0 ? void 0 : _a.performance)) {
supported = true;
perf = globalThis.perf_hooks.performance;
} else {
supported = false;
}
return supported;
}
function now() {
return isPerformanceSupported() ? perf.now() : Date.now();
}
// node_modules/@vue/devtools-api/lib/esm/proxy.js
var ApiProxy = class {
constructor(plugin, hook) {
this.target = null;
this.targetQueue = [];
this.onQueue = [];
this.plugin = plugin;
this.hook = hook;
const defaultSettings = {};
if (plugin.settings) {
for (const id in plugin.settings) {
const item = plugin.settings[id];
defaultSettings[id] = item.defaultValue;
}
}
const localSettingsSaveId = `__vue-devtools-plugin-settings__${plugin.id}`;
let currentSettings = Object.assign({}, defaultSettings);
try {
const raw = localStorage.getItem(localSettingsSaveId);
const data = JSON.parse(raw);
Object.assign(currentSettings, data);
} catch (e) {
}
this.fallbacks = {
getSettings() {
return currentSettings;
},
setSettings(value) {
try {
localStorage.setItem(localSettingsSaveId, JSON.stringify(value));
} catch (e) {
}
currentSettings = value;
},
now() {
return now();
}
};
if (hook) {
hook.on(HOOK_PLUGIN_SETTINGS_SET, (pluginId, value) => {
if (pluginId === this.plugin.id) {
this.fallbacks.setSettings(value);
}
});
}
this.proxiedOn = new Proxy({}, {
get: (_target, prop) => {
if (this.target) {
return this.target.on[prop];
} else {
return (...args) => {
this.onQueue.push({
method: prop,
args
});
};
}
}
});
this.proxiedTarget = new Proxy({}, {
get: (_target, prop) => {
if (this.target) {
return this.target[prop];
} else if (prop === "on") {
return this.proxiedOn;
} else if (Object.keys(this.fallbacks).includes(prop)) {
return (...args) => {
this.targetQueue.push({
method: prop,
args,
resolve: () => {
}
});
return this.fallbacks[prop](...args);
};
} else {
return (...args) => {
return new Promise((resolve) => {
this.targetQueue.push({
method: prop,
args,
resolve
});
});
};
}
}
});
}
async setRealTarget(target) {
this.target = target;
for (const item of this.onQueue) {
this.target.on[item.method](...item.args);
}
for (const item of this.targetQueue) {
item.resolve(await this.target[item.method](...item.args));
}
}
};
// node_modules/@vue/devtools-api/lib/esm/index.js
function setupDevtoolsPlugin(pluginDescriptor, setupFn) {
const descriptor = pluginDescriptor;
const target = getTarget();
const hook = getDevtoolsGlobalHook();
const enableProxy = isProxyAvailable && descriptor.enableEarlyProxy;
if (hook && (target.__VUE_DEVTOOLS_PLUGIN_API_AVAILABLE__ || !enableProxy)) {
hook.emit(HOOK_SETUP, pluginDescriptor, setupFn);
} else {
const proxy = enableProxy ? new ApiProxy(descriptor, hook) : null;
const list = target.__VUE_DEVTOOLS_PLUGINS__ = target.__VUE_DEVTOOLS_PLUGINS__ || [];
list.push({
pluginDescriptor: descriptor,
setupFn,
proxy
});
if (proxy) {
setupFn(proxy.proxiedTarget);
}
}
}
export {
setupDevtoolsPlugin
};
//# sourceMappingURL=chunk-YFT6OQ5R.js.map

File diff suppressed because one or more lines are too long

@ -1,6 +0,0 @@
import {
require_dayjs_min
} from "./chunk-QZC7O2C6.js";
import "./chunk-G3PMV62Z.js";
export default require_dayjs_min();
//# sourceMappingURL=dayjs.js.map

@ -1,7 +0,0 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -1,201 +0,0 @@
import "./chunk-G3PMV62Z.js";
// node_modules/element-plus/es/locale/lang/zh-cn.mjs
var zhCn = {
name: "zh-cn",
el: {
breadcrumb: {
label: "面包屑"
},
colorpicker: {
confirm: "确定",
clear: "清空",
defaultLabel: "颜色选择器",
description: "当前颜色 {color},按 Enter 键选择新颜色",
alphaLabel: "选择透明度的值",
alphaDescription: "透明度 {alpha}, 当前颜色 {color}",
hueLabel: "选择色相值",
hueDescription: "色相 {hue}, 当前颜色 {color}",
svLabel: "选择饱和度与明度的值",
svDescription: "饱和度 {saturation}, 明度 {brightness}, 当前颜色 {color}",
predefineDescription: "选择 {value} 作为颜色"
},
datepicker: {
now: "此刻",
today: "今天",
cancel: "取消",
clear: "清空",
confirm: "确定",
dateTablePrompt: "使用方向键与 Enter 键可选择日期",
monthTablePrompt: "使用方向键与 Enter 键可选择月份",
yearTablePrompt: "使用方向键与 Enter 键可选择年份",
selectedDate: "已选日期",
selectDate: "选择日期",
selectTime: "选择时间",
startDate: "开始日期",
startTime: "开始时间",
endDate: "结束日期",
endTime: "结束时间",
prevYear: "前一年",
nextYear: "后一年",
prevMonth: "上个月",
nextMonth: "下个月",
year: "年",
month1: "1 月",
month2: "2 月",
month3: "3 月",
month4: "4 月",
month5: "5 月",
month6: "6 月",
month7: "7 月",
month8: "8 月",
month9: "9 月",
month10: "10 月",
month11: "11 月",
month12: "12 月",
weeks: {
sun: "日",
mon: "一",
tue: "二",
wed: "三",
thu: "四",
fri: "五",
sat: "六"
},
weeksFull: {
sun: "星期日",
mon: "星期一",
tue: "星期二",
wed: "星期三",
thu: "星期四",
fri: "星期五",
sat: "星期六"
},
months: {
jan: "一月",
feb: "二月",
mar: "三月",
apr: "四月",
may: "五月",
jun: "六月",
jul: "七月",
aug: "八月",
sep: "九月",
oct: "十月",
nov: "十一月",
dec: "十二月"
}
},
inputNumber: {
decrease: "减少数值",
increase: "增加数值"
},
select: {
loading: "加载中",
noMatch: "无匹配数据",
noData: "无数据",
placeholder: "请选择"
},
mention: {
loading: "加载中"
},
dropdown: {
toggleDropdown: "切换下拉选项"
},
cascader: {
noMatch: "无匹配数据",
loading: "加载中",
placeholder: "请选择",
noData: "暂无数据"
},
pagination: {
goto: "前往",
pagesize: "条/页",
total: "共 {total} 条",
pageClassifier: "页",
page: "页",
prev: "上一页",
next: "下一页",
currentPage: "第 {pager} 页",
prevPages: "向前 {pager} 页",
nextPages: "向后 {pager} 页",
deprecationWarning: "你使用了一些已被废弃的用法,请参考 el-pagination 的官方文档"
},
dialog: {
close: "关闭此对话框"
},
drawer: {
close: "关闭此对话框"
},
messagebox: {
title: "提示",
confirm: "确定",
cancel: "取消",
error: "输入的数据不合法!",
close: "关闭此对话框"
},
upload: {
deleteTip: "按 Delete 键可删除",
delete: "删除",
preview: "查看图片",
continue: "继续上传"
},
slider: {
defaultLabel: "滑块介于 {min} 至 {max}",
defaultRangeStartLabel: "选择起始值",
defaultRangeEndLabel: "选择结束值"
},
table: {
emptyText: "暂无数据",
confirmFilter: "筛选",
resetFilter: "重置",
clearFilter: "全部",
sumText: "合计",
selectAllLabel: "选择所有行",
selectRowLabel: "选择当前行",
expandRowLabel: "展开当前行",
collapseRowLabel: "收起当前行",
sortLabel: "按 {column} 排序",
filterLabel: "按 {column} 过滤"
},
tag: {
close: "关闭此标签"
},
tour: {
next: "下一步",
previous: "上一步",
finish: "结束导览",
close: "关闭此对话框"
},
tree: {
emptyText: "暂无数据"
},
transfer: {
noMatch: "无匹配数据",
noData: "无数据",
titles: ["列表 1", "列表 2"],
filterPlaceholder: "请输入搜索内容",
noCheckedFormat: "共 {total} 项",
hasCheckedFormat: "已选 {checked}/{total} 项"
},
image: {
error: "加载失败"
},
pageHeader: {
title: "返回"
},
popconfirm: {
confirmButtonText: "确定",
cancelButtonText: "取消"
},
carousel: {
leftArrow: "上一张幻灯片",
rightArrow: "下一张幻灯片",
indicator: "幻灯片切换至索引 {index}"
}
}
};
export {
zhCn as default
};
//# sourceMappingURL=element-plus_es_locale_lang_zh-cn.js.map

File diff suppressed because one or more lines are too long

@ -1,3 +0,0 @@
{
"type": "module"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -1,348 +0,0 @@
import {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBaseVNode,
createBlock,
createCommentVNode,
createElementBlock,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
nodeOps,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
patchProp,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
} from "./chunk-QJ6DQKGF.js";
import "./chunk-G3PMV62Z.js";
export {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBlock,
createCommentVNode,
createElementBlock,
createBaseVNode as createElementVNode,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
nodeOps,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
patchProp,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
};
//# sourceMappingURL=vue.js.map

@ -1,7 +0,0 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

@ -34,18 +34,6 @@ export const dishAPI = {
getDishById(id) {
return request.get(`/dishes/${id}`)
},
// 创建菜品
createDish(data) {
return request.post('/dishes', data)
},
// 更新菜品
updateDish(id, data) {
return request.put(`/dishes/${id}`, data)
},
// 删除菜品
deleteDish(id) {
return request.delete(`/dishes/${id}`)
},
// 获取分类列表
getCategories() {
return request.get('/dishes/categories')
@ -66,18 +54,6 @@ export const menuAPI = {
getMenuById(id) {
return request.get(`/menus/${id}`)
},
// 创建菜单
createMenu(data) {
return request.post('/menus', data)
},
// 更新菜单
updateMenu(id, data) {
return request.put(`/menus/${id}`, data)
},
// 删除菜单
deleteMenu(id) {
return request.delete(`/menus/${id}`)
},
// 获取菜单中的菜品
getMenuDishes(id) {
return request.get(`/menus/${id}/dishes`)
@ -134,10 +110,6 @@ export const reservationAPI = {
updateReservation(id, data) {
return request.put(`/reservations/${id}`, data)
},
// 更新预订状态
updateReservationStatus(id, data) {
return request.put(`/reservations/${id}/status`, data)
},
// 取消预订
cancelReservation(id, data) {
return request.put(`/reservations/${id}/cancel`, data)
@ -154,18 +126,6 @@ export const reservationAPI = {
// 会员管理相关API
export const memberAPI = {
// 获取会员列表(管理员)
getMembers(params) {
return request.get('/members', { params })
},
// 获取会员详情
getMemberById(id) {
return request.get(`/members/${id}`)
},
// 获取会员优惠券
getMemberCoupons(id) {
return request.get(`/members/${id}/coupons`)
},
// 获取我的会员信息
getMyMemberInfo() {
return request.get('/members/my/info')
@ -211,7 +171,7 @@ export const paymentAPI = {
// 库存管理相关API管理员功能
export const inventoryAPI = {
// 获取库存列表
getInventoryItems(params) {
getInventory(params) {
return request.get('/inventory', { params })
},
// 获取库存详情
@ -219,21 +179,9 @@ export const inventoryAPI = {
return request.get(`/inventory/${id}`)
},
// 创建库存项
createInventoryItem(data) {
createInventory(data) {
return request.post('/inventory', data)
},
// 更新库存项
updateInventoryItem(id, data) {
return request.put(`/inventory/${id}`, data)
},
// 删除库存项
deleteInventoryItem(id) {
return request.delete(`/inventory/${id}`)
},
// 补货
restockInventory(id, data) {
return request.post(`/inventory/${id}/restock`, data)
},
// 调整库存
adjustInventory(id, data) {
return request.post(`/inventory/${id}/adjust`, data)
@ -254,8 +202,8 @@ export const analyticsAPI = {
getSalesTrend(params) {
return request.get('/analytics/sales-trend', { params })
},
// 获取热门菜品 (注意:后端是 top-dishes不是 popular-dishes)
getPopularDishes(params) {
// 获取热门菜品
getTopDishes(params) {
return request.get('/analytics/top-dishes', { params })
},
// 获取分类销售
@ -265,33 +213,5 @@ export const analyticsAPI = {
// 获取会员排行
getTopMembers(params) {
return request.get('/analytics/top-members', { params })
},
// 获取订单类型分布 (注意:后端是 order-types不是 order-type-distribution)
getOrderTypeDistribution() {
return request.get('/analytics/order-types')
},
// 获取时段分析
getHourlyAnalysis(params) {
return request.get('/analytics/hourly-analysis', { params })
},
// 获取支付方式统计
getPaymentMethodStats(params) {
return request.get('/analytics/payment-methods', { params })
},
// 获取月度对比
getMonthlyComparison(params) {
return request.get('/analytics/monthly-comparison', { params })
},
// 获取库存价值
getInventoryValue() {
return request.get('/analytics/inventory-value')
},
// 获取优惠券统计
getCouponStats(params) {
return request.get('/analytics/coupon-stats', { params })
},
// 获取预订转化率
getReservationConversion(params) {
return request.get('/analytics/reservation-conversion', { params })
}
}

@ -122,49 +122,19 @@ let memberLevelChart = null
//
const loadStats = async () => {
try {
const today = new Date().toISOString().split('T')[0]
const res = await analyticsAPI.getDashboard({ start_date: today, end_date: today })
// Update stats from dashboard data
stats.totalOrders = res.data.orders?.total_orders || 0
stats.totalRevenue = res.data.orders?.total_revenue || 0
stats.totalMembers = res.data.members?.total_members || 0
stats.totalReservations = res.data.reservations?.total_reservations || 0
const res = await analyticsAPI.getOverallStats()
Object.assign(stats, res.data)
} catch (error) {
ElMessage.error('加载统计数据失败')
console.error('加载统计失败:', error)
}
}
//
const loadRevenueTrend = async () => {
try {
const endDate = new Date()
const startDate = new Date()
startDate.setDate(startDate.getDate() - 7)
console.log('📊 请求营业额趋势:', {
start_date: startDate.toISOString().split('T')[0],
end_date: endDate.toISOString().split('T')[0]
})
const res = await analyticsAPI.getSalesTrend({
start_date: startDate.toISOString().split('T')[0],
end_date: endDate.toISOString().split('T')[0]
})
console.log('✅ 营业额趋势数据:', res)
// Check if res.data.data exists, otherwise use res.data directly
const dataArray = res.data.data || res.data || []
if (!dataArray.length) {
console.warn('⚠️ 营业额趋势数据为空')
ElMessage.warning('暂无营业额数据')
return
}
const dates = dataArray.map(item => item.date)
const revenues = dataArray.map(item => item.revenue || item.total_amount || 0)
const res = await analyticsAPI.getRevenueTrend({ days: 30 })
const dates = res.data.map(item => item.date)
const revenues = res.data.map(item => item.revenue)
revenueChart = echarts.init(document.getElementById('revenueChart'))
revenueChart.setOption({
@ -190,14 +160,7 @@ const loadRevenueTrend = async () => {
color: ['#67c23a']
})
} catch (error) {
console.error('❌ 加载营业额趋势失败:', error)
if (error.response?.status === 401) {
ElMessage.error('请先登录')
} else if (error.response?.status === 403) {
ElMessage.error('需要管理员或服务员权限才能查看数据统计')
} else {
ElMessage.error('加载营业额趋势失败')
}
ElMessage.error('加载营业额趋势失败')
}
}
@ -205,9 +168,7 @@ const loadRevenueTrend = async () => {
const loadOrderTypeDistribution = async () => {
try {
const res = await analyticsAPI.getOrderTypeDistribution()
// Ensure we have an array to work with
const dataArray = Array.isArray(res.data) ? res.data : (res.data.data || [])
const data = dataArray.map(item => ({
const data = res.data.map(item => ({
name: item.order_type === 'dine_in' ? '堂食' : item.order_type === 'takeaway' ? '外卖' : '预订',
value: item.count
}))
@ -237,7 +198,6 @@ const loadOrderTypeDistribution = async () => {
})
} catch (error) {
ElMessage.error('加载订单分布失败')
console.error('加载订单分布失败:', error)
}
}
@ -245,10 +205,8 @@ const loadOrderTypeDistribution = async () => {
const loadPopularDishes = async () => {
try {
const res = await analyticsAPI.getPopularDishes({ limit: 10 })
// Check if res.data is already the array or if it's nested in res.data.data or res.data.dishes
const dataArray = Array.isArray(res.data) ? res.data : (res.data.dishes || res.data.data || [])
const dishes = dataArray.map(item => item.dish_name)
const counts = dataArray.map(item => item.total_quantity || item.total_sold || 0)
const dishes = res.data.map(item => item.dish_name)
const counts = res.data.map(item => item.order_count)
popularDishesChart = echarts.init(document.getElementById('popularDishesChart'))
popularDishesChart.setOption({
@ -277,30 +235,23 @@ const loadPopularDishes = async () => {
})
} catch (error) {
ElMessage.error('加载热门菜品失败')
console.error('加载热门菜品失败:', error)
}
}
//
const loadMemberLevelDistribution = async () => {
try {
const today = new Date().toISOString().split('T')[0]
const res = await analyticsAPI.getDashboard({ start_date: today, end_date: today })
const members = res.data.members
const res = await analyticsAPI.getMemberLevelDistribution()
const levelNames = {
bronze_members: '普通会员',
silver_members: '银卡会员',
gold_members: '金卡会员',
diamond_members: '钻石会员'
bronze: '普通会员',
silver: '银卡会员',
gold: '金卡会员',
platinum: '钻石会员'
}
const data = [
{ name: levelNames.bronze_members, value: members?.bronze_members || 0 },
{ name: levelNames.silver_members, value: members?.silver_members || 0 },
{ name: levelNames.gold_members, value: members?.gold_members || 0 },
{ name: levelNames.diamond_members, value: members?.diamond_members || 0 }
]
const data = res.data.map(item => ({
name: levelNames[item.level] || item.level,
value: item.count
}))
memberLevelChart = echarts.init(document.getElementById('memberLevelChart'))
memberLevelChart.setOption({
@ -338,7 +289,6 @@ const loadMemberLevelDistribution = async () => {
})
} catch (error) {
ElMessage.error('加载会员分布失败')
console.error('加载会员分布失败:', error)
}
}

@ -23,7 +23,7 @@
</div>
<div class="stat-content">
<div class="stat-label">今日营业额</div>
<div class="stat-value">¥{{ parseFloat(dashboard.orders?.total_revenue || 0).toFixed(2) }}</div>
<div class="stat-value">¥{{ dashboard.orders?.total_revenue?.toFixed(2) || 0 }}</div>
</div>
</el-card>
</el-col>

@ -45,9 +45,9 @@
<el-table-column label="图片" width="100">
<template #default="{ row }">
<el-image
v-if="row.image"
:src="row.image"
:preview-src-list="[row.image]"
v-if="row.image_url"
:src="row.image_url"
:preview-src-list="[row.image_url]"
fit="cover"
style="width: 60px; height: 60px; border-radius: 4px;"
/>
@ -136,13 +136,13 @@
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio value="available">在售</el-radio>
<el-radio value="sold_out">售罄</el-radio>
<el-radio value="discontinued">下架</el-radio>
<el-radio label="available">在售</el-radio>
<el-radio label="sold_out">售罄</el-radio>
<el-radio label="discontinued">下架</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="图片">
<el-input v-model="form.image" placeholder="请输入图片URL" />
<el-input v-model="form.image_url" placeholder="请输入图片URL" />
<div class="form-tip">提示暂时支持输入图片URL后续可添加上传功能</div>
</el-form-item>
</el-form>
@ -197,7 +197,7 @@ const form = reactive({
ingredients: '',
description: '',
status: 'available',
image: ''
image_url: ''
})
//
@ -212,23 +212,16 @@ const rules = {
const loadDishes = async () => {
loading.value = true
try {
//
const params = {
page: pagination.page,
pageSize: pagination.limit
limit: pagination.limit,
...searchForm
}
//
if (searchForm.name) params.name = searchForm.name
if (searchForm.category_id) params.category_id = searchForm.category_id
if (searchForm.status) params.status = searchForm.status
const res = await dishAPI.getDishes(params)
dishes.value = res.data.list || []
pagination.total = res.data.pagination?.total || 0
dishes.value = res.data.dishes
pagination.total = res.data.total
} catch (error) {
ElMessage.error('加载菜品列表失败')
console.error('加载菜品失败:', error)
} finally {
loading.value = false
}
@ -262,10 +255,7 @@ const handleAdd = () => {
//
const handleEdit = (row) => {
dialogTitle.value = '编辑菜品'
Object.assign(form, {
...row,
price: parseFloat(row.price)
})
Object.assign(form, row)
dialogVisible.value = true
}
@ -324,7 +314,7 @@ const resetForm = () => {
form.ingredients = ''
form.description = ''
form.status = 'available'
form.image = ''
form.image_url = ''
formRef.value?.clearValidate()
}

@ -229,15 +229,14 @@ const loadInventory = async () => {
try {
const params = {
page: pagination.page,
pageSize: pagination.limit,
limit: pagination.limit,
...searchForm
}
const res = await inventoryAPI.getInventoryItems(params)
inventory.value = res.data.list || []
pagination.total = res.data.pagination?.total || 0
inventory.value = res.data.items
pagination.total = res.data.total
} catch (error) {
ElMessage.error('加载库存列表失败')
console.error('加载库存失败:', error)
} finally {
loading.value = false
}
@ -260,12 +259,7 @@ const handleAdd = () => {
//
const handleEdit = (row) => {
dialogTitle.value = '编辑库存'
Object.assign(form, {
...row,
unit_price: parseFloat(row.unit_price),
reorder_threshold: parseFloat(row.reorder_threshold),
max_stock: parseFloat(row.max_stock)
})
Object.assign(form, row)
dialogVisible.value = true
}

@ -188,23 +188,16 @@ const couponsLoading = ref(false)
const loadMembers = async () => {
loading.value = true
try {
//
const params = {
page: pagination.page,
pageSize: pagination.limit
limit: pagination.limit,
...searchForm
}
//
if (searchForm.name) params.name = searchForm.name
if (searchForm.phone) params.phone = searchForm.phone
if (searchForm.level) params.level = searchForm.level
const res = await memberAPI.getMembers(params)
members.value = res.data.list || []
pagination.total = res.data.pagination?.total || 0
members.value = res.data.members
pagination.total = res.data.total
} catch (error) {
ElMessage.error('加载会员列表失败')
console.error('加载会员失败:', error)
} finally {
loading.value = false
}
@ -222,12 +215,11 @@ const resetSearch = () => {
//
const handleViewDetail = async (row) => {
try {
const res = await memberAPI.getMemberById(row.user_id)
const res = await memberAPI.getMemberById(row.id)
currentMember.value = res.data
detailVisible.value = true
} catch (error) {
ElMessage.error('加载会员详情失败')
console.error('加载会员详情失败:', error)
}
}
@ -238,11 +230,10 @@ const handleViewCoupons = async (row) => {
couponsLoading.value = true
try {
const res = await memberAPI.getMemberCoupons(row.user_id)
const res = await memberAPI.getMemberCoupons(row.id)
memberCoupons.value = res.data
} catch (error) {
ElMessage.error('加载优惠券失败')
console.error('加载优惠券失败:', error)
} finally {
couponsLoading.value = false
}

@ -50,9 +50,11 @@
</el-form-item>
<el-form-item label="菜单类型" prop="menu_type">
<el-select v-model="form.menu_type" placeholder="请选择类型" style="width: 100%;">
<el-option label="早餐" value="breakfast" />
<el-option label="午餐" value="lunch" />
<el-option label="晚餐" value="dinner" />
<el-option label="节日套餐" value="holiday" />
<el-option label="特殊套餐" value="special" />
</el-select>
</el-form-item>
<el-form-item label="描述">
@ -176,11 +178,9 @@ const loadMenus = async () => {
loading.value = true
try {
const res = await menuAPI.getMenus()
// Backend returns array directly in res.data
menus.value = Array.isArray(res.data) ? res.data : (res.data.list || [])
menus.value = res.data
} catch (error) {
ElMessage.error('加载菜单列表失败')
console.error('加载菜单失败:', error)
} finally {
loading.value = false
}
@ -189,15 +189,14 @@ const loadMenus = async () => {
//
const loadAllDishes = async () => {
try {
const res = await dishAPI.getDishes({ pageSize: 1000 })
allDishes.value = (res.data.list || []).map(dish => ({
const res = await dishAPI.getDishes({ limit: 1000 })
allDishes.value = res.data.dishes.map(dish => ({
key: dish.id,
label: `${dish.name} - ¥${dish.price}`,
disabled: dish.status !== 'available'
}))
} catch (error) {
ElMessage.error('加载菜品列表失败')
console.error('加载菜品失败:', error)
}
}
@ -307,11 +306,9 @@ const handleViewDishes = async (row) => {
try {
const res = await menuAPI.getMenuDishes(row.id)
// Ensure we always set an array, handle different response structures
menuDishes.value = Array.isArray(res.data) ? res.data : (res.data.dishes || res.data.list || [])
menuDishes.value = res.data
} catch (error) {
ElMessage.error('加载菜品失败')
console.error('加载菜品失败:', error)
} finally {
dishesLoading.value = false
}
@ -320,9 +317,11 @@ const handleViewDishes = async (row) => {
//
const getMenuTypeText = (type) => {
const texts = {
breakfast: '早餐',
lunch: '午餐',
dinner: '晚餐',
holiday: '节日套餐'
holiday: '节日套餐',
special: '特殊套餐'
}
return texts[type] || type
}
@ -330,11 +329,13 @@ const getMenuTypeText = (type) => {
//
const getMenuTypeColor = (type) => {
const colors = {
breakfast: '',
lunch: 'success',
dinner: 'warning',
holiday: 'danger'
holiday: 'danger',
special: 'primary'
}
return colors[type] || 'info'
return colors[type] || ''
}
//

@ -26,10 +26,8 @@
<el-form-item label="状态">
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
<el-option label="待确认" value="pending" />
<el-option label="已确认" value="confirmed" />
<el-option label="制作中" value="cooking" />
<el-option label="已上菜" value="served" />
<el-option label="已支付" value="paid" />
<el-option label="进行中" value="in_progress" />
<el-option label="已完成" value="completed" />
<el-option label="已取消" value="cancelled" />
</el-select>
</el-form-item>
@ -73,7 +71,7 @@
type="primary"
size="small"
@click="handleUpdateStatus(row)"
v-if="(userStore.isAdmin || userStore.isWaiter) && row.status !== 'paid' && row.status !== 'cancelled'"
v-if="(userStore.isAdmin || userStore.isWaiter) && row.status !== 'completed' && row.status !== 'cancelled'"
>
更新状态
</el-button>
@ -154,10 +152,8 @@
<el-form-item label="新状态">
<el-select v-model="statusForm.status" placeholder="请选择新状态" style="width: 100%;">
<el-option label="待确认" value="pending" />
<el-option label="已确认" value="confirmed" />
<el-option label="制作中" value="cooking" />
<el-option label="已上菜" value="served" />
<el-option label="已支付" value="paid" />
<el-option label="进行中" value="in_progress" />
<el-option label="已完成" value="completed" />
</el-select>
</el-form-item>
</el-form>
@ -172,9 +168,9 @@
<el-form :model="createForm" :rules="createRules" ref="createFormRef" label-width="100px">
<el-form-item label="订单类型" prop="order_type">
<el-radio-group v-model="createForm.order_type">
<el-radio value="dine_in">堂食</el-radio>
<el-radio value="takeaway">外卖</el-radio>
<el-radio value="reservation">预订</el-radio>
<el-radio label="dine_in">堂食</el-radio>
<el-radio label="takeaway">外卖</el-radio>
<el-radio label="reservation">预订</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="桌号" prop="table_number" v-if="createForm.order_type === 'dine_in'">
@ -312,8 +308,7 @@ const createRules = {
//
const orderTotal = computed(() => {
const total = createForm.items.reduce((sum, item) => sum + (parseFloat(item.subtotal) || 0), 0)
return Number(total).toFixed(2)
return createForm.items.reduce((sum, item) => sum + (item.subtotal || 0), 0).toFixed(2)
})
//
@ -322,15 +317,14 @@ const loadOrders = async () => {
try {
const params = {
page: pagination.page,
pageSize: pagination.limit,
limit: pagination.limit,
...searchForm
}
const res = await orderAPI.getOrders(params)
orders.value = res.data.list || []
pagination.total = res.data.pagination?.total || 0
orders.value = res.data.orders
pagination.total = res.data.total
} catch (error) {
ElMessage.error('加载订单列表失败')
console.error('加载订单失败:', error)
} finally {
loading.value = false
}
@ -339,11 +333,10 @@ const loadOrders = async () => {
//
const loadAvailableDishes = async () => {
try {
const res = await dishAPI.getDishes({ status: 'available', pageSize: 1000 })
availableDishes.value = res.data.list || []
const res = await dishAPI.getDishes({ status: 'available', limit: 1000 })
availableDishes.value = res.data.dishes
} catch (error) {
ElMessage.error('加载菜品列表失败')
console.error('加载菜品失败:', error)
}
}
@ -512,21 +505,19 @@ const getOrderTypeText = (type) => {
//
const getOrderTypeColor = (type) => {
const colors = {
dine_in: 'primary',
dine_in: '',
takeaway: 'success',
reservation: 'warning'
}
return colors[type] || 'info'
return colors[type] || ''
}
//
const getStatusType = (status) => {
const types = {
pending: 'warning',
confirmed: 'primary',
cooking: 'warning',
served: 'success',
paid: 'success',
in_progress: 'primary',
completed: 'success',
cancelled: 'info'
}
return types[status] || 'info'
@ -536,10 +527,8 @@ const getStatusType = (status) => {
const getStatusText = (status) => {
const texts = {
pending: '待确认',
confirmed: '已确认',
cooking: '制作中',
served: '已上菜',
paid: '已支付',
in_progress: '进行中',
completed: '已完成',
cancelled: '已取消'
}
return texts[status] || status

@ -72,23 +72,6 @@ const validateConfirmPassword = (rule, value, callback) => {
}
}
const validatePasswordRule = (rule, value, callback) => {
if (!value || value.length < 6) {
callback(new Error('密码长度至少6位'))
return
}
const hasLetter = /[a-zA-Z]/.test(value)
const hasNumber = /\d/.test(value)
if (!hasLetter || !hasNumber) {
callback(new Error('密码必须包含字母和数字'))
return
}
callback()
}
const registerRules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
@ -96,7 +79,7 @@ const registerRules = {
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ validator: validatePasswordRule, trigger: 'blur' }
{ min: 6, message: '密码长度至少6位', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请再次输入密码', trigger: 'blur' },

@ -26,6 +26,7 @@
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable>
<el-option label="待确认" value="pending" />
<el-option label="已确认" value="confirmed" />
<el-option label="已到达" value="arrived" />
<el-option label="已完成" value="completed" />
<el-option label="已取消" value="cancelled" />
</el-select>
@ -64,7 +65,10 @@
<el-button type="success" size="small" @click="handleConfirm(row)" v-if="row.status === 'pending'">
确认
</el-button>
<el-button type="primary" size="small" @click="handleComplete(row)" v-if="row.status === 'confirmed'">
<el-button type="primary" size="small" @click="handleArrive(row)" v-if="row.status === 'confirmed'">
到达
</el-button>
<el-button type="info" size="small" @click="handleComplete(row)" v-if="row.status === 'arrived'">
完成
</el-button>
<el-button type="danger" size="small" @click="handleCancel(row)" v-if="row.status === 'pending' || row.status === 'confirmed'">
@ -92,10 +96,13 @@
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
<el-form-item label="预订类型" prop="reservation_type">
<el-radio-group v-model="form.reservation_type">
<el-radio value="table">桌位预订</el-radio>
<el-radio value="takeaway">外卖预订</el-radio>
<el-radio label="table">桌位预订</el-radio>
<el-radio label="takeaway">外卖预订</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="客户姓名" prop="customer_name">
<el-input v-model="form.customer_name" placeholder="请输入客户姓名" />
</el-form-item>
<el-form-item label="联系电话" prop="phone">
<el-input v-model="form.phone" placeholder="请输入联系电话" />
</el-form-item>
@ -109,7 +116,6 @@
placeholder="选择预订日期"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
:disabled-date="(date) => date < new Date(new Date().setHours(0, 0, 0, 0))"
style="width: 100%;"
/>
</el-form-item>
@ -172,6 +178,7 @@ const submitting = ref(false)
const form = reactive({
reservation_type: 'table',
customer_name: '',
phone: '',
party_size: 2,
reservation_date: '',
@ -181,10 +188,8 @@ const form = reactive({
const rules = {
reservation_type: [{ required: true, message: '请选择预订类型', trigger: 'change' }],
phone: [
{ required: true, message: '请输入联系电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码格式11位1开头', trigger: 'blur' }
],
customer_name: [{ required: true, message: '请输入客户姓名', trigger: 'blur' }],
phone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
party_size: [{ required: true, message: '请输入预订人数', trigger: 'blur' }],
reservation_date: [{ required: true, message: '请选择预订日期', trigger: 'change' }],
reservation_time: [{ required: true, message: '请选择预订时间', trigger: 'change' }]
@ -196,15 +201,14 @@ const loadReservations = async () => {
try {
const params = {
page: pagination.page,
pageSize: pagination.limit,
limit: pagination.limit,
...searchForm
}
const res = await reservationAPI.getReservations(params)
reservations.value = res.data.list || []
pagination.total = res.data.pagination?.total || 0
reservations.value = res.data.reservations
pagination.total = res.data.total
} catch (error) {
ElMessage.error('加载预订列表失败')
console.error('加载预订失败:', error)
} finally {
loading.value = false
}
@ -248,6 +252,7 @@ const submitCreate = async () => {
//
const resetForm = () => {
form.reservation_type = 'table'
form.customer_name = ''
form.phone = ''
form.party_size = 2
form.reservation_date = ''
@ -267,6 +272,17 @@ const handleConfirm = async (row) => {
}
}
//
const handleArrive = async (row) => {
try {
await reservationAPI.updateReservationStatus(row.id, { status: 'arrived' })
ElMessage.success('客户已到达')
loadReservations()
} catch (error) {
ElMessage.error('更新失败')
}
}
//
const handleComplete = async (row) => {
try {
@ -302,7 +318,8 @@ const getStatusType = (status) => {
const types = {
pending: 'warning',
confirmed: 'primary',
completed: 'success',
arrived: 'success',
completed: 'info',
cancelled: 'danger'
}
return types[status] || 'info'
@ -313,6 +330,7 @@ const getStatusText = (status) => {
const texts = {
pending: '待确认',
confirmed: '已确认',
arrived: '已到达',
completed: '已完成',
cancelled: '已取消'
}

Loading…
Cancel
Save