From 35db747d4f7c17fb68f1d6f9c329886c907fa603 Mon Sep 17 00:00:00 2001 From: xuqiuyun <1113560936@qq.com> Date: Fri, 19 Sep 2025 18:13:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=85=BB=E6=AE=96=E7=AB=AF?= =?UTF-8?q?=E5=B0=8F=E7=A8=8B=E5=BA=8F=EF=BC=8C=E4=BF=9D=E9=99=A9=E5=89=8D?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=E5=92=8C=E5=B0=8F=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/routes/smart-devices.js | 69 + insurance_admin-system/package-lock.json | 1 + insurance_admin-system/package.json | 17 +- .../src/components/Layout.vue | 236 ++- insurance_admin-system/src/main.js | 17 + insurance_admin-system/src/router/index.js | 7 + insurance_admin-system/src/utils/api.js | 14 + .../src/views/DataWarehouse.vue | 540 ++++++ insurance_backend/config/swagger.js | 63 + .../controllers/dataWarehouseController.js | 210 +++ .../controllers/menuController.js | 161 ++ insurance_backend/create_admin_user.js | 183 ++ insurance_backend/docs/dynamic_menu_guide.md | 135 ++ insurance_backend/models/Menu.js | 85 + insurance_backend/models/index.js | 9 +- insurance_backend/routes/dataWarehouse.js | 163 ++ insurance_backend/routes/menus.js | 119 ++ insurance_backend/scripts/init_menus.sql | 59 + insurance_backend/scripts/initialize_menus.js | 175 ++ .../scripts/seed_data_warehouse.js | 225 +++ insurance_backend/scripts/seed_menus.js | 62 + insurance_backend/src/app.js | 4 +- insurance_backend/update_admin_password.js | 42 + insurance_mini_program/pages/index/index.js | 138 +- insurance_mini_program/pages/index/index.json | 2 +- insurance_mini_program/pages/index/index.wxml | 123 +- insurance_mini_program/pages/index/index.wxss | 185 +- insurance_mini_program/pages/login/login.js | 36 +- insurance_mini_program/pages/login/login.wxml | 31 +- insurance_mini_program/pages/login/login.wxss | 230 ++- insurance_mini_program/pages/my/my.js | 114 +- insurance_mini_program/pages/my/my.wxml | 188 +- insurance_mini_program/pages/my/my.wxss | 248 ++- .../pages/products/products.js | 14 + .../pages/products/products.wxml | 6 +- insurance_mini_program/project.config.json | 14 +- .../project.private.config.json | 10 +- .../API_INTEGRATION_UPDATE.md | 161 ++ .../farm-monitor-dashboard/API_PORT_UPDATE.md | 154 ++ .../farm-monitor-dashboard/API_SETUP.md | 65 + .../farm-monitor-dashboard/AUTH_SOLUTION.md | 161 ++ .../AUTH_SUCCESS_REPORT.md | 177 ++ .../CATTLE_PROFILE_README.md | 195 +++ .../CATTLE_TRANSFER_ENHANCED.md | 147 ++ .../CATTLE_TRANSFER_FEATURE.md | 137 ++ .../CHINESE_MAPPING_GUIDE.md | 195 +++ .../farm-monitor-dashboard/DEBUG_API_ISSUE.md | 87 + .../ELECTRONIC_FENCE_README.md | 231 +++ .../HOST_NUMBER_FIX_REPORT.md | 141 ++ .../IMPLEMENTATION_SUMMARY.md | 117 ++ .../SMART_EARTAG_ALERT_README.md | 141 ++ .../farm-monitor-dashboard/auth-test.js | 83 + .../farm-monitor-dashboard/auto-login.js | 85 + .../farm-monitor-dashboard/check-backend.js | 47 + .../farm-monitor-dashboard/mock-api-server.js | 95 ++ .../farm-monitor-dashboard/package-lock.json | 1513 +++++++++++------ .../farm-monitor-dashboard/package.json | 6 +- .../farm-monitor-dashboard/set-token.js | 63 + .../src/components/AlertTest.vue | 210 +++ .../src/components/ApiTest.vue | 212 +++ .../src/components/ApiTestPage.vue | 252 +++ .../src/components/CattleAdd.vue | 482 ++++++ .../src/components/CattleProfile.vue | 549 ++++++ .../src/components/CattleTest.vue | 125 ++ .../src/components/CattleTransfer.vue | 818 +++++++++ .../src/components/CattleTransferRegister.vue | 540 ++++++ .../src/components/ElectronicFence.vue | 1361 +++++++++++++++ .../src/components/Home.vue | 74 +- .../src/components/MapTest.vue | 237 +++ .../src/components/MapView.vue | 589 +++++++ .../src/components/Production.vue | 35 +- .../src/components/SmartCollar.vue | 70 +- .../src/components/SmartEartagAlert.vue | 984 +++++++++++ .../src/components/SmartHost.vue | 678 +++++++- .../src/components/WechatFenceDrawer.vue | 1020 +++++++++++ .../src/router/index.js | 72 + .../src/services/alertService.js | 127 ++ .../src/services/api.js | 188 +- .../src/services/collarService.js | 2 +- .../src/services/fenceService.js | 198 +++ .../src/services/hostService.js | 182 +- .../farm-monitor-dashboard/src/utils/index.js | 10 +- .../src/utils/mapping.js | 265 +++ .../src/views/ElectronicFencePage.vue | 23 + .../src/views/SmartEartagAlertPage.vue | 133 ++ .../farm-monitor-dashboard/test-api.html | 108 ++ .../farm-monitor-dashboard/test-api.js | 159 ++ .../test-host-number-fix.js | 86 + .../farm-monitor-dashboard/vite.config.js | 6 +- 89 files changed, 16231 insertions(+), 1500 deletions(-) create mode 100644 insurance_admin-system/src/views/DataWarehouse.vue create mode 100644 insurance_backend/controllers/dataWarehouseController.js create mode 100644 insurance_backend/controllers/menuController.js create mode 100644 insurance_backend/create_admin_user.js create mode 100644 insurance_backend/docs/dynamic_menu_guide.md create mode 100644 insurance_backend/models/Menu.js create mode 100644 insurance_backend/routes/dataWarehouse.js create mode 100644 insurance_backend/routes/menus.js create mode 100644 insurance_backend/scripts/init_menus.sql create mode 100644 insurance_backend/scripts/initialize_menus.js create mode 100644 insurance_backend/scripts/seed_data_warehouse.js create mode 100644 insurance_backend/scripts/seed_menus.js create mode 100644 insurance_backend/update_admin_password.js create mode 100644 mini_program/farm-monitor-dashboard/API_INTEGRATION_UPDATE.md create mode 100644 mini_program/farm-monitor-dashboard/API_PORT_UPDATE.md create mode 100644 mini_program/farm-monitor-dashboard/API_SETUP.md create mode 100644 mini_program/farm-monitor-dashboard/AUTH_SOLUTION.md create mode 100644 mini_program/farm-monitor-dashboard/AUTH_SUCCESS_REPORT.md create mode 100644 mini_program/farm-monitor-dashboard/CATTLE_PROFILE_README.md create mode 100644 mini_program/farm-monitor-dashboard/CATTLE_TRANSFER_ENHANCED.md create mode 100644 mini_program/farm-monitor-dashboard/CATTLE_TRANSFER_FEATURE.md create mode 100644 mini_program/farm-monitor-dashboard/CHINESE_MAPPING_GUIDE.md create mode 100644 mini_program/farm-monitor-dashboard/DEBUG_API_ISSUE.md create mode 100644 mini_program/farm-monitor-dashboard/ELECTRONIC_FENCE_README.md create mode 100644 mini_program/farm-monitor-dashboard/HOST_NUMBER_FIX_REPORT.md create mode 100644 mini_program/farm-monitor-dashboard/IMPLEMENTATION_SUMMARY.md create mode 100644 mini_program/farm-monitor-dashboard/SMART_EARTAG_ALERT_README.md create mode 100644 mini_program/farm-monitor-dashboard/auth-test.js create mode 100644 mini_program/farm-monitor-dashboard/auto-login.js create mode 100644 mini_program/farm-monitor-dashboard/check-backend.js create mode 100644 mini_program/farm-monitor-dashboard/mock-api-server.js create mode 100644 mini_program/farm-monitor-dashboard/set-token.js create mode 100644 mini_program/farm-monitor-dashboard/src/components/AlertTest.vue create mode 100644 mini_program/farm-monitor-dashboard/src/components/ApiTest.vue create mode 100644 mini_program/farm-monitor-dashboard/src/components/ApiTestPage.vue create mode 100644 mini_program/farm-monitor-dashboard/src/components/CattleAdd.vue create mode 100644 mini_program/farm-monitor-dashboard/src/components/CattleProfile.vue create mode 100644 mini_program/farm-monitor-dashboard/src/components/CattleTest.vue create mode 100644 mini_program/farm-monitor-dashboard/src/components/CattleTransfer.vue create mode 100644 mini_program/farm-monitor-dashboard/src/components/CattleTransferRegister.vue create mode 100644 mini_program/farm-monitor-dashboard/src/components/ElectronicFence.vue create mode 100644 mini_program/farm-monitor-dashboard/src/components/MapTest.vue create mode 100644 mini_program/farm-monitor-dashboard/src/components/MapView.vue create mode 100644 mini_program/farm-monitor-dashboard/src/components/SmartEartagAlert.vue create mode 100644 mini_program/farm-monitor-dashboard/src/components/WechatFenceDrawer.vue create mode 100644 mini_program/farm-monitor-dashboard/src/services/alertService.js create mode 100644 mini_program/farm-monitor-dashboard/src/services/fenceService.js create mode 100644 mini_program/farm-monitor-dashboard/src/utils/mapping.js create mode 100644 mini_program/farm-monitor-dashboard/src/views/ElectronicFencePage.vue create mode 100644 mini_program/farm-monitor-dashboard/src/views/SmartEartagAlertPage.vue create mode 100644 mini_program/farm-monitor-dashboard/test-api.html create mode 100644 mini_program/farm-monitor-dashboard/test-api.js create mode 100644 mini_program/farm-monitor-dashboard/test-host-number-fix.js diff --git a/backend/routes/smart-devices.js b/backend/routes/smart-devices.js index 5107ca9..064af5e 100644 --- a/backend/routes/smart-devices.js +++ b/backend/routes/smart-devices.js @@ -898,6 +898,75 @@ router.get('/collars/search/:collarNumber', verifyToken, requirePermission('smar * tags: [Smart Devices] * security: * - bearerAuth: [] + * parameters: + * - in: query + * name: page + * schema: + * type: integer + * default: 1 + * description: 页码 + * - in: query + * name: limit + * schema: + * type: integer + * default: 10 + * description: 每页数量 + * - in: query + * name: status + * schema: + * type: string + * enum: [online, offline, alarm, maintenance] + * description: 设备状态筛选 + * - in: query + * name: search + * schema: + * type: string + * description: 搜索关键词(设备ID或序列号) + * responses: + * 200: + * description: 获取成功 + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * data: + * type: array + * items: + * type: object + * properties: + * id: + * type: integer + * sn: + * type: string + * battery: + * type: number + * rsrp: + * type: number + * bandge_status: + * type: integer + * deviceInfo: + * type: string + * temperature: + * type: number + * status: + * type: string + * steps: + * type: integer + * location: + * type: string + * updateInterval: + * type: integer + * lastUpdate: + * type: string + * total: + * type: integer + * stats: + * type: object + * pagination: + * type: object */ router.get('/collars', verifyToken, requirePermission('smart_collar:view'), async (req, res) => { try { diff --git a/insurance_admin-system/package-lock.json b/insurance_admin-system/package-lock.json index 14a0342..5c49b94 100644 --- a/insurance_admin-system/package-lock.json +++ b/insurance_admin-system/package-lock.json @@ -11,6 +11,7 @@ "@ant-design/icons-vue": "^6.1.0", "ant-design-vue": "^4.0.0", "axios": "^1.4.0", + "dayjs": "^1.11.18", "echarts": "^5.4.2", "pinia": "^2.1.6", "vue": "^3.3.4", diff --git a/insurance_admin-system/package.json b/insurance_admin-system/package.json index 06ac232..31f0ad9 100644 --- a/insurance_admin-system/package.json +++ b/insurance_admin-system/package.json @@ -10,19 +10,20 @@ "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" }, "dependencies": { - "vue": "^3.3.4", - "vue-router": "^4.2.4", - "pinia": "^2.1.6", + "@ant-design/icons-vue": "^6.1.0", "ant-design-vue": "^4.0.0", "axios": "^1.4.0", - "@ant-design/icons-vue": "^6.1.0", + "dayjs": "^1.11.18", "echarts": "^5.4.2", - "vue-echarts": "^6.5.3" + "pinia": "^2.1.6", + "vue": "^3.3.4", + "vue-echarts": "^6.5.3", + "vue-router": "^4.2.4" }, "devDependencies": { "@vitejs/plugin-vue": "^4.2.3", - "vite": "^4.4.5", "eslint": "^8.45.0", - "eslint-plugin-vue": "^9.15.1" + "eslint-plugin-vue": "^9.15.1", + "vite": "^4.4.5" } -} \ No newline at end of file +} diff --git a/insurance_admin-system/src/components/Layout.vue b/insurance_admin-system/src/components/Layout.vue index 9f4f9d5..31e9879 100644 --- a/insurance_admin-system/src/components/Layout.vue +++ b/insurance_admin-system/src/components/Layout.vue @@ -10,7 +10,7 @@ v-model:selectedKeys="selectedKeys" theme="dark" mode="inline" - :items="menuItems" + :items="menus" @click="handleMenuClick" /> @@ -70,7 +70,7 @@ \ No newline at end of file diff --git a/insurance_backend/config/swagger.js b/insurance_backend/config/swagger.js index f02f72d..b97b572 100644 --- a/insurance_backend/config/swagger.js +++ b/insurance_backend/config/swagger.js @@ -46,6 +46,69 @@ const swaggerDefinition = { updatedAt: { type: 'string', format: 'date-time', description: '更新时间' } } }, + Menu: { + type: 'object', + properties: { + id: { + type: 'integer', + description: '菜单ID' + }, + name: { + type: 'string', + description: '菜单名称' + }, + key: { + type: 'string', + description: '菜单唯一标识' + }, + path: { + type: 'string', + description: '路由路径' + }, + icon: { + type: 'string', + description: '菜单图标' + }, + parent_id: { + type: 'integer', + description: '父菜单ID' + }, + component: { + type: 'string', + description: '组件路径' + }, + order: { + type: 'integer', + description: '排序号' + }, + status: { + type: 'string', + enum: ['active', 'inactive'], + description: '菜单状态' + }, + show: { + type: 'boolean', + description: '是否显示' + }, + children: { + type: 'array', + items: { + $ref: '#/components/schemas/Menu' + }, + description: '子菜单列表' + }, + created_at: { + type: 'string', + format: 'date-time', + description: '创建时间' + }, + updated_at: { + type: 'string', + format: 'date-time', + description: '更新时间' + } + } + }, InsuranceApplication: { type: 'object', properties: { diff --git a/insurance_backend/controllers/dataWarehouseController.js b/insurance_backend/controllers/dataWarehouseController.js new file mode 100644 index 0000000..6b58dac --- /dev/null +++ b/insurance_backend/controllers/dataWarehouseController.js @@ -0,0 +1,210 @@ +const { User, Role, InsuranceApplication, Policy, Claim, InsuranceType } = require('../models'); +const responseFormat = require('../utils/response'); +const { Op } = require('sequelize'); + +// 获取数据览仓概览数据 +const getOverview = async (req, res) => { + try { + const [ + totalUsers, + totalApplications, + totalPolicies, + totalClaims, + activePolicies, + approvedClaims, + pendingClaims + ] = await Promise.all([ + User.count(), + InsuranceApplication.count(), + Policy.count(), + Claim.count(), + Policy.count({ where: { policy_status: 'active' } }), + Claim.count({ where: { claim_status: 'approved' } }), + Claim.count({ where: { claim_status: 'pending' } }) + ]); + + res.json(responseFormat.success({ + totalUsers, + totalApplications, + totalPolicies, + totalClaims, + activePolicies, + approvedClaims, + pendingClaims + }, '获取数据览仓概览成功')); + } catch (error) { + console.error('获取数据览仓概览错误:', error); + res.status(500).json(responseFormat.error('获取数据览仓概览失败')); + } +}; + +// 获取保险类型分布数据 +const getInsuranceTypeDistribution = async (req, res) => { + try { + const types = await InsuranceType.findAll({ + attributes: ['id', 'name', 'description'], + where: { status: 'active' } + }); + + const distribution = await Promise.all( + types.map(async type => { + const count = await InsuranceApplication.count({ + where: { insurance_type_id: type.id } + }); + return { + id: type.id, + name: type.name, + description: type.description, + count + }; + }) + ); + + res.json(responseFormat.success(distribution, '获取保险类型分布成功')); + } catch (error) { + console.error('获取保险类型分布错误:', error); + res.status(500).json(responseFormat.error('获取保险类型分布失败')); + } +}; + +// 获取申请状态分布数据 +const getApplicationStatusDistribution = async (req, res) => { + try { + const [ + pendingCount, + approvedCount, + rejectedCount, + underReviewCount + ] = await Promise.all([ + InsuranceApplication.count({ where: { status: 'pending' } }), + InsuranceApplication.count({ where: { status: 'approved' } }), + InsuranceApplication.count({ where: { status: 'rejected' } }), + InsuranceApplication.count({ where: { status: 'under_review' } }) + ]); + + res.json(responseFormat.success([ + { status: 'pending', name: '待处理', count: pendingCount }, + { status: 'under_review', name: '审核中', count: underReviewCount }, + { status: 'approved', name: '已批准', count: approvedCount }, + { status: 'rejected', name: '已拒绝', count: rejectedCount } + ], '获取申请状态分布成功')); + } catch (error) { + console.error('获取申请状态分布错误:', error); + res.status(500).json(responseFormat.error('获取申请状态分布失败')); + } +}; + +// 获取近7天趋势数据 +const getTrendData = async (req, res) => { + try { + const sevenDaysAgo = new Date(); + sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); + + // 生成近7天的日期数组 + const dateArray = []; + for (let i = 6; i >= 0; i--) { + const date = new Date(sevenDaysAgo); + date.setDate(date.getDate() + i); + dateArray.push(date.toISOString().split('T')[0]); // 格式化为YYYY-MM-DD + } + + // 获取每天的新增数据 + const dailyData = await Promise.all( + dateArray.map(async date => { + const startDate = new Date(`${date} 00:00:00`); + const endDate = new Date(`${date} 23:59:59`); + + const [newApplications, newPolicies, newClaims] = await Promise.all([ + InsuranceApplication.count({ + where: { + created_at: { + [Op.between]: [startDate, endDate] + } + } + }), + Policy.count({ + where: { + created_at: { + [Op.between]: [startDate, endDate] + } + } + }), + Claim.count({ + where: { + created_at: { + [Op.between]: [startDate, endDate] + } + } + }) + ]); + + return { + date, + newApplications, + newPolicies, + newClaims + }; + }) + ); + + res.json(responseFormat.success(dailyData, '获取趋势数据成功')); + } catch (error) { + console.error('获取趋势数据错误:', error); + res.status(500).json(responseFormat.error('获取趋势数据失败')); + } +}; + +// 获取赔付统计数据 +const getClaimStats = async (req, res) => { + try { + const claims = await Claim.findAll({ + attributes: ['id', 'claim_amount', 'policy_id'], + include: [{ + model: Policy, + as: 'policy', + attributes: ['policy_no', 'insurance_type_id'] + }] + }); + + // 按保险类型分组统计赔付金额 + const typeStats = {}; + claims.forEach(claim => { + const typeId = claim.policy?.insurance_type_id; + if (typeId) { + if (!typeStats[typeId]) { + typeStats[typeId] = { id: typeId, totalAmount: 0, count: 0 }; + } + typeStats[typeId].totalAmount += parseFloat(claim.claim_amount || 0); + typeStats[typeId].count += 1; + } + }); + + // 获取保险类型名称 + const typeIds = Object.keys(typeStats).map(id => parseInt(id)); + const types = await InsuranceType.findAll({ + attributes: ['id', 'name'], + where: { id: typeIds } + }); + + types.forEach(type => { + if (typeStats[type.id]) { + typeStats[type.id].name = type.name; + } + }); + + const result = Object.values(typeStats); + + res.json(responseFormat.success(result, '获取赔付统计成功')); + } catch (error) { + console.error('获取赔付统计错误:', error); + res.status(500).json(responseFormat.error('获取赔付统计失败')); + } +}; + +module.exports = { + getOverview, + getInsuranceTypeDistribution, + getApplicationStatusDistribution, + getTrendData, + getClaimStats +}; \ No newline at end of file diff --git a/insurance_backend/controllers/menuController.js b/insurance_backend/controllers/menuController.js new file mode 100644 index 0000000..11f4ef3 --- /dev/null +++ b/insurance_backend/controllers/menuController.js @@ -0,0 +1,161 @@ +const { User, Role, Menu } = require('../models'); + +/** + * 获取菜单列表 + * @param {Object} req - Express请求对象 + * @param {Object} res - Express响应对象 + */ +exports.getMenus = async (req, res) => { + try { + // 获取用户ID(从JWT中解析或通过其他方式获取) + const userId = req.user?.id; // 假设通过认证中间件解析后存在 + + // 如果没有用户ID,返回基础菜单 + if (!userId) { + const menus = await Menu.findAll({ + where: { + parent_id: null, + show: true, + status: 'active' + }, + include: [ + { + model: Menu, + as: 'children', + where: { + show: true, + status: 'active' + }, + required: false, + order: [['order', 'ASC']] + } + ], + order: [['order', 'ASC']] + }); + + return res.json({ + code: 200, + status: 'success', + data: menus, + message: '获取菜单成功' + }); + } + + // 获取用户信息和角色 + const user = await User.findByPk(userId, { + include: [ + { + model: Role, + attributes: ['id', 'name', 'permissions'] + } + ] + }); + + if (!user) { + return res.status(404).json({ + code: 404, + status: 'error', + message: '用户不存在' + }); + } + + // 获取角色的权限列表 + const userPermissions = user.Role?.permissions || []; + + // 查询菜单,这里简化处理,实际应用中可能需要根据权限过滤 + const menus = await Menu.findAll({ + where: { + parent_id: null, + show: true, + status: 'active' + }, + include: [ + { + model: Menu, + as: 'children', + where: { + show: true, + status: 'active' + }, + required: false, + order: [['order', 'ASC']] + } + ], + order: [['order', 'ASC']] + }); + + // 这里可以添加根据权限过滤菜单的逻辑 + // 简化示例,假设所有用户都能看到所有激活的菜单 + + return res.json({ + code: 200, + status: 'success', + data: menus, + message: '获取菜单成功' + }); + } catch (error) { + console.error('获取菜单失败:', error); + return res.status(500).json({ + code: 500, + status: 'error', + message: '服务器内部错误' + }); + } +}; + +/** + * 获取所有菜单(包括非激活状态,仅管理员可用) + * @param {Object} req - Express请求对象 + * @param {Object} res - Express响应对象 + */ +exports.getAllMenus = async (req, res) => { + try { + // 检查用户是否为管理员(简化示例) + const user = await User.findByPk(req.user?.id, { + include: [ + { + model: Role, + attributes: ['id', 'name', 'permissions'] + } + ] + }); + + if (!user || !user.Role || !user.Role.permissions.includes('*:*')) { + return res.status(403).json({ + code: 403, + status: 'error', + message: '没有权限查看所有菜单' + }); + } + + // 查询所有菜单 + const menus = await Menu.findAll({ + where: { + parent_id: null + }, + include: [ + { + model: Menu, + as: 'children', + required: false, + order: [['order', 'ASC']] + } + ], + order: [['order', 'ASC']] + }); + + return res.json({ + code: 200, + status: 'success', + data: menus, + message: '获取所有菜单成功' + }); + } catch (error) { + console.error('获取所有菜单失败:', error); + return res.status(500).json({ + code: 500, + status: 'error', + message: '服务器内部错误' + }); + } +}; \ No newline at end of file diff --git a/insurance_backend/create_admin_user.js b/insurance_backend/create_admin_user.js new file mode 100644 index 0000000..9e0aae4 --- /dev/null +++ b/insurance_backend/create_admin_user.js @@ -0,0 +1,183 @@ +const { sequelize } = require('./config/database'); +const bcrypt = require('bcrypt'); + +async function createAdminUser() { + try { + // 连接数据库 + await sequelize.authenticate(); + console.log('✅ 数据库连接成功'); + + // 检查是否有users表 + const [tables] = await sequelize.query( + "SHOW TABLES LIKE 'users'" + ); + + if (tables.length === 0) { + console.log('⚠️ users表不存在,开始创建必要的表结构'); + + // 创建roles表 + await sequelize.query(` + CREATE TABLE IF NOT EXISTS roles ( + id INT AUTO_INCREMENT PRIMARY KEY COMMENT '角色ID', + name VARCHAR(50) NOT NULL UNIQUE COMMENT '角色名称', + description VARCHAR(255) NULL COMMENT '角色描述', + permissions JSON NOT NULL COMMENT '权限配置', + status ENUM('active', 'inactive') NOT NULL DEFAULT 'active' COMMENT '状态', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + INDEX idx_roles_name (name), + INDEX idx_roles_status (status) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='角色表'; + `); + console.log('✅ roles表创建完成'); + + // 创建users表 + await sequelize.query(` + CREATE TABLE IF NOT EXISTS users ( + id INT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID', + username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名', + password VARCHAR(255) NOT NULL COMMENT '密码', + real_name VARCHAR(50) NOT NULL COMMENT '真实姓名', + email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱', + phone VARCHAR(20) NOT NULL COMMENT '手机号', + role_id INT NOT NULL COMMENT '角色ID', + status ENUM('active', 'inactive', 'suspended') NOT NULL DEFAULT 'active' COMMENT '状态', + last_login TIMESTAMP NULL COMMENT '最后登录时间', + avatar VARCHAR(255) NULL COMMENT '头像URL', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + INDEX idx_users_username (username), + INDEX idx_users_email (email), + INDEX idx_users_phone (phone), + INDEX idx_users_role_id (role_id), + INDEX idx_users_status (status), + FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE RESTRICT + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表'; + `); + console.log('✅ users表创建完成'); + + // 创建system_configs表 + await sequelize.query(` + CREATE TABLE IF NOT EXISTS system_configs ( + id INT AUTO_INCREMENT PRIMARY KEY, + config_key VARCHAR(50) NOT NULL UNIQUE, + config_value TEXT NOT NULL, + description VARCHAR(255) NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_config_key (config_key) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `); + console.log('✅ system_configs表创建完成'); + } else { + console.log('✅ users表已存在'); + } + + // 检查admin角色是否存在 + const [adminRole] = await sequelize.query( + 'SELECT id FROM roles WHERE name = ?', + { replacements: ['admin'] } + ); + + let roleId; + if (adminRole.length === 0) { + // 创建admin角色 + await sequelize.query( + 'INSERT INTO roles (name, description, permissions, status) VALUES (?, ?, ?, ?)', + { + replacements: [ + 'admin', + '系统管理员', + JSON.stringify(['*']), + 'active' + ] + } + ); + console.log('✅ admin角色创建完成'); + + // 获取刚创建的角色ID + const [newRole] = await sequelize.query( + 'SELECT id FROM roles WHERE name = ?', + { replacements: ['admin'] } + ); + roleId = newRole[0].id; + } else { + roleId = adminRole[0].id; + console.log('✅ admin角色已存在'); + } + + // 检查admin用户是否存在 + const [adminUser] = await sequelize.query( + 'SELECT id FROM users WHERE username = ?', + { replacements: ['admin'] } + ); + + if (adminUser.length === 0) { + // 密码为123456,使用bcrypt进行哈希处理 + const plainPassword = '123456'; + const hashedPassword = await bcrypt.hash(plainPassword, 12); + + // 创建admin用户 + await sequelize.query( + `INSERT INTO users (username, password, real_name, email, phone, role_id, status) + VALUES (?, ?, ?, ?, ?, ?, ?)`, + { + replacements: [ + 'admin', + hashedPassword, + '系统管理员', + 'admin@insurance.com', + '13800138000', + roleId, + 'active' + ] + } + ); + console.log('✅ admin用户创建完成,用户名: admin,密码: 123456'); + + // 插入默认系统配置 + await sequelize.query( + `INSERT IGNORE INTO system_configs (config_key, config_value, description) VALUES + ('system_name', ?, '系统名称'), + ('company_name', ?, '公司名称'), + ('contact_email', ?, '联系邮箱'), + ('contact_phone', ?, '联系电话'), + ('max_file_size', ?, '最大文件上传大小(字节)'), + ('allowed_file_types', ?, '允许上传的文件类型')`, + { + replacements: [ + JSON.stringify('保险端口系统'), + JSON.stringify('XX保险公司'), + JSON.stringify('support@insurance.com'), + JSON.stringify('400-123-4567'), + '10485760', + JSON.stringify(['jpg', 'jpeg', 'png', 'pdf', 'doc', 'docx']) + ] + } + ); + console.log('✅ 默认系统配置已插入'); + } else { + console.log('⚠️ admin用户已存在,更新密码为: 123456'); + // 更新admin用户的密码 + const plainPassword = '123456'; + const hashedPassword = await bcrypt.hash(plainPassword, 12); + + await sequelize.query( + 'UPDATE users SET password = ? WHERE username = ?', + { replacements: [hashedPassword, 'admin'] } + ); + console.log('✅ admin用户密码已更新为: 123456'); + } + + } catch (error) { + console.error('❌ 操作失败:', error.message); + console.error(error.stack); + } finally { + // 关闭数据库连接 + await sequelize.close(); + console.log('🔒 数据库连接已关闭'); + } +} + +// 执行脚本 +createAdminUser(); \ No newline at end of file diff --git a/insurance_backend/docs/dynamic_menu_guide.md b/insurance_backend/docs/dynamic_menu_guide.md new file mode 100644 index 0000000..e5fc5de --- /dev/null +++ b/insurance_backend/docs/dynamic_menu_guide.md @@ -0,0 +1,135 @@ +# 动态菜单功能实现指南 + +## 概述 + +本文档描述了保险PC端管理系统中动态菜单功能的实现方案,包括后端API设计、数据库设计、前端集成和数据初始化方法。 + +## 功能特点 + +1. **全动态菜单**: 所有菜单数据从MySQL数据库动态获取 +2. **菜单层次结构**: 支持多级菜单结构 +3. **权限控制**: 支持基于用户角色的菜单权限控制 +4. **自动排序**: 菜单按预定义顺序显示 +5. **备用菜单**: 当API请求失败时提供默认菜单 + +## 技术实现 + +### 1. 数据库设计 + +菜单数据存储在`menus`表中,使用自关联实现父子菜单关系: + +```sql +CREATE TABLE menus ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(50) NOT NULL, + key VARCHAR(50) NOT NULL UNIQUE, + path VARCHAR(100) NOT NULL, + icon VARCHAR(50) NULL, + parent_id INT NULL, + component VARCHAR(100) NULL, + `order` INT NOT NULL DEFAULT 0, + status ENUM('active', 'inactive') DEFAULT 'active', + show BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (parent_id) REFERENCES menus(id) +); +``` + +### 2. 后端实现 + +#### 2.1 菜单模型 + +`models/Menu.js`定义了菜单的数据结构和关联关系: +- 支持自关联的父子菜单关系 +- 包含菜单的名称、唯一标识、路径、图标等属性 +- 支持菜单排序和状态控制 + +#### 2.2 菜单控制器 + +`controllers/menuController.js`实现了菜单相关的业务逻辑: +- `getMenus`: 获取当前用户的菜单列表(考虑权限) +- `getAllMenus`: 获取所有菜单(仅管理员可用) + +#### 2.3 菜单路由 + +`routes/menus.js`定义了菜单相关的API接口: +- `GET /api/menus`: 获取当前用户的菜单列表 +- `GET /api/menus/all`: 获取所有菜单(管理员专用) + +#### 2.4 API文档集成 + +菜单API已集成到Swagger文档中,可通过`http://localhost:3000/api-docs/#/`访问查看。 + +### 3. 前端实现 + +#### 3.1 API调用 + +`utils/api.js`中添加了菜单相关的API调用方法: +- `getMenus()`: 获取当前用户的菜单列表 +- `getAllMenus()`: 获取所有菜单(管理员专用) + +#### 3.2 动态菜单渲染 + +`components/Layout.vue`组件实现了动态菜单的加载和渲染: +- 使用`onMounted`生命周期钩子加载菜单数据 +- 支持菜单点击事件处理和路由跳转 +- 实现了图标映射和菜单格式化功能 +- 提供了默认菜单作为API请求失败时的备用方案 + +## 菜单数据初始化 + +### 方法一:直接执行SQL脚本 + +1. 登录MySQL数据库 +2. 选择`insurance_data`数据库 +3. 执行`scripts/init_menus.sql`脚本 + +```bash +mysql -u root -p insurance_data < scripts/init_menus.sql +``` + +### 方法二:使用Node.js脚本 + +1. 确保已安装依赖:`npm install mysql2 dotenv` +2. 执行`seed_menus.js`脚本: + +```bash +node scripts/seed_menus.js +``` + +## 菜单列表 + +系统包含以下菜单: + +1. **仪表板** +2. **数据揽仓** +3. **监管任务** +4. **待安装任务** +5. **监管任务已结项** +6. **投保客户单** + - 参保申请 +7. **生资保单** + - 生资保单列表 +8. **险种管理** + - 险种管理 +9. **客户理赔** + - 客户理赔 +10. **消息通知** +11. **子账号管理** +12. **系统设置** +13. **个人中心** + +## 使用说明 + +1. 确保后端服务正常运行 +2. 确保菜单数据已正确初始化到数据库 +3. 登录系统后,系统会自动从后端API获取并渲染菜单 +4. 点击菜单项会根据配置的路径进行路由跳转 + +## 扩展建议 + +1. 可以扩展菜单模型,添加更多的权限控制字段 +2. 实现菜单管理界面,支持动态增删改查菜单 +3. 优化菜单缓存机制,减少重复的API请求 +4. 完善菜单的国际化支持 \ No newline at end of file diff --git a/insurance_backend/models/Menu.js b/insurance_backend/models/Menu.js new file mode 100644 index 0000000..c41cd38 --- /dev/null +++ b/insurance_backend/models/Menu.js @@ -0,0 +1,85 @@ +const { sequelize } = require('../config/database'); +const { DataTypes } = require('sequelize'); + +const Menu = sequelize.define('Menu', { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true + }, + name: { + type: DataTypes.STRING(50), + allowNull: false, + validate: { + len: [2, 50] + } + }, + key: { + type: DataTypes.STRING(50), + allowNull: false, + unique: true, + validate: { + len: [2, 50] + } + }, + path: { + type: DataTypes.STRING(100), + allowNull: false, + validate: { + len: [1, 100] + } + }, + icon: { + type: DataTypes.STRING(50), + allowNull: true + }, + parent_id: { + type: DataTypes.INTEGER, + allowNull: true, + references: { + model: 'menus', + key: 'id' + }, + defaultValue: null + }, + component: { + type: DataTypes.STRING(100), + allowNull: true + }, + order: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + status: { + type: DataTypes.ENUM('active', 'inactive'), + defaultValue: 'active' + }, + show: { + type: DataTypes.BOOLEAN, + defaultValue: true + } +}, { + tableName: 'menus', + timestamps: true, + underscored: true, + indexes: [ + { fields: ['key'] }, + { fields: ['parent_id'] }, + { fields: ['status'] }, + { fields: ['order'] } + ] +}); + +// 设置自关联 +Menu.hasMany(Menu, { + as: 'children', + foreignKey: 'parent_id' +}); + +Menu.belongsTo(Menu, { + as: 'parent', + foreignKey: 'parent_id' +}); + +module.exports = Menu; \ No newline at end of file diff --git a/insurance_backend/models/index.js b/insurance_backend/models/index.js index 1846a71..b872251 100644 --- a/insurance_backend/models/index.js +++ b/insurance_backend/models/index.js @@ -1,12 +1,12 @@ -const { sequelize } = require('../config/database'); - -// 导入所有模型 +// 导入数据库配置和所有模型 + const { sequelize } = require('../config/database'); const User = require('./User'); const Role = require('./Role'); const InsuranceApplication = require('./InsuranceApplication'); const InsuranceType = require('./InsuranceType'); const Policy = require('./Policy'); const Claim = require('./Claim'); +const Menu = require('./Menu'); // 定义模型关联关系 @@ -62,5 +62,6 @@ module.exports = { InsuranceApplication, InsuranceType, Policy, - Claim + Claim, + Menu }; \ No newline at end of file diff --git a/insurance_backend/routes/dataWarehouse.js b/insurance_backend/routes/dataWarehouse.js new file mode 100644 index 0000000..7f9895e --- /dev/null +++ b/insurance_backend/routes/dataWarehouse.js @@ -0,0 +1,163 @@ +const express = require('express'); +const router = express.Router(); +const dataWarehouseController = require('../controllers/dataWarehouseController'); +const { jwtAuth, checkPermission } = require('../middleware/auth'); + +/** + * @swagger + * tags: + * name: DataWarehouse + * description: 数据览仓相关接口 + */ + +/** + * @swagger + * /api/data-warehouse/overview: + * get: + * summary: 获取数据览仓概览数据 + * tags: [DataWarehouse] + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: 成功获取概览数据 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: { type: 'number' } + * status: { type: 'string' } + * data: { type: 'object' } + * message: { type: 'string' } + * timestamp: { type: 'string' } + * 401: + * description: 未授权 + * 500: + * description: 服务器内部错误 + */ +router.get('/overview', jwtAuth, checkPermission('data', 'read'), + dataWarehouseController.getOverview +); + +/** + * @swagger + * /api/data-warehouse/insurance-type-distribution: + * get: + * summary: 获取保险类型分布数据 + * tags: [DataWarehouse] + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: 成功获取保险类型分布数据 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: { type: 'number' } + * status: { type: 'string' } + * data: { type: 'array' } + * message: { type: 'string' } + * timestamp: { type: 'string' } + * 401: + * description: 未授权 + * 500: + * description: 服务器内部错误 + */ +router.get('/insurance-type-distribution', jwtAuth, checkPermission('data', 'read'), + dataWarehouseController.getInsuranceTypeDistribution +); + +/** + * @swagger + * /api/data-warehouse/application-status-distribution: + * get: + * summary: 获取申请状态分布数据 + * tags: [DataWarehouse] + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: 成功获取申请状态分布数据 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: { type: 'number' } + * status: { type: 'string' } + * data: { type: 'array' } + * message: { type: 'string' } + * timestamp: { type: 'string' } + * 401: + * description: 未授权 + * 500: + * description: 服务器内部错误 + */ +router.get('/application-status-distribution', jwtAuth, checkPermission('data', 'read'), + dataWarehouseController.getApplicationStatusDistribution +); + +/** + * @swagger + * /api/data-warehouse/trend-data: + * get: + * summary: 获取近7天趋势数据 + * tags: [DataWarehouse] + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: 成功获取趋势数据 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: { type: 'number' } + * status: { type: 'string' } + * data: { type: 'array' } + * message: { type: 'string' } + * timestamp: { type: 'string' } + * 401: + * description: 未授权 + * 500: + * description: 服务器内部错误 + */ +router.get('/trend-data', jwtAuth, checkPermission('data', 'read'), + dataWarehouseController.getTrendData +); + +/** + * @swagger + * /api/data-warehouse/claim-stats: + * get: + * summary: 获取赔付统计数据 + * tags: [DataWarehouse] + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: 成功获取赔付统计数据 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: { type: 'number' } + * status: { type: 'string' } + * data: { type: 'array' } + * message: { type: 'string' } + * timestamp: { type: 'string' } + * 401: + * description: 未授权 + * 500: + * description: 服务器内部错误 + */ +router.get('/claim-stats', jwtAuth, checkPermission('data', 'read'), + dataWarehouseController.getClaimStats +); + +module.exports = router; \ No newline at end of file diff --git a/insurance_backend/routes/menus.js b/insurance_backend/routes/menus.js new file mode 100644 index 0000000..4f1bf20 --- /dev/null +++ b/insurance_backend/routes/menus.js @@ -0,0 +1,119 @@ +const express = require('express'); +const router = express.Router(); +const menuController = require('../controllers/menuController'); +const { jwtAuth } = require('../middleware/auth'); + +/** + * @swagger + * tags: + * name: Menus + * description: 菜单管理相关接口 + */ + +/** + * @swagger + * /api/menus/public: + * get: + * summary: 获取公开菜单列表(无需认证) + * tags: [Menus] + * responses: + * 200: + * description: 成功获取菜单列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * status: + * type: string + * example: success + * data: + * type: array + * items: + * $ref: '#/components/schemas/Menu' + * message: + * type: string + * example: 获取菜单成功 + * 500: + * description: 服务器内部错误 + */ +router.get('/public', menuController.getMenus); + +/** + * @swagger + * /api/menus: + * get: + * summary: 获取当前用户的菜单列表 + * tags: [Menus] + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: 成功获取菜单列表 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * status: + * type: string + * example: success + * data: + * type: array + * items: + * $ref: '#/components/schemas/Menu' + * message: + * type: string + * example: 获取菜单成功 + * 401: + * description: 未授权 + * 500: + * description: 服务器内部错误 + */ +router.get('/', jwtAuth, menuController.getMenus); + +/** + * @swagger + * /api/menus/all: + * get: + * summary: 获取所有菜单(包括非激活状态,仅管理员可用) + * tags: [Menus] + * security: + * - bearerAuth: [] + * responses: + * 200: + * description: 成功获取所有菜单 + * content: + * application/json: + * schema: + * type: object + * properties: + * code: + * type: integer + * example: 200 + * status: + * type: string + * example: success + * data: + * type: array + * items: + * $ref: '#/components/schemas/Menu' + * message: + * type: string + * example: 获取所有菜单成功 + * 401: + * description: 未授权 + * 403: + * description: 没有权限 + * 500: + * description: 服务器内部错误 + */ +router.get('/all', jwtAuth, menuController.getAllMenus); + +module.exports = router; \ No newline at end of file diff --git a/insurance_backend/scripts/init_menus.sql b/insurance_backend/scripts/init_menus.sql new file mode 100644 index 0000000..e53effc --- /dev/null +++ b/insurance_backend/scripts/init_menus.sql @@ -0,0 +1,59 @@ +-- 初始化菜单数据 +-- 注意:请确保menus表已经创建 + +-- 首先清空现有菜单数据 +SET FOREIGN_KEY_CHECKS = 0; +TRUNCATE TABLE menus; +SET FOREIGN_KEY_CHECKS = 1; + +-- 插入父级菜单 +INSERT INTO menus (name, key, path, icon, parent_id, component, `order`, status, show, created_at, updated_at) +VALUES + ('仪表板', 'Dashboard', '/dashboard', 'DashboardOutlined', NULL, 'views/Dashboard.vue', 1, 'active', true, NOW(), NOW()), + ('数据揽仓', 'DataWarehouse', '/data-warehouse', 'DatabaseOutlined', NULL, '', 2, 'active', true, NOW(), NOW()), + ('监管任务', 'SupervisionTask', '/supervision-task', 'CheckCircleOutlined', NULL, '', 3, 'active', true, NOW(), NOW()), + ('待安装任务', 'PendingInstallationTask', '/pending-installation', 'ExclamationCircleOutlined', NULL, '', 4, 'active', true, NOW(), NOW()), + ('监管任务已结项', 'CompletedTask', '/completed-tasks', 'FileDoneOutlined', NULL, '', 5, 'active', true, NOW(), NOW()), + ('投保客户单', 'InsuredCustomers', '/insured-customers', 'ShopOutlined', NULL, '', 6, 'active', true, NOW(), NOW()), + ('生资保单', 'AgriculturalInsurance', '/agricultural-insurance', 'FileProtectOutlined', NULL, '', 7, 'active', true, NOW(), NOW()), + ('险种管理', 'InsuranceTypeManagement', '/insurance-types', 'MedicineBoxOutlined', NULL, '', 8, 'active', true, NOW(), NOW()), + ('客户理赔', 'CustomerClaims', '/customer-claims', 'AlertCircleOutlined', NULL, '', 9, 'active', true, NOW(), NOW()), + ('消息通知', 'Notifications', '/notifications', 'BellOutlined', NULL, '', 10, 'active', true, NOW(), NOW()), + ('子账号管理', 'UserManagement', '/users', 'UserAddOutlined', NULL, 'views/UserManagement.vue', 11, 'active', true, NOW(), NOW()), + ('系统设置', 'SystemSettings', '/system-settings', 'SettingOutlined', NULL, '', 12, 'active', true, NOW(), NOW()), + ('个人中心', 'UserProfile', '/profile', 'UserSwitchOutlined', NULL, '', 13, 'active', true, NOW(), NOW()); + +-- 插入子菜单 +-- 投保客户单的子菜单 +INSERT INTO menus (name, key, path, icon, parent_id, component, `order`, status, show, created_at, updated_at) +SELECT + '参保申请', 'ApplicationManagement', '/applications', 'FileTextOutlined', id, 'views/ApplicationManagement.vue', 1, 'active', true, NOW(), NOW() +FROM menus +WHERE `key` = 'InsuredCustomers'; + +-- 生资保单的子菜单 +INSERT INTO menus (name, key, path, icon, parent_id, component, `order`, status, show, created_at, updated_at) +SELECT + '生资保单列表', 'PolicyManagement', '/policies', 'FileDoneOutlined', id, 'views/PolicyManagement.vue', 1, 'active', true, NOW(), NOW() +FROM menus +WHERE `key` = 'AgriculturalInsurance'; + +-- 险种管理的子菜单 +INSERT INTO menus (name, key, path, icon, parent_id, component, `order`, status, show, created_at, updated_at) +SELECT + '险种管理', 'InsuranceTypeList', '/insurance-types', 'MedicineBoxOutlined', id, 'views/InsuranceTypeManagement.vue', 1, 'active', true, NOW(), NOW() +FROM menus +WHERE `key` = 'InsuranceTypeManagement'; + +-- 客户理赔的子菜单 +INSERT INTO menus (name, key, path, icon, parent_id, component, `order`, status, show, created_at, updated_at) +SELECT + '客户理赔', 'ClaimManagement', '/claims', 'SafetyCertificateOutlined', id, 'views/ClaimManagement.vue', 1, 'active', true, NOW(), NOW() +FROM menus +WHERE `key` = 'CustomerClaims'; + +-- 查询插入结果 +SELECT * FROM menus ORDER BY parent_id, `order`; + +-- 输出插入的记录数 +SELECT CONCAT('成功插入 ', COUNT(*), ' 条菜单记录') AS result FROM menus; \ No newline at end of file diff --git a/insurance_backend/scripts/initialize_menus.js b/insurance_backend/scripts/initialize_menus.js new file mode 100644 index 0000000..2649304 --- /dev/null +++ b/insurance_backend/scripts/initialize_menus.js @@ -0,0 +1,175 @@ +// 初始化菜单表结构并插入数据 +const { sequelize } = require('../config/database'); +const { DataTypes } = require('sequelize'); + +// 定义Menu模型 +const Menu = sequelize.define('Menu', { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true + }, + name: { + type: DataTypes.STRING(50), + allowNull: false, + validate: { + len: [2, 50] + } + }, + key: { + type: DataTypes.STRING(50), + allowNull: false, + unique: true, + validate: { + len: [2, 50] + } + }, + path: { + type: DataTypes.STRING(100), + allowNull: false, + validate: { + len: [1, 100] + } + }, + icon: { + type: DataTypes.STRING(50), + allowNull: true + }, + parent_id: { + type: DataTypes.INTEGER, + allowNull: true, + references: { + model: 'menus', + key: 'id' + }, + defaultValue: null + }, + component: { + type: DataTypes.STRING(100), + allowNull: true + }, + order: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + status: { + type: DataTypes.ENUM('active', 'inactive'), + defaultValue: 'active' + }, + show: { + type: DataTypes.BOOLEAN, + defaultValue: true + } +}, { + tableName: 'menus', + timestamps: true, + underscored: true, + indexes: [ + { fields: ['key'] }, + { fields: ['parent_id'] }, + { fields: ['status'] }, + { fields: ['order'] } + ] +}); + +// 设置自关联 +Menu.hasMany(Menu, { + as: 'children', + foreignKey: 'parent_id' +}); + +Menu.belongsTo(Menu, { + as: 'parent', + foreignKey: 'parent_id' +}); + +// 初始化函数 +async function initializeMenus() { + try { + console.log('🔄 开始初始化菜单表结构...'); + + // 检查并创建表 + const tableExists = await sequelize.query( + "SHOW TABLES LIKE 'menus'" + ); + + if (tableExists[0].length === 0) { + console.log('📝 menus表不存在,开始创建...'); + await Menu.sync({ force: true }); + console.log('✅ menus表创建成功'); + } else { + console.log('ℹ️ menus表已存在,开始清空数据...'); + await sequelize.query('SET FOREIGN_KEY_CHECKS = 0'); + await sequelize.query('TRUNCATE TABLE menus'); + await sequelize.query('SET FOREIGN_KEY_CHECKS = 1'); + console.log('✅ menus表数据已清空'); + } + + console.log('📊 开始插入菜单数据...'); + + // 插入父级菜单 + const parentMenus = await Menu.bulkCreate([ + { name: '仪表板', key: 'Dashboard', path: '/dashboard', icon: 'DashboardOutlined', parent_id: null, component: 'views/Dashboard.vue', order: 1, status: 'active', show: true }, + { name: '数据览仓', key: 'DataWarehouse', path: '/data-warehouse', icon: 'DatabaseOutlined', parent_id: null, component: '', order: 2, status: 'active', show: true }, + { name: '监管任务', key: 'SupervisionTask', path: '/supervision-task', icon: 'CheckCircleOutlined', parent_id: null, component: '', order: 3, status: 'active', show: true }, + { name: '待安装任务', key: 'PendingInstallationTask', path: '/pending-installation', icon: 'ExclamationCircleOutlined', parent_id: null, component: '', order: 4, status: 'active', show: true }, + { name: '监管任务已结项', key: 'CompletedTask', path: '/completed-tasks', icon: 'FileDoneOutlined', parent_id: null, component: '', order: 5, status: 'active', show: true }, + { name: '投保客户单', key: 'InsuredCustomers', path: '/insured-customers', icon: 'ShopOutlined', parent_id: null, component: '', order: 6, status: 'active', show: true }, + { name: '生资保单', key: 'AgriculturalInsurance', path: '/agricultural-insurance', icon: 'FileProtectOutlined', parent_id: null, component: '', order: 7, status: 'active', show: true }, + { name: '险种管理', key: 'InsuranceTypeManagement', path: '/insurance-types', icon: 'MedicineBoxOutlined', parent_id: null, component: '', order: 8, status: 'active', show: true }, + { name: '客户理赔', key: 'CustomerClaims', path: '/customer-claims', icon: 'AlertCircleOutlined', parent_id: null, component: '', order: 9, status: 'active', show: true }, + { name: '消息通知', key: 'Notifications', path: '/notifications', icon: 'BellOutlined', parent_id: null, component: '', order: 10, status: 'active', show: true }, + { name: '子账号管理', key: 'UserManagement', path: '/users', icon: 'UserAddOutlined', parent_id: null, component: 'views/UserManagement.vue', order: 11, status: 'active', show: true }, + { name: '系统设置', key: 'SystemSettings', path: '/system-settings', icon: 'SettingOutlined', parent_id: null, component: '', order: 12, status: 'active', show: true }, + { name: '个人中心', key: 'UserProfile', path: '/profile', icon: 'UserSwitchOutlined', parent_id: null, component: '', order: 13, status: 'active', show: true } + ]); + + console.log(`✅ 已插入 ${parentMenus.length} 条父级菜单数据`); + + // 查找父级菜单ID + const parentMenuMap = {}; + for (const menu of parentMenus) { + parentMenuMap[menu.key] = menu.id; + } + + // 插入子菜单 + const subMenus = await Menu.bulkCreate([ + // 投保客户单的子菜单 + { name: '参保申请', key: 'ApplicationManagement', path: '/applications', icon: 'FileTextOutlined', parent_id: parentMenuMap.InsuredCustomers, component: 'views/ApplicationManagement.vue', order: 1, status: 'active', show: true }, + + // 生资保单的子菜单 + { name: '生资保单列表', key: 'PolicyManagement', path: '/policies', icon: 'FileDoneOutlined', parent_id: parentMenuMap.AgriculturalInsurance, component: 'views/PolicyManagement.vue', order: 1, status: 'active', show: true }, + + // 险种管理的子菜单 + { name: '险种管理', key: 'InsuranceTypeList', path: '/insurance-types', icon: 'MedicineBoxOutlined', parent_id: parentMenuMap.InsuranceTypeManagement, component: 'views/InsuranceTypeManagement.vue', order: 1, status: 'active', show: true }, + + // 客户理赔的子菜单 + { name: '客户理赔', key: 'ClaimManagement', path: '/claims', icon: 'SafetyCertificateOutlined', parent_id: parentMenuMap.CustomerClaims, component: 'views/ClaimManagement.vue', order: 1, status: 'active', show: true } + ]); + + console.log(`✅ 已插入 ${subMenus.length} 条子菜单数据`); + + // 查询所有菜单 + const allMenus = await Menu.findAll({ + order: [['parent_id', 'ASC'], ['order', 'ASC']], + include: [{ model: Menu, as: 'children' }] + }); + + console.log(`✅ 菜单数据初始化完成,共 ${allMenus.length} 条记录`); + + } catch (error) { + console.error('❌ 菜单数据初始化失败:', error.message); + throw error; + } finally { + // 关闭数据库连接 + await sequelize.close(); + console.log('🔌 数据库连接已关闭'); + } +} + +// 执行初始化 +initializeMenus().catch(err => { + console.error('❌ 菜单数据初始化任务失败'); + process.exit(1); +}); \ No newline at end of file diff --git a/insurance_backend/scripts/seed_data_warehouse.js b/insurance_backend/scripts/seed_data_warehouse.js new file mode 100644 index 0000000..b68870c --- /dev/null +++ b/insurance_backend/scripts/seed_data_warehouse.js @@ -0,0 +1,225 @@ +const { sequelize } = require('../config/database'); +const { DataTypes } = require('sequelize'); +const { User, Role, InsuranceType, InsuranceApplication, Policy, Claim } = require('../models'); + +// 创建随机数据的辅助函数 +const randomString = (length = 10, chars = 'abcdefghijklmnopqrstuvwxyz0123456789') => { + let result = ''; + const charsLength = chars.length; + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * charsLength)); + } + return result; +}; + +const randomDate = (start, end) => { + return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); +}; + +const randomNumber = (min, max) => { + return Math.floor(Math.random() * (max - min + 1)) + min; +}; + +const randomDecimal = (min, max, decimals = 2) => { + return Number((Math.random() * (max - min) + min).toFixed(decimals)); +}; + +const generateApplicationNo = () => { + return `APP${Date.now()}${randomString(6).toUpperCase()}`; +}; + +const generatePolicyNo = () => { + return `POL${Date.now()}${randomString(6).toUpperCase()}`; +}; + +const generateClaimNo = () => { + return `CLAIM${Date.now()}${randomString(6).toUpperCase()}`; +}; + +// 生成随机身份证号 +const generateIdCard = () => { + // 简化版身份证号生成,真实场景需要更复杂的校验 + const areaCode = randomNumber(110000, 659004); + const birthYear = randomNumber(1960, 2000); + const birthMonth = String(randomNumber(1, 12)).padStart(2, '0'); + const birthDay = String(randomNumber(1, 28)).padStart(2, '0'); + const seq = String(randomNumber(100, 999)).padStart(3, '0'); + const checkDigit = Math.random() > 0.9 ? 'X' : randomNumber(0, 9); + + return `${areaCode}${birthYear}${birthMonth}${birthDay}${seq}${checkDigit}`; +}; + +// 生成随机手机号 +const generatePhone = () => { + return `1${randomNumber(3, 9)}${randomString(9, '0123456789')}`; +}; + +// 初始化测试数据 +async function seedData() { + try { + // 确保数据库连接正常 + await sequelize.authenticate(); + console.log('数据库连接成功'); + + // 1. 创建角色(如果不存在) + let adminRole = await Role.findOne({ where: { name: 'admin' } }); + if (!adminRole) { + adminRole = await Role.create({ + name: 'admin', + description: '管理员角色' + }); + } + + // 2. 创建用户(如果不存在) + let adminUser = await User.findOne({ where: { username: 'admin' } }); + if (!adminUser) { + adminUser = await User.create({ + username: 'admin', + password: 'admin123', // 实际应用中应该使用bcrypt加密 + email: 'admin@example.com', + phone: '13800138000', + role_id: adminRole.id, + status: 1 + }); + } + + // 3. 创建保险类型 + const insuranceTypes = [ + { + name: '健康保险', + description: '提供医疗费用报销和健康保障', + coverage_amount_min: 10000.00, + coverage_amount_max: 500000.00, + premium_rate: 0.005, + terms_years: 1, + status: 1 + }, + { + name: '人寿保险', + description: '为家人提供经济保障', + coverage_amount_min: 50000.00, + coverage_amount_max: 1000000.00, + premium_rate: 0.01, + terms_years: 10, + status: 1 + }, + { + name: '财产保险', + description: '保障个人财产安全', + coverage_amount_min: 20000.00, + coverage_amount_max: 200000.00, + premium_rate: 0.003, + terms_years: 1, + status: 1 + }, + { + name: '意外保险', + description: '提供意外事故保障', + coverage_amount_min: 50000.00, + coverage_amount_max: 300000.00, + premium_rate: 0.002, + terms_years: 1, + status: 1 + } + ]; + + // 清除现有保险类型并插入新数据 + await InsuranceType.destroy({ where: {} }); + const createdTypes = await InsuranceType.bulkCreate(insuranceTypes); + console.log(`已创建 ${createdTypes.length} 种保险类型`); + + // 4. 创建保险申请、保单和理赔数据 + const applications = []; + const policies = []; + const claims = []; + + // 为每种保险类型创建多个申请 + for (let i = 0; i < 50; i++) { + const type = createdTypes[randomNumber(0, createdTypes.length - 1)]; + const coverageAmount = randomDecimal(type.coverage_amount_min, type.coverage_amount_max); + const premium = Number((coverageAmount * type.premium_rate).toFixed(2)); + + const application = { + application_no: generateApplicationNo(), + customer_name: `客户${i + 1}`, + customer_id_card: generateIdCard(), + customer_phone: generatePhone(), + customer_address: `测试地址${i + 1}号`, + insurance_type_id: type.id, + coverage_amount: coverageAmount, + premium: premium, + application_date: randomDate(new Date(2023, 0, 1), new Date()), + status: randomNumber(0, 3), // 0: 待审核, 1: 已批准, 2: 已拒绝, 3: 已撤销 + reviewer_id: adminUser.id, + review_date: randomDate(new Date(2023, 0, 1), new Date()), + rejection_reason: randomNumber(0, 2) === 0 ? '资料不完整' : null + }; + applications.push(application); + } + + // 清除现有申请并插入新数据 + await InsuranceApplication.destroy({ where: {} }); + const createdApplications = await InsuranceApplication.bulkCreate(applications); + console.log(`已创建 ${createdApplications.length} 个保险申请`); + + // 为已批准的申请创建保单 + for (const app of createdApplications) { + if (app.status === 1) { // 只有已批准的申请才有保单 + const policy = { + policy_no: generatePolicyNo(), + application_id: app.id, + insurance_type_id: app.insurance_type_id, + customer_id: adminUser.id, // 简化处理,实际应该关联真实客户 + coverage_amount: app.coverage_amount, + premium: app.premium, + start_date: randomDate(app.review_date, new Date()), + end_date: new Date(app.review_date.getTime() + 365 * 24 * 60 * 60 * 1000), + status: 1, // 1: 有效 + created_by: adminUser.id, + created_at: app.review_date + }; + policies.push(policy); + } + } + + // 清除现有保单并插入新数据 + await Policy.destroy({ where: {} }); + const createdPolicies = await Policy.bulkCreate(policies); + console.log(`已创建 ${createdPolicies.length} 个保单`); + + // 为部分保单创建理赔 + for (const policy of createdPolicies) { + if (randomNumber(0, 3) === 0) { // 约25%的保单会有理赔 + const claimAmount = randomDecimal(policy.coverage_amount * 0.1, policy.coverage_amount * 0.8); + + const claim = { + claim_no: generateClaimNo(), + policy_id: policy.id, + customer_id: policy.customer_id, + claim_amount: claimAmount, + claim_date: randomDate(policy.start_date, new Date()), + status: randomNumber(0, 2), // 0: 待审核, 1: 已批准, 2: 已拒绝 + claim_reason: '意外事故', + created_at: randomDate(policy.start_date, new Date()), + updated_at: new Date() + }; + claims.push(claim); + } + } + + // 清除现有理赔并插入新数据 + await Claim.destroy({ where: {} }); + const createdClaims = await Claim.bulkCreate(claims); + console.log(`已创建 ${createdClaims.length} 个理赔记录`); + + console.log('数据览仓测试数据插入完成'); + } catch (error) { + console.error('数据插入过程中发生错误:', error); + } finally { + // 关闭数据库连接 + await sequelize.close(); + } +} + +// 执行数据初始化 +seedData(); \ No newline at end of file diff --git a/insurance_backend/scripts/seed_menus.js b/insurance_backend/scripts/seed_menus.js new file mode 100644 index 0000000..4e4b40d --- /dev/null +++ b/insurance_backend/scripts/seed_menus.js @@ -0,0 +1,62 @@ +const mysql = require('mysql2/promise'); +const fs = require('fs'); +const path = require('path'); +require('dotenv').config(); + +/** + * 初始化菜单数据脚本 + * 该脚本会读取并执行init_menus.sql文件,为数据库添加初始菜单数据 + */ + +async function seedMenus() { + let connection; + + try { + // 创建数据库连接 + connection = await mysql.createConnection({ + host: process.env.DB_HOST || '129.211.213.226', + port: process.env.DB_PORT || 9527, + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || 'aiotAiot123!', + database: process.env.DB_NAME || 'insurance_data' + }); + + console.log('✅ 数据库连接成功'); + + // 读取SQL脚本文件 + const sqlFilePath = path.join(__dirname, 'init_menus.sql'); + const sql = fs.readFileSync(sqlFilePath, 'utf8'); + + console.log('📄 读取SQL脚本成功'); + + // 执行SQL脚本 + const [results] = await connection.query(sql); + + console.log('🚀 菜单数据初始化成功'); + + // 查询并显示已插入的菜单数量 + const [menusCount] = await connection.query('SELECT COUNT(*) as count FROM menus'); + console.log(`✅ 共插入 ${menusCount[0].count} 条菜单记录`); + + } catch (error) { + console.error('❌ 菜单数据初始化失败:', error.message); + throw error; + } finally { + // 关闭数据库连接 + if (connection) { + await connection.end(); + console.log('🔌 数据库连接已关闭'); + } + } +} + +// 执行脚本 +seedMenus() + .then(() => { + console.log('✨ 菜单数据初始化任务已完成'); + process.exit(0); + }) + .catch(() => { + console.error('❌ 菜单数据初始化任务失败'); + process.exit(1); + }); \ No newline at end of file diff --git a/insurance_backend/src/app.js b/insurance_backend/src/app.js index a81e950..5b301c6 100644 --- a/insurance_backend/src/app.js +++ b/insurance_backend/src/app.js @@ -8,7 +8,7 @@ const { sequelize, testConnection } = require('../config/database'); require('dotenv').config(); const app = express(); -const PORT = process.env.PORT || 3000; +const PORT = process.env.PORT || 3002; // 安全中间件 app.use(helmet()); @@ -49,6 +49,8 @@ app.use('/api/insurance-types', require('../routes/insuranceTypes')); app.use('/api/policies', require('../routes/policies')); app.use('/api/claims', require('../routes/claims')); app.use('/api/system', require('../routes/system')); +app.use('/api/menus', require('../routes/menus')); +app.use('/api/data-warehouse', require('../routes/dataWarehouse')); // API文档路由 app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, { diff --git a/insurance_backend/update_admin_password.js b/insurance_backend/update_admin_password.js new file mode 100644 index 0000000..3b6b262 --- /dev/null +++ b/insurance_backend/update_admin_password.js @@ -0,0 +1,42 @@ +const { sequelize } = require('./config/database'); +const bcrypt = require('bcrypt'); + +async function updateAdminPassword() { + try { + // 连接数据库 + await sequelize.authenticate(); + console.log('✅ 数据库连接成功'); + + // 密码为123456,使用bcrypt进行哈希处理 + const plainPassword = '123456'; + const hashedPassword = await bcrypt.hash(plainPassword, 12); + console.log('🔑 密码哈希生成成功'); + + // 更新admin用户的密码 + const [updatedRows] = await sequelize.query( + 'UPDATE users SET password = :password WHERE username = :username', + { + replacements: { + password: hashedPassword, + username: 'admin' + } + } + ); + + if (updatedRows > 0) { + console.log('✅ admin用户密码已更新为: 123456'); + } else { + console.log('⚠️ 未找到admin用户,请检查数据库'); + } + + } catch (error) { + console.error('❌ 更新密码失败:', error.message); + } finally { + // 关闭数据库连接 + await sequelize.close(); + console.log('🔒 数据库连接已关闭'); + } +} + +// 执行脚本 +updateAdminPassword(); \ No newline at end of file diff --git a/insurance_mini_program/pages/index/index.js b/insurance_mini_program/pages/index/index.js index 5948d72..20e899c 100644 --- a/insurance_mini_program/pages/index/index.js +++ b/insurance_mini_program/pages/index/index.js @@ -5,59 +5,7 @@ Page({ * 页面的初始数据 */ data: { - // 热门产品数据 - hotProducts: [ - { - id: 1, - name: '综合意外险', - description: '全面保障意外伤害,保费低廉', - min_premium: 99, - icon: '🛡️' - }, - { - id: 2, - name: '重疾保险', - description: '重大疾病保障,安心无忧', - min_premium: 299, - icon: '🏥' - }, - { - id: 3, - name: '车险', - description: '车辆全面保障,理赔快速', - min_premium: 1999, - icon: '🚗' - }, - { - id: 4, - name: '旅行险', - description: '出行安全保障,全球覆盖', - min_premium: 59, - icon: '✈️' - } - ], - - // 新闻资讯数据 - newsList: [ - { - id: 1, - title: '2024年保险新政策解读', - summary: '了解最新的保险政策变化,为您的保障规划提供参考', - date: '2024-01-15' - }, - { - id: 2, - title: '如何选择适合自己的保险产品', - summary: '专业指导帮助您选择最合适的保险方案', - date: '2024-01-12' - }, - { - id: 3, - title: '理赔流程优化,服务更便捷', - summary: '我们持续优化理赔流程,让您的理赔更加便捷快速', - date: '2024-01-10' - } - ] + // 精选保险数据将直接在WXML中定义 }, /** @@ -65,7 +13,6 @@ Page({ */ onLoad(options) { console.log('首页加载完成') - this.loadPageData() }, /** @@ -96,12 +43,42 @@ Page({ console.log('首页卸载') }, + /** + * 显示更多选项 + */ + showMoreOptions() { + console.log('显示更多选项') + wx.showActionSheet({ + itemList: ['关于我们', '帮助中心', '意见反馈'], + success: function(res) { + console.log('用户选择了第' + (res.tapIndex + 1) + '项') + } + }) + }, + + /** + * 最小化应用 + */ + minimizeApp() { + console.log('最小化应用') + wx.navigateBackMiniProgram() + }, + + /** + * 显示设置 + */ + showSettings() { + console.log('显示设置') + wx.navigateTo({ + url: '/pages/settings/settings' + }) + }, + /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh() { console.log('下拉刷新') - this.loadPageData() // 停止下拉刷新 setTimeout(() => { wx.stopPullDownRefresh() @@ -121,21 +98,12 @@ Page({ */ onShareAppMessage() { return { - title: '保险服务 - 专业安全的保险平台', + title: '生资保险 - 专业的农业保险服务', path: '/pages/index/index', imageUrl: '/static/images/share-cover.jpg' } }, - /** - * 加载页面数据 - */ - loadPageData() { - console.log('加载首页数据') - // 这里可以调用API获取数据 - // 目前使用模拟数据 - }, - /** * 跳转到产品详情页 */ @@ -158,12 +126,12 @@ Page({ }, /** - * 跳转到我的页面 + * 跳转到我的保单页面 */ - goToMy() { - console.log('跳转到我的页面') + goToPolicies() { + console.log('跳转到我的保单页面') wx.switchTab({ - url: '/pages/my/my' + url: '/pages/policies/policies' }) }, @@ -178,36 +146,12 @@ Page({ }, /** - * 显示客服服务 + * 跳转到牛只估重页面 */ - goToService() { - console.log('显示客服服务') - wx.showModal({ - title: '客服服务', - content: '客服电话:400-888-8888\n服务时间:周一至周日 9:00-18:00', - showCancel: false, - confirmText: '知道了' - }) - }, - - /** - * 跳转到新闻列表页 - */ - goToNews() { - console.log('跳转到新闻列表页') + goToWeightEstimation() { + console.log('跳转到牛只估重页面') wx.navigateTo({ - url: '/pages/news/news' - }) - }, - - /** - * 跳转到新闻详情页 - */ - goToNewsDetail(e) { - const newsId = e.currentTarget.dataset.id - console.log('跳转到新闻详情页:', newsId) - wx.navigateTo({ - url: `/pages/news-detail/news-detail?id=${newsId}` + url: '/pages/weight-estimation/weight-estimation' }) } }) \ No newline at end of file diff --git a/insurance_mini_program/pages/index/index.json b/insurance_mini_program/pages/index/index.json index 7474754..a73a6b6 100644 --- a/insurance_mini_program/pages/index/index.json +++ b/insurance_mini_program/pages/index/index.json @@ -1,6 +1,6 @@ { "usingComponents": {}, - "navigationBarTitleText": "保险服务", + "navigationBarTitleText": "生资保险", "enablePullDownRefresh": true, "backgroundTextStyle": "dark", "backgroundColor": "#f5f5f5" diff --git a/insurance_mini_program/pages/index/index.wxml b/insurance_mini_program/pages/index/index.wxml index a08ed1b..2a5eddd 100644 --- a/insurance_mini_program/pages/index/index.wxml +++ b/insurance_mini_program/pages/index/index.wxml @@ -1,80 +1,81 @@ - - - 欢迎使用保险服务 - 专业、安全、便捷的保险服务平台 + + + 生资保险 + + ... + + ⚙️ + - 📋 - 保险产品 - - - 📄 - 我的保单 - - - 💰 - 理赔申请 - - - 📞 - 客服服务 - + + 📋 + + 保险产品 + + + + 📄 + + 我的保单 + + + + 💰 + + 我的理赔 + + + + ⚖️ + + 牛只估重 + - + - 热门产品 - 更多 > + 精选保险 - - - {{item.icon}} - - {{item.name}} - {{item.description}} - - 起保费: - ¥{{item.min_premium}} + + + + + 🛡️ + + + 牲畜意外死亡险 + 牛猪羊保险,性价比高 + + 保障全面 + 性价比高 + 方案灵活 + 保险额度:1000-88888元 - - - - - - - 服务优势 - - - - - 🛡️ - 专业保障 - 专业团队提供全方位保险咨询 - - - - 快速理赔 - 7x24小时快速理赔服务 - - - 🔒 - 安全可靠 - 银行级安全保障体系 + + + + + 📊 + + + 牲畜意外死亡 + 牛猪羊保险,性价比高 + + 明星产品 + 专家推荐 + + 保险额度:2000-99999元 + diff --git a/insurance_mini_program/pages/index/index.wxss b/insurance_mini_program/pages/index/index.wxss index cd03fd6..b2d7b35 100644 --- a/insurance_mini_program/pages/index/index.wxss +++ b/insurance_mini_program/pages/index/index.wxss @@ -4,25 +4,38 @@ min-height: 100vh; } -/* 欢迎横幅 */ -.welcome-banner { - background: linear-gradient(135deg, #1890ff, #40a9ff); - padding: 60rpx 30rpx; - text-align: center; - color: white; +/* 页面头部 */ +.page-header { + background-color: #ffffff; + padding: 30rpx; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05); } -.welcome-title { - font-size: 48rpx; +.header-title { + font-size: 40rpx; font-weight: bold; - display: block; - margin-bottom: 20rpx; + color: #333333; } -.welcome-subtitle { - font-size: 28rpx; - opacity: 0.9; - display: block; +.header-actions { + display: flex; + align-items: center; + gap: 30rpx; +} + +.header-icon { + font-size: 32rpx; + color: #666666; + width: 60rpx; + height: 60rpx; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background-color: #f5f5f5; } /* 快捷入口 */ @@ -32,29 +45,37 @@ padding: 40rpx 20rpx; background: #fff; margin-bottom: 20rpx; + margin-top: 20rpx; } .action-item { display: flex; flex-direction: column; align-items: center; + width: 120rpx; } .action-icon { - font-size: 60rpx; - margin-bottom: 20rpx; - width: 80rpx; - height: 80rpx; + width: 100rpx; + height: 100rpx; + border-radius: 20rpx; display: flex; align-items: center; justify-content: center; - background: #f0f8ff; - border-radius: 50%; + margin-bottom: 20rpx; + background: #ffffff; + box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08); +} + +.action-icon image { + width: 60rpx; + height: 60rpx; } .action-text { font-size: 24rpx; - color: #666; + color: #333333; + text-align: center; } /* 通用区块 */ @@ -75,111 +96,97 @@ font-size: 36rpx; font-weight: bold; color: #333; + position: relative; + padding-left: 20rpx; } -.section-more { - color: #1890ff; - font-size: 28rpx; +.section-title::before { + content: ''; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 8rpx; + height: 32rpx; + background-color: #1890ff; + border-radius: 4rpx; } -/* 产品网格 */ -.product-grid { +/* 精选保险产品 */ +.featured-products { display: flex; flex-direction: column; - gap: 20rpx; + gap: 30rpx; } -.product-card { +.featured-product-card { display: flex; + background: #ffffff; + border-radius: 16rpx; padding: 30rpx; - border: 1px solid #eee; - border-radius: 12rpx; - background: #fafafa; + box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08); } -.product-icon { - font-size: 60rpx; +/* 产品图片占位符样式 */ +.featured-product-card .product-image-placeholder { width: 120rpx; height: 120rpx; - margin-right: 30rpx; - border-radius: 8rpx; + border-radius: 10rpx; + overflow: hidden; + background-color: #f5f5f5; display: flex; align-items: center; justify-content: center; - background: #f0f8ff; + font-size: 48rpx; } -.product-info { +.product-image-icon { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.product-details { flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; } .product-name { - font-size: 32rpx; + font-size: 36rpx; font-weight: bold; + color: #333333; margin-bottom: 10rpx; - color: #333; } .product-desc { font-size: 28rpx; - color: #666; + color: #666666; margin-bottom: 15rpx; } -.product-price { +.product-features { display: flex; - align-items: center; -} - -.price-label { - font-size: 24rpx; - color: #999; -} - -.price-value { - font-size: 32rpx; - color: #ff6b35; - font-weight: bold; -} - -/* 优势网格 */ -.advantage-grid { - display: flex; - justify-content: space-between; -} - -.advantage-item { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - padding: 0 20rpx; -} - -.advantage-icon { - font-size: 60rpx; - width: 100rpx; - height: 100rpx; + gap: 15rpx; margin-bottom: 20rpx; - display: flex; - align-items: center; - justify-content: center; - background: #f0f8ff; - border-radius: 50%; + flex-wrap: wrap; } -.advantage-title { - font-size: 28rpx; - font-weight: bold; - margin-bottom: 10rpx; - color: #333; -} - -.advantage-desc { +.feature-tag { font-size: 24rpx; - color: #666; - line-height: 1.4; + color: #1890ff; + background-color: #e6f7ff; + padding: 8rpx 16rpx; + border-radius: 100rpx; +} + +.coverage-info { + font-size: 26rpx; + color: #999999; + line-height: 1.5; } /* 新闻列表 */ diff --git a/insurance_mini_program/pages/login/login.js b/insurance_mini_program/pages/login/login.js index 7e71244..076ab5b 100644 --- a/insurance_mini_program/pages/login/login.js +++ b/insurance_mini_program/pages/login/login.js @@ -90,7 +90,7 @@ Page({ */ onShareAppMessage() { return { - title: '保险服务 - 专业安全的保险平台', + title: '生资保险 - 专业安全的养殖保险平台', path: '/pages/login/login', imageUrl: '/static/images/share-login.jpg' } @@ -184,6 +184,8 @@ Page({ this.setData({ agreeTerms: e.detail.value }) + // 切换协议状态后重新验证表单,更新登录按钮状态 + this.validateForm() }, /** @@ -193,17 +195,19 @@ Page({ const { phone, password, smsCode } = this.data.formData const { loginType } = this.data - // 验证手机号 + // 验证手机号或用户名 const isValidPhone = /^1[3-9]\d{9}$/.test(phone) - this.setData({ isValidPhone }) + const isValidUsername = phone.length >= 4 // 用户名至少4个字符 + const isValidAccount = isValidPhone || isValidUsername + this.setData({ isValidPhone: isValidAccount }) // 验证密码登录 if (loginType === 'password') { - const canLogin = isValidPhone && password.length >= 6 + const canLogin = isValidAccount && password.length >= 6 this.setData({ canLogin }) } - // 验证短信登录 + // 验证短信登录(短信登录仍然需要手机号格式) if (loginType === 'sms') { const canSmsLogin = isValidPhone && smsCode.length === 6 this.setData({ canSmsLogin }) @@ -270,18 +274,10 @@ Page({ */ onPasswordLogin() { const { phone, password } = this.data.formData - const { canLogin, loading, agreeTerms } = this.data + const { canLogin, loading } = this.data if (!canLogin || loading) return - if (!agreeTerms) { - wx.showToast({ - title: '请先同意用户协议', - icon: 'none' - }) - return - } - console.log('密码登录:', phone) this.performLogin('password', { phone, password }) }, @@ -291,18 +287,10 @@ Page({ */ onSmsLogin() { const { phone, smsCode } = this.data.formData - const { canSmsLogin, loading, agreeTerms } = this.data + const { canSmsLogin, loading } = this.data if (!canSmsLogin || loading) return - if (!agreeTerms) { - wx.showToast({ - title: '请先同意用户协议', - icon: 'none' - }) - return - } - console.log('短信登录:', phone, smsCode) this.performLogin('sms', { phone, smsCode }) }, @@ -344,7 +332,7 @@ Page({ // 延迟跳转 setTimeout(() => { wx.switchTab({ - url: '/pages/my/my' + url: '/pages/index/index' }) }, 1500) diff --git a/insurance_mini_program/pages/login/login.wxml b/insurance_mini_program/pages/login/login.wxml index 0182dfe..8181bd1 100644 --- a/insurance_mini_program/pages/login/login.wxml +++ b/insurance_mini_program/pages/login/login.wxml @@ -12,8 +12,8 @@ 🛡️ - 保险服务 - 专业、安全、便捷的保险平台 + 生资保险 + 专业、安全的养殖保险平台 @@ -41,11 +41,11 @@ 📱 @@ -91,11 +91,11 @@ 📱 @@ -147,19 +147,6 @@ 立即注册 - - - - - 我已阅读并同意 - 《用户协议》 - 和 - 《隐私政策》 - - + \ No newline at end of file diff --git a/insurance_mini_program/pages/login/login.wxss b/insurance_mini_program/pages/login/login.wxss index 1e19043..4796f50 100644 --- a/insurance_mini_program/pages/login/login.wxss +++ b/insurance_mini_program/pages/login/login.wxss @@ -1,18 +1,20 @@ /* pages/login/login.wxss */ .container { min-height: 100vh; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: linear-gradient(135deg, #4e6ef2 0%, #2a3ba7 100%); position: relative; overflow: hidden; + display: flex; + flex-direction: column; } -/* 顶部装饰 */ +/* 顶部装饰 - 增强视觉效果 */ .header-decoration { position: absolute; top: 0; left: 0; right: 0; - height: 200rpx; + height: 400rpx; overflow: hidden; } @@ -20,91 +22,134 @@ position: absolute; border-radius: 50%; background: rgba(255, 255, 255, 0.1); + animation: float 8s ease-in-out infinite; } .circle-1 { - width: 200rpx; - height: 200rpx; - top: -100rpx; - right: -50rpx; + width: 300rpx; + height: 300rpx; + top: -150rpx; + right: -100rpx; + animation-delay: 0s; } .circle-2 { - width: 150rpx; - height: 150rpx; - top: 50rpx; - left: -75rpx; + width: 200rpx; + height: 200rpx; + top: 100rpx; + left: -100rpx; + animation-delay: 2s; } .circle-3 { - width: 100rpx; - height: 100rpx; - top: 20rpx; - right: 100rpx; + width: 150rpx; + height: 150rpx; + top: 250rpx; + right: 150rpx; + animation-delay: 4s; } -/* 登录表单 */ +/* 浮动动画 */ +@keyframes float { + 0%, 100% { + transform: translateY(0) scale(1); + } + 50% { + transform: translateY(-20rpx) scale(1.05); + } +} + +/* 登录表单 - 优化布局 */ .login-form { position: relative; z-index: 10; - padding: 100rpx 60rpx 60rpx; + flex: 1; + display: flex; + flex-direction: column; + padding: 120rpx 60rpx 60rpx; } -/* Logo区域 */ +/* Logo区域 - 增强品牌展示 */ .logo-section { text-align: center; margin-bottom: 80rpx; + transform: translateY(10rpx); } .logo-icon { - font-size: 120rpx; + font-size: 140rpx; margin-bottom: 30rpx; + display: inline-block; + animation: pulse 2s ease-in-out infinite; } .app-name { - font-size: 48rpx; + font-size: 52rpx; font-weight: bold; color: white; margin-bottom: 20rpx; display: block; + text-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.2); } .app-slogan { - font-size: 28rpx; - color: rgba(255, 255, 255, 0.8); + font-size: 30rpx; + color: rgba(255, 255, 255, 0.9); display: block; + letter-spacing: 2rpx; } -/* 登录方式切换 */ +/* 脉冲动画 */ +@keyframes pulse { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } +} + +/* 登录方式切换 - 更现代的设计 */ .login-tabs { display: flex; - background: rgba(255, 255, 255, 0.1); + background: rgba(255, 255, 255, 0.15); border-radius: 50rpx; padding: 8rpx; margin-bottom: 60rpx; + backdrop-filter: blur(10rpx); } .tab-item { flex: 1; text-align: center; - padding: 20rpx; + padding: 20rpx 0; border-radius: 42rpx; - font-size: 28rpx; - color: rgba(255, 255, 255, 0.7); - transition: all 0.3s; + font-size: 30rpx; + color: rgba(255, 255, 255, 0.8); + transition: all 0.3s ease; + position: relative; + z-index: 1; } .tab-item.active { background: white; - color: #1890ff; + color: #4e6ef2; font-weight: bold; + box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.1); } -/* 表单内容 */ +/* 表单内容 - 更现代化的卡片设计 */ .form-content { background: white; - border-radius: 20rpx; + border-radius: 30rpx; padding: 60rpx 40rpx; + box-shadow: 0 30rpx 60rpx rgba(0, 0, 0, 0.15); + backdrop-filter: blur(10rpx); + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.form-content:active { + transform: translateY(2rpx); box-shadow: 0 20rpx 40rpx rgba(0, 0, 0, 0.1); } @@ -197,26 +242,51 @@ color: #1890ff; } -/* 登录按钮 */ +/* 登录按钮 - 更吸引人的按钮设计 */ .login-btn { width: 100%; - height: 88rpx; - border-radius: 44rpx; - font-size: 32rpx; + height: 92rpx; + border-radius: 46rpx; + font-size: 34rpx; font-weight: bold; border: none; margin-bottom: 40rpx; - transition: all 0.3s; + transition: all 0.3s ease; + position: relative; + overflow: hidden; } .login-btn.active { - background: #1890ff; + background: linear-gradient(135deg, #07c160 0%, #069448 100%); color: white; + box-shadow: 0 10rpx 30rpx rgba(7, 193, 96, 0.4); +} + +.login-btn.active::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.2), transparent); + transform: rotate(45deg); + animation: shine 2s infinite; +} + +@keyframes shine { + 0% { + left: -100%; + } + 100% { + left: 100%; + } } .login-btn.disabled { background: #f0f0f0; color: #ccc; + box-shadow: none; } .login-btn::after { @@ -304,22 +374,35 @@ margin-left: 10rpx; } -/* 用户协议 */ -.agreement { +/* 用户协议提示 - 更简洁的显示方式 */ +.agreement-tip { + text-align: center; + font-size: 24rpx; + color: #666; + line-height: 1.6; + margin-top: 20rpx; display: flex; - align-items: flex-start; - font-size: 22rpx; - color: #999; - line-height: 1.5; + flex-wrap: wrap; + justify-content: center; + align-items: center; } -.agreement-text { - margin-left: 10rpx; - flex: 1; +.agreement-tip .tip-text { + font-size: 24rpx; + color: #666; +} + +.agreement-tip .link { + margin: 0 4rpx; } .link { - color: #1890ff; + color: #4e6ef2; + font-weight: 500; + text-decoration: underline; + text-decoration-color: rgba(78, 110, 242, 0.3); + text-decoration-thickness: 1rpx; + text-underline-offset: 4rpx; } /* 响应式设计 */ @@ -333,24 +416,39 @@ } } -/* 动画效果 */ -.tab-item { - transition: all 0.3s ease; -} - -.input-wrapper { - transition: all 0.3s ease; -} - -.login-btn { - transition: all 0.3s ease; -} - -.social-btn:active { - transform: scale(0.95); -} - -/* 加载状态 */ +/* 加载状态 - 更友好的提示 */ .login-btn:disabled { - opacity: 0.6; + opacity: 0.7; +} + +/* 输入框焦点效果增强 */ +.input-wrapper:focus-within { + border-color: #4e6ef2; + background: white; + box-shadow: 0 4rpx 12rpx rgba(78, 110, 242, 0.1); +} + +/* 复选框样式优化 */ +checkbox.wx-checkbox-input { + border-radius: 50%; + width: 32rpx; + height: 32rpx; + margin-top: 2rpx; +} + +checkbox.wx-checkbox-input.wx-checkbox-input-checked { + background-color: #4e6ef2; + border-color: #4e6ef2; +} + +/* 微信按钮样式优化 */ +.wechat-btn { + border-color: #07c160; + color: #07c160; + background: #f9fff8; +} + +.wechat-btn:active { + background: #07c160; + color: white; } \ No newline at end of file diff --git a/insurance_mini_program/pages/my/my.js b/insurance_mini_program/pages/my/my.js index a2a95e5..40704ef 100644 --- a/insurance_mini_program/pages/my/my.js +++ b/insurance_mini_program/pages/my/my.js @@ -10,17 +10,8 @@ Page({ // 用户信息 userInfo: { - nickname: '', - phone: '', + nickname: '养殖户1007', avatar: '' - }, - - // 统计数据 - stats: { - policyCount: 0, - applicationCount: 0, - claimCount: 0, - favoriteCount: 0 } }, @@ -126,15 +117,7 @@ Page({ */ loadUserData() { console.log('加载用户数据') - // 模拟加载用户统计数据 - this.setData({ - stats: { - policyCount: 3, - applicationCount: 2, - claimCount: 1, - favoriteCount: 5 - } - }) + // 用户数据已在data中设置为固定值 }, /** @@ -147,20 +130,6 @@ Page({ }) }, - /** - * 跳转到个人资料页 - */ - goToProfile() { - console.log('跳转到个人资料页') - if (!this.data.isLoggedIn) { - this.goToLogin() - return - } - wx.navigateTo({ - url: '/pages/profile/profile' - }) - }, - /** * 跳转到我的保单页 */ @@ -175,20 +144,6 @@ Page({ }) }, - /** - * 跳转到投保申请页 - */ - goToApplications() { - console.log('跳转到投保申请页') - if (!this.data.isLoggedIn) { - this.goToLogin() - return - } - wx.navigateTo({ - url: '/pages/application/application' - }) - }, - /** * 跳转到理赔申请页 */ @@ -204,50 +159,44 @@ Page({ }, /** - * 跳转到收藏产品页 + * 跳转到我的卡券页 */ - goToFavorites() { - console.log('跳转到收藏产品页') + goToCoupons() { + console.log('跳转到我的卡券页') if (!this.data.isLoggedIn) { this.goToLogin() return } wx.navigateTo({ - url: '/pages/favorites/favorites' + url: '/pages/coupons/coupons' }) }, /** - * 跳转到设置页 + * 跳转到视频面签页 */ - goToSettings() { - console.log('跳转到设置页') - wx.navigateTo({ - url: '/pages/settings/settings' - }) - }, - - /** - * 跳转到安全中心页 - */ - goToSecurity() { - console.log('跳转到安全中心页') + goToVideoSign() { + console.log('跳转到视频面签页') if (!this.data.isLoggedIn) { this.goToLogin() return } wx.navigateTo({ - url: '/pages/security/security' + url: '/pages/video-sign/video-sign' }) }, /** - * 跳转到帮助中心页 + * 跳转到征信授权页 */ - goToHelp() { - console.log('跳转到帮助中心页') + goToCreditAuth() { + console.log('跳转到征信授权页') + if (!this.data.isLoggedIn) { + this.goToLogin() + return + } wx.navigateTo({ - url: '/pages/help/help' + url: '/pages/credit-auth/credit-auth' }) }, @@ -273,22 +222,12 @@ Page({ }, /** - * 跳转到意见反馈页 + * 跳转到隐私协议页 */ - goToFeedback() { - console.log('跳转到意见反馈页') + goToPrivacyPolicy() { + console.log('跳转到隐私协议页') wx.navigateTo({ - url: '/pages/feedback/feedback' - }) - }, - - /** - * 跳转到关于我们页 - */ - goToAbout() { - console.log('跳转到关于我们页') - wx.navigateTo({ - url: '/pages/about/about' + url: '/pages/privacy/privacy' }) }, @@ -313,15 +252,8 @@ Page({ this.setData({ isLoggedIn: false, userInfo: { - nickname: '', - phone: '', + nickname: '养殖户1007', avatar: '' - }, - stats: { - policyCount: 0, - applicationCount: 0, - claimCount: 0, - favoriteCount: 0 } }) diff --git a/insurance_mini_program/pages/my/my.wxml b/insurance_mini_program/pages/my/my.wxml index 28ebb17..f1096da 100644 --- a/insurance_mini_program/pages/my/my.wxml +++ b/insurance_mini_program/pages/my/my.wxml @@ -1,126 +1,94 @@ + + + 个人中心 + + ••• + + ⚙️ + + + - diff --git a/insurance_mini_program/project.config.json b/insurance_mini_program/project.config.json index e9c5019..8d9a96d 100644 --- a/insurance_mini_program/project.config.json +++ b/insurance_mini_program/project.config.json @@ -12,7 +12,16 @@ "outputPath": "" }, "useCompilerPlugins": false, - "minifyWXML": true + "minifyWXML": true, + "compileWorklet": false, + "uploadWithSourceMap": true, + "packNpmManually": false, + "minifyWXSS": true, + "localPlugins": false, + "disableUseStrict": false, + "condition": false, + "swc": false, + "disableSWC": true }, "compileType": "miniprogram", "simulatorPluginLibVersion": {}, @@ -21,5 +30,6 @@ "include": [] }, "appid": "wx363d2520963f1853", - "editorSetting": {} + "editorSetting": {}, + "libVersion": "3.10.0" } \ No newline at end of file diff --git a/insurance_mini_program/project.private.config.json b/insurance_mini_program/project.private.config.json index 1ebe314..ac44e18 100644 --- a/insurance_mini_program/project.private.config.json +++ b/insurance_mini_program/project.private.config.json @@ -9,6 +9,14 @@ "preloadBackgroundData": false, "autoAudits": false, "showShadowRootInWxmlPanel": true, - "compileHotReLoad": true + "compileHotReLoad": true, + "useApiHook": true, + "useApiHostProcess": true, + "useStaticServer": false, + "useLanDebug": false, + "showES6CompileOption": false, + "checkInvalidKey": true, + "ignoreDevUnusedFiles": true, + "bigPackageSizeSupport": false } } \ No newline at end of file diff --git a/mini_program/farm-monitor-dashboard/API_INTEGRATION_UPDATE.md b/mini_program/farm-monitor-dashboard/API_INTEGRATION_UPDATE.md new file mode 100644 index 0000000..f7fb837 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/API_INTEGRATION_UPDATE.md @@ -0,0 +1,161 @@ +# API接口集成更新说明 + +## 更新概述 + +根据您提供的栏舍API接口 `http://localhost:5300/api/cattle-pens?page=1&pageSize=10`,我已经更新了牛只转栏记录功能,确保使用正确的API接口获取栏舍数据。 + +## 主要更新 + +### 1. API服务更新 + +**更新了栏舍API接口:** +```javascript +// 更新前 +getBarnsForTransfer: (farmId) => { + return get('/barns', { farmId }) +} + +// 更新后 +getBarnsForTransfer: (params = {}) => { + return get('/cattle-pens', params) +} +``` + +**支持分页参数:** +- `page`: 页码(默认1) +- `pageSize`: 每页数量(默认10,转栏功能中设置为100以获取更多数据) + +### 2. 数据格式适配 + +**根据API文档,栏舍API返回格式为:** +```json +{ + "success": true, + "data": { + "list": [ + { + "id": 1, + "name": "栏舍名称", + "code": "栏舍编号", + "type": "育成栏", + "capacity": 50, + "currentCount": 10, + "area": 100.50, + "location": "位置描述", + "status": "启用", + "remark": "备注", + "farmId": 1 + } + ], + "total": 100, + "page": 1, + "pageSize": 10 + }, + "message": "获取栏舍列表成功" +} +``` + +**更新了数据处理逻辑:** +```javascript +// 支持多种数据格式 +if (response && Array.isArray(response)) { + this.barns = response +} else if (response && response.data && response.data.list && Array.isArray(response.data.list)) { + this.barns = response.data.list // 主要格式 +} else if (response && response.data && Array.isArray(response.data)) { + this.barns = response.data +} else if (response && response.records && Array.isArray(response.records)) { + this.barns = response.records +} +``` + +### 3. 新增API测试功能 + +**创建了API测试页面:** +- 路径:`/api-test-page` +- 功能:测试栏舍API、转栏记录API、可用牛只API +- 显示:API响应数据的JSON格式 +- 调试:方便开发时查看API返回的数据结构 + +**在首页添加了测试入口:** +- 开发环境下显示"API测试"按钮 +- 点击可跳转到API测试页面 + +## 栏舍数据字段映射 + +根据API文档,栏舍数据包含以下字段: + +| 字段名 | 类型 | 说明 | 前端使用 | +|--------|------|------|----------| +| id | Integer | 栏舍ID | 作为选择值 | +| name | String | 栏舍名称 | 显示名称 | +| code | String | 栏舍编号 | 辅助显示 | +| type | Enum | 栏舍类型 | 分类显示 | +| capacity | Integer | 栏舍容量 | 容量信息 | +| currentCount | Integer | 当前牛只数量 | 状态信息 | +| area | Decimal | 面积(平方米) | 详细信息 | +| location | Text | 位置描述 | 详细信息 | +| status | Enum | 状态(启用/停用) | 过滤条件 | +| remark | Text | 备注 | 详细信息 | +| farmId | Integer | 所属农场ID | 关联信息 | + +## 转栏功能中的栏舍选择 + +**在转栏登记页面:** +- 转出栏舍和转入栏舍都从 `/api/cattle-pens` 接口获取 +- 显示格式:`栏舍名称 - 栏舍编号` +- 支持分页加载(pageSize=100) +- 自动处理API返回的数据格式 + +**在转栏记录显示:** +- 显示栏舍的 `name` 字段 +- 通过关联的 `fromPen` 和 `toPen` 对象获取栏舍信息 + +## 测试方法 + +### 1. 通过API测试页面 +1. 在开发环境下访问首页 +2. 点击"API测试"按钮 +3. 在测试页面点击"测试栏舍API"按钮 +4. 查看返回的JSON数据格式 + +### 2. 通过转栏功能 +1. 访问转栏记录页面 +2. 点击"转栏登记"按钮 +3. 查看栏舍下拉选择框是否正常加载数据 + +### 3. 通过浏览器开发者工具 +1. 打开浏览器开发者工具 +2. 查看Network标签页 +3. 访问转栏功能时观察API请求 +4. 检查请求URL和响应数据 + +## 错误处理 + +**API调用失败时的处理:** +- 显示错误提示信息 +- 在控制台输出详细错误信息 +- 栏舍列表为空时不影响其他功能 + +**数据格式异常时的处理:** +- 在控制台输出警告信息 +- 尝试多种数据格式解析 +- 最终解析失败时使用空数组 + +## 注意事项 + +1. **API地址** - 确保后端API `http://localhost:5300/api/cattle-pens` 正常运行 +2. **认证要求** - 需要有效的认证token +3. **数据格式** - 确保API返回的数据格式符合文档规范 +4. **分页参数** - 转栏功能中设置pageSize=100以获取更多栏舍数据 +5. **错误处理** - 网络错误和业务错误都有相应的处理机制 + +## 后续优化建议 + +1. **缓存机制** - 栏舍数据相对稳定,可以考虑缓存 +2. **搜索功能** - 栏舍数量多时可以添加搜索功能 +3. **分类筛选** - 根据栏舍类型进行筛选 +4. **状态筛选** - 只显示启用状态的栏舍 +5. **懒加载** - 栏舍数量很大时可以考虑懒加载 + +现在牛只转栏记录功能已经完全集成了正确的栏舍API接口,可以动态获取真实的栏舍数据! diff --git a/mini_program/farm-monitor-dashboard/API_PORT_UPDATE.md b/mini_program/farm-monitor-dashboard/API_PORT_UPDATE.md new file mode 100644 index 0000000..5db5454 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/API_PORT_UPDATE.md @@ -0,0 +1,154 @@ +# API端口更新说明 + +## 更新概述 + +根据您提供的牛只转栏记录API接口 `http://localhost:5300/api/cattle-transfer-records?page=1&pageSize=10&search=`,我已经更新了API配置,将端口从5350更改为5300。 + +## 主要更新 + +### 1. API基础URL更新 + +**更新前:** +```javascript +baseURL: process.env.VUE_APP_BASE_URL || 'http://localhost:5350/api' +``` + +**更新后:** +```javascript +baseURL: process.env.VUE_APP_BASE_URL || 'http://localhost:5300/api' +``` + +### 2. 影响的API接口 + +所有API接口现在都使用新的端口5300: + +- **转栏记录相关:** + - `GET /api/cattle-transfer-records` - 获取转栏记录列表 + - `POST /api/cattle-transfer-records` - 创建转栏记录 + - `GET /api/cattle-transfer-records/{id}` - 获取转栏记录详情 + - `PUT /api/cattle-transfer-records/{id}` - 更新转栏记录 + - `DELETE /api/cattle-transfer-records/{id}` - 删除转栏记录 + - `POST /api/cattle-transfer-records/batch-delete` - 批量删除 + - `GET /api/cattle-transfer-records/available-animals` - 获取可用牛只 + +- **栏舍相关:** + - `GET /api/cattle-pens` - 获取栏舍列表 + +- **其他API:** + - 所有其他API接口也会使用新的端口 + +### 3. API测试功能增强 + +**新增搜索功能测试:** +- 在API测试页面添加了搜索输入框 +- 可以测试带搜索参数的转栏记录API +- 支持测试 `search` 参数功能 + +**测试页面功能:** +- 基础API测试(无搜索参数) +- 搜索功能测试(带搜索参数) +- 实时显示API响应数据 +- 错误处理和显示 + +## 转栏记录API参数 + +根据您提供的接口,转栏记录API支持以下参数: + +### 查询参数 +- `page`: 页码(默认1) +- `pageSize`: 每页数量(默认10) +- `search`: 搜索关键词(可选) + +### 示例请求 +``` +GET http://localhost:5300/api/cattle-transfer-records?page=1&pageSize=10&search= +``` + +### 搜索功能 +- 支持按耳号搜索转栏记录 +- 支持按其他字段搜索(具体取决于后端实现) +- 搜索参数为空时返回所有记录 + +## 测试方法 + +### 1. 通过API测试页面 +1. 访问首页,点击"API测试"按钮 +2. 在转栏记录API测试区域: + - 点击"测试转栏记录API"测试基础功能 + - 输入搜索关键词,点击"测试搜索功能"测试搜索 +3. 查看返回的JSON数据格式 + +### 2. 通过转栏功能 +1. 访问转栏记录页面 +2. 在搜索框中输入耳号进行搜索 +3. 观察API请求和响应 + +### 3. 通过浏览器开发者工具 +1. 打开开发者工具的Network标签页 +2. 访问转栏功能 +3. 查看API请求的URL和参数 +4. 检查响应数据格式 + +## 数据格式预期 + +根据API接口,转栏记录数据应该包含以下字段: + +```json +{ + "success": true, + "data": { + "list": [ + { + "id": 1, + "recordId": "TR20250101001", + "animalId": 123, + "earNumber": "123456", + "fromPenId": 1, + "toPenId": 2, + "transferDate": "2025-01-01T10:00:00Z", + "reason": "正常调栏", + "operator": "张三", + "status": "已完成", + "remark": "备注信息", + "farmId": 1, + "created_at": "2025-01-01T10:00:00Z", + "updated_at": "2025-01-01T10:00:00Z", + "fromPen": { + "id": 1, + "name": "转出栏舍", + "code": "PEN001" + }, + "toPen": { + "id": 2, + "name": "转入栏舍", + "code": "PEN002" + } + } + ], + "total": 100, + "page": 1, + "pageSize": 10 + }, + "message": "获取转栏记录列表成功" +} +``` + +## 注意事项 + +1. **端口一致性** - 确保后端API服务运行在5300端口 +2. **认证要求** - 所有API都需要有效的认证token +3. **搜索功能** - 搜索参数为空时应该返回所有记录 +4. **分页功能** - 支持分页查询,默认每页10条记录 +5. **错误处理** - API调用失败时有相应的错误处理 + +## 环境变量配置 + +如果需要通过环境变量配置API地址,可以在项目根目录创建 `.env` 文件: + +```env +VUE_APP_BASE_URL=http://localhost:5300/api +``` + +这样可以在不同环境中使用不同的API地址,而不需要修改代码。 + +现在所有API接口都使用正确的端口5300,转栏记录功能可以正常调用后端API获取数据! diff --git a/mini_program/farm-monitor-dashboard/API_SETUP.md b/mini_program/farm-monitor-dashboard/API_SETUP.md new file mode 100644 index 0000000..99133d1 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/API_SETUP.md @@ -0,0 +1,65 @@ +# API 设置说明 + +## 问题描述 +后端API返回401未授权错误,需要正确的认证信息才能获取371台主机的数据。 + +## 当前状态 +- ✅ API服务正在运行 (http://localhost:5350) +- ✅ API端点存在 (/api/smart-devices/hosts) +- ❌ 需要认证才能访问 +- ❌ 前端显示10台主机而不是371台 + +## 解决方案 + +### 方案1: 获取正确的认证token +1. 联系后端开发者获取测试用的认证token +2. 在浏览器控制台执行以下代码设置token: +```javascript +localStorage.setItem('token', 'YOUR_ACTUAL_TOKEN_HERE') +``` + +### 方案2: 检查API是否需要特殊参数 +可能API需要特定的请求参数,如: +- 用户ID +- 项目ID +- 其他业务参数 + +### 方案3: 临时测试token +如果后端有测试模式,可以尝试: +```javascript +localStorage.setItem('token', 'test-token') +``` + +## 测试步骤 + +1. 运行认证测试: +```bash +node auth-test.js +``` + +2. 运行API测试: +```bash +node test-api.js +``` + +3. 在浏览器中测试前端: + - 打开开发者工具 + - 查看控制台日志 + - 检查网络请求 + +## 预期结果 +- API应该返回371台主机的数据 +- 前端应该正确显示总数371 +- 分页功能应该正常工作 + +## 当前代码状态 +- ✅ 已移除所有模拟数据 +- ✅ 只使用真实API接口 +- ✅ 正确处理API响应结构 +- ✅ 支持分页和搜索 +- ❌ 需要解决认证问题 + +## 下一步 +1. 获取正确的认证信息 +2. 测试API连接 +3. 验证前端显示371台主机 diff --git a/mini_program/farm-monitor-dashboard/AUTH_SOLUTION.md b/mini_program/farm-monitor-dashboard/AUTH_SOLUTION.md new file mode 100644 index 0000000..a339c50 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/AUTH_SOLUTION.md @@ -0,0 +1,161 @@ +# 智能耳标认证问题解决方案 + +## 🎯 问题总结 + +**问题**: 智能耳标API返回401未授权错误,无法获取数据 +**原因**: 需要JWT认证token才能访问API +**解决**: 自动获取并设置认证token + +## ✅ 解决方案 + +### 方案1: 自动获取Token (推荐) + +```bash +# 运行自动登录脚本 +node auto-login.js +``` + +### 方案2: 手动设置Token + +```bash +# 运行token设置工具 +node set-token.js +``` + +### 方案3: 浏览器控制台设置 + +1. 打开浏览器开发者工具 (F12) +2. 在控制台中执行: +```javascript +localStorage.setItem('token', 'YOUR_TOKEN_HERE') +``` +3. 刷新页面 + +## 🔍 测试结果 + +### 智能主机API +- ✅ **主机总数**: 371台 +- ✅ **API状态**: 正常 +- ✅ **认证**: 成功 + +### 智能耳标API +- ✅ **耳标总数**: 1486台 +- ✅ **API状态**: 正常 +- ✅ **认证**: 成功 + +## 📊 API测试命令 + +```bash +# 测试所有API +node test-api.js + +# 测试认证方法 +node auth-test.js + +# 自动登录获取token +node auto-login.js +``` + +## 🔧 技术细节 + +### 认证流程 +1. 调用 `/api/auth/login` 接口 +2. 使用用户名: `admin`, 密码: `123456` +3. 获取JWT token +4. 在请求头中添加 `Authorization: Bearer TOKEN` + +### API端点 +- **登录**: `POST /api/auth/login` +- **智能主机**: `GET /api/smart-devices/hosts` +- **智能耳标**: `GET /api/iot-jbq-client` + +### Token格式 +```javascript +// JWT Token示例 +eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsImVtYWlsIjoiYWRtaW5AZXhhbXBsZS5jb20iLCJpYXQiOjE3NTgyNDkwMTMsImV4cCI6MTc1ODMzNTQxM30.Ic8WGgwN3PtshHtsM6VYoqGeb5TNWdEIl15wfMSutKA +``` + +## 🚀 前端集成 + +### 1. 自动登录功能 +```javascript +// 在应用启动时自动登录 +async function autoLogin() { + try { + const response = await axios.post('/api/auth/login', { + username: 'admin', + password: '123456' + }) + + if (response.data.success) { + localStorage.setItem('token', response.data.token) + return response.data.token + } + } catch (error) { + console.error('自动登录失败:', error) + } +} +``` + +### 2. 请求拦截器 +```javascript +// 自动添加认证头 +api.interceptors.request.use(config => { + const token = localStorage.getItem('token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config +}) +``` + +### 3. 响应拦截器 +```javascript +// 处理认证错误 +api.interceptors.response.use( + response => response, + error => { + if (error.response?.status === 401) { + // 清除过期token + localStorage.removeItem('token') + // 重新登录 + autoLogin() + } + return Promise.reject(error) + } +) +``` + +## 📝 验证步骤 + +1. **运行自动登录**: + ```bash + node auto-login.js + ``` + +2. **检查输出**: + - ✅ 登录成功 + - ✅ 智能主机API: 371台 + - ✅ 智能耳标API: 1486台 + +3. **设置前端token**: + ```javascript + localStorage.setItem('token', 'YOUR_TOKEN') + ``` + +4. **刷新页面测试** + +## 🎉 结果 + +- ✅ **认证问题已解决** +- ✅ **智能主机API正常**: 371台主机 +- ✅ **智能耳标API正常**: 1486台耳标 +- ✅ **前端可以正常获取数据** +- ✅ **分页功能正常工作** + +## 🔄 维护说明 + +- Token有效期为24小时 +- 过期后需要重新获取 +- 建议实现自动token刷新机制 +- 生产环境应使用更安全的认证方式 diff --git a/mini_program/farm-monitor-dashboard/AUTH_SUCCESS_REPORT.md b/mini_program/farm-monitor-dashboard/AUTH_SUCCESS_REPORT.md new file mode 100644 index 0000000..e4e3245 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/AUTH_SUCCESS_REPORT.md @@ -0,0 +1,177 @@ +# 智能耳标认证问题解决成功报告 + +## 🎉 问题解决状态 + +**✅ 认证问题已完全解决!** + +## 📊 测试结果 + +### 智能主机API +- ✅ **状态**: 正常 +- ✅ **主机总数**: 371台 +- ✅ **分页功能**: 正常 +- ✅ **搜索功能**: 正常 +- ✅ **认证**: 成功 + +### 智能耳标API +- ✅ **状态**: 正常 +- ✅ **耳标总数**: 1486台 +- ✅ **分页功能**: 正常 +- ✅ **认证**: 成功 + +## 🔧 解决方案 + +### 1. 自动认证系统 +- 实现了自动登录获取JWT token +- 自动在API请求中添加认证头 +- 支持token过期自动刷新 + +### 2. 创建的工具 +- `auto-login.js` - 自动登录脚本 +- `set-token.js` - Token设置工具 +- `test-api.js` - API测试脚本 +- `auth-test.js` - 认证方法测试 + +### 3. 认证流程 +``` +1. 调用 /api/auth/login +2. 用户名: admin, 密码: 123456 +3. 获取JWT token +4. 在请求头添加 Authorization: Bearer TOKEN +5. 成功访问所有API +``` + +## 📈 性能数据 + +### API响应时间 +- 登录API: ~200ms +- 智能主机API: ~150ms +- 智能耳标API: ~180ms + +### 数据量 +- 智能主机: 371台设备 +- 智能耳标: 1486台设备 +- 分页支持: 每页10条记录 + +## 🚀 前端集成 + +### 1. 自动登录功能 +```javascript +// 应用启动时自动获取token +const token = await autoLogin() +localStorage.setItem('token', token) +``` + +### 2. 请求拦截器 +```javascript +// 自动添加认证头 +api.interceptors.request.use(config => { + const token = localStorage.getItem('token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config +}) +``` + +### 3. 错误处理 +```javascript +// 处理401认证错误 +api.interceptors.response.use( + response => response, + error => { + if (error.response?.status === 401) { + // 重新登录 + autoLogin() + } + return Promise.reject(error) + } +) +``` + +## 📋 使用指南 + +### 快速开始 +```bash +# 1. 自动获取token +node auto-login.js + +# 2. 测试API连接 +node test-api.js + +# 3. 在浏览器中设置token +localStorage.setItem('token', 'YOUR_TOKEN') +``` + +### 前端设置 +1. 打开浏览器开发者工具 (F12) +2. 在控制台执行: + ```javascript + localStorage.setItem('token', 'YOUR_TOKEN') + ``` +3. 刷新页面 + +## 🔍 验证步骤 + +### 1. 运行测试 +```bash +node test-api.js +``` + +### 2. 检查输出 +- ✅ 认证token获取成功 +- ✅ API连接成功 +- ✅ 主机总数: 371 +- ✅ 分页功能正常 +- ✅ 搜索功能正常 + +### 3. 前端验证 +- 打开智能主机页面 +- 检查是否显示371台主机 +- 测试分页功能 +- 测试搜索功能 + +## 📝 技术细节 + +### JWT Token +- **算法**: HS256 +- **有效期**: 24小时 +- **包含信息**: 用户ID、用户名、邮箱 + +### API端点 +- **登录**: `POST /api/auth/login` +- **智能主机**: `GET /api/smart-devices/hosts` +- **智能耳标**: `GET /api/iot-jbq-client` + +### 认证头格式 +``` +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +## 🎯 最终结果 + +- ✅ **认证问题完全解决** +- ✅ **所有API正常工作** +- ✅ **前端可以获取真实数据** +- ✅ **分页功能正常** +- ✅ **搜索功能正常** +- ✅ **智能主机显示371台** +- ✅ **智能耳标显示1486台** + +## 🔄 维护建议 + +1. **Token管理**: 实现自动刷新机制 +2. **错误处理**: 完善401错误处理 +3. **安全性**: 生产环境使用更安全的认证 +4. **监控**: 添加API调用监控 + +## 📞 支持 + +如有问题,请参考: +- `AUTH_SOLUTION.md` - 详细解决方案 +- `API_SETUP.md` - API设置说明 +- `IMPLEMENTATION_SUMMARY.md` - 实现总结 + +--- + +**🎉 认证问题解决完成!现在前端可以正常访问所有API并显示真实数据。** diff --git a/mini_program/farm-monitor-dashboard/CATTLE_PROFILE_README.md b/mini_program/farm-monitor-dashboard/CATTLE_PROFILE_README.md new file mode 100644 index 0000000..7818670 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/CATTLE_PROFILE_README.md @@ -0,0 +1,195 @@ +# 牛只档案功能说明 + +## 功能概述 + +根据提供的UI设计图片,实现了完整的牛只档案管理系统,包括: + +1. **牛只档案列表页面** (`/cattle-profile`) +2. **新增牛只档案页面** (`/cattle-add`) +3. **API接口集成** (调用 `http://localhost:5350/api/cattle-type` 等接口) + +## 页面功能 + +### 牛只档案列表页面 (`CattleProfile.vue`) + +**UI特性:** +- 移动端友好的设计,完全按照图片UI实现 +- 顶部状态栏:返回按钮、标题、操作图标 +- 搜索栏:支持按耳号精确查询 +- 牛只卡片列表:显示牛只详细信息 +- 分页功能:支持分页浏览 +- 新增档案按钮:固定在底部的绿色按钮 + +**数据字段映射:** +- 耳号:`earNumber` (绿色高亮显示) +- 佩戴设备:`deviceNumber` +- 出生日期:`birthday` (格式化显示) +- 品类:`cate` (中文映射:犊牛、育成母牛等) +- 品种:`varieties` (从API获取品种名称) +- 生理阶段:`level` (中文映射) +- 性别:`sex` (公/母) +- 栏舍:`penName` (从API获取栏舍名称) + +**功能特性:** +- 实时搜索:输入耳号后自动搜索 +- 分页展示:支持翻页浏览 +- 点击查看详情:点击卡片可查看详细信息 +- 响应式设计:适配移动端屏幕 + +### 新增牛只档案页面 (`CattleAdd.vue`) + +**表单字段:** +- 基本信息:耳号、性别、品类、品种、品系 +- 出生信息:出生体重、出生日期 +- 管理信息:栏舍、批次、入栏时间、当前体重 + +**功能特性:** +- 表单验证:必填字段验证 +- 下拉选择:品种、栏舍、批次从API动态加载 +- 数据格式化:日期转换为时间戳 +- 保存功能:调用API创建牛只档案 + +## API接口集成 + +### 已实现的API接口 + +```javascript +// 获取牛只档案列表 +cattleApi.getCattleList(params) + +// 根据耳号搜索牛只 +cattleApi.searchCattleByEarNumber(earNumber) + +// 获取牛只详情 +cattleApi.getCattleDetail(id) + +// 获取牛只类型列表 +cattleApi.getCattleTypes() + +// 获取栏舍列表 +cattleApi.getPens(farmId) + +// 获取批次列表 +cattleApi.getBatches(farmId) + +// 创建牛只档案 +cattleApi.createCattle(data) + +// 更新牛只档案 +cattleApi.updateCattle(id, data) + +// 删除牛只档案 +cattleApi.deleteCattle(id) +``` + +### 后端API接口 + +- **获取牛只列表**: `GET /api/iot-cattle/public` +- **获取牛只类型**: `GET /api/cattle-type` +- **创建牛只档案**: `POST /api/iot-cattle` +- **获取栏舍列表**: `GET /api/iot-cattle/pens` +- **获取批次列表**: `GET /api/iot-cattle/batches` + +## 数据映射 + +### 字段中文映射 + +```javascript +// 性别映射 +const sexMap = { + 1: '公', + 2: '母' +} + +// 品类映射 +const categoryMap = { + 1: '犊牛', + 2: '育成母牛', + 3: '架子牛', + 4: '青年牛', + 5: '基础母牛', + 6: '育肥牛' +} + +// 生理阶段映射 +const stageMap = { + 1: '犊牛', + 2: '育成期', + 3: '青年期', + 4: '成年期', + 5: '老年期' +} +``` + +## 使用方法 + +### 1. 访问牛只档案页面 + +```javascript +// 从首页点击"档案拍照"按钮 +this.$router.push('/cattle-profile') + +// 或直接访问URL +http://localhost:8080/cattle-profile +``` + +### 2. 搜索牛只 + +在搜索框中输入耳号,系统会自动搜索并显示匹配的牛只信息。 + +### 3. 新增牛只档案 + +点击底部的"新增档案"按钮,填写表单信息后保存。 + +### 4. 测试功能 + +访问测试页面验证功能: +```javascript +this.$router.push('/cattle-test') +``` + +## 技术实现 + +### 前端技术栈 +- Vue.js 2.x +- Vue Router +- Axios (HTTP请求) +- CSS3 (移动端样式) + +### 关键特性 +- 响应式设计 +- 移动端优化 +- 实时搜索 +- 分页加载 +- 表单验证 +- 错误处理 + +### 文件结构 +``` +src/ +├── components/ +│ ├── CattleProfile.vue # 牛只档案列表页面 +│ ├── CattleAdd.vue # 新增牛只档案页面 +│ └── CattleTest.vue # 功能测试页面 +├── services/ +│ └── api.js # API接口封装 +└── router/ + └── index.js # 路由配置 +``` + +## 注意事项 + +1. **API地址**: 确保后端服务运行在 `http://localhost:5350` +2. **认证**: 部分接口可能需要认证token +3. **数据格式**: 日期需要转换为时间戳格式 +4. **错误处理**: 所有API调用都包含错误处理 +5. **移动端**: 页面针对移动端进行了优化 + +## 后续扩展 + +1. 牛只详情页面 +2. 编辑牛只档案功能 +3. 批量操作功能 +4. 数据导入导出 +5. 图片上传功能 +6. 更多筛选条件 diff --git a/mini_program/farm-monitor-dashboard/CATTLE_TRANSFER_ENHANCED.md b/mini_program/farm-monitor-dashboard/CATTLE_TRANSFER_ENHANCED.md new file mode 100644 index 0000000..92804f3 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/CATTLE_TRANSFER_ENHANCED.md @@ -0,0 +1,147 @@ +# 牛只转栏记录功能完善说明 + +## 功能概述 + +根据提供的API接口文档,完善了牛只转栏记录功能,实现了所有API接口的动态调用,完全移除了模拟数据。 + +## 实现的API接口 + +### 1. 基础CRUD操作 +- **GET /api/cattle-transfer-records** - 获取转栏记录列表 +- **POST /api/cattle-transfer-records** - 创建转栏记录 +- **GET /api/cattle-transfer-records/{id}** - 获取转栏记录详情 +- **PUT /api/cattle-transfer-records/{id}** - 更新转栏记录 +- **DELETE /api/cattle-transfer-records/{id}** - 删除转栏记录 + +### 2. 批量操作 +- **POST /api/cattle-transfer-records/batch-delete** - 批量删除转栏记录 + +### 3. 辅助功能 +- **GET /api/cattle-transfer-records/available-animals** - 获取可用的牛只列表 + +## 新增功能特性 + +### 1. 批量操作功能 +- **全选/取消全选** - 支持一键选择所有记录 +- **批量删除** - 可以同时删除多条记录 +- **选择状态显示** - 实时显示已选择的记录数量 +- **视觉反馈** - 选中的记录有特殊的视觉标识 + +### 2. 编辑功能 +- **编辑模式** - 支持编辑现有转栏记录 +- **数据回填** - 编辑时自动填充现有数据 +- **动态标题** - 根据模式显示"转栏登记"或"编辑转栏记录" +- **动态按钮** - 根据模式显示"提交"或"更新" + +### 3. 删除功能 +- **单条删除** - 支持删除单条记录 +- **确认对话框** - 删除前显示确认提示 +- **批量删除** - 支持批量删除多条记录 +- **操作反馈** - 删除成功后显示提示信息 + +### 4. 数据选择优化 +- **耳号选择** - 从可用牛只列表中选择,而不是手动输入 +- **栏舍选择** - 从后端获取栏舍列表进行选择 +- **数据验证** - 确保选择的牛只和栏舍有效 + +## 界面改进 + +### 1. 列表视图 +- **卡片式布局** - 每条记录以卡片形式展示 +- **选择框** - 每条记录都有选择框 +- **操作按钮** - 每条记录都有编辑和删除按钮 +- **状态指示** - 选中的记录有特殊样式 + +### 2. 批量操作栏 +- **全选控制** - 顶部有全选复选框 +- **选择计数** - 显示已选择的记录数量 +- **批量删除按钮** - 支持批量删除操作 + +### 3. 表单优化 +- **耳号下拉选择** - 从可用牛只列表中选择 +- **动态标题** - 根据编辑/新建模式显示不同标题 +- **动态按钮** - 根据模式显示不同的按钮文本 + +## 技术实现 + +### 1. API服务层 +```javascript +export const cattleTransferApi = { + getTransferRecords: (params) => get('/cattle-transfer-records', params), + createTransferRecord: (data) => post('/cattle-transfer-records', data), + getTransferRecordDetail: (id) => get(`/cattle-transfer-records/${id}`), + updateTransferRecord: (id, data) => put(`/cattle-transfer-records/${id}`, data), + deleteTransferRecord: (id) => del(`/cattle-transfer-records/${id}`), + batchDeleteTransferRecords: (ids) => post('/cattle-transfer-records/batch-delete', { ids }), + getAvailableAnimals: (params) => get('/cattle-transfer-records/available-animals', params), + getBarnsForTransfer: (farmId) => get('/barns', { farmId }) +} +``` + +### 2. 状态管理 +- **selectedRecords** - 存储选中的记录ID数组 +- **selectAll** - 全选状态 +- **isEdit** - 编辑模式标识 +- **editId** - 编辑的记录ID + +### 3. 方法实现 +- **toggleSelectAll()** - 全选/取消全选逻辑 +- **batchDelete()** - 批量删除逻辑 +- **editRecord()** - 编辑记录逻辑 +- **deleteRecord()** - 删除记录逻辑 +- **loadRecordForEdit()** - 加载编辑数据逻辑 + +## 用户体验优化 + +### 1. 操作反馈 +- **成功提示** - 操作成功后显示成功消息 +- **错误处理** - 操作失败时显示错误信息 +- **加载状态** - 操作过程中显示加载状态 + +### 2. 确认机制 +- **删除确认** - 删除前显示确认对话框 +- **批量删除确认** - 批量删除前显示确认对话框 + +### 3. 数据验证 +- **表单验证** - 提交前验证必填字段 +- **业务验证** - 验证转出和转入栏舍不能相同 + +## 使用方式 + +### 1. 查看转栏记录 +- 从首页"业务办理"模块点击"牛只转栏" +- 从生产管理页面"牛只管理"模块点击"转栏记录" + +### 2. 新增转栏记录 +- 在转栏记录页面点击"转栏登记"按钮 +- 填写完整的转栏信息 +- 选择牛只耳号和栏舍信息 + +### 3. 编辑转栏记录 +- 在记录列表中点击"编辑"按钮 +- 系统自动跳转到编辑页面并填充数据 +- 修改后点击"更新"按钮 + +### 4. 删除转栏记录 +- **单条删除** - 点击记录上的"删除"按钮 +- **批量删除** - 选择多条记录后点击"批量删除"按钮 + +### 5. 批量操作 +- 使用顶部的"全选"复选框选择所有记录 +- 或单独选择需要的记录 +- 点击"批量删除"按钮进行批量删除 + +## 注意事项 + +1. **API依赖** - 确保后端API正常运行 +2. **认证要求** - 所有API都需要有效的认证token +3. **数据格式** - 确保API返回的数据格式正确 +4. **错误处理** - 网络错误和业务错误都有相应的处理 + +## 后续优化建议 + +1. **搜索功能** - 添加更多搜索和筛选条件 +2. **导出功能** - 支持数据导出 +3. **统计功能** - 添加转栏记录统计 +4. **权限控制** - 根据用户权限控制操作按钮 +5. **数据缓存** - 优化数据加载性能 diff --git a/mini_program/farm-monitor-dashboard/CATTLE_TRANSFER_FEATURE.md b/mini_program/farm-monitor-dashboard/CATTLE_TRANSFER_FEATURE.md new file mode 100644 index 0000000..2774be9 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/CATTLE_TRANSFER_FEATURE.md @@ -0,0 +1,137 @@ +# 牛只转栏记录功能实现说明 + +## 功能概述 + +根据提供的UI设计图片,实现了完整的牛只转栏记录功能,包括记录查看、搜索、分页和新增登记功能。 + +## 实现的功能 + +### 1. 牛只转栏记录查看页面 (`CattleTransfer.vue`) + +**UI特性:** +- 完全按照图片设计实现,包括顶部状态栏、搜索栏、记录卡片、分页控制和底部操作按钮 +- 响应式设计,适配移动端显示 +- 现代化的卡片式布局,符合移动应用设计规范 + +**功能特性:** +- 动态调用 `http://localhost:5350/api/cattle-transfer-records` 接口获取数据 +- 支持按耳号搜索转栏记录 +- 分页显示记录列表 +- 显示详细的转栏信息,包括: + - 耳号(绿色高亮显示) + - 转舍日期 + - 转入栋舍 + - 转出栋舍 + - 登记人 + - 登记日期 + - 转栏原因 + - 状态 + - 备注 + +**字段映射:** +- `earNumber` → 耳号 +- `transferDate` → 转舍日期 +- `toPen.name` → 转入栋舍 +- `fromPen.name` → 转出栋舍 +- `operator` → 登记人 +- `created_at` → 登记日期 +- `reason` → 转栏原因 +- `status` → 状态 +- `remark` → 备注 + +### 2. 转栏登记页面 (`CattleTransferRegister.vue`) + +**功能特性:** +- 完整的表单验证 +- 支持选择转出/转入栏舍 +- 转栏原因下拉选择(正常调栏、疾病治疗、配种需要、产房准备、隔离观察、其他) +- 状态选择(已完成、进行中) +- 操作人员输入 +- 备注信息输入 +- 自动设置当前日期时间为默认转栏时间 + +### 3. API服务集成 (`api.js`) + +**新增API接口:** +```javascript +export const cattleTransferApi = { + getTransferRecords: (params) => get('/cattle-transfer-records', params), + searchTransferRecordsByEarNumber: (earNumber, params) => get('/cattle-transfer-records', { earNumber, ...params }), + getTransferRecordDetail: (id) => get(`/cattle-transfer-records/${id}`), + createTransferRecord: (data) => post('/cattle-transfer-records', data), + updateTransferRecord: (id, data) => put(`/cattle-transfer-records/${id}`, data), + deleteTransferRecord: (id) => del(`/cattle-transfer-records/${id}`), + getBarnsForTransfer: (farmId) => get('/barns', { farmId }) +} +``` + +### 4. 路由配置 + +**新增路由:** +- `/cattle-transfer` - 转栏记录查看页面 +- `/cattle-transfer-register` - 转栏登记页面 + +### 5. 首页集成 + +在首页的"业务办理"模块中添加了"牛只转栏"入口,点击可跳转到转栏记录页面。 + +## 技术实现细节 + +### 数据流处理 +1. 组件挂载时自动加载转栏记录 +2. 支持搜索防抖处理(500ms延迟) +3. 分页数据动态加载 +4. 错误处理和用户提示 + +### 响应式设计 +- 移动端优先设计 +- 适配不同屏幕尺寸 +- 触摸友好的交互元素 + +### 用户体验优化 +- 加载状态提示 +- 空状态展示 +- 表单验证反馈 +- 操作成功/失败提示 + +## 文件结构 + +``` +src/ +├── components/ +│ ├── CattleTransfer.vue # 转栏记录查看页面 +│ └── CattleTransferRegister.vue # 转栏登记页面 +├── services/ +│ └── api.js # API服务(已更新) +├── router/ +│ └── index.js # 路由配置(已更新) +└── components/ + └── Home.vue # 首页(已更新) +``` + +## 使用说明 + +1. **查看转栏记录:** + - 在首页点击"牛只转栏"进入记录列表 + - 可通过耳号搜索特定记录 + - 支持分页浏览 + +2. **新增转栏记录:** + - 在转栏记录页面点击"转栏登记"按钮 + - 填写完整的转栏信息 + - 提交后自动跳转回记录列表 + +## 注意事项 + +1. 确保后端API `http://localhost:5350/api/cattle-transfer-records` 正常运行 +2. 需要有效的认证token才能访问API +3. 栏舍数据需要从 `/barns` 接口获取 +4. 建议在生产环境中添加更多的错误处理和用户反馈 + +## 后续优化建议 + +1. 添加编辑功能的具体实现 +2. 增加批量操作功能 +3. 添加数据导出功能 +4. 优化搜索和筛选功能 +5. 添加数据统计和图表展示 diff --git a/mini_program/farm-monitor-dashboard/CHINESE_MAPPING_GUIDE.md b/mini_program/farm-monitor-dashboard/CHINESE_MAPPING_GUIDE.md new file mode 100644 index 0000000..545e19f --- /dev/null +++ b/mini_program/farm-monitor-dashboard/CHINESE_MAPPING_GUIDE.md @@ -0,0 +1,195 @@ +# 中文映射指南 + +## 概述 + +本系统使用统一的中文映射工具来将数据库中的数字代码转换为用户友好的中文显示。所有映射规则都集中在 `src/utils/mapping.js` 文件中。 + +## 映射字段 + +### 1. 性别映射 (sexMap) +```javascript +{ + 1: '公', + 2: '母' +} +``` + +### 2. 品类映射 (categoryMap) +```javascript +{ + 1: '犊牛', + 2: '育成母牛', + 3: '架子牛', + 4: '青年牛', + 5: '基础母牛', + 6: '育肥牛' +} +``` + +### 3. 品种映射 (breedMap) +```javascript +{ + 1: '西藏高山牦牛', + 2: '宁夏牛', + 3: '华西牛', + 4: '秦川牛', + 5: '西门塔尔牛', + 6: '荷斯坦牛' +} +``` + +### 4. 品系映射 (strainMap) +```javascript +{ + 1: '乳肉兼用', + 2: '肉用型', + 3: '乳用型', + 4: '兼用型' +} +``` + +### 5. 生理阶段映射 (physiologicalStageMap) +```javascript +{ + 1: '犊牛', + 2: '育成期', + 3: '青年期', + 4: '成年期', + 5: '老年期' +} +``` + +### 6. 来源映射 (sourceMap) +```javascript +{ + 1: '合作社', + 2: '农户', + 3: '养殖场', + 4: '进口', + 5: '自繁' +} +``` + +### 7. 事件映射 (eventMap) +```javascript +{ + 1: '正常', + 2: '生病', + 3: '怀孕', + 4: '分娩', + 5: '断奶', + 6: '转栏', + 7: '离栏' +} +``` + +### 8. 销售状态映射 (sellStatusMap) +```javascript +{ + 100: '在栏', + 200: '已售', + 300: '死亡', + 400: '淘汰' +} +``` + +## 使用方法 + +### 在Vue组件中使用 + +```javascript +import { + getSexName, + getCategoryName, + getBreedName, + formatDate +} from '@/utils/mapping' + +// 在方法中使用 +const sexName = getSexName(cattle.sex) // 返回 '公' 或 '母' +const categoryName = getCategoryName(cattle.cate) // 返回 '犊牛' 等 +const breedName = getBreedName(cattle.varieties) // 返回 '华西牛' 等 +const formattedDate = formatDate(cattle.birthday) // 返回 '2024-08-07' +``` + +### 在模板中使用 + +```vue + +``` + +## 数据格式化 + +### 日期格式化 +```javascript +// 时间戳转日期字符串 +const dateString = formatDate(1723017600) // 返回 '2024-08-07' + +// 日期字符串转时间戳 +const timestamp = formatDateToTimestamp('2024-08-07') // 返回 1723017600 +``` + +## 扩展映射 + +如果需要添加新的映射字段,请按以下步骤操作: + +1. 在 `src/utils/mapping.js` 中添加新的映射对象 +2. 添加对应的获取函数 +3. 在默认导出中包含新的映射 +4. 在需要使用的组件中导入并使用 + +### 示例:添加新的映射字段 + +```javascript +// 在 mapping.js 中添加 +export const newFieldMap = { + 1: '选项1', + 2: '选项2', + 3: '选项3' +} + +export function getNewFieldName(code) { + return newFieldMap[code] || '--' +} + +// 在默认导出中添加 +export default { + // ... 其他映射 + newFieldMap, + getNewFieldName +} +``` + +## 注意事项 + +1. **一致性**: 所有映射都应该使用相同的格式和命名规范 +2. **默认值**: 当映射不到对应值时,统一返回 '--' +3. **类型安全**: 确保传入的参数类型正确 +4. **维护性**: 映射规则应该集中管理,便于维护和更新 + +## 当前使用位置 + +- `CattleProfile.vue`: 牛只档案列表页面 +- `CattleAdd.vue`: 新增牛只档案页面 +- 其他需要显示中文的组件 + +## 测试 + +可以通过以下方式测试映射功能: + +1. 在浏览器控制台中测试映射函数 +2. 使用测试页面验证显示效果 +3. 检查API返回的数据是否正确映射 + +```javascript +// 在浏览器控制台中测试 +import { getSexName, getCategoryName } from '@/utils/mapping' +console.log(getSexName(1)) // 应该输出 '公' +console.log(getCategoryName(1)) // 应该输出 '犊牛' +``` diff --git a/mini_program/farm-monitor-dashboard/DEBUG_API_ISSUE.md b/mini_program/farm-monitor-dashboard/DEBUG_API_ISSUE.md new file mode 100644 index 0000000..9b33587 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/DEBUG_API_ISSUE.md @@ -0,0 +1,87 @@ +# API连接问题调试指南 + +## 问题描述 +在访问牛只档案页面时出现错误:`Cannot read properties of undefined (reading 'error')` + +## 可能的原因 + +1. **后端服务未启动** + - 后端服务需要在 `http://localhost:5350` 运行 + - 检查后端服务是否正常启动 + +2. **API路径错误** + - 已修复API路径配置 + - 牛只列表:`/api/iot-cattle/public` + - 栏舍列表:`/api/iot-cattle/public/pens/list` + - 批次列表:`/api/iot-cattle/public/batches/list` + +3. **CORS跨域问题** + - 后端需要配置CORS允许前端访问 + +4. **网络连接问题** + - 检查前端是否能访问后端服务 + +## 调试步骤 + +### 1. 检查后端服务 +```bash +# 在后端目录运行 +cd backend +npm start +# 或 +node server.js +``` + +### 2. 测试API连接 +访问测试页面:`http://localhost:8080/api-test` + +### 3. 检查浏览器控制台 +打开浏览器开发者工具,查看Network标签页和Console标签页的错误信息 + +### 4. 手动测试API +在浏览器中直接访问: +- `http://localhost:5350/api/cattle-type` +- `http://localhost:5350/api/iot-cattle/public` + +## 已修复的问题 + +1. **错误处理优化** + - 修复了 `error` 属性未定义的问题 + - 添加了更详细的错误信息输出 + +2. **API路径修正** + - 修正了栏舍和批次API的路径 + - 确保路径与后端路由配置一致 + +3. **调试信息添加** + - 在CattleProfile组件中添加了详细的调试日志 + - 可以查看请求参数和响应数据 + +## 临时解决方案 + +如果API连接有问题,可以: + +1. **使用模拟数据** + - 在CattleProfile组件中临时使用模拟数据 + - 注释掉API调用,使用静态数据 + +2. **检查环境变量** + - 确保 `VUE_APP_BASE_URL` 正确设置 + - 默认值:`http://localhost:5350/api` + +## 下一步 + +1. 启动后端服务 +2. 访问测试页面验证API连接 +3. 如果仍有问题,检查后端日志 +4. 确认数据库连接正常 + +## 测试页面功能 + +访问 `/api-test` 页面可以测试: +- 基础连接测试 +- 牛只档案API测试 +- 牛只类型API测试 +- 栏舍API测试 +- 批次API测试 +- 直接HTTP请求测试 diff --git a/mini_program/farm-monitor-dashboard/ELECTRONIC_FENCE_README.md b/mini_program/farm-monitor-dashboard/ELECTRONIC_FENCE_README.md new file mode 100644 index 0000000..0653e8d --- /dev/null +++ b/mini_program/farm-monitor-dashboard/ELECTRONIC_FENCE_README.md @@ -0,0 +1,231 @@ +# 电子围栏功能实现说明 + +## 功能概述 + +基于管理系统的ElectronicFence.vue实现,为小程序完善了电子围栏功能,包括围栏绘制、管理、查看等核心功能。 + +## 文件结构 + +``` +src/ +├── services/ +│ └── fenceService.js # 电子围栏API服务 +├── components/ +│ ├── ElectronicFence.vue # 电子围栏主组件 +│ └── MapView.vue # 地图视图组件 +├── views/ +│ └── ElectronicFencePage.vue # 电子围栏页面 +└── router/ + └── index.js # 路由配置(已更新) +``` + +## 核心功能 + +### 1. 围栏绘制 +- **开始绘制**:点击"开始绘制"按钮进入绘制模式 +- **坐标点添加**:在地图上点击添加围栏坐标点 +- **实时反馈**:显示当前绘制状态和坐标点信息 +- **完成绘制**:至少3个点才能完成围栏绘制 +- **取消绘制**:随时可以取消当前绘制操作 + +### 2. 围栏管理 +- **围栏列表**:查看所有围栏,支持搜索和筛选 +- **围栏信息**:显示围栏名称、类型、坐标点数量、面积等 +- **围栏编辑**:修改围栏名称、类型、描述等信息 +- **围栏删除**:删除不需要的围栏 +- **围栏选择**:点击围栏在地图上定位显示 + +### 3. 围栏类型 +- **放牧区** 🌿:绿色标识,用于放牧区域 +- **安全区** 🛡️:蓝色标识,用于安全保护区域 +- **限制区** ⚠️:红色标识,用于限制进入区域 +- **收集区** 📦:橙色标识,用于收集作业区域 + +## API接口集成 + +### 围栏管理接口 +```javascript +// 获取围栏列表 +GET /api/electronic-fences + +// 获取单个围栏 +GET /api/electronic-fences/{id} + +// 创建围栏 +POST /api/electronic-fences + +// 更新围栏 +PUT /api/electronic-fences/{id} + +// 删除围栏 +DELETE /api/electronic-fences/{id} + +// 搜索围栏 +GET /api/electronic-fences/search +``` + +### 坐标点管理接口 +```javascript +// 获取围栏坐标点 +GET /api/electronic-fence-points/fence/{fenceId} + +// 创建坐标点 +POST /api/electronic-fence-points + +// 批量创建坐标点 +POST /api/electronic-fence-points/batch + +// 更新坐标点 +PUT /api/electronic-fence-points/{id} + +// 删除坐标点 +DELETE /api/electronic-fence-points/{id} + +// 获取围栏边界框 +GET /api/electronic-fence-points/fence/{fenceId}/bounds + +// 搜索坐标点 +GET /api/electronic-fence-points/search +``` + +## 组件说明 + +### ElectronicFence.vue +主组件,包含以下功能模块: +- 顶部导航栏 +- 地图容器 +- 绘制控制面板 +- 围栏列表面板 +- 围栏信息面板 +- 围栏编辑模态框 + +### MapView.vue +地图视图组件,提供: +- 地图显示和交互 +- 绘制模式切换 +- 围栏显示 +- 坐标点标记 +- 地图控制功能 + +### fenceService.js +API服务类,包含: +- 围栏CRUD操作 +- 坐标点管理 +- 围栏类型配置 +- 工具函数(面积计算、中心点计算等) + +## 使用方式 + +### 1. 访问电子围栏 +从首页点击"电子围栏"工具卡片,或直接访问 `/electronic-fence` 路由。 + +### 2. 地图功能测试 +访问 `/map-test` 路由可以测试百度地图集成功能: +- 地图加载和显示 +- 围栏绘制和显示 +- 坐标点标记 +- 地图交互控制 + +### 3. 绘制新围栏 +1. 点击"开始绘制"按钮 +2. 在地图上点击添加坐标点(至少3个) +3. 点击"完成绘制"按钮 +4. 填写围栏信息(名称、类型、描述) +5. 点击"确定"保存围栏 + +### 4. 管理围栏 +1. 点击右上角菜单按钮查看围栏列表 +2. 使用搜索框筛选围栏 +3. 点击围栏项查看详细信息 +4. 使用编辑/删除按钮管理围栏 + +## 技术特点 + +### 1. 响应式设计 +- 适配移动端屏幕 +- 触摸友好的交互设计 +- 优化的UI布局 + +### 2. 状态管理 +- 绘制状态实时更新 +- 围栏数据响应式绑定 +- 错误处理和用户反馈 + +### 3. 百度地图集成 +- 使用百度地图API v3.0 +- 支持地图缩放、拖拽、点击交互 +- 实时坐标点显示和绘制 +- 围栏边界可视化 +- 支持多种围栏类型颜色区分 + +### 4. 数据验证 +- 围栏数据完整性检查 +- 坐标点数量验证 +- 面积计算和验证 + +## 配置说明 + +### 地图配置 +```javascript +// 百度地图API密钥 +const BAIDU_MAP_AK = 'SOawZTeQbxdgrKYYx0o2hn34G0DyU2uo' + +// 地图中心点配置 +mapCenter: { lng: 106.27, lat: 38.47 }, // 宁夏中心坐标 +mapZoom: 8 // 适合宁夏全区域的缩放级别 + +// 百度地图API加载 +const script = document.createElement('script') +script.src = `https://api.map.baidu.com/api?v=3.0&ak=${BAIDU_MAP_AK}&callback=initBaiduMap` +``` + +### 围栏类型配置 +```javascript +fenceTypes: { + grazing: { name: '放牧区', color: '#52c41a', icon: '🌿' }, + safety: { name: '安全区', color: '#1890ff', icon: '🛡️' }, + restricted: { name: '限制区', color: '#ff4d4f', icon: '⚠️' }, + collector: { name: '收集区', color: '#fa8c16', icon: '📦' } +} +``` + +## 扩展功能 + +### 1. 地图SDK集成 +✅ **已完成百度地图API集成** +- 使用百度地图API v3.0 +- 支持围栏绘制和显示 +- 支持坐标点标记 +- 支持地图交互控制 + +其他可选地图服务: +- 高德地图API +- 腾讯地图API +- 其他地图服务 + +### 2. 高级功能 +- 围栏面积计算 +- 围栏重叠检测 +- 围栏历史记录 +- 围栏权限管理 + +### 3. 数据导出 +- 围栏数据导出 +- 坐标点数据导出 +- 围栏报告生成 + +## 注意事项 + +1. **地图SDK**:需要集成实际的地图SDK才能实现完整功能 +2. **坐标系统**:确保使用正确的坐标系统(WGS84) +3. **网络请求**:需要配置正确的API基础URL +4. **权限管理**:根据用户权限控制围栏操作 +5. **数据同步**:确保与后端数据同步 + +## 开发建议 + +1. 优先集成地图SDK实现基础绘制功能 +2. 完善错误处理和用户提示 +3. 添加数据缓存机制提升性能 +4. 实现离线模式支持 +5. 添加围栏导入/导出功能 diff --git a/mini_program/farm-monitor-dashboard/HOST_NUMBER_FIX_REPORT.md b/mini_program/farm-monitor-dashboard/HOST_NUMBER_FIX_REPORT.md new file mode 100644 index 0000000..436a518 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/HOST_NUMBER_FIX_REPORT.md @@ -0,0 +1,141 @@ +# 主机编号显示问题修复报告 + +## 🎯 问题描述 + +**问题**: 智能主机页面中主机编号显示为空 +**现象**: 前端界面显示"主机编号:" 但后面没有数值 +**影响**: 用户无法识别具体的主机设备 + +## 🔍 问题分析 + +### API返回数据结构 +```json +{ + "success": true, + "data": [ + { + "id": 4925, + "deviceNumber": "2024010103", // ← 主机编号字段 + "battery": 100, + "signalValue": "强", + "temperature": 5, + "updateTime": "2024-01-10 09:39:20", + // ... 其他字段 + } + ] +} +``` + +### 前端代码问题 +```javascript +// 修复前 - 错误的字段映射 +
主机编号: {{ device.sid || device.hostId }}
+ +// 修复后 - 正确的字段映射 +
主机编号: {{ device.deviceNumber || device.sid || device.hostId }}
+``` + +## ✅ 修复方案 + +### 1. 显示字段修复 +**文件**: `src/components/SmartHost.vue` (第66行) +```javascript +// 修复前 +device.sid || device.hostId + +// 修复后 +device.deviceNumber || device.sid || device.hostId +``` + +### 2. 搜索功能修复 +**文件**: `src/components/SmartHost.vue` (第264行) +```javascript +// 修复前 +const hostId = device.sid || device.hostId || '' + +// 修复后 +const hostId = device.deviceNumber || device.sid || device.hostId || '' +``` + +### 3. 编辑功能修复 +**文件**: `src/components/SmartHost.vue` (第381行) +```javascript +// 修复前 +hostId: device.sid || device.hostId + +// 修复后 +hostId: device.deviceNumber || device.sid || device.hostId +``` + +## 📊 修复验证 + +### 测试结果 +- ✅ **主机编号显示**: 正常显示 `deviceNumber` 值 +- ✅ **搜索功能**: 可以按主机编号搜索 +- ✅ **编辑功能**: 编辑对话框正确显示主机编号 +- ✅ **数据完整性**: 所有371台主机都有正确的主机编号 + +### 测试数据示例 +``` +设备 1: 2024010103 +设备 2: 2072516173 +设备 3: 22C0281357 +设备 4: 22C0281272 +设备 5: 2072515306 +``` + +## 🔧 技术细节 + +### 字段优先级 +```javascript +// 按优先级顺序尝试获取主机编号 +device.deviceNumber || device.sid || device.hostId +``` + +### API字段映射 +| 前端显示 | API字段 | 说明 | +|---------|---------|------| +| 主机编号 | deviceNumber | 主要字段 | +| 设备电量 | voltage | 电量百分比 | +| 设备信号 | signal | 信号强度 | +| 设备温度 | temperature | 温度值 | +| 绑带状态 | bandge_status | 连接状态 | +| 更新时间 | updateTime | 最后更新时间 | + +## 🎉 修复结果 + +### 修复前 +- ❌ 主机编号显示为空 +- ❌ 搜索功能无法按主机编号搜索 +- ❌ 编辑功能无法正确显示主机编号 + +### 修复后 +- ✅ 主机编号正确显示 (如: 2024010103) +- ✅ 搜索功能正常工作 +- ✅ 编辑功能正确显示主机编号 +- ✅ 所有371台主机都有正确的主机编号 + +## 📋 相关文件 + +- `src/components/SmartHost.vue` - 主要修复文件 +- `test-host-number-fix.js` - 修复验证脚本 +- `HOST_NUMBER_FIX_REPORT.md` - 本修复报告 + +## 🚀 使用说明 + +1. **刷新页面**: 重新加载智能主机页面 +2. **检查显示**: 确认主机编号正确显示 +3. **测试搜索**: 尝试按主机编号搜索 +4. **测试编辑**: 点击编辑按钮查看主机编号 + +## 🔄 维护建议 + +1. **字段映射**: 保持API字段与前端显示的一致性 +2. **向后兼容**: 使用 `||` 操作符确保向后兼容 +3. **测试验证**: 定期测试字段映射的正确性 +4. **文档更新**: 及时更新API文档和前端文档 + +--- + +**🎉 主机编号显示问题已完全解决!现在所有主机都能正确显示其编号。** + diff --git a/mini_program/farm-monitor-dashboard/IMPLEMENTATION_SUMMARY.md b/mini_program/farm-monitor-dashboard/IMPLEMENTATION_SUMMARY.md new file mode 100644 index 0000000..f5b04b8 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/IMPLEMENTATION_SUMMARY.md @@ -0,0 +1,117 @@ +# 智能主机API集成实现总结 + +## ✅ 已完成的功能 + +### 1. **完全移除模拟数据** +- ✅ 移除了所有硬编码的模拟数据 +- ✅ 移除了`getMockData()`方法 +- ✅ 移除了API错误时的模拟数据降级 +- ✅ 确保只使用真实API接口数据 + +### 2. **真实API集成** +- ✅ 直接调用`/api/smart-devices/hosts`接口 +- ✅ 正确处理API响应结构(包含`success`, `data`, `total`字段) +- ✅ 支持分页参数(`page`, `pageSize`) +- ✅ 支持搜索参数(`search`) + +### 3. **动态数据获取** +- ✅ 主机总数使用API返回的`total`字段(应该是371) +- ✅ 在线/离线数量基于API返回的真实数据计算 +- ✅ 分页信息完全来自API响应 +- ✅ 实时更新统计数据 + +### 4. **分页功能** +- ✅ 完整的分页控件(上一页/下一页/页码) +- ✅ 当前页高亮显示 +- ✅ 分页信息显示(共X条记录,第X/X页) +- ✅ 智能页码显示逻辑 + +### 5. **搜索功能** +- ✅ 按主机编号精确搜索 +- ✅ 搜索时重置到第一页 +- ✅ 实时过滤结果 + +### 6. **错误处理** +- ✅ 详细的API调用日志 +- ✅ 认证错误检测 +- ✅ 网络错误处理 +- ✅ 用户友好的错误提示 + +## 🔧 技术实现 + +### API服务层 (`hostService.js`) +```javascript +// 直接调用真实API,无模拟数据 +export const getHostDevices = async (params = {}) => { + const response = await api.get('/api/smart-devices/hosts', { params }) + // 处理API响应结构 + return { + data: apiData.data, + pagination: { + total: apiData.total, // 使用API返回的371 + // ... + } + } +} +``` + +### 组件层 (`SmartHost.vue`) +```javascript +// 使用API返回的真实数据 +this.totalCount = this.pagination.total || this.devices.length +this.onlineCount = this.devices.filter(device => device.isOnline).length +this.offlineCount = this.devices.filter(device => !device.isOnline).length +``` + +## 🚨 当前问题 + +### 认证问题 +- ❌ API返回401未授权错误 +- ❌ 需要正确的认证token才能访问 +- ❌ 前端无法获取371台主机的数据 + +### 解决方案 +1. **获取认证token**: + ```bash + node set-token.js + ``` + +2. **在浏览器中设置token**: + ```javascript + localStorage.setItem('token', 'YOUR_ACTUAL_TOKEN') + ``` + +3. **测试API连接**: + ```bash + node test-api.js + ``` + +## 📊 预期结果 + +一旦解决认证问题,前端应该: +- ✅ 显示主机总数:371 +- ✅ 正确显示在线/离线数量 +- ✅ 分页显示所有371台主机 +- ✅ 搜索功能正常工作 +- ✅ 编辑功能正常工作 + +## 🛠️ 测试工具 + +1. **API测试**:`node test-api.js` +2. **认证测试**:`node auth-test.js` +3. **Token设置**:`node set-token.js` + +## 📝 下一步 + +1. 联系后端开发者获取正确的认证信息 +2. 设置认证token +3. 测试API连接 +4. 验证前端显示371台主机 + +## 🎯 代码特点 + +- **无硬编码**:所有数据都来自API +- **无模拟数据**:完全使用真实接口 +- **统一接口**:使用标准的REST API +- **动态更新**:实时获取最新数据 +- **错误处理**:完善的错误处理机制 diff --git a/mini_program/farm-monitor-dashboard/SMART_EARTAG_ALERT_README.md b/mini_program/farm-monitor-dashboard/SMART_EARTAG_ALERT_README.md new file mode 100644 index 0000000..bf77592 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/SMART_EARTAG_ALERT_README.md @@ -0,0 +1,141 @@ +# 智能耳标预警功能实现说明 + +## 功能概述 + +基于PC端 `SmartEartagAlert.vue` 的分析,在微信小程序端实现了完整的智能耳标预警功能,包括预警展示、筛选、搜索、处理等功能。 + +## 实现文件 + +### 1. 核心组件 +- `src/components/SmartEartagAlert.vue` - 主要预警组件 +- `src/views/SmartEartagAlertPage.vue` - 预警页面包装器 +- `src/components/AlertTest.vue` - 功能测试组件 + +### 2. 服务层 +- `src/services/alertService.js` - 预警相关API服务 + +### 3. 路由配置 +- 在 `src/router/index.js` 中添加了预警页面路由 + +### 4. 导航集成 +- 在 `src/components/Home.vue` 中添加了预警功能入口 + +## 主要功能 + +### 1. 预警展示 +- **统计卡片**: 显示总预警数、严重预警、一般预警、已处理数量 +- **预警列表**: 展示预警详情,包括设备ID、预警内容、级别、状态等 +- **分页功能**: 支持分页浏览大量预警数据 + +### 2. 筛选和搜索 +- **级别筛选**: 按严重、一般、信息级别筛选 +- **状态筛选**: 按未处理、已处理状态筛选 +- **关键词搜索**: 支持按设备ID或预警内容搜索 + +### 3. 预警处理 +- **详情查看**: 点击预警查看详细信息 +- **状态更新**: 支持将预警标记为已处理 +- **批量操作**: 支持批量处理预警(API已准备) + +### 4. 实时功能 +- **自动刷新**: 30秒自动刷新预警数据 +- **手动刷新**: 支持手动刷新数据 +- **刷新控制**: 可开启/关闭自动刷新 + +### 5. 响应式设计 +- **移动端优化**: 针对手机屏幕优化的界面布局 +- **触摸友好**: 适合触摸操作的按钮和交互 + +## 技术特点 + +### 1. 数据管理 +- 使用Vue 2 Options API +- 响应式数据绑定 +- 计算属性优化性能 + +### 2. 状态管理 +- 本地状态管理 +- 筛选状态持久化 +- 分页状态管理 + +### 3. 用户体验 +- 加载状态提示 +- 空数据状态展示 +- 错误处理机制 + +### 4. 样式设计 +- 现代化UI设计 +- 卡片式布局 +- 颜色编码预警级别 +- 响应式布局 + +## API接口设计 + +### 预警管理 +- `GET /smart-eartag-alerts` - 获取预警列表 +- `GET /smart-eartag-alerts/:id` - 获取预警详情 +- `PUT /smart-eartag-alerts/:id/resolve` - 处理预警 +- `DELETE /smart-eartag-alerts/:id` - 删除预警 + +### 批量操作 +- `PUT /smart-eartag-alerts/batch-resolve` - 批量处理预警 + +### 统计分析 +- `GET /smart-eartag-alerts/stats` - 获取预警统计 + +### 设备相关 +- `GET /smart-eartag-alerts/device/:deviceId` - 获取设备预警历史 + +### 规则管理 +- `GET /smart-eartag-alerts/rules` - 获取预警规则 +- `POST /smart-eartag-alerts/rules` - 创建预警规则 +- `PUT /smart-eartag-alerts/rules/:id` - 更新预警规则 +- `DELETE /smart-eartag-alerts/rules/:id` - 删除预警规则 + +## 使用方式 + +### 1. 访问预警页面 +- 在首页点击"智能耳标预警"按钮 +- 或直接访问 `/smart-eartag-alert` 路由 + +### 2. 功能测试 +- 访问 `/alert-test` 路由进行功能测试 +- 测试各种API调用和功能 + +### 3. 预警处理流程 +1. 查看预警列表 +2. 使用筛选和搜索功能 +3. 点击预警查看详情 +4. 处理预警或标记为已处理 + +## 模拟数据 + +当前使用模拟数据进行功能演示,包括: +- 体温异常预警 +- 活动量异常预警 +- 设备离线预警 +- 位置异常预警 + +## 后续优化 + +### 1. API集成 +- 替换模拟数据为真实API调用 +- 添加错误处理和重试机制 +- 优化数据加载性能 + +### 2. 功能增强 +- 添加预警规则配置 +- 实现推送通知 +- 添加数据导出功能 + +### 3. 性能优化 +- 虚拟滚动处理大量数据 +- 缓存机制减少API调用 +- 懒加载优化首屏性能 + +## 注意事项 + +1. 当前使用模拟数据,需要根据实际API调整数据结构 +2. 自动刷新功能默认开启,可根据需要调整刷新间隔 +3. 分页大小可根据实际需求调整 +4. 样式可根据设计规范进一步优化 diff --git a/mini_program/farm-monitor-dashboard/auth-test.js b/mini_program/farm-monitor-dashboard/auth-test.js new file mode 100644 index 0000000..ef393db --- /dev/null +++ b/mini_program/farm-monitor-dashboard/auth-test.js @@ -0,0 +1,83 @@ +// 认证测试脚本 - 帮助获取正确的认证信息 +const axios = require('axios') + +async function testAuthMethods() { + const baseURL = process.env.VUE_APP_API_BASE_URL || 'http://localhost:5350' + + console.log('🔐 开始测试认证方法...') + console.log('API地址:', baseURL) + + // 测试1: 无认证访问 + console.log('\n1. 测试无认证访问...') + try { + const response = await axios.get(`${baseURL}/api/smart-devices/hosts`, { + params: { page: 1, pageSize: 10 }, + timeout: 10000 + }) + console.log('✅ 无认证访问成功!') + console.log('响应:', response.data) + } catch (error) { + console.log('❌ 无认证访问失败:', error.response?.status, error.response?.data?.message) + } + + // 测试2: 尝试不同的认证头 + const authMethods = [ + { name: 'Bearer Token', header: 'Authorization', value: 'Bearer test-token' }, + { name: 'API Key', header: 'X-API-Key', value: 'test-api-key' }, + { name: 'Basic Auth', header: 'Authorization', value: 'Basic dGVzdDp0ZXN0' }, + { name: 'Custom Token', header: 'X-Auth-Token', value: 'test-token' } + ] + + for (const method of authMethods) { + console.log(`\n2. 测试${method.name}...`) + try { + const response = await axios.get(`${baseURL}/api/smart-devices/hosts`, { + params: { page: 1, pageSize: 10 }, + headers: { [method.header]: method.value }, + timeout: 10000 + }) + console.log(`✅ ${method.name}成功!`) + console.log('响应:', response.data) + break + } catch (error) { + console.log(`❌ ${method.name}失败:`, error.response?.status, error.response?.data?.message) + } + } + + // 测试3: 检查是否有登录接口 + console.log('\n3. 检查登录接口...') + const loginEndpoints = [ + '/api/auth/login', + '/api/login', + '/api/user/login', + '/api/authenticate', + '/login' + ] + + for (const endpoint of loginEndpoints) { + try { + const response = await axios.post(`${baseURL}${endpoint}`, { + username: 'test', + password: 'test' + }, { timeout: 5000 }) + console.log(`✅ 找到登录接口: ${endpoint}`) + console.log('响应:', response.data) + break + } catch (error) { + console.log(`❌ ${endpoint}:`, error.response?.status) + } + } + + console.log('\n💡 建议:') + console.log('1. 检查后端API文档了解正确的认证方式') + console.log('2. 联系后端开发者获取测试用的认证信息') + console.log('3. 检查是否有公开的API端点不需要认证') + console.log('4. 确认API是否需要特定的请求头或参数') +} + +// 运行测试 +if (require.main === module) { + testAuthMethods().catch(console.error) +} + +module.exports = { testAuthMethods } diff --git a/mini_program/farm-monitor-dashboard/auto-login.js b/mini_program/farm-monitor-dashboard/auto-login.js new file mode 100644 index 0000000..77e3dbb --- /dev/null +++ b/mini_program/farm-monitor-dashboard/auto-login.js @@ -0,0 +1,85 @@ +// 自动登录脚本 - 解决认证问题 +const axios = require('axios') + +const API_BASE_URL = process.env.VUE_APP_API_BASE_URL || 'http://localhost:5350' + +async function autoLogin() { + console.log('🔐 开始自动登录解决认证问题...') + console.log('API地址:', API_BASE_URL) + + try { + // 尝试登录 + console.log('\n1. 尝试登录...') + const loginResponse = await axios.post(`${API_BASE_URL}/api/auth/login`, { + username: 'admin', + password: '123456' + }) + + if (loginResponse.data.success) { + const token = loginResponse.data.token + console.log('✅ 登录成功!') + console.log('Token:', token.substring(0, 20) + '...') + console.log('用户:', loginResponse.data.user.username) + console.log('角色:', loginResponse.data.role.name) + + // 测试智能主机API + console.log('\n2. 测试智能主机API...') + const hostResponse = await axios.get(`${API_BASE_URL}/api/smart-devices/hosts`, { + headers: { Authorization: `Bearer ${token}` }, + params: { page: 1, pageSize: 10 } + }) + + if (hostResponse.data.success) { + console.log('✅ 智能主机API成功!') + console.log('主机总数:', hostResponse.data.total) + console.log('当前页数据:', hostResponse.data.data.length, '条') + } + + // 测试智能耳标API + console.log('\n3. 测试智能耳标API...') + const earTagResponse = await axios.get(`${API_BASE_URL}/api/iot-jbq-client`, { + headers: { Authorization: `Bearer ${token}` }, + params: { page: 1, pageSize: 10 } + }) + + if (earTagResponse.data.success) { + console.log('✅ 智能耳标API成功!') + console.log('耳标总数:', earTagResponse.data.pagination.total) + console.log('当前页数据:', earTagResponse.data.data.length, '条') + } + + console.log('\n🎉 认证问题已解决!') + console.log('\n📋 前端设置步骤:') + console.log('1. 打开浏览器开发者工具 (F12)') + console.log('2. 在控制台中执行以下代码:') + console.log(`localStorage.setItem('token', '${token}')`) + console.log('3. 刷新页面') + console.log('\n💡 或者运行以下命令设置token:') + console.log(`node set-token.js`) + + return token + } else { + console.log('❌ 登录失败:', loginResponse.data.message) + } + } catch (error) { + console.error('❌ 自动登录失败:') + if (error.response) { + console.error('状态码:', error.response.status) + console.error('错误信息:', error.response.data) + } else { + console.error('网络错误:', error.message) + } + } +} + +// 运行自动登录 +if (require.main === module) { + autoLogin().then(token => { + if (token) { + console.log('\n✅ 认证问题解决完成!') + console.log('现在前端应该能正常访问所有API了。') + } + }).catch(console.error) +} + +module.exports = { autoLogin } diff --git a/mini_program/farm-monitor-dashboard/check-backend.js b/mini_program/farm-monitor-dashboard/check-backend.js new file mode 100644 index 0000000..adf8e88 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/check-backend.js @@ -0,0 +1,47 @@ +// 检查后端服务是否运行 +const axios = require('axios'); + +async function checkBackend() { + const baseURL = 'http://localhost:5350/api'; + + console.log('检查后端服务...'); + console.log('基础URL:', baseURL); + + try { + // 测试基础连接 + console.log('\n1. 测试基础连接...'); + const response = await axios.get(`${baseURL}/cattle-type`, { + timeout: 5000 + }); + console.log('✅ 基础连接成功'); + console.log('状态码:', response.status); + console.log('响应数据:', response.data); + + // 测试牛只档案API + console.log('\n2. 测试牛只档案API...'); + const cattleResponse = await axios.get(`${baseURL}/iot-cattle/public`, { + params: { page: 1, pageSize: 5 }, + timeout: 5000 + }); + console.log('✅ 牛只档案API成功'); + console.log('状态码:', cattleResponse.status); + console.log('响应数据:', cattleResponse.data); + + } catch (error) { + console.error('❌ 后端服务检查失败'); + + if (error.code === 'ECONNREFUSED') { + console.error('错误: 无法连接到后端服务'); + console.error('请确保后端服务在 http://localhost:5350 运行'); + console.error('启动命令: cd backend && npm start'); + } else if (error.response) { + console.error('错误: 后端返回错误'); + console.error('状态码:', error.response.status); + console.error('错误信息:', error.response.data); + } else { + console.error('错误:', error.message); + } + } +} + +checkBackend(); diff --git a/mini_program/farm-monitor-dashboard/mock-api-server.js b/mini_program/farm-monitor-dashboard/mock-api-server.js new file mode 100644 index 0000000..9b783f9 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/mock-api-server.js @@ -0,0 +1,95 @@ +// 模拟API服务器 - 用于测试前端显示 +const express = require('express') +const cors = require('cors') + +const app = express() +const PORT = 5351 // 使用不同端口避免冲突 + +app.use(cors()) +app.use(express.json()) + +// 模拟371台主机数据 +const generateMockHosts = (page = 1, pageSize = 10) => { + const totalHosts = 371 + const startIndex = (page - 1) * pageSize + const endIndex = Math.min(startIndex + pageSize, totalHosts) + + const hosts = [] + for (let i = startIndex; i < endIndex; i++) { + const hostId = `2490246${String(426 + i).padStart(3, '0')}` + hosts.push({ + hostId: hostId, + sid: hostId, + isOnline: Math.random() > 0.3, // 70% 在线概率 + battery: Math.floor(Math.random() * 40) + 60, // 60-100% + voltage: Math.floor(Math.random() * 40) + 60, + signal: Math.floor(Math.random() * 50) + 10, // 10-60% + signa: Math.floor(Math.random() * 50) + 10, + temperature: (Math.random() * 10 + 20).toFixed(1), // 20-30°C + state: Math.random() > 0.2 ? 1 : 0, // 80% 连接状态 + bandge_status: Math.random() > 0.2 ? 1 : 0, + updateTime: new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000).toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }).replace(/\//g, '-'), + lastUpdateTime: new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000).toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }).replace(/\//g, '-') + }) + } + + return { + success: true, + data: hosts, + total: totalHosts, + page: page, + pageSize: pageSize, + totalPages: Math.ceil(totalHosts / pageSize), + message: '获取智能主机列表成功' + } +} + +// API路由 +app.get('/api/smart-devices/hosts', (req, res) => { + const page = parseInt(req.query.page) || 1 + const pageSize = parseInt(req.query.pageSize) || 10 + const search = req.query.search || '' + + console.log(`📡 API请求: page=${page}, pageSize=${pageSize}, search=${search}`) + + let response = generateMockHosts(page, pageSize) + + // 如果有搜索条件,过滤数据 + if (search) { + response.data = response.data.filter(host => + host.hostId.includes(search) || host.sid.includes(search) + ) + response.total = response.data.length + response.totalPages = Math.ceil(response.total / pageSize) + } + + console.log(`📊 返回数据: ${response.data.length}条,总数: ${response.total}`) + + res.json(response) +}) + +// 启动服务器 +app.listen(PORT, () => { + console.log(`🚀 模拟API服务器启动成功!`) + console.log(`📍 地址: http://localhost:${PORT}`) + console.log(`🔗 API端点: http://localhost:${PORT}/api/smart-devices/hosts`) + console.log(`📊 模拟数据: 371台主机`) + console.log(`\n💡 测试命令:`) + console.log(`curl "http://localhost:${PORT}/api/smart-devices/hosts?page=1&pageSize=10"`) +}) + +module.exports = app diff --git a/mini_program/farm-monitor-dashboard/package-lock.json b/mini_program/farm-monitor-dashboard/package-lock.json index 0d71cb5..45a3a7e 100644 --- a/mini_program/farm-monitor-dashboard/package-lock.json +++ b/mini_program/farm-monitor-dashboard/package-lock.json @@ -11,7 +11,9 @@ "@dcloudio/uni-app": "^2.0.2-alpha-4080120250905001", "@vue/composition-api": "^1.4.0", "axios": "^0.27.2", + "cors": "^2.8.5", "dayjs": "^1.11.0", + "express": "^5.1.0", "pinia": "^2.1.6", "vue": "^2.6.14", "vue-router": "^3.6.5", @@ -2028,44 +2030,24 @@ "dev": true }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=18" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, "node_modules/bonjour-service": { "version": "1.3.0", "resolved": "https://registry.npmmirror.com/bonjour-service/-/bonjour-service-1.3.0.tgz", @@ -2171,7 +2153,6 @@ "version": "3.1.2", "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -2192,7 +2173,6 @@ "version": "1.0.4", "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -2607,10 +2587,9 @@ } }, "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "dependencies": { "safe-buffer": "5.2.1" }, @@ -2622,7 +2601,6 @@ "version": "1.0.5", "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -2631,16 +2609,17 @@ "version": "0.7.1", "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.1.tgz", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "dev": true, "engines": { "node": ">= 0.6" } }, "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/copy-webpack-plugin": { "version": "9.1.0", @@ -2684,6 +2663,18 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -3040,10 +3031,9 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dependencies": { "ms": "^2.1.3" }, @@ -3175,7 +3165,6 @@ "version": "2.0.0", "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -3374,8 +3363,7 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { "version": "1.5.220", @@ -3402,7 +3390,6 @@ "version": "2.0.0", "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -3506,8 +3493,7 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "node_modules/escape-string-regexp": { "version": "1.0.5", @@ -3793,7 +3779,6 @@ "version": "1.8.1", "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -3908,65 +3893,84 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmmirror.com/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "dev": true, + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "node_modules/express/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dependencies": { - "ms": "2.0.0" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "engines": { + "node": ">= 0.6" + } }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -4076,38 +4080,21 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dev": true, + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { "node": ">= 0.8" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz", @@ -4191,7 +4178,6 @@ "version": "0.2.0", "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -4210,12 +4196,11 @@ } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/fs-extra": { @@ -4678,7 +4663,6 @@ "version": "2.0.0", "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -4690,6 +4674,14 @@ "node": ">= 0.8" } }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/http-parser-js": { "version": "0.5.10", "resolved": "https://registry.npmmirror.com/http-parser-js/-/http-parser-js-0.5.10.tgz", @@ -4744,12 +4736,11 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" @@ -4841,8 +4832,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/intersection-observer": { "version": "0.7.0", @@ -4997,6 +4987,11 @@ "node": ">=0.10.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, "node_modules/is-stream": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-1.1.0.tgz", @@ -5584,12 +5579,11 @@ "dev": true }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/memfs": { @@ -5605,10 +5599,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "dev": true, + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -5659,6 +5655,18 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", @@ -5823,8 +5831,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/multicast-dns": { "version": "7.2.5", @@ -6035,7 +6042,6 @@ "version": "4.1.1", "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6044,7 +6050,6 @@ "version": "1.13.4", "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -6062,7 +6067,6 @@ "version": "2.4.1", "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, "dependencies": { "ee-first": "1.1.1" }, @@ -6083,7 +6087,6 @@ "version": "1.4.0", "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -6377,7 +6380,6 @@ "version": "1.3.3", "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -6426,10 +6428,13 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "dev": true + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/path-type": { "version": "4.0.0", @@ -7184,7 +7189,6 @@ "version": "2.0.7", "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -7197,7 +7201,6 @@ "version": "1.9.1", "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, "engines": { "node": ">= 0.10" } @@ -7228,12 +7231,11 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, + "version": "6.14.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -7275,24 +7277,37 @@ "version": "1.2.1", "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, "engines": { "node": ">= 0.6" } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.4.24", + "iconv-lite": "0.7.0", "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/read-pkg": { @@ -7543,6 +7558,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", @@ -7576,7 +7606,6 @@ "version": "5.2.1", "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -7595,8 +7624,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { "version": "1.92.1", @@ -7733,63 +7761,43 @@ } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmmirror.com/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "bin": { - "mime": "cli.js" + "node_modules/send/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" }, "engines": { - "node": ">=4" + "node": ">= 0.6" } }, "node_modules/serialize-javascript": { @@ -7880,25 +7888,23 @@ } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" } }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/shallow-clone": { "version": "3.0.1", @@ -7949,7 +7955,6 @@ "version": "1.1.0", "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -7968,7 +7973,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -7984,7 +7988,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -8002,7 +8005,6 @@ "version": "1.0.2", "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -8173,10 +8175,9 @@ "dev": true }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "engines": { "node": ">= 0.8" } @@ -8526,7 +8527,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, "engines": { "node": ">=0.6" } @@ -8577,13 +8577,32 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" }, "engines": { "node": ">= 0.6" @@ -8621,7 +8640,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -8721,7 +8739,6 @@ "version": "1.1.2", "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -9232,12 +9249,208 @@ "ajv": "^8.8.2" } }, + "node_modules/webpack-dev-server/node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/webpack-dev-server/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/webpack-dev-server/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/webpack-dev-server/node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmmirror.com/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/webpack-dev-server/node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/webpack-dev-server/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server/node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true + }, + "node_modules/webpack-dev-server/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/webpack-dev-server/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/webpack-dev-server/node_modules/schema-utils": { "version": "4.3.2", "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.2.tgz", @@ -9257,6 +9470,76 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/webpack-dev-server/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmmirror.com/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/webpack-dev-server/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/webpack-dev-server/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/webpack-merge": { "version": "5.10.0", "resolved": "https://registry.npmmirror.com/webpack-merge/-/webpack-merge-5.10.0.tgz", @@ -9483,8 +9766,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { "version": "8.18.0", @@ -11067,40 +11349,19 @@ "dev": true }, "body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" } }, "bonjour-service": { @@ -11170,8 +11431,7 @@ "bytes": { "version": "3.1.2", "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "call-bind-apply-helpers": { "version": "1.0.2", @@ -11186,7 +11446,6 @@ "version": "1.0.4", "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, "requires": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -11497,10 +11756,9 @@ } }, "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "requires": { "safe-buffer": "5.2.1" } @@ -11508,20 +11766,17 @@ "content-type": { "version": "1.0.5", "resolved": "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "cookie": { "version": "0.7.1", "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "dev": true + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" }, "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==" }, "copy-webpack-plugin": { "version": "9.1.0", @@ -11554,6 +11809,15 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -11789,10 +12053,9 @@ "dev": true }, "debug": { - "version": "4.3.7", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "requires": { "ms": "^2.1.3" } @@ -11881,8 +12144,7 @@ "depd": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, "destroy": { "version": "1.2.0", @@ -12031,8 +12293,7 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { "version": "1.5.220", @@ -12055,8 +12316,7 @@ "encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, "end-of-stream": { "version": "1.4.5", @@ -12139,8 +12399,7 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "escape-string-regexp": { "version": "1.0.5", @@ -12344,8 +12603,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "event-pubsub": { "version": "4.3.0", @@ -12432,58 +12690,65 @@ } }, "express": { - "version": "4.21.2", - "resolved": "https://registry.npmmirror.com/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "dev": true, + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "requires": { - "ms": "2.0.0" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true + "mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" + }, + "mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "requires": { + "mime-db": "^1.54.0" + } + }, + "negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" } } }, @@ -12570,35 +12835,16 @@ } }, "finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dev": true, + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", "requires": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" } }, "find-up": { @@ -12654,8 +12900,7 @@ "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" }, "fraction.js": { "version": "4.3.7", @@ -12664,10 +12909,9 @@ "dev": true }, "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==" }, "fs-extra": { "version": "10.1.0", @@ -12999,13 +13243,19 @@ "version": "2.0.0", "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, "requires": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" + }, + "dependencies": { + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + } } }, "http-parser-js": { @@ -13045,12 +13295,11 @@ "dev": true }, "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, + "version": "0.6.3", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "icss-utils": { @@ -13107,8 +13356,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "intersection-observer": { "version": "0.7.0", @@ -13215,6 +13463,11 @@ "isobject": "^3.0.1" } }, + "is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-1.1.0.tgz", @@ -13684,10 +13937,9 @@ "dev": true }, "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==" }, "memfs": { "version": "3.5.3", @@ -13699,10 +13951,9 @@ } }, "merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==" }, "merge-source-map": { "version": "1.1.0", @@ -13741,6 +13992,12 @@ "picomatch": "^2.3.1" } }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", @@ -13864,8 +14121,7 @@ "ms": { "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "multicast-dns": { "version": "7.2.5", @@ -14023,14 +14279,12 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" }, "obuf": { "version": "1.1.2", @@ -14042,7 +14296,6 @@ "version": "2.4.1", "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, "requires": { "ee-first": "1.1.1" } @@ -14057,7 +14310,6 @@ "version": "1.4.0", "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -14281,8 +14533,7 @@ "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "pascal-case": { "version": "3.1.2", @@ -14319,10 +14570,9 @@ "dev": true }, "path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "dev": true + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==" }, "path-type": { "version": "4.0.0", @@ -14796,7 +15046,6 @@ "version": "2.0.7", "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, "requires": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -14805,8 +15054,7 @@ "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" } } }, @@ -14833,12 +15081,11 @@ "dev": true }, "qs": { - "version": "6.13.0", - "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, + "version": "6.14.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "requires": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" } }, "queue-microtask": { @@ -14859,19 +15106,27 @@ "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", "requires": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.4.24", + "iconv-lite": "0.7.0", "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } } }, "read-pkg": { @@ -15055,6 +15310,18 @@ "glob": "^7.1.3" } }, + "router": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "requires": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + } + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", @@ -15073,14 +15340,12 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { "version": "1.92.1", @@ -15154,54 +15419,35 @@ "dev": true }, "send": { - "version": "0.19.0", - "resolved": "https://registry.npmmirror.com/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" + }, + "mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } + "mime-db": "^1.54.0" } - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true } } }, @@ -15283,22 +15529,20 @@ } }, "serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dev": true, + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", "requires": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" } }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "shallow-clone": { "version": "3.0.1", @@ -15334,7 +15578,6 @@ "version": "1.1.0", "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, "requires": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -15347,7 +15590,6 @@ "version": "1.0.0", "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, "requires": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -15357,7 +15599,6 @@ "version": "1.0.1", "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, "requires": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -15369,7 +15610,6 @@ "version": "1.0.2", "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "requires": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -15515,10 +15755,9 @@ "dev": true }, "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" }, "string_decoder": { "version": "1.3.0", @@ -15763,8 +16002,7 @@ "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "totalist": { "version": "3.0.1", @@ -15800,13 +16038,28 @@ "dev": true }, "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "dependencies": { + "mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==" + }, + "mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "requires": { + "mime-db": "^1.54.0" + } + } } }, "typescript": { @@ -15830,8 +16083,7 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "update-browserslist-db": { "version": "1.1.3", @@ -15903,8 +16155,7 @@ "vary": { "version": "1.1.2", "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, "vue": { "version": "2.6.14", @@ -16328,12 +16579,172 @@ "fast-deep-equal": "^3.1.3" } }, + "body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "requires": { + "safe-buffer": "5.2.1" + } + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + } + } + }, + "express": { + "version": "4.21.2", + "resolved": "https://registry.npmmirror.com/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true + }, + "merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true + }, + "qs": { + "version": "6.13.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "requires": { + "side-channel": "^1.0.6" + } + }, + "raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, "schema-utils": { "version": "4.3.2", "resolved": "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.3.2.tgz", @@ -16345,6 +16756,63 @@ "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } + }, + "send": { + "version": "0.19.0", + "resolved": "https://registry.npmmirror.com/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "requires": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } } } }, @@ -16465,8 +16933,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "ws": { "version": "8.18.0", diff --git a/mini_program/farm-monitor-dashboard/package.json b/mini_program/farm-monitor-dashboard/package.json index ec0c9fc..c73312f 100644 --- a/mini_program/farm-monitor-dashboard/package.json +++ b/mini_program/farm-monitor-dashboard/package.json @@ -4,16 +4,18 @@ "description": "养殖端微信小程序 - 基于Vue.js和Node.js 16.20.2", "main": "main.js", "scripts": { - "serve": "vue-cli-service serve", + "serve": "cross-env VUE_APP_BASE_URL=/api vue-cli-service serve", "build": "vue-cli-service build", - "dev:h5": "vue-cli-service serve --mode development", + "dev:h5": "cross-env VUE_APP_BASE_URL=/api vue-cli-service serve --mode development", "build:h5": "vue-cli-service build --mode production" }, "dependencies": { "@dcloudio/uni-app": "^2.0.2-alpha-4080120250905001", "@vue/composition-api": "^1.4.0", "axios": "^0.27.2", + "cors": "^2.8.5", "dayjs": "^1.11.0", + "express": "^5.1.0", "pinia": "^2.1.6", "vue": "^2.6.14", "vue-router": "^3.6.5", diff --git a/mini_program/farm-monitor-dashboard/set-token.js b/mini_program/farm-monitor-dashboard/set-token.js new file mode 100644 index 0000000..f8fa4bc --- /dev/null +++ b/mini_program/farm-monitor-dashboard/set-token.js @@ -0,0 +1,63 @@ +// Token设置工具 - 自动获取并设置API认证token +const axios = require('axios') +const readline = require('readline') + +const API_BASE_URL = process.env.VUE_APP_API_BASE_URL || 'http://localhost:5350' + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}) + +async function getToken() { + try { + console.log('🔐 正在自动获取API认证token...') + const response = await axios.post(`${API_BASE_URL}/api/auth/login`, { + username: 'admin', + password: '123456' + }) + + if (response.data.success) { + return response.data.token + } + throw new Error('登录失败') + } catch (error) { + console.error('❌ 自动获取token失败:', error.message) + return null + } +} + +console.log('🔐 API Token 设置工具') +console.log('====================') +console.log('') +console.log('此工具将帮助您设置API认证token,以便前端能正确调用后端API。') +console.log('') + +// 自动获取token +getToken().then(token => { + if (token) { + console.log('✅ 自动获取token成功!') + console.log('Token:', token.substring(0, 20) + '...') + console.log('') + console.log('📋 请在前端浏览器控制台中执行以下代码:') + console.log('') + console.log(`localStorage.setItem('token', '${token}')`) + console.log('') + console.log('然后刷新页面测试API连接。') + console.log('') + console.log('🔍 测试API连接:') + console.log('node test-api.js') + } else { + console.log('⚠️ 自动获取token失败') + console.log('') + console.log('💡 手动解决方案:') + console.log('1. 联系后端开发者获取正确的认证信息') + console.log('2. 检查API文档了解认证方式') + console.log('3. 尝试以下测试token:') + console.log(' localStorage.setItem("token", "test-token")') + console.log(' localStorage.setItem("apiKey", "test-api-key")') + } + + console.log('') + rl.close() +}) diff --git a/mini_program/farm-monitor-dashboard/src/components/AlertTest.vue b/mini_program/farm-monitor-dashboard/src/components/AlertTest.vue new file mode 100644 index 0000000..960fbc8 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/components/AlertTest.vue @@ -0,0 +1,210 @@ + + + + + diff --git a/mini_program/farm-monitor-dashboard/src/components/ApiTest.vue b/mini_program/farm-monitor-dashboard/src/components/ApiTest.vue new file mode 100644 index 0000000..7b6c3e2 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/components/ApiTest.vue @@ -0,0 +1,212 @@ + + + + + \ No newline at end of file diff --git a/mini_program/farm-monitor-dashboard/src/components/ApiTestPage.vue b/mini_program/farm-monitor-dashboard/src/components/ApiTestPage.vue new file mode 100644 index 0000000..a8eeaa3 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/components/ApiTestPage.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/mini_program/farm-monitor-dashboard/src/components/CattleAdd.vue b/mini_program/farm-monitor-dashboard/src/components/CattleAdd.vue new file mode 100644 index 0000000..8454ae7 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/components/CattleAdd.vue @@ -0,0 +1,482 @@ + + + + + diff --git a/mini_program/farm-monitor-dashboard/src/components/CattleProfile.vue b/mini_program/farm-monitor-dashboard/src/components/CattleProfile.vue new file mode 100644 index 0000000..846cbd4 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/components/CattleProfile.vue @@ -0,0 +1,549 @@ + + + + + diff --git a/mini_program/farm-monitor-dashboard/src/components/CattleTest.vue b/mini_program/farm-monitor-dashboard/src/components/CattleTest.vue new file mode 100644 index 0000000..1a86f5f --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/components/CattleTest.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/mini_program/farm-monitor-dashboard/src/components/CattleTransfer.vue b/mini_program/farm-monitor-dashboard/src/components/CattleTransfer.vue new file mode 100644 index 0000000..e087e18 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/components/CattleTransfer.vue @@ -0,0 +1,818 @@ + + + + + \ No newline at end of file diff --git a/mini_program/farm-monitor-dashboard/src/components/CattleTransferRegister.vue b/mini_program/farm-monitor-dashboard/src/components/CattleTransferRegister.vue new file mode 100644 index 0000000..230fbf2 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/components/CattleTransferRegister.vue @@ -0,0 +1,540 @@ + + + + + diff --git a/mini_program/farm-monitor-dashboard/src/components/ElectronicFence.vue b/mini_program/farm-monitor-dashboard/src/components/ElectronicFence.vue new file mode 100644 index 0000000..0e06522 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/components/ElectronicFence.vue @@ -0,0 +1,1361 @@ + + + + + diff --git a/mini_program/farm-monitor-dashboard/src/components/Home.vue b/mini_program/farm-monitor-dashboard/src/components/Home.vue index 621e284..711ae94 100644 --- a/mini_program/farm-monitor-dashboard/src/components/Home.vue +++ b/mini_program/farm-monitor-dashboard/src/components/Home.vue @@ -101,6 +101,9 @@ + @@ -141,14 +144,16 @@ export default { ], smartTools: [ { key: 'fence', icon: '🎯', label: '电子围栏', color: '#ff9500' }, + { key: 'smart-eartag-alert', icon: '⚠️', label: '智能耳标预警', color: '#ff3b30' }, { key: 'scan', icon: '🛡️', label: '扫码溯源', color: '#007aff' }, { key: 'photo', icon: '📷', label: '档案拍照', color: '#ff3b30' }, - { key: 'detect', icon: '📊', label: '检测工具', color: '#af52de' } + { key: 'detect', icon: '📊', label: '检测工具', color: '#af52de' }, ], businessModules: [ { key: 'quarantine', icon: '📋', label: '电子检疫', color: '#ff9500' }, { key: 'rights', icon: '🆔', label: '电子确权', color: '#007aff' }, - { key: 'disposal', icon: '♻️', label: '无害化处理申报', color: '#af52de' } + { key: 'disposal', icon: '♻️', label: '无害化处理申报', color: '#af52de' }, + { key: 'cattle-transfer', icon: '🔄', label: '牛只转栏', color: '#34c759' } ], bottomNavItems: [ { key: 'home', icon: '🏠', label: '首页', color: '#34c759' }, @@ -162,22 +167,27 @@ export default { const alertData = { collar: [ { key: 'not_collected', icon: '📊', label: '今日未被采集', value: '6', urgent: false }, - { key: 'strap_cut', icon: '✂️', label: '项圈绑带剪断', value: '0', urgent: false }, - { key: 'fence', icon: '🚧', label: '电子围栏', value: '3', urgent: false }, + { key: 'strap_cut', icon: '✂️', label: '温度过高', value: '0', urgent: false }, + { key: 'fence', icon: '🚧', label: '温度过低', value: '3', urgent: false }, { key: 'high_activity', icon: '📈', label: '今日运动量偏高', value: '0', urgent: false }, { key: 'low_activity', icon: '📉', label: '今日运动量偏低', value: '3', urgent: true }, - { key: 'fast_transmission', icon: '⚡', label: '传输频次过快', value: '0', urgent: false }, { key: 'low_battery', icon: '🔋', label: '电量偏低', value: '2', urgent: false } ], ear: [ - { key: 'not_collected', icon: '📊', label: '今日未被采集', value: '4', urgent: false }, - { key: 'damaged', icon: '⚠️', label: '耳标损坏', value: '1', urgent: true }, - { key: 'low_battery', icon: '🔋', label: '电量偏低', value: '3', urgent: false } + { key: 'not_collected', icon: '📊', label: '今日未被采集', value: '6', urgent: false }, + { key: 'strap_cut', icon: '✂️', label: '温度过高', value: '0', urgent: false }, + { key: 'fence', icon: '🚧', label: '温度过低', value: '3', urgent: false }, + { key: 'high_activity', icon: '📈', label: '今日运动量偏高', value: '0', urgent: false }, + { key: 'low_activity', icon: '📉', label: '今日运动量偏低', value: '3', urgent: true }, + { key: 'low_battery', icon: '🔋', label: '电量偏低', value: '2', urgent: false } ], ankle: [ - { key: 'not_collected', icon: '📊', label: '今日未被采集', value: '2', urgent: false }, - { key: 'loose', icon: '🔓', label: '脚环松动', value: '1', urgent: true }, - { key: 'low_battery', icon: '🔋', label: '电量偏低', value: '1', urgent: false } + { key: 'not_collected', icon: '📊', label: '今日未被采集', value: '6', urgent: false }, + { key: 'strap_cut', icon: '✂️', label: '温度过高', value: '0', urgent: false }, + { key: 'fence', icon: '🚧', label: '温度过低', value: '3', urgent: false }, + { key: 'high_activity', icon: '📈', label: '今日运动量偏高', value: '0', urgent: false }, + { key: 'low_activity', icon: '📉', label: '今日运动量偏低', value: '3', urgent: true }, + { key: 'low_battery', icon: '🔋', label: '电量偏低', value: '2', urgent: false } ], host: [ { key: 'offline', icon: '📴', label: '主机离线', value: '0', urgent: false }, @@ -214,11 +224,46 @@ export default { }, handleToolClick(tool) { console.log('点击工具:', tool.label) - // 这里可以添加工具点击逻辑 + // 根据工具类型跳转到不同页面 + switch(tool.key) { + case 'fence': + this.$router.push('/electronic-fence') + break + case 'scan': + console.log('跳转到扫码溯源页面') + break + case 'photo': + this.$router.push('/cattle-profile') + break + case 'detect': + console.log('跳转到检测工具页面') + break + case 'api-test': + this.$router.push('/api-test') + break + default: + console.log('未知工具类型') + } }, handleBusinessClick(business) { console.log('点击业务:', business.label) - // 这里可以添加业务点击逻辑 + // 根据业务类型跳转到不同页面 + switch(business.key) { + case 'quarantine': + console.log('跳转到电子检疫页面') + break + case 'rights': + console.log('跳转到电子确权页面') + break + case 'disposal': + console.log('跳转到无害化处理申报页面') + break + case 'cattle-transfer': + this.$router.push('/cattle-transfer') + break + default: + console.log('未知业务类型') + } }, navigateTo(route) { this.$router.push(route) @@ -235,6 +280,9 @@ export default { goToAuthTest() { this.$router.push('/auth-test') }, + goToApiTest() { + this.$router.push('/api-test-page') + }, get isDevelopment() { return process.env.NODE_ENV === 'development' } diff --git a/mini_program/farm-monitor-dashboard/src/components/MapTest.vue b/mini_program/farm-monitor-dashboard/src/components/MapTest.vue new file mode 100644 index 0000000..80aa763 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/components/MapTest.vue @@ -0,0 +1,237 @@ + + + + + diff --git a/mini_program/farm-monitor-dashboard/src/components/MapView.vue b/mini_program/farm-monitor-dashboard/src/components/MapView.vue new file mode 100644 index 0000000..2c0e2c9 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/components/MapView.vue @@ -0,0 +1,589 @@ + + + + + diff --git a/mini_program/farm-monitor-dashboard/src/components/Production.vue b/mini_program/farm-monitor-dashboard/src/components/Production.vue index 4c861be..4834478 100644 --- a/mini_program/farm-monitor-dashboard/src/components/Production.vue +++ b/mini_program/farm-monitor-dashboard/src/components/Production.vue @@ -180,7 +180,40 @@ export default { methods: { handleFunctionClick(animalType, func) { console.log('点击功能:', animalType, func.label) - // 这里可以添加具体的功能点击逻辑 + + // 根据动物类型和功能类型进行跳转 + if (animalType === 'cattle' && func.key === 'archive') { + // 牛档案跳转到牛只档案页面 + this.$router.push('/cattle-profile') + } else if (animalType === 'cattle' && func.key === 'transfer') { + // 牛只转栏记录跳转 + this.$router.push('/cattle-transfer') + } else if (animalType === 'pig' && func.key === 'archive') { + // 猪档案跳转(待实现) + console.log('跳转到猪档案页面') + // this.$router.push('/pig-profile') + } else if (animalType === 'pig' && func.key === 'transfer') { + // 猪只转栏记录跳转(待实现) + console.log('跳转到猪只转栏记录页面') + // this.$router.push('/pig-transfer') + } else if (animalType === 'sheep' && func.key === 'archive') { + // 羊档案跳转(待实现) + console.log('跳转到羊档案页面') + // this.$router.push('/sheep-profile') + } else if (animalType === 'sheep' && func.key === 'transfer') { + // 羊只转栏记录跳转(待实现) + console.log('跳转到羊只转栏记录页面') + // this.$router.push('/sheep-transfer') + } else if (animalType === 'poultry' && func.key === 'archive') { + // 家禽档案跳转(待实现) + console.log('跳转到家禽档案页面') + // this.$router.push('/poultry-profile') + } else { + // 其他功能暂时显示提示 + console.log(`${animalType} - ${func.label} 功能开发中`) + // 可以添加用户提示 + // this.$message.info(`${func.label} 功能开发中`) + } }, handleNavClick(nav) { this.activeNav = nav.key diff --git a/mini_program/farm-monitor-dashboard/src/components/SmartCollar.vue b/mini_program/farm-monitor-dashboard/src/components/SmartCollar.vue index da7679f..f75a2ee 100644 --- a/mini_program/farm-monitor-dashboard/src/components/SmartCollar.vue +++ b/mini_program/farm-monitor-dashboard/src/components/SmartCollar.vue @@ -90,7 +90,7 @@ class="device-card" >
-
项圈编号: {{ device.collarId }}
+
项圈编号: {{ device.sn }}
设备电量/%: @@ -101,20 +101,20 @@ {{ device.temperature }}
- 被采集主机: - {{ device.collectedHost }} + 设备信号值: + {{ device.rsrp }}
总运动量: - {{ device.totalMovement }} + {{ device.steps }}
今日运动量: {{ device.todayMovement }}
- GPS位置: - {{ device.gpsLocation }} + 绑带状态: + {{ getBandStatusText(device) }}
数据更新时间: @@ -615,6 +615,64 @@ export default { device.state === 1 || device.state === '1' }, + // 获取绑带状态文本 + getBandStatusText(device) { + // 优先使用bandge_status字段,其次使用state字段 + const bandStatus = device.bandge_status !== undefined ? device.bandge_status : device.state + if (bandStatus === 1 || bandStatus === '1') { + return '连接' + } else if (bandStatus === 0 || bandStatus === '0') { + return '断开' + } else { + return '未知' + } + }, + + // 格式化数据更新时间 + formatUpdateTime(device) { + // 优先使用time字段,其次使用uptime字段,最后使用updateTime字段 + const updateTime = device.time || device.uptime || device.updateTime + + if (!updateTime || updateTime === '未知') { + return '未知' + } + + try { + // 如果是时间戳(数字),转换为日期 + if (typeof updateTime === 'number' || /^\d+$/.test(updateTime)) { + const timestamp = parseInt(updateTime) + // 判断是秒级还是毫秒级时间戳 + const date = new Date(timestamp < 10000000000 ? timestamp * 1000 : timestamp) + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }) + } + + // 如果是字符串,尝试解析 + const date = new Date(updateTime) + if (!isNaN(date.getTime())) { + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }) + } + + return updateTime + } catch (error) { + console.warn('时间格式化失败:', error, updateTime) + return updateTime + } + }, + // 加载统计信息 async loadStatistics() { try { diff --git a/mini_program/farm-monitor-dashboard/src/components/SmartEartagAlert.vue b/mini_program/farm-monitor-dashboard/src/components/SmartEartagAlert.vue new file mode 100644 index 0000000..a520138 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/components/SmartEartagAlert.vue @@ -0,0 +1,984 @@ + + + + + diff --git a/mini_program/farm-monitor-dashboard/src/components/SmartHost.vue b/mini_program/farm-monitor-dashboard/src/components/SmartHost.vue index a35e12a..7417f88 100644 --- a/mini_program/farm-monitor-dashboard/src/components/SmartHost.vue +++ b/mini_program/farm-monitor-dashboard/src/components/SmartHost.vue @@ -29,7 +29,7 @@ @@ -63,41 +63,38 @@ class="device-card" >
-
主机编号: {{ device.hostId }}
+
主机编号: {{ device.deviceNumber || device.sid || device.hostId }}
- 设备状态: - - {{ device.isOnline ? '在线' : '离线' }} - + 设备电量: + {{ device.voltage || device.battery || 0 }}%
- CPU使用率: - {{ device.cpuUsage }}% + 设备信号值: + {{ device.signa || device.signal || 0 }}%
- 内存使用率: - {{ device.memoryUsage }}% + 设备温度: + {{ device.temperature || 0 }}°C
- 存储空间: - {{ device.storageUsage }}% -
-
- 网络状态: - {{ device.networkStatus }} -
-
- 连接设备数: - {{ device.connectedDevices }} + 绑带状态: + {{ getBandStatusText(device) }}
数据更新时间: - {{ device.updateTime }} + {{ device.updateTime || device.lastUpdateTime || '未知' }}
+
暂无主机设备
+ + + + + +
+
+
+

编辑主机信息

+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ + diff --git a/mini_program/farm-monitor-dashboard/src/router/index.js b/mini_program/farm-monitor-dashboard/src/router/index.js index c31e98c..583c712 100644 --- a/mini_program/farm-monitor-dashboard/src/router/index.js +++ b/mini_program/farm-monitor-dashboard/src/router/index.js @@ -12,6 +12,18 @@ import Login from '@/components/Login.vue' import SmsLogin from '@/components/SmsLogin.vue' import Register from '@/components/Register.vue' import PasswordLogin from '@/components/PasswordLogin.vue' +import ElectronicFencePage from '@/views/ElectronicFencePage.vue' +import SmartEartagAlertPage from '@/views/SmartEartagAlertPage.vue' +import AlertTest from '@/components/AlertTest.vue' +import MapTest from '@/components/MapTest.vue' +import ApiTest from '@/components/ApiTest.vue' +import WechatFenceDrawer from '@/components/WechatFenceDrawer.vue' +import CattleProfile from '@/components/CattleProfile.vue' +import CattleAdd from '@/components/CattleAdd.vue' +import CattleTest from '@/components/CattleTest.vue' +import CattleTransfer from '@/components/CattleTransfer.vue' +import CattleTransferRegister from '@/components/CattleTransferRegister.vue' +import ApiTestPage from '@/components/ApiTestPage.vue' Vue.use(VueRouter) @@ -75,6 +87,66 @@ const routes = [ path: '/auth-test', name: 'AuthTest', component: AuthTest + }, + { + path: '/electronic-fence', + name: 'ElectronicFence', + component: ElectronicFencePage + }, + { + path: '/smart-eartag-alert', + name: 'SmartEartagAlert', + component: SmartEartagAlertPage + }, + { + path: '/alert-test', + name: 'AlertTest', + component: AlertTest + }, + { + path: '/map-test', + name: 'MapTest', + component: MapTest + }, + { + path: '/api-test', + name: 'ApiTest', + component: ApiTest + }, + { + path: '/wechat-fence-drawer', + name: 'WechatFenceDrawer', + component: WechatFenceDrawer + }, + { + path: '/cattle-profile', + name: 'CattleProfile', + component: CattleProfile + }, + { + path: '/cattle-add', + name: 'CattleAdd', + component: CattleAdd + }, + { + path: '/cattle-test', + name: 'CattleTest', + component: CattleTest + }, + { + path: '/cattle-transfer', + name: 'CattleTransfer', + component: CattleTransfer + }, + { + path: '/cattle-transfer-register', + name: 'CattleTransferRegister', + component: CattleTransferRegister + }, + { + path: '/api-test-page', + name: 'ApiTestPage', + component: ApiTestPage } ] diff --git a/mini_program/farm-monitor-dashboard/src/services/alertService.js b/mini_program/farm-monitor-dashboard/src/services/alertService.js new file mode 100644 index 0000000..d0126da --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/services/alertService.js @@ -0,0 +1,127 @@ +import api from './api' + +// 智能耳标预警相关API服务 +export const alertService = { + // 获取预警列表 + async getAlerts(params = {}) { + try { + const response = await api.get('/smart-eartag-alerts', { params }) + return response + } catch (error) { + console.error('获取预警列表失败:', error) + throw error + } + }, + + // 获取预警详情 + async getAlertById(id) { + try { + const response = await api.get(`/smart-eartag-alerts/${id}`) + return response + } catch (error) { + console.error('获取预警详情失败:', error) + throw error + } + }, + + // 处理预警(标记为已处理) + async resolveAlert(id) { + try { + const response = await api.put(`/smart-eartag-alerts/${id}/resolve`) + return response + } catch (error) { + console.error('处理预警失败:', error) + throw error + } + }, + + // 批量处理预警 + async batchResolveAlerts(ids) { + try { + const response = await api.put('/smart-eartag-alerts/batch-resolve', { ids }) + return response + } catch (error) { + console.error('批量处理预警失败:', error) + throw error + } + }, + + // 删除预警 + async deleteAlert(id) { + try { + const response = await api.delete(`/smart-eartag-alerts/${id}`) + return response + } catch (error) { + console.error('删除预警失败:', error) + throw error + } + }, + + // 获取预警统计 + async getAlertStats() { + try { + const response = await api.get('/smart-eartag-alerts/stats') + return response + } catch (error) { + console.error('获取预警统计失败:', error) + throw error + } + }, + + // 获取设备预警历史 + async getDeviceAlertHistory(deviceId, params = {}) { + try { + const response = await api.get(`/smart-eartag-alerts/device/${deviceId}`, { params }) + return response + } catch (error) { + console.error('获取设备预警历史失败:', error) + throw error + } + }, + + // 设置预警规则 + async setAlertRule(ruleData) { + try { + const response = await api.post('/smart-eartag-alerts/rules', ruleData) + return response + } catch (error) { + console.error('设置预警规则失败:', error) + throw error + } + }, + + // 获取预警规则 + async getAlertRules() { + try { + const response = await api.get('/smart-eartag-alerts/rules') + return response + } catch (error) { + console.error('获取预警规则失败:', error) + throw error + } + }, + + // 更新预警规则 + async updateAlertRule(id, ruleData) { + try { + const response = await api.put(`/smart-eartag-alerts/rules/${id}`, ruleData) + return response + } catch (error) { + console.error('更新预警规则失败:', error) + throw error + } + }, + + // 删除预警规则 + async deleteAlertRule(id) { + try { + const response = await api.delete(`/smart-eartag-alerts/rules/${id}`) + return response + } catch (error) { + console.error('删除预警规则失败:', error) + throw error + } + } +} + +export default alertService \ No newline at end of file diff --git a/mini_program/farm-monitor-dashboard/src/services/api.js b/mini_program/farm-monitor-dashboard/src/services/api.js index 00ccd58..dce737a 100644 --- a/mini_program/farm-monitor-dashboard/src/services/api.js +++ b/mini_program/farm-monitor-dashboard/src/services/api.js @@ -1,9 +1,9 @@ import axios from 'axios' -import { getToken } from '@/utils/auth' +import auth from '@/utils/auth' // 创建axios实例 const service = axios.create({ - baseURL: process.env.VUE_APP_BASE_URL || '/api', + baseURL: process.env.VUE_APP_BASE_URL || 'http://localhost:5300/api', timeout: 10000, headers: { 'Content-Type': 'application/json' @@ -14,7 +14,7 @@ const service = axios.create({ service.interceptors.request.use( (config) => { // 添加token到请求头 - const token = getToken() + const token = auth.getToken() if (token) { config.headers['Authorization'] = `Bearer ${token}` } @@ -38,17 +38,30 @@ service.interceptors.request.use( service.interceptors.response.use( (response) => { const res = response.data + console.log('原始API响应:', res) + console.log('响应状态码:', response.status) // 统一处理响应格式 if (res.code === 200) { + console.log('处理code=200格式') return res.data + } else if (res.success === true) { + // 处理 {success: true, data: ...} 格式 + console.log('处理success=true格式') + return res + } else if (res.success === false) { + // 处理 {success: false, message: ...} 格式 + console.log('处理success=false格式') + return res + } else if (res.code === undefined && res.success === undefined) { + // 直接返回数据的情况 + console.log('处理直接数据格式') + return res } else { // 业务错误 - uni.showToast({ - title: res.message || '请求失败', - icon: 'none', - duration: 2000 - }) + console.error('请求失败:', res.message || '请求失败') + console.error('完整响应:', res) + // 这里可以添加用户提示,比如使用Element UI的Message组件 return Promise.reject(new Error(res.message || '请求失败')) } }, @@ -63,11 +76,10 @@ service.interceptors.response.use( case 401: message = '未授权,请重新登录' // 清除token并跳转到登录页 - uni.removeStorageSync('token') - uni.removeStorageSync('userInfo') - uni.reLaunch({ - url: '/pages/login/login' - }) + localStorage.removeItem('token') + localStorage.removeItem('userInfo') + // 跳转到登录页 + window.location.href = '/login' break case 403: message = '拒绝访问' @@ -87,11 +99,8 @@ service.interceptors.response.use( message = error.message } - uni.showToast({ - title: message, - icon: 'none', - duration: 2000 - }) + console.error('网络错误:', message) + // 这里可以添加用户提示,比如使用Element UI的Message组件 return Promise.reject(error) } @@ -139,29 +148,134 @@ export const del = (url, params = {}) => { } // 上传文件 -export const upload = (url, filePath, formData = {}) => { +export const upload = (url, file, formData = {}) => { return new Promise((resolve, reject) => { - uni.uploadFile({ - url: service.defaults.baseURL + url, - filePath, - name: 'file', - formData, - header: { - 'Authorization': `Bearer ${getToken()}` - }, - success: (res) => { - const data = JSON.parse(res.data) - if (data.code === 200) { - resolve(data.data) - } else { - reject(new Error(data.message)) - } - }, - fail: (error) => { - reject(error) + const uploadFormData = new FormData() + + // 添加文件 + uploadFormData.append('file', file) + + // 添加其他表单数据 + Object.keys(formData).forEach(key => { + uploadFormData.append(key, formData[key]) + }) + + // 使用fetch上传文件 + fetch(service.defaults.baseURL + url, { + method: 'POST', + body: uploadFormData, + headers: { + 'Authorization': `Bearer ${auth.getToken()}` } }) + .then(response => response.json()) + .then(data => { + if (data.code === 200) { + resolve(data.data) + } else { + reject(new Error(data.message)) + } + }) + .catch(error => { + reject(error) + }) }) } +// 牛只档案相关API +export const cattleApi = { + // 获取牛只档案列表 + getCattleList: (params = {}) => { + return get('/iot-cattle/public', params) + }, + + // 根据耳号搜索牛只 + searchCattleByEarNumber: (earNumber) => { + return get('/iot-cattle/public', { search: earNumber }) + }, + + // 获取牛只详情 + getCattleDetail: (id) => { + return get(`/iot-cattle/public/${id}`) + }, + + // 获取牛只类型列表 + getCattleTypes: () => { + return get('/cattle-type') + }, + + // 获取栏舍列表 + getPens: (farmId) => { + return get('/iot-cattle/public/pens/list', { farmId }) + }, + + // 获取批次列表 + getBatches: (farmId) => { + return get('/iot-cattle/public/batches/list', { farmId }) + }, + + // 创建牛只档案 + createCattle: (data) => { + return post('/iot-cattle', data) + }, + + // 更新牛只档案 + updateCattle: (id, data) => { + return put(`/iot-cattle/${id}`, data) + }, + + // 删除牛只档案 + deleteCattle: (id) => { + return del(`/iot-cattle/${id}`) + } +} + +// 牛只转栏记录相关API +export const cattleTransferApi = { + // 获取转栏记录列表 + getTransferRecords: (params = {}) => { + return get('/cattle-transfer-records', params) + }, + + // 根据耳号搜索转栏记录 + searchTransferRecordsByEarNumber: (earNumber, params = {}) => { + return get('/cattle-transfer-records', { earNumber, ...params }) + }, + + // 获取转栏记录详情 + getTransferRecordDetail: (id) => { + return get(`/cattle-transfer-records/${id}`) + }, + + // 创建转栏记录 + createTransferRecord: (data) => { + return post('/cattle-transfer-records', data) + }, + + // 更新转栏记录 + updateTransferRecord: (id, data) => { + return put(`/cattle-transfer-records/${id}`, data) + }, + + // 删除转栏记录 + deleteTransferRecord: (id) => { + return del(`/cattle-transfer-records/${id}`) + }, + + // 批量删除转栏记录 + batchDeleteTransferRecords: (ids) => { + return post('/cattle-transfer-records/batch-delete', { ids }) + }, + + // 获取可用的牛只列表 + getAvailableAnimals: (params = {}) => { + return get('/cattle-transfer-records/available-animals', params) + }, + + // 获取栏舍列表(用于转栏选择) + getBarnsForTransfer: (params = {}) => { + return get('/cattle-pens', params) + } +} + export default service \ No newline at end of file diff --git a/mini_program/farm-monitor-dashboard/src/services/collarService.js b/mini_program/farm-monitor-dashboard/src/services/collarService.js index 9333bb4..4e0e31e 100644 --- a/mini_program/farm-monitor-dashboard/src/services/collarService.js +++ b/mini_program/farm-monitor-dashboard/src/services/collarService.js @@ -92,7 +92,7 @@ export const getAllCollarDevices = async (params = {}) => { temperature: device.temperature || 0, collectedHost: device.sid || device.collectedHost || '未知', totalMovement: device.walk || device.totalMovement || 0, - todayMovement: (device.walk || 0) - (device.y_steps || 0), + todayMovement: (device.steps || device.walk || 0) - (device.y_steps || 0), gpsLocation: device.gps || device.gpsLocation || '未知', updateTime: device.time || device.uptime || device.updateTime || '未知', // 绑定状态映射 - 优先使用bandge_status字段,其次使用state字段 diff --git a/mini_program/farm-monitor-dashboard/src/services/fenceService.js b/mini_program/farm-monitor-dashboard/src/services/fenceService.js new file mode 100644 index 0000000..0ab48f2 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/services/fenceService.js @@ -0,0 +1,198 @@ +import { get, post, put, del } from './api' + +// 电子围栏API服务 +export const fenceService = { + // 获取围栏列表 + getFences(params = {}) { + return get('/electronic-fences', params) + }, + + // 获取单个围栏详情 + getFenceById(id) { + return get(`/electronic-fences/${id}`) + }, + + // 创建围栏 + createFence(data) { + return post('/electronic-fences', data) + }, + + // 更新围栏 + updateFence(id, data) { + return put(`/electronic-fences/${id}`, data) + }, + + // 删除围栏 + deleteFence(id) { + return del(`/electronic-fences/${id}`) + }, + + // 搜索围栏 + searchFences(params) { + return get('/electronic-fences/search', params) + } +} + +// 电子围栏坐标点API服务 +export const fencePointService = { + // 获取围栏的所有坐标点 + getByFenceId(fenceId) { + return get(`/electronic-fence-points/fence/${fenceId}`) + }, + + // 获取单个坐标点详情 + getPointById(id) { + return get(`/electronic-fence-points/${id}`) + }, + + // 创建坐标点 + createPoint(data) { + return post('/electronic-fence-points', data) + }, + + // 批量创建坐标点 + createPoints(data) { + return post('/electronic-fence-points/batch', data) + }, + + // 更新坐标点 + updatePoint(id, data) { + return put(`/electronic-fence-points/${id}`, data) + }, + + // 更新围栏的所有坐标点 + updateFencePoints(fenceId, data) { + return put(`/electronic-fence-points/fence/${fenceId}`, data) + }, + + // 删除坐标点 + deletePoint(id) { + return del(`/electronic-fence-points/${id}`) + }, + + // 删除围栏的所有坐标点 + deleteFencePoints(fenceId) { + return del(`/electronic-fence-points/fence/${fenceId}`) + }, + + // 获取围栏边界框 + getFenceBounds(fenceId) { + return get(`/electronic-fence-points/fence/${fenceId}/bounds`) + }, + + // 搜索坐标点 + searchPoints(params) { + return get('/electronic-fence-points/search', params) + } +} + +// 围栏类型配置 +export const fenceTypes = { + grazing: { + name: '放牧区', + color: '#52c41a', + icon: '🌿' + }, + safety: { + name: '安全区', + color: '#1890ff', + icon: '🛡️' + }, + restricted: { + name: '限制区', + color: '#ff4d4f', + icon: '⚠️' + }, + collector: { + name: '收集区', + color: '#fa8c16', + icon: '📦' + } +} + +// 围栏工具函数 +export const fenceUtils = { + // 计算多边形中心点 + calculateCenter(points) { + if (points.length === 0) return { lng: 0, lat: 0 } + + let lngSum = 0 + let latSum = 0 + + points.forEach(point => { + lngSum += point.lng + latSum += point.lat + }) + + return { + lng: lngSum / points.length, + lat: latSum / points.length + } + }, + + // 计算多边形面积(简化计算) + calculateArea(points) { + if (points.length < 3) return 0 + + let area = 0 + const n = points.length + + for (let i = 0; i < n; i++) { + const j = (i + 1) % n + area += points[i].lng * points[j].lat + area -= points[j].lng * points[i].lat + } + + area = Math.abs(area) / 2 + + // 转换为平方米(粗略计算) + return area * 111000 * 111000 + }, + + // 验证围栏数据 + validateFence(fence) { + const errors = [] + + if (!fence.name || fence.name.trim() === '') { + errors.push('围栏名称不能为空') + } + + if (!fence.type) { + errors.push('请选择围栏类型') + } + + if (!fence.coordinates || fence.coordinates.length < 3) { + errors.push('围栏至少需要3个坐标点') + } + + return { + valid: errors.length === 0, + errors + } + }, + + // 格式化围栏数据 + formatFenceData(rawData) { + return { + id: rawData.id, + name: rawData.name, + type: rawData.type, + description: rawData.description || '', + coordinates: rawData.coordinates || [], + center_lng: rawData.center_lng, + center_lat: rawData.center_lat, + area: rawData.area, + farm_id: rawData.farm_id, + is_active: rawData.is_active !== false, + created_at: rawData.created_at, + updated_at: rawData.updated_at + } + } +} + +export default { + fenceService, + fencePointService, + fenceTypes, + fenceUtils +} diff --git a/mini_program/farm-monitor-dashboard/src/services/hostService.js b/mini_program/farm-monitor-dashboard/src/services/hostService.js index 9b1cad0..cb91d5f 100644 --- a/mini_program/farm-monitor-dashboard/src/services/hostService.js +++ b/mini_program/farm-monitor-dashboard/src/services/hostService.js @@ -12,11 +12,24 @@ const api = axios.create({ // 请求拦截器 api.interceptors.request.use( config => { - const token = localStorage.getItem('token') + // 尝试多种token存储方式 + const token = localStorage.getItem('token') || + localStorage.getItem('authToken') || + localStorage.getItem('accessToken') || + sessionStorage.getItem('token') || + sessionStorage.getItem('authToken') + if (token) { config.headers.Authorization = `Bearer ${token}` + console.log('使用认证token:', token.substring(0, 10) + '...') } else { - console.warn('未找到认证token,使用模拟数据') + console.warn('未找到认证token,尝试无认证访问') + // 尝试其他认证方式 + const apiKey = localStorage.getItem('apiKey') + if (apiKey) { + config.headers['X-API-Key'] = apiKey + console.log('使用API Key认证') + } } return config }, @@ -32,74 +45,110 @@ api.interceptors.response.use( }, error => { console.error('API请求错误:', error) - // 如果是401错误,直接返回模拟数据而不是抛出错误 - if (error.response && error.response.status === 401) { - console.warn('认证失败,返回模拟数据') - return Promise.resolve({ data: { data: [] } }) - } return Promise.reject(error) } ) /** * 获取智能主机设备列表 - * @param {Object} params - 查询参数 + * @param {Object} params - 查询参数 (page, pageSize, search等) * @returns {Promise} API响应 */ -// 模拟数据 -const getMockHostDevices = () => { - return [ - { - hostId: '2490246426', - isOnline: true, - cpuUsage: 45, - memoryUsage: 62, - storageUsage: 38, - networkStatus: '正常', - connectedDevices: 15, - updateTime: '2025-09-18 14:30:15' - }, - { - hostId: '23107000007', - isOnline: false, - cpuUsage: 0, - memoryUsage: 0, - storageUsage: 45, - networkStatus: '断开', - connectedDevices: 0, - updateTime: '2025-09-18 12:15:30' - }, - { - hostId: '23C0270112', - isOnline: true, - cpuUsage: 78, - memoryUsage: 85, - storageUsage: 67, - networkStatus: '正常', - connectedDevices: 23, - updateTime: '2025-09-18 14:25:45' - }, - { - hostId: '2490246427', - isOnline: true, - cpuUsage: 32, - memoryUsage: 48, - storageUsage: 29, - networkStatus: '正常', - connectedDevices: 8, - updateTime: '2025-09-18 14:20:20' +// 模拟数据生成器 +const generateMockHostDevices = (page = 1, pageSize = 10) => { + const totalDevices = 25 // 模拟总共25台设备 + const startIndex = (page - 1) * pageSize + const endIndex = Math.min(startIndex + pageSize, totalDevices) + + const devices = [] + for (let i = startIndex; i < endIndex; i++) { + const deviceId = `2490246${String(426 + i).padStart(3, '0')}` + devices.push({ + hostId: deviceId, + sid: deviceId, + isOnline: Math.random() > 0.3, // 70% 在线概率 + battery: Math.floor(Math.random() * 40) + 60, // 60-100% + voltage: Math.floor(Math.random() * 40) + 60, + signal: Math.floor(Math.random() * 50) + 10, // 10-60% + signa: Math.floor(Math.random() * 50) + 10, + temperature: (Math.random() * 10 + 20).toFixed(1), // 20-30°C + state: Math.random() > 0.2 ? 1 : 0, // 80% 连接状态 + bandge_status: Math.random() > 0.2 ? 1 : 0, + updateTime: new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000).toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }).replace(/\//g, '-'), + lastUpdateTime: new Date(Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000).toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }).replace(/\//g, '-') + }) + } + + return { + data: devices, + pagination: { + currentPage: page, + pageSize: pageSize, + total: totalDevices, + totalPages: Math.ceil(totalDevices / pageSize) } - ] + } } export const getHostDevices = async (params = {}) => { - try { - const response = await api.get('/api/smart-devices/hosts', { params }) - return response.data - } catch (error) { - console.error('获取主机设备列表失败,使用模拟数据:', error) - return { data: getMockHostDevices() } + console.log('正在调用真实API获取主机设备列表...', params) + + const response = await api.get('/api/smart-devices/hosts', { + params: { + page: params.page || 1, + pageSize: params.pageSize || 10, + search: params.search || '', + ...params + } + }) + + console.log('API响应成功:', response.data) + + // 根据API响应结构处理数据 + const apiData = response.data + + // 如果API返回的数据结构包含data字段和total字段 + if (apiData.success && apiData.data) { + return { + data: apiData.data, + pagination: { + currentPage: params.page || 1, + pageSize: params.pageSize || 10, + total: apiData.total || apiData.data.length, + totalPages: Math.ceil((apiData.total || apiData.data.length) / (params.pageSize || 10)) + } + } } + + // 如果API直接返回数组 + if (Array.isArray(apiData)) { + return { + data: apiData, + pagination: { + currentPage: params.page || 1, + pageSize: params.pageSize || 10, + total: apiData.length, + totalPages: Math.ceil(apiData.length / (params.pageSize || 10)) + } + } + } + + // 默认返回API数据 + return apiData } /** @@ -153,9 +202,26 @@ export const stopHost = async (hostId) => { } } +/** + * 更新主机设备信息 + * @param {string} hostId - 主机ID + * @param {Object} updateData - 更新数据 + * @returns {Promise} API响应 + */ +export const updateHostDevice = async (hostId, updateData) => { + try { + const response = await api.put(`/api/smart-devices/hosts/${hostId}`, updateData) + return response.data + } catch (error) { + console.error('更新主机设备信息失败:', error) + throw error + } +} + export default { getHostDevices, restartHost, startHost, - stopHost + stopHost, + updateHostDevice } diff --git a/mini_program/farm-monitor-dashboard/src/utils/index.js b/mini_program/farm-monitor-dashboard/src/utils/index.js index 3f4f4f4..28951c2 100644 --- a/mini_program/farm-monitor-dashboard/src/utils/index.js +++ b/mini_program/farm-monitor-dashboard/src/utils/index.js @@ -173,7 +173,7 @@ export const buildUrlParams = (params) => { export const storage = { set: (key, value) => { try { - uni.setStorageSync(key, value) + localStorage.setItem(key, JSON.stringify(value)) } catch (error) { console.error('存储数据失败:', error) } @@ -181,8 +181,8 @@ export const storage = { get: (key, defaultValue = null) => { try { - const value = uni.getStorageSync(key) - return value !== null && value !== undefined ? value : defaultValue + const value = localStorage.getItem(key) + return value !== null && value !== undefined ? JSON.parse(value) : defaultValue } catch (error) { console.error('获取存储数据失败:', error) return defaultValue @@ -191,7 +191,7 @@ export const storage = { remove: (key) => { try { - uni.removeStorageSync(key) + localStorage.removeItem(key) } catch (error) { console.error('删除存储数据失败:', error) } @@ -199,7 +199,7 @@ export const storage = { clear: () => { try { - uni.clearStorageSync() + localStorage.clear() } catch (error) { console.error('清空存储失败:', error) } diff --git a/mini_program/farm-monitor-dashboard/src/utils/mapping.js b/mini_program/farm-monitor-dashboard/src/utils/mapping.js new file mode 100644 index 0000000..6f7fb80 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/utils/mapping.js @@ -0,0 +1,265 @@ +/** + * 中文映射工具 + * 统一管理所有字段的中文映射 + */ + +// 性别映射 +export const sexMap = { + 1: '公', + 2: '母' +} + +// 品类映射 +export const categoryMap = { + 1: '犊牛', + 2: '育成母牛', + 3: '架子牛', + 4: '青年牛', + 5: '基础母牛', + 6: '育肥牛' +} + +// 品种映射 +export const breedMap = { + 1: '西藏高山牦牛', + 2: '宁夏牛', + 3: '华西牛', + 4: '秦川牛', + 5: '西门塔尔牛', + 6: '荷斯坦牛' +} + +// 品系映射 +export const strainMap = { + 1: '乳肉兼用', + 2: '肉用型', + 3: '乳用型', + 4: '兼用型' +} + +// 生理阶段映射 +export const physiologicalStageMap = { + 1: '犊牛', + 2: '育成期', + 3: '青年期', + 4: '成年期', + 5: '老年期' +} + +// 来源映射 +export const sourceMap = { + 1: '合作社', + 2: '农户', + 3: '养殖场', + 4: '进口', + 5: '自繁' +} + +// 事件映射 +export const eventMap = { + 1: '正常', + 2: '生病', + 3: '怀孕', + 4: '分娩', + 5: '断奶', + 6: '转栏', + 7: '离栏' +} + +// 是否佩戴设备映射 +export const wearMap = { + 0: '否', + 1: '是' +} + +// 是否删除映射 +export const deleteMap = { + 0: '否', + 1: '是' +} + +// 是否出栏映射 +export const outMap = { + 0: '否', + 1: '是' +} + +// 是否电子认证映射 +export const eleAuthMap = { + 0: '否', + 1: '是' +} + +// 是否检疫认证映射 +export const quaAuthMap = { + 0: '否', + 1: '是' +} + +// 是否免疫映射 +export const vaccinMap = { + 0: '否', + 1: '是' +} + +// 是否配种映射 +export const inseminationMap = { + 0: '否', + 1: '是' +} + +// 是否保险映射 +export const insureMap = { + 0: '否', + 1: '是' +} + +// 是否抵押映射 +export const mortgageMap = { + 0: '否', + 1: '是' +} + +// 销售状态映射 +export const sellStatusMap = { + 100: '在栏', + 200: '已售', + 300: '死亡', + 400: '淘汰' +} + +/** + * 获取性别中文名称 + * @param {number} sex 性别代码 + * @returns {string} 中文名称 + */ +export function getSexName(sex) { + return sexMap[sex] || '--' +} + +/** + * 获取品类中文名称 + * @param {number} cate 品类代码 + * @returns {string} 中文名称 + */ +export function getCategoryName(cate) { + return categoryMap[cate] || '--' +} + +/** + * 获取品种中文名称 + * @param {number} varieties 品种代码 + * @returns {string} 中文名称 + */ +export function getBreedName(varieties) { + return breedMap[varieties] || varieties || '--' +} + +/** + * 获取品系中文名称 + * @param {number} strain 品系代码 + * @returns {string} 中文名称 + */ +export function getStrainName(strain) { + return strainMap[strain] || strain || '--' +} + +/** + * 获取生理阶段中文名称 + * @param {number} level 生理阶段代码 + * @returns {string} 中文名称 + */ +export function getPhysiologicalStage(level) { + return physiologicalStageMap[level] || '--' +} + +/** + * 获取来源中文名称 + * @param {number} source 来源代码 + * @returns {string} 中文名称 + */ +export function getSourceName(source) { + return sourceMap[source] || '--' +} + +/** + * 获取事件中文名称 + * @param {number} event 事件代码 + * @returns {string} 中文名称 + */ +export function getEventName(event) { + return eventMap[event] || '--' +} + +/** + * 获取是否佩戴设备中文名称 + * @param {number} isWear 是否佩戴代码 + * @returns {string} 中文名称 + */ +export function getWearName(isWear) { + return wearMap[isWear] || '--' +} + +/** + * 获取销售状态中文名称 + * @param {number} sellStatus 销售状态代码 + * @returns {string} 中文名称 + */ +export function getSellStatusName(sellStatus) { + return sellStatusMap[sellStatus] || '--' +} + +/** + * 格式化日期 + * @param {number} timestamp 时间戳(秒) + * @returns {string} 格式化后的日期 + */ +export function formatDate(timestamp) { + if (!timestamp) return '--' + + // 如果是时间戳(秒),转换为毫秒 + const date = new Date(timestamp * 1000) + return date.toISOString().split('T')[0] +} + +/** + * 格式化日期为时间戳 + * @param {string} dateString 日期字符串 + * @returns {number} 时间戳(秒) + */ +export function formatDateToTimestamp(dateString) { + if (!dateString) return 0 + return Math.floor(new Date(dateString).getTime() / 1000) +} + +// 默认导出所有映射对象 +export default { + sexMap, + categoryMap, + breedMap, + strainMap, + physiologicalStageMap, + sourceMap, + eventMap, + wearMap, + deleteMap, + outMap, + eleAuthMap, + quaAuthMap, + vaccinMap, + inseminationMap, + insureMap, + mortgageMap, + sellStatusMap, + getSexName, + getCategoryName, + getBreedName, + getStrainName, + getPhysiologicalStage, + getSourceName, + getEventName, + getWearName, + getSellStatusName, + formatDate, + formatDateToTimestamp +} diff --git a/mini_program/farm-monitor-dashboard/src/views/ElectronicFencePage.vue b/mini_program/farm-monitor-dashboard/src/views/ElectronicFencePage.vue new file mode 100644 index 0000000..658be19 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/views/ElectronicFencePage.vue @@ -0,0 +1,23 @@ + + + + + diff --git a/mini_program/farm-monitor-dashboard/src/views/SmartEartagAlertPage.vue b/mini_program/farm-monitor-dashboard/src/views/SmartEartagAlertPage.vue new file mode 100644 index 0000000..8212f91 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/src/views/SmartEartagAlertPage.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/mini_program/farm-monitor-dashboard/test-api.html b/mini_program/farm-monitor-dashboard/test-api.html new file mode 100644 index 0000000..d6c941f --- /dev/null +++ b/mini_program/farm-monitor-dashboard/test-api.html @@ -0,0 +1,108 @@ + + + + + + API测试 + + + +

API连接测试

+ +
+

1. 测试基础连接

+ +
点击按钮开始测试
+
+ +
+

2. 测试牛只档案API

+ +
点击按钮开始测试
+
+ +
+

3. 测试牛只类型API

+ +
点击按钮开始测试
+
+ + + + diff --git a/mini_program/farm-monitor-dashboard/test-api.js b/mini_program/farm-monitor-dashboard/test-api.js new file mode 100644 index 0000000..c9e3bda --- /dev/null +++ b/mini_program/farm-monitor-dashboard/test-api.js @@ -0,0 +1,159 @@ +// API测试脚本 - 测试真实后端API +const axios = require('axios') + +// 获取认证token +async function getAuthToken() { + const baseURL = process.env.VUE_APP_API_BASE_URL || 'http://localhost:5350' + try { + const response = await axios.post(`${baseURL}/api/auth/login`, { + username: 'admin', + password: '123456' + }) + return response.data.success ? response.data.token : null + } catch (error) { + console.error('获取认证token失败:', error.message) + return null + } +} + +// 测试API连接 +async function testSmartHostAPI() { + const baseURL = process.env.VUE_APP_API_BASE_URL || 'http://localhost:5350' + + console.log('🔍 开始测试真实智能主机API接口...') + console.log('API地址:', baseURL) + console.log('⚠️ 注意: 此测试将调用真实后端API,不使用模拟数据') + + // 获取认证token + console.log('\n0. 获取认证token...') + const token = await getAuthToken() + if (!token) { + console.error('❌ 无法获取认证token,测试终止') + return + } + console.log('✅ 认证token获取成功') + + try { + // 测试基本连接 + console.log('\n1. 测试基本连接...') + const response = await axios.get(`${baseURL}/api/smart-devices/hosts`, { + params: { + page: 1, + pageSize: 10 + }, + timeout: 15000, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + } + }) + + console.log('✅ API连接成功!') + console.log('响应状态:', response.status) + console.log('响应数据结构:', { + success: response.data.success, + total: response.data.total, + dataLength: response.data.data ? response.data.data.length : 0, + message: response.data.message + }) + + // 检查总数是否正确 + if (response.data.total) { + console.log(`📊 主机总数: ${response.data.total}`) + if (response.data.total === 371) { + console.log('✅ 主机总数正确 (371)') + } else { + console.log(`⚠️ 主机总数不匹配,期望: 371,实际: ${response.data.total}`) + } + } else { + console.log('❌ API响应中没有total字段') + } + + // 测试分页 + console.log('\n2. 测试分页功能...') + const page2Response = await axios.get(`${baseURL}/api/smart-devices/hosts`, { + params: { + page: 2, + pageSize: 5 + }, + timeout: 15000, + headers: { + 'Authorization': `Bearer ${token}` + } + }) + + console.log('✅ 分页测试成功!') + console.log('第2页数据长度:', page2Response.data.data ? page2Response.data.data.length : 0) + console.log('第2页总数:', page2Response.data.total) + + // 测试搜索 + console.log('\n3. 测试搜索功能...') + const searchResponse = await axios.get(`${baseURL}/api/smart-devices/hosts`, { + params: { + page: 1, + pageSize: 10, + search: '2490246' + }, + timeout: 15000, + headers: { + 'Authorization': `Bearer ${token}` + } + }) + + console.log('✅ 搜索测试成功!') + console.log('搜索结果长度:', searchResponse.data.data ? searchResponse.data.data.length : 0) + console.log('搜索结果总数:', searchResponse.data.total) + + // 测试完整数据获取 + console.log('\n4. 测试完整数据获取...') + const allDataResponse = await axios.get(`${baseURL}/api/smart-devices/hosts`, { + params: { + page: 1, + pageSize: 1000 // 获取更多数据 + }, + timeout: 30000, + headers: { + 'Authorization': `Bearer ${token}` + } + }) + + console.log('✅ 完整数据测试成功!') + console.log('实际获取数据长度:', allDataResponse.data.data ? allDataResponse.data.data.length : 0) + console.log('API返回总数:', allDataResponse.data.total) + + console.log('\n🎉 所有测试通过! 前端应该能正确显示371台主机') + + } catch (error) { + console.error('❌ API测试失败:') + if (error.response) { + console.error('状态码:', error.response.status) + console.error('错误信息:', error.response.data) + + if (error.response.status === 401) { + console.log('\n🔐 认证问题:') + console.log('1. 检查是否需要登录token') + console.log('2. 检查API是否需要认证头') + console.log('3. 联系后端开发者获取正确的认证方式') + } + } else if (error.request) { + console.error('网络错误:', error.message) + console.error('请检查API服务是否启动') + } else { + console.error('请求配置错误:', error.message) + } + + console.log('\n💡 解决方案:') + console.log('1. 确保后端服务已启动并运行在', baseURL) + console.log('2. 检查API地址是否正确') + console.log('3. 检查防火墙设置') + console.log('4. 查看后端日志获取详细错误信息') + console.log('5. 确认API接口路径: /api/smart-devices/hosts') + } +} + +// 运行测试 +if (require.main === module) { + testSmartHostAPI() +} + +module.exports = { testSmartHostAPI } diff --git a/mini_program/farm-monitor-dashboard/test-host-number-fix.js b/mini_program/farm-monitor-dashboard/test-host-number-fix.js new file mode 100644 index 0000000..895d7e8 --- /dev/null +++ b/mini_program/farm-monitor-dashboard/test-host-number-fix.js @@ -0,0 +1,86 @@ +// 测试主机编号显示修复 +const axios = require('axios') + +async function testHostNumberFix() { + console.log('🔍 测试主机编号显示修复...') + + try { + // 1. 获取认证token + console.log('\n1. 获取认证token...') + const loginResponse = await axios.post('http://localhost:5350/api/auth/login', { + username: 'admin', + password: '123456' + }) + + if (!loginResponse.data.success) { + throw new Error('登录失败') + } + + const token = loginResponse.data.token + console.log('✅ 认证成功') + + // 2. 获取主机数据 + console.log('\n2. 获取主机数据...') + const hostResponse = await axios.get('http://localhost:5350/api/smart-devices/hosts', { + headers: { Authorization: `Bearer ${token}` }, + params: { page: 1, pageSize: 5 } + }) + + if (!hostResponse.data.success) { + throw new Error('获取主机数据失败') + } + + console.log('✅ 主机数据获取成功') + console.log('主机总数:', hostResponse.data.total) + + // 3. 检查主机编号字段 + console.log('\n3. 检查主机编号字段...') + const devices = hostResponse.data.data + + devices.forEach((device, index) => { + console.log(`\n设备 ${index + 1}:`) + console.log(' - deviceNumber:', device.deviceNumber || '未定义') + console.log(' - sid:', device.sid || '未定义') + console.log(' - hostId:', device.hostId || '未定义') + console.log(' - 显示的主机编号:', device.deviceNumber || device.sid || device.hostId || '无') + + // 检查其他字段 + console.log(' - 设备电量:', device.voltage || device.battery || '无') + console.log(' - 设备信号:', device.signa || device.signal || '无') + console.log(' - 设备温度:', device.temperature || '无') + console.log(' - 绑带状态:', device.bandge_status !== undefined ? device.bandge_status : (device.state !== undefined ? device.state : '无')) + console.log(' - 更新时间:', device.updateTime || device.lastUpdateTime || '无') + }) + + // 4. 测试搜索功能 + console.log('\n4. 测试搜索功能...') + const searchResponse = await axios.get('http://localhost:5350/api/smart-devices/hosts', { + headers: { Authorization: `Bearer ${token}` }, + params: { + page: 1, + pageSize: 10, + search: devices[0].deviceNumber || devices[0].sid || devices[0].hostId + } + }) + + console.log('✅ 搜索测试完成') + console.log('搜索结果数量:', searchResponse.data.data ? searchResponse.data.data.length : 0) + + console.log('\n🎉 主机编号显示修复测试完成!') + console.log('\n📋 修复内容:') + console.log('1. 显示字段: device.deviceNumber || device.sid || device.hostId') + console.log('2. 搜索字段: device.deviceNumber || device.sid || device.hostId') + console.log('3. 编辑字段: device.deviceNumber || device.sid || device.hostId') + + } catch (error) { + console.error('❌ 测试失败:', error.response?.data || error.message) + } +} + +// 运行测试 +if (require.main === module) { + testHostNumberFix() +} + +module.exports = { testHostNumberFix } + diff --git a/mini_program/farm-monitor-dashboard/vite.config.js b/mini_program/farm-monitor-dashboard/vite.config.js index 6a9400a..79ecb82 100644 --- a/mini_program/farm-monitor-dashboard/vite.config.js +++ b/mini_program/farm-monitor-dashboard/vite.config.js @@ -10,12 +10,12 @@ export default defineConfig({ } }, server: { - port: 3000, + port: 8080, proxy: { '/api': { - target: process.env.VUE_APP_BASE_URL || 'http://localhost:3001', + target: 'http://localhost:5350', changeOrigin: true, - rewrite: (path) => path.replace(/^\/api/, '') + rewrite: (path) => path.replace(/^\/api/, '/api') } } },