前端服务共享
This commit is contained in:
@@ -31,7 +31,7 @@ log() {
|
||||
# 数据库连接信息(可通过环境变量覆盖)
|
||||
DB_HOST=${POSTGRES_HOST:-"localhost"}
|
||||
DB_PORT=${POSTGRES_PORT:-"5432"}
|
||||
DB_NAME=${POSTGRES_DB:-"urban-lifeline"}
|
||||
DB_NAME=${POSTGRES_DB:-"urban_lifeline"}
|
||||
DB_USER=${POSTGRES_USER:-"postgres"}
|
||||
DB_PASSWORD=${POSTGRES_PASSWORD:-"postgres"}
|
||||
|
||||
|
||||
@@ -12,12 +12,13 @@ CREATE DATABASE urban_lifeline
|
||||
LC_CTYPE 'zh_CN.UTF-8';
|
||||
|
||||
-- 连接到新创建的数据库
|
||||
\c urban-lifeline;
|
||||
\c urban_lifeline;
|
||||
|
||||
-- -- 创建扩展(如果需要)
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- UUID 支持
|
||||
CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- 文本搜索支持
|
||||
CREATE EXTENSION IF NOT EXISTS "btree_gist"; -- GiST 索引支持
|
||||
CREATE EXTENSION IF NOT EXISTS "vector"; -- 向量
|
||||
|
||||
-- 设置搜索路径(可选,但建议设置)
|
||||
-- ALTER DATABASE urban-lifeline SET search_path TO sys, public;
|
||||
|
||||
@@ -158,7 +158,10 @@ CREATE TABLE sys.tb_sys_view (
|
||||
url VARCHAR(255) DEFAULT NULL, -- 视图URL
|
||||
component VARCHAR(255) DEFAULT NULL, -- 视图组件
|
||||
icon VARCHAR(100) DEFAULT NULL, -- 视图图标
|
||||
type INTEGER DEFAULT 0, -- 视图类型
|
||||
type INTEGER DEFAULT 0, -- 视图类型:0=目录 1=菜单页面 2=按钮
|
||||
view_type VARCHAR(20) DEFAULT 'route', -- 页面类型:route=路由页面 iframe=嵌入页面
|
||||
iframe_url VARCHAR(500) DEFAULT NULL, -- iframe URL(仅当view_type=iframe时有效)
|
||||
service VARCHAR(20) DEFAULT 'platform', -- 所属服务:platform=平台应用 bidding=招标应用 workcase=客服应用
|
||||
layout VARCHAR(100) DEFAULT NULL, -- 布局组件路径名称
|
||||
order_num INTEGER DEFAULT 0, -- 视图排序号
|
||||
dept_path VARCHAR(255) DEFAULT NULL, -- 部门全路径
|
||||
@@ -172,7 +175,11 @@ CREATE TABLE sys.tb_sys_view (
|
||||
PRIMARY KEY (view_id),
|
||||
UNIQUE (optsn)
|
||||
);
|
||||
COMMENT ON TABLE sys.tb_sys_view IS '视图表';
|
||||
-- 创建索引
|
||||
CREATE INDEX idx_sys_view_parent ON sys.tb_sys_view USING btree (parent_id) WHERE deleted = false;
|
||||
CREATE INDEX idx_sys_view_service ON sys.tb_sys_view USING btree (service) WHERE deleted = false;
|
||||
CREATE INDEX idx_sys_view_type ON sys.tb_sys_view USING btree (view_type) WHERE deleted = false;
|
||||
COMMENT ON TABLE sys.tb_sys_view IS '视图表(菜单表)';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.optsn IS '流水号';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.view_id IS '视图ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.name IS '视图名称';
|
||||
@@ -180,7 +187,10 @@ COMMENT ON COLUMN sys.tb_sys_view.parent_id IS '父视图ID';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.url IS '视图URL';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.component IS '视图组件';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.icon IS '视图图标';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.type IS '视图类型';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.type IS '视图类型:0=目录 1=菜单页面 2=按钮';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.view_type IS '页面类型:route=路由页面 iframe=嵌入页面';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.iframe_url IS 'iframe URL(仅当view_type=iframe时有效)';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.service IS '所属服务:platform=平台应用 bidding=招标应用 workcase=客服应用';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.layout IS '布局组件路径名称';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.order_num IS '视图排序号';
|
||||
COMMENT ON COLUMN sys.tb_sys_view.description IS '视图描述';
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
\i createTableBidding.sql
|
||||
|
||||
-- 8. 智能客服业务模块
|
||||
\i createTableCustomerService.sql
|
||||
\i createTableWorkcase.sql
|
||||
|
||||
-- 9. 智能体模块(暂不启用)
|
||||
-- \i createTableAgent.sql
|
||||
|
||||
@@ -59,7 +59,7 @@ INSERT INTO sys.tb_sys_module (
|
||||
('MODULE-0006', 'module_bidding', '招投标', '招投标业务管理',
|
||||
'system', NULL, now(), false),
|
||||
|
||||
('MODULE-0007', 'module_customer_service', '智能客服', '客服工单管理',
|
||||
('MODULE-0007', 'module_workcase', '智能客服', '客服工单管理',
|
||||
'system', NULL, now(), false);
|
||||
|
||||
-- =============================
|
||||
@@ -144,6 +144,18 @@ INSERT INTO sys.tb_sys_permission (
|
||||
('PERM-0401', 'perm_log_view', '日志查看', 'log:log:view', '查看系统日志', 'module_system',
|
||||
true, 'system', NULL, now(), false),
|
||||
('PERM-0402', 'perm_log_export', '日志导出', 'log:log:export', '导出系统日志数据', 'module_system',
|
||||
true, 'system', NULL, now(), false),
|
||||
|
||||
-- 平台基础菜单访问权限(所有登录用户都有)
|
||||
('PERM-0501', 'perm_platform_home', '工作台访问', 'platform:home:view', '访问平台工作台', 'module_system',
|
||||
true, 'system', NULL, now(), false),
|
||||
('PERM-0502', 'perm_platform_chat', 'AI助手访问', 'platform:chat:view', '访问AI助手', 'module_system',
|
||||
true, 'system', NULL, now(), false),
|
||||
('PERM-0503', 'perm_platform_bidding', '招标助手访问', 'platform:bidding:view', '访问招标助手(iframe)', 'module_bidding',
|
||||
true, 'system', NULL, now(), false),
|
||||
('PERM-0504', 'perm_platform_workcase', '泰豪小电访问', 'platform:workcase:view', '访问泰豪小电客服(iframe)', 'module_workcase',
|
||||
true, 'system', NULL, now(), false),
|
||||
('PERM-0505', 'perm_platform_workflow', '智能体编排访问', 'platform:workflow:view', '访问智能体编排(iframe)', 'module_system',
|
||||
true, 'system', NULL, now(), false);
|
||||
|
||||
-- =============================
|
||||
@@ -151,36 +163,84 @@ INSERT INTO sys.tb_sys_permission (
|
||||
-- =============================
|
||||
INSERT INTO sys.tb_sys_view (
|
||||
optsn, view_id, name, parent_id, url, component, icon, type,
|
||||
layout, order_num, description, creator, create_time, deleted
|
||||
view_type, iframe_url, service, layout, order_num, description,
|
||||
creator, create_time, deleted
|
||||
) VALUES
|
||||
-- =========================
|
||||
-- 平台应用菜单 (platform)
|
||||
-- =========================
|
||||
-- 一级菜单
|
||||
('VIEW-0001', 'view_system', '系统管理', NULL, '/system', NULL, 'Settings', 0,
|
||||
'MainLayout', 100, '系统管理菜单', 'system', now(), false),
|
||||
('VIEW-P001', 'view_platform_home', '工作台', NULL, '/home', 'Home', 'Grid', 1,
|
||||
'route', NULL, 'platform', 'SidebarLayout', 10, '平台工作台首页', 'system', now(), false),
|
||||
|
||||
('VIEW-0002', 'view_business', '业务管理', NULL, '/business', NULL, 'Briefcase', 0,
|
||||
'MainLayout', 200, '业务管理菜单', 'system', now(), false),
|
||||
('VIEW-P002', 'view_platform_chat', 'AI助手', NULL, '/chat', 'Chat', 'ChatDotRound', 1,
|
||||
'route', NULL, 'platform', 'SidebarLayout', 20, 'AI智能对话助手', 'system', now(), false),
|
||||
|
||||
-- iframe 嵌入菜单
|
||||
('VIEW-P003', 'view_platform_bidding', '招标助手', NULL, NULL, NULL, 'Document', 1,
|
||||
'iframe', 'http://localhost:5002', 'platform', 'SidebarLayout', 30, '招标应用(iframe)', 'system', now(), false),
|
||||
|
||||
('VIEW-P004', 'view_platform_workcase', '泰豪小电', NULL, NULL, NULL, 'Service', 1,
|
||||
'iframe', 'http://localhost:5003', 'platform', 'SidebarLayout', 40, '客服应用(iframe)', 'system', now(), false),
|
||||
|
||||
('VIEW-P005', 'view_platform_workflow', '智能体编排', NULL, NULL, NULL, 'Connection', 1,
|
||||
'iframe', 'http://localhost:3000', 'platform', 'SidebarLayout', 50, 'Dify智能体编排(iframe)', 'system', now(), false),
|
||||
|
||||
-- 系统管理目录
|
||||
('VIEW-P100', 'view_system', '系统管理', NULL, '/system', NULL, 'Settings', 0,
|
||||
'route', NULL, 'platform', 'SidebarLayout', 100, '系统管理目录', 'system', now(), false),
|
||||
|
||||
-- 系统管理子菜单
|
||||
('VIEW-0101', 'view_user', '用户管理', 'view_system', '/system/user', 'system/UserList', 'Users', 1,
|
||||
'MainLayout', 10, '用户管理页面', 'system', now(), false),
|
||||
('VIEW-P101', 'view_user', '用户管理', 'view_system', '/system/user', 'system/UserList', 'Users', 1,
|
||||
'route', NULL, 'platform', 'SidebarLayout', 10, '用户管理页面', 'system', now(), false),
|
||||
|
||||
('VIEW-0102', 'view_role', '角色管理', 'view_system', '/system/role', 'system/RoleList', 'Shield', 1,
|
||||
'MainLayout', 20, '角色管理页面', 'system', now(), false),
|
||||
('VIEW-P102', 'view_role', '角色管理', 'view_system', '/system/role', 'system/RoleList', 'Shield', 1,
|
||||
'route', NULL, 'platform', 'SidebarLayout', 20, '角色管理页面', 'system', now(), false),
|
||||
|
||||
('VIEW-0103', 'view_dept', '部门管理', 'view_system', '/system/dept', 'system/DeptList', 'Building', 1,
|
||||
'MainLayout', 30, '部门管理页面', 'system', now(), false),
|
||||
('VIEW-P103', 'view_dept', '部门管理', 'view_system', '/system/dept', 'system/DeptList', 'Building', 1,
|
||||
'route', NULL, 'platform', 'SidebarLayout', 30, '部门管理页面', 'system', now(), false),
|
||||
|
||||
('VIEW-0104', 'view_permission', '权限管理', 'view_system', '/system/permission', 'system/PermissionList', 'Lock', 1,
|
||||
'MainLayout', 40, '权限管理页面', 'system', now(), false),
|
||||
('VIEW-P104', 'view_permission', '权限管理', 'view_system', '/system/permission', 'system/PermissionList', 'Lock', 1,
|
||||
'route', NULL, 'platform', 'SidebarLayout', 40, '权限管理页面', 'system', now(), false),
|
||||
|
||||
('VIEW-0105', 'view_config', '配置管理', 'view_system', '/system/config', 'system/ConfigList', 'Settings', 1,
|
||||
'MainLayout', 50, '配置管理页面', 'system', now(), false),
|
||||
('VIEW-P105', 'view_config', '配置管理', 'view_system', '/system/config', 'system/ConfigList', 'Settings', 1,
|
||||
'route', NULL, 'platform', 'SidebarLayout', 50, '配置管理页面', 'system', now(), false),
|
||||
|
||||
('VIEW-0106', 'view_file', '文件管理', 'view_system', '/system/file', 'system/FileList', 'FileText', 1,
|
||||
'MainLayout', 60, '文件管理页面', 'system', now(), false),
|
||||
('VIEW-P106', 'view_file', '文件管理', 'view_system', '/system/file', 'system/FileList', 'FileText', 1,
|
||||
'route', NULL, 'platform', 'SidebarLayout', 60, '文件管理页面', 'system', now(), false),
|
||||
|
||||
('VIEW-0107', 'view_message', '消息管理', 'view_system', '/system/message', 'system/MessageList', 'Mail', 1,
|
||||
'MainLayout', 70, '消息管理页面', 'system', now(), false);
|
||||
('VIEW-P107', 'view_message', '消息管理', 'view_system', '/system/message', 'system/MessageList', 'Mail', 1,
|
||||
'route', NULL, 'platform', 'SidebarLayout', 70, '消息管理页面', 'system', now(), false),
|
||||
|
||||
-- =========================
|
||||
-- 招标应用菜单 (bidding)
|
||||
-- =========================
|
||||
('VIEW-B001', 'view_bidding_home', '首页', NULL, '/home', 'Home', 'House', 1,
|
||||
'route', NULL, 'bidding', 'DefaultLayout', 10, '招标应用首页', 'system', now(), false),
|
||||
|
||||
('VIEW-B002', 'view_bidding_list', '招标列表', NULL, '/bidding/list', 'bidding/List', 'List', 1,
|
||||
'route', NULL, 'bidding', 'DefaultLayout', 20, '招标项目列表', 'system', now(), false),
|
||||
|
||||
('VIEW-B003', 'view_bidding_detail', '招标详情', NULL, '/bidding/detail', 'bidding/Detail', 'Document', 1,
|
||||
'route', NULL, 'bidding', 'DefaultLayout', 30, '招标项目详情', 'system', now(), false),
|
||||
|
||||
('VIEW-B004', 'view_bidding_offer', '投标管理', NULL, '/bidding/offer', 'bidding/Offer', 'Edit', 1,
|
||||
'route', NULL, 'bidding', 'DefaultLayout', 40, '投标管理页面', 'system', now(), false),
|
||||
|
||||
-- =========================
|
||||
-- 客服应用菜单 (workcase)
|
||||
-- =========================
|
||||
('VIEW-W001', 'view_workcase_home', '首页', NULL, '/home', 'Home', 'House', 1,
|
||||
'route', NULL, 'workcase', 'DefaultLayout', 10, '客服应用首页', 'system', now(), false),
|
||||
|
||||
('VIEW-W002', 'view_workcase_list', '工单列表', NULL, '/workcase/list', 'workcase/List', 'Tickets', 1,
|
||||
'route', NULL, 'workcase', 'DefaultLayout', 20, '工单列表页面', 'system', now(), false),
|
||||
|
||||
('VIEW-W003', 'view_workcase_detail', '工单详情', NULL, '/workcase/detail', 'workcase/Detail', 'Document', 1,
|
||||
'route', NULL, 'workcase', 'DefaultLayout', 30, '工单详情页面', 'system', now(), false),
|
||||
|
||||
('VIEW-W004', 'view_workcase_chat', '智能客服', NULL, '/workcase/chat', 'workcase/Chat', 'ChatDotRound', 1,
|
||||
'route', NULL, 'workcase', 'DefaultLayout', 40, '智能客服聊天', 'system', now(), false);
|
||||
|
||||
-- =============================
|
||||
-- 6. 角色权限关联(超级管理员拥有所有权限)
|
||||
@@ -199,7 +259,7 @@ SELECT
|
||||
FROM sys.tb_sys_permission
|
||||
WHERE deleted = false;
|
||||
|
||||
-- 系统管理员权限(除了用户删除外的系统管理权限)
|
||||
-- 系统管理员权限(除了用户删除外的系统管理权限 + 所有平台基础菜单)
|
||||
INSERT INTO sys.tb_sys_role_permission (
|
||||
optsn, role_id, permission_id, creator, dept_path, create_time, deleted
|
||||
)
|
||||
@@ -214,73 +274,94 @@ SELECT
|
||||
FROM sys.tb_sys_permission
|
||||
WHERE deleted = false
|
||||
AND code NOT IN ('system:user:delete', 'system:role:delete', 'system:dept:delete')
|
||||
AND module_id IN ('module_system', 'module_file', 'module_message', 'module_config');
|
||||
AND (
|
||||
module_id IN ('module_system', 'module_file', 'module_message', 'module_config', 'module_bidding', 'module_workcase')
|
||||
OR code LIKE 'platform:%:view' -- 包含所有平台基础菜单权限
|
||||
);
|
||||
|
||||
-- 普通用户权限(基础查看和文件操作)
|
||||
-- 普通用户权限(基础查看和文件操作 + 平台基础菜单访问)
|
||||
INSERT INTO sys.tb_sys_role_permission (
|
||||
optsn, role_id, permission_id, creator, dept_path, create_time, deleted
|
||||
) VALUES
|
||||
('RP-U-0001', 'role_user', 'perm_user_view', 'system', NULL, now(), false),
|
||||
('RP-U-0002', 'role_user', 'perm_file_view', 'system', NULL, now(), false),
|
||||
('RP-U-0003', 'role_user', 'perm_file_upload', 'system', NULL, now(), false),
|
||||
('RP-U-0004', 'role_user', 'perm_file_download', 'system', NULL, now(), false),
|
||||
('RP-U-0005', 'role_user', 'perm_message_view', 'system', NULL, now(), false),
|
||||
('RP-U-0006', 'role_user', 'perm_config_view', 'system', NULL, now(), false);
|
||||
-- 平台基础菜单访问权限
|
||||
('RP-U-0001', 'role_user', 'perm_platform_home', 'system', NULL, now(), false),
|
||||
('RP-U-0002', 'role_user', 'perm_platform_chat', 'system', NULL, now(), false),
|
||||
('RP-U-0003', 'role_user', 'perm_platform_bidding', 'system', NULL, now(), false),
|
||||
('RP-U-0004', 'role_user', 'perm_platform_workcase', 'system', NULL, now(), false),
|
||||
('RP-U-0005', 'role_user', 'perm_platform_workflow', 'system', NULL, now(), false),
|
||||
-- 系统功能权限
|
||||
('RP-U-0011', 'role_user', 'perm_user_view', 'system', NULL, now(), false),
|
||||
('RP-U-0012', 'role_user', 'perm_file_view', 'system', NULL, now(), false),
|
||||
('RP-U-0013', 'role_user', 'perm_file_upload', 'system', NULL, now(), false),
|
||||
('RP-U-0014', 'role_user', 'perm_file_download', 'system', NULL, now(), false),
|
||||
('RP-U-0015', 'role_user', 'perm_message_view', 'system', NULL, now(), false),
|
||||
('RP-U-0016', 'role_user', 'perm_config_view', 'system', NULL, now(), false);
|
||||
|
||||
-- 访客权限(仅查看)
|
||||
-- 访客权限(仅查看 + 基础菜单访问)
|
||||
INSERT INTO sys.tb_sys_role_permission (
|
||||
optsn, role_id, permission_id, creator, dept_path, create_time, deleted
|
||||
) VALUES
|
||||
('RP-G-0001', 'role_guest', 'perm_user_view', 'system', NULL, now(), false),
|
||||
('RP-G-0002', 'role_guest', 'perm_file_view', 'system', NULL, now(), false),
|
||||
('RP-G-0003', 'role_guest', 'perm_message_view', 'system', NULL, now(), false);
|
||||
-- 平台基础菜单访问权限
|
||||
('RP-G-0001', 'role_guest', 'perm_platform_home', 'system', NULL, now(), false),
|
||||
('RP-G-0002', 'role_guest', 'perm_platform_chat', 'system', NULL, now(), false),
|
||||
-- 系统功能权限(仅查看)
|
||||
('RP-G-0011', 'role_guest', 'perm_user_view', 'system', NULL, now(), false),
|
||||
('RP-G-0012', 'role_guest', 'perm_file_view', 'system', NULL, now(), false),
|
||||
('RP-G-0013', 'role_guest', 'perm_message_view', 'system', NULL, now(), false);
|
||||
|
||||
-- =============================
|
||||
-- 7. 视图权限关联
|
||||
-- =============================
|
||||
-- 将视图与对应模块的权限关联
|
||||
-- 将视图与对应模块的权限关联(使用新的 view_id)
|
||||
INSERT INTO sys.tb_sys_view_permission (
|
||||
optsn, view_id, permission_id, creator, dept_path, create_time, deleted
|
||||
) VALUES
|
||||
-- 用户管理视图关联用户权限
|
||||
('VP-0001', 'view_user', 'perm_user_view', 'system', NULL, now(), false),
|
||||
('VP-0002', 'view_user', 'perm_user_create', 'system', NULL, now(), false),
|
||||
('VP-0003', 'view_user', 'perm_user_edit', 'system', NULL, now(), false),
|
||||
('VP-0004', 'view_user', 'perm_user_delete', 'system', NULL, now(), false),
|
||||
('VP-0005', 'view_user', 'perm_user_export', 'system', NULL, now(), false),
|
||||
-- 平台基础菜单权限关联(所有登录用户都可访问)
|
||||
('VP-P001', 'VIEW-P001', 'perm_platform_home', 'system', NULL, now(), false),
|
||||
('VP-P002', 'VIEW-P002', 'perm_platform_chat', 'system', NULL, now(), false),
|
||||
('VP-P003', 'VIEW-P003', 'perm_platform_bidding', 'system', NULL, now(), false),
|
||||
('VP-P004', 'VIEW-P004', 'perm_platform_workcase', 'system', NULL, now(), false),
|
||||
('VP-P005', 'VIEW-P005', 'perm_platform_workflow', 'system', NULL, now(), false),
|
||||
|
||||
-- 角色管理视图关联角色权限
|
||||
('VP-0011', 'view_role', 'perm_role_view', 'system', NULL, now(), false),
|
||||
('VP-0012', 'view_role', 'perm_role_create', 'system', NULL, now(), false),
|
||||
('VP-0013', 'view_role', 'perm_role_edit', 'system', NULL, now(), false),
|
||||
('VP-0014', 'view_role', 'perm_role_delete', 'system', NULL, now(), false),
|
||||
('VP-0015', 'view_role', 'perm_role_export', 'system', NULL, now(), false),
|
||||
-- 用户管理视图关联用户权限(VIEW-P101)
|
||||
('VP-0001', 'VIEW-P101', 'perm_user_view', 'system', NULL, now(), false),
|
||||
('VP-0002', 'VIEW-P101', 'perm_user_create', 'system', NULL, now(), false),
|
||||
('VP-0003', 'VIEW-P101', 'perm_user_edit', 'system', NULL, now(), false),
|
||||
('VP-0004', 'VIEW-P101', 'perm_user_delete', 'system', NULL, now(), false),
|
||||
('VP-0005', 'VIEW-P101', 'perm_user_export', 'system', NULL, now(), false),
|
||||
|
||||
-- 部门管理视图关联部门权限
|
||||
('VP-0021', 'view_dept', 'perm_dept_view', 'system', NULL, now(), false),
|
||||
('VP-0022', 'view_dept', 'perm_dept_create', 'system', NULL, now(), false),
|
||||
('VP-0023', 'view_dept', 'perm_dept_edit', 'system', NULL, now(), false),
|
||||
('VP-0024', 'view_dept', 'perm_dept_delete', 'system', NULL, now(), false),
|
||||
('VP-0025', 'view_dept', 'perm_dept_export', 'system', NULL, now(), false),
|
||||
-- 角色管理视图关联角色权限(VIEW-P102)
|
||||
('VP-0011', 'VIEW-P102', 'perm_role_view', 'system', NULL, now(), false),
|
||||
('VP-0012', 'VIEW-P102', 'perm_role_create', 'system', NULL, now(), false),
|
||||
('VP-0013', 'VIEW-P102', 'perm_role_edit', 'system', NULL, now(), false),
|
||||
('VP-0014', 'VIEW-P102', 'perm_role_delete', 'system', NULL, now(), false),
|
||||
('VP-0015', 'VIEW-P102', 'perm_role_export', 'system', NULL, now(), false),
|
||||
|
||||
-- 权限管理视图关联权限管理权限
|
||||
('VP-0031', 'view_permission', 'perm_permission_view', 'system', NULL, now(), false),
|
||||
('VP-0032', 'view_permission', 'perm_permission_manage', 'system', NULL, now(), false),
|
||||
-- 部门管理视图关联部门权限(VIEW-P103)
|
||||
('VP-0021', 'VIEW-P103', 'perm_dept_view', 'system', NULL, now(), false),
|
||||
('VP-0022', 'VIEW-P103', 'perm_dept_create', 'system', NULL, now(), false),
|
||||
('VP-0023', 'VIEW-P103', 'perm_dept_edit', 'system', NULL, now(), false),
|
||||
('VP-0024', 'VIEW-P103', 'perm_dept_delete', 'system', NULL, now(), false),
|
||||
('VP-0025', 'VIEW-P103', 'perm_dept_export', 'system', NULL, now(), false),
|
||||
|
||||
-- 配置管理视图关联配置权限
|
||||
('VP-0041', 'view_config', 'perm_config_view', 'system', NULL, now(), false),
|
||||
('VP-0042', 'view_config', 'perm_config_edit', 'system', NULL, now(), false),
|
||||
('VP-0043', 'view_config', 'perm_config_export', 'system', NULL, now(), false),
|
||||
-- 权限管理视图关联权限管理权限(VIEW-P104)
|
||||
('VP-0031', 'VIEW-P104', 'perm_permission_view', 'system', NULL, now(), false),
|
||||
('VP-0032', 'VIEW-P104', 'perm_permission_manage', 'system', NULL, now(), false),
|
||||
|
||||
-- 文件管理视图关联文件权限
|
||||
('VP-0051', 'view_file', 'perm_file_view', 'system', NULL, now(), false),
|
||||
('VP-0052', 'view_file', 'perm_file_upload', 'system', NULL, now(), false),
|
||||
('VP-0053', 'view_file', 'perm_file_download', 'system', NULL, now(), false),
|
||||
('VP-0054', 'view_file', 'perm_file_delete', 'system', NULL, now(), false),
|
||||
('VP-0055', 'view_file', 'perm_file_export', 'system', NULL, now(), false),
|
||||
-- 配置管理视图关联配置权限(VIEW-P105)
|
||||
('VP-0041', 'VIEW-P105', 'perm_config_view', 'system', NULL, now(), false),
|
||||
('VP-0042', 'VIEW-P105', 'perm_config_edit', 'system', NULL, now(), false),
|
||||
('VP-0043', 'VIEW-P105', 'perm_config_export', 'system', NULL, now(), false),
|
||||
|
||||
-- 消息管理视图关联消息权限
|
||||
('VP-0061', 'view_message', 'perm_message_view', 'system', NULL, now(), false),
|
||||
('VP-0062', 'view_message', 'perm_message_send', 'system', NULL, now(), false),
|
||||
('VP-0063', 'view_message', 'perm_message_manage', 'system', NULL, now(), false),
|
||||
('VP-0064', 'view_message', 'perm_message_export', 'system', NULL, now(), false);
|
||||
-- 文件管理视图关联文件权限(VIEW-P106)
|
||||
('VP-0051', 'VIEW-P106', 'perm_file_view', 'system', NULL, now(), false),
|
||||
('VP-0052', 'VIEW-P106', 'perm_file_upload', 'system', NULL, now(), false),
|
||||
('VP-0053', 'VIEW-P106', 'perm_file_download', 'system', NULL, now(), false),
|
||||
('VP-0054', 'VIEW-P106', 'perm_file_delete', 'system', NULL, now(), false),
|
||||
('VP-0055', 'VIEW-P106', 'perm_file_export', 'system', NULL, now(), false),
|
||||
|
||||
-- 消息管理视图关联消息权限(VIEW-P107)
|
||||
('VP-0061', 'VIEW-P107', 'perm_message_view', 'system', NULL, now(), false),
|
||||
('VP-0062', 'VIEW-P107', 'perm_message_send', 'system', NULL, now(), false),
|
||||
('VP-0063', 'VIEW-P107', 'perm_message_manage', 'system', NULL, now(), false),
|
||||
('VP-0064', 'VIEW-P107', 'perm_message_export', 'system', NULL, now(), false);
|
||||
|
||||
@@ -12,7 +12,7 @@ urban-lifeline:
|
||||
- /** # 认证服务的所有接口都放行
|
||||
security:
|
||||
aes:
|
||||
secret-key: 1234567890qwer
|
||||
secret-key: MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI= # Base64 编码,32字节(256位)
|
||||
# ================== Spring ==================
|
||||
spring:
|
||||
application:
|
||||
|
||||
@@ -36,9 +36,18 @@ public class TbSysViewDTO extends BaseDTO {
|
||||
@Schema(description = "图标")
|
||||
private String icon;
|
||||
|
||||
@Schema(description = "类型")
|
||||
@Schema(description = "类型:0=目录 1=菜单页面 2=按钮")
|
||||
private Integer type;
|
||||
|
||||
@Schema(description = "页面类型:route=路由页面 iframe=嵌入页面")
|
||||
private String viewType;
|
||||
|
||||
@Schema(description = "iframe URL(仅当viewType=iframe时有效)")
|
||||
private String iframeUrl;
|
||||
|
||||
@Schema(description = "所属服务:platform=平台应用 bidding=招标应用 workcase=客服应用")
|
||||
private String service;
|
||||
|
||||
@Schema(description = "布局")
|
||||
private String layout;
|
||||
|
||||
|
||||
@@ -37,9 +37,9 @@ spring:
|
||||
- id: auth-service
|
||||
uri: lb://auth-service
|
||||
predicates:
|
||||
- Path=/auth/**
|
||||
- Path=/urban-lifeline/auth/**
|
||||
filters:
|
||||
- RewritePath=/auth/(?<segment>.*), /urban-lifeline/auth/$\{segment}
|
||||
# 不需要重写,直接转发保持原路径
|
||||
- name: RequestRateLimiter
|
||||
args:
|
||||
redis-rate-limiter.replenishRate: 100
|
||||
@@ -49,73 +49,55 @@ spring:
|
||||
- id: system-service
|
||||
uri: lb://system-service
|
||||
predicates:
|
||||
- Path=/system/**
|
||||
filters:
|
||||
- RewritePath=/system/(?<segment>.*), /urban-lifeline/system/$\{segment}
|
||||
- Path=/urban-lifeline/system/**
|
||||
|
||||
# ==================== 日志服务路由 ====================
|
||||
- id: log-service
|
||||
uri: lb://log-service
|
||||
predicates:
|
||||
- Path=/log/**
|
||||
filters:
|
||||
- RewritePath=/log/(?<segment>.*), /urban-lifeline/log/$\{segment}
|
||||
- Path=/urban-lifeline/log/**
|
||||
|
||||
# ==================== 文件服务路由 ====================
|
||||
- id: file-service
|
||||
uri: lb://file-service
|
||||
predicates:
|
||||
- Path=/file/**
|
||||
filters:
|
||||
- RewritePath=/file/(?<segment>.*), /urban-lifeline/file/$\{segment}
|
||||
- Path=/urban-lifeline/file/**
|
||||
|
||||
# ==================== 消息服务路由 ====================
|
||||
- id: message-service
|
||||
uri: lb://message-service
|
||||
predicates:
|
||||
- Path=/message/**
|
||||
filters:
|
||||
- RewritePath=/message/(?<segment>.*), /urban-lifeline/message/$\{segment}
|
||||
- Path=/urban-lifeline/message/**
|
||||
|
||||
# ==================== 招投标服务路由 ====================
|
||||
- id: bidding-service
|
||||
uri: lb://bidding-service
|
||||
predicates:
|
||||
- Path=/bidding/**
|
||||
filters:
|
||||
- RewritePath=/bidding/(?<segment>.*), /urban-lifeline/bidding/$\{segment}
|
||||
- Path=/urban-lifeline/bidding/**
|
||||
|
||||
# ==================== 平台服务路由 ====================
|
||||
- id: platform-service
|
||||
uri: lb://platform-service
|
||||
predicates:
|
||||
- Path=/platform/**
|
||||
filters:
|
||||
- RewritePath=/platform/(?<segment>.*), /urban-lifeline/platform/$\{segment}
|
||||
- Path=/urban-lifeline/platform/**
|
||||
|
||||
# ==================== 工单服务路由 ====================
|
||||
- id: workcase-service
|
||||
uri: lb://workcase-service
|
||||
predicates:
|
||||
- Path=/workcase/**
|
||||
filters:
|
||||
- RewritePath=/workcase/(?<segment>.*), /urban-lifeline/workcase/$\{segment}
|
||||
- Path=/urban-lifeline/workcase/**
|
||||
|
||||
# ==================== 定时任务服务路由 ====================
|
||||
- id: crontab-service
|
||||
uri: lb://crontab-service
|
||||
predicates:
|
||||
- Path=/crontab/**
|
||||
filters:
|
||||
- RewritePath=/crontab/(?<segment>.*), /urban-lifeline/crontab/$\{segment}
|
||||
- Path=/urban-lifeline/crontab/**
|
||||
|
||||
# ==================== AI Agent 服务路由 ====================
|
||||
- id: agent-service
|
||||
uri: lb://agent-service
|
||||
predicates:
|
||||
- Path=/agent/**
|
||||
filters:
|
||||
- RewritePath=/agent/(?<segment>.*), /urban-lifeline/agent/$\{segment}
|
||||
- Path=/urban-lifeline/agent/**
|
||||
|
||||
# 全局跨域配置
|
||||
globalcors:
|
||||
@@ -175,7 +157,7 @@ auth:
|
||||
- /error
|
||||
security:
|
||||
aes:
|
||||
secret-key: 1234567890qwer
|
||||
secret-key: MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI= # Base64 编码,32字节(256位)
|
||||
# Actuator 监控端点
|
||||
management:
|
||||
endpoints:
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
<result column="component" property="component" jdbcType="VARCHAR"/>
|
||||
<result column="icon" property="icon" jdbcType="VARCHAR"/>
|
||||
<result column="type" property="type" jdbcType="INTEGER"/>
|
||||
<result column="view_type" property="viewType" jdbcType="VARCHAR"/>
|
||||
<result column="iframe_url" property="iframeUrl" jdbcType="VARCHAR"/>
|
||||
<result column="service" property="service" jdbcType="VARCHAR"/>
|
||||
<result column="layout" property="layout" jdbcType="VARCHAR"/>
|
||||
<result column="order_num" property="orderNum" jdbcType="INTEGER"/>
|
||||
<result column="description" property="description" jdbcType="VARCHAR"/>
|
||||
@@ -29,7 +32,7 @@
|
||||
|
||||
<!-- 基础列 -->
|
||||
<sql id="Base_Column_List">
|
||||
view_id, name, parent_id, url, component, icon, type, layout, order_num, description,
|
||||
view_id, name, parent_id, url, component, icon, type, view_type, iframe_url, service, layout, order_num, description,
|
||||
optsn, creator, updater, dept_path, remark, create_time, update_time, delete_time, deleted
|
||||
</sql>
|
||||
|
||||
@@ -44,11 +47,14 @@
|
||||
layout,
|
||||
order_num,
|
||||
optsn,
|
||||
<!-- 可选字段:parent_id, url, component, icon, description 及基础字段 -->
|
||||
<!-- 可选字段:parent_id, url, component, icon, view_type, iframe_url, service, description 及基础字段 -->
|
||||
<if test="parentId != null and parentId != ''">parent_id,</if>
|
||||
<if test="url != null and url != ''">url,</if>
|
||||
<if test="component != null and component != ''">component,</if>
|
||||
<if test="icon != null and icon != ''">icon,</if>
|
||||
<if test="viewType != null and viewType != ''">view_type,</if>
|
||||
<if test="iframeUrl != null and iframeUrl != ''">iframe_url,</if>
|
||||
<if test="service != null and service != ''">service,</if>
|
||||
<if test="description != null and description != ''">description,</if>
|
||||
<if test="creator != null and creator != ''">creator,</if>
|
||||
<if test="deptPath != null and deptPath != ''">dept_path,</if>
|
||||
@@ -72,6 +78,9 @@
|
||||
<if test="url != null and url != ''">#{url},</if>
|
||||
<if test="component != null and component != ''">#{component},</if>
|
||||
<if test="icon != null and icon != ''">#{icon},</if>
|
||||
<if test="viewType != null and viewType != ''">#{viewType},</if>
|
||||
<if test="iframeUrl != null and iframeUrl != ''">#{iframeUrl},</if>
|
||||
<if test="service != null and service != ''">#{service},</if>
|
||||
<if test="description != null and description != ''">#{description},</if>
|
||||
<if test="creator != null and creator != ''">#{creator},</if>
|
||||
<if test="deptPath != null and deptPath != ''">#{deptPath},</if>
|
||||
@@ -105,6 +114,15 @@
|
||||
<if test="type != null">
|
||||
type = #{type},
|
||||
</if>
|
||||
<if test="viewType != null">
|
||||
view_type = #{viewType},
|
||||
</if>
|
||||
<if test="iframeUrl != null">
|
||||
iframe_url = #{iframeUrl},
|
||||
</if>
|
||||
<if test="service != null">
|
||||
service = #{service},
|
||||
</if>
|
||||
<if test="layout != null">
|
||||
layout = #{layout},
|
||||
</if>
|
||||
@@ -168,6 +186,12 @@
|
||||
<if test="filter.type != null">
|
||||
AND type = #{filter.type}
|
||||
</if>
|
||||
<if test="filter.viewType != null and filter.viewType != ''">
|
||||
AND view_type = #{filter.viewType}
|
||||
</if>
|
||||
<if test="filter.service != null and filter.service != ''">
|
||||
AND service = #{filter.service}
|
||||
</if>
|
||||
<if test="filter.deptPath != null and filter.deptPath != ''">
|
||||
AND dept_path LIKE CONCAT(#{filter.deptPath}, '%')
|
||||
</if>
|
||||
@@ -194,6 +218,12 @@
|
||||
<if test="filter.type != null">
|
||||
AND type = #{filter.type}
|
||||
</if>
|
||||
<if test="filter.viewType != null and filter.viewType != ''">
|
||||
AND view_type = #{filter.viewType}
|
||||
</if>
|
||||
<if test="filter.service != null and filter.service != ''">
|
||||
AND service = #{filter.service}
|
||||
</if>
|
||||
<if test="filter.deptPath != null and filter.deptPath != ''">
|
||||
AND dept_path LIKE CONCAT(#{filter.deptPath}, '%')
|
||||
</if>
|
||||
@@ -220,6 +250,12 @@
|
||||
<if test="filter.type != null">
|
||||
AND type = #{filter.type}
|
||||
</if>
|
||||
<if test="filter.viewType != null and filter.viewType != ''">
|
||||
AND view_type = #{filter.viewType}
|
||||
</if>
|
||||
<if test="filter.service != null and filter.service != ''">
|
||||
AND service = #{filter.service}
|
||||
</if>
|
||||
<if test="filter.deptPath != null and filter.deptPath != ''">
|
||||
AND dept_path LIKE CONCAT(#{filter.deptPath}, '%')
|
||||
</if>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Urban Lifeline Web 微前端项目
|
||||
|
||||
基于 Import Maps 的微前端架构,包含共享模块和多个业务应用。
|
||||
基于 **@module-federation/vite** 的微前端架构,包含共享模块和多个业务应用。
|
||||
|
||||
> ✨ **v2.0 升级**:已从 `@originjs/vite-plugin-federation` 迁移到官方维护的 `@module-federation/vite`,获得更好的开发体验和稳定性!
|
||||
|
||||
## 📦 项目结构
|
||||
|
||||
@@ -19,40 +21,43 @@ urbanLifelineWeb/
|
||||
### 1. 安装依赖
|
||||
|
||||
```bash
|
||||
# 一键安装所有包的依赖
|
||||
npm run install:all
|
||||
# 使用 pnpm 安装(推荐)
|
||||
pnpm install
|
||||
|
||||
# 或单独安装
|
||||
npm run install:shared
|
||||
npm run install:platform
|
||||
npm run install:bidding
|
||||
npm run install:workcase
|
||||
# 或使用 install.bat 脚本
|
||||
install.bat
|
||||
```
|
||||
|
||||
### 2. 启动开发服务器
|
||||
|
||||
```bash
|
||||
# 同时启动所有服务
|
||||
npm run dev:all
|
||||
# 方式1: 使用启动脚本(Windows,推荐)
|
||||
start-all.bat
|
||||
|
||||
# 或单独启动
|
||||
npm run dev:shared # http://localhost:5000
|
||||
npm run dev:platform # http://localhost:5001
|
||||
npm run dev:bidding # http://localhost:5002
|
||||
npm run dev:workcase # http://localhost:5003
|
||||
# 方式2: 使用 pnpm 并行启动所有服务
|
||||
pnpm run dev
|
||||
|
||||
# 方式3: 手动启动(需要两个终端)
|
||||
# 终端1 - 启动 shared
|
||||
cd packages/shared
|
||||
pnpm run dev # http://localhost:5000
|
||||
|
||||
# 终端2 - 启动 platform
|
||||
cd packages/platform
|
||||
pnpm run dev # http://localhost:5001
|
||||
```
|
||||
|
||||
### 3. 构建生产版本
|
||||
|
||||
```bash
|
||||
# 构建所有应用
|
||||
npm run build:all
|
||||
pnpm run build
|
||||
|
||||
# 或单独构建
|
||||
npm run build:shared
|
||||
npm run build:platform
|
||||
npm run build:bidding
|
||||
npm run build:workcase
|
||||
cd packages/shared && pnpm run build
|
||||
cd packages/platform && pnpm run build
|
||||
cd packages/bidding && pnpm run build
|
||||
cd packages/workcase && pnpm run build
|
||||
```
|
||||
|
||||
## 🌐 端口分配
|
||||
@@ -73,24 +78,144 @@ npm run build:workcase
|
||||
- **路由**: Vue Router 4.5
|
||||
- **工具库**: VueUse
|
||||
|
||||
## 📝 开发说明
|
||||
## 📝 Module Federation 配置
|
||||
|
||||
### Import Maps
|
||||
### Shared 配置 (packages/shared/vite.config.ts)
|
||||
|
||||
本项目使用 Import Maps 实现模块共享:
|
||||
```typescript
|
||||
import { federation } from '@module-federation/vite'
|
||||
|
||||
```html
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"@shared/components": "http://localhost:5000/shared/components.js",
|
||||
"@shared/utils": "http://localhost:5000/shared/utils.js",
|
||||
"@shared/api": "http://localhost:5000/shared/api.js"
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
federation({
|
||||
name: 'shared',
|
||||
filename: 'remoteEntry.js',
|
||||
exposes: {
|
||||
'./FileUpload': './src/components/fileupload/FileUpload.vue',
|
||||
'./DynamicFormItem': './src/components/dynamicFormItem/DynamicFormItem.vue',
|
||||
'./api': './src/api/index.ts',
|
||||
'./authAPI': './src/api/auth/auth.ts',
|
||||
'./fileAPI': './src/api/file/file.ts',
|
||||
'./utils': './src/utils/index.ts',
|
||||
'./types': './src/types/index.ts',
|
||||
'./components': './src/components/index.ts'
|
||||
},
|
||||
shared: {
|
||||
vue: {},
|
||||
'vue-router': {},
|
||||
'element-plus': {},
|
||||
'@element-plus/icons-vue': {},
|
||||
axios: {}
|
||||
}
|
||||
})
|
||||
],
|
||||
server: {
|
||||
port: 5000,
|
||||
strictPort: true, // 关键:锁定端口,避免漂移
|
||||
host: true,
|
||||
cors: true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Platform 配置 (packages/platform/vite.config.ts)
|
||||
|
||||
```typescript
|
||||
import { federation } from '@module-federation/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
federation({
|
||||
name: 'platform',
|
||||
remotes: {
|
||||
shared: {
|
||||
type: 'module', // 关键:必须指定 ES module 类型
|
||||
name: 'shared',
|
||||
entry: 'http://localhost:5000/remoteEntry.js'
|
||||
}
|
||||
},
|
||||
shared: {
|
||||
vue: {},
|
||||
'vue-router': {},
|
||||
'element-plus': {},
|
||||
axios: {}
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
### 使用示例
|
||||
|
||||
```typescript
|
||||
// 在 platform 中导入 shared 的组件
|
||||
import FileUpload from 'shared/FileUpload'
|
||||
import DynamicFormItem from 'shared/DynamicFormItem'
|
||||
import { authAPI } from 'shared/authAPI'
|
||||
import { getAesInstance } from 'shared/utils'
|
||||
import type { LoginParam, SysUserVO } from 'shared/types'
|
||||
|
||||
// 在组件中使用
|
||||
<template>
|
||||
<FileUpload mode="cover" v-model:coverImg="image" />
|
||||
<DynamicFormItem :config="config" v-model="value" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 调用 API
|
||||
const result = await authAPI.login(params)
|
||||
</script>
|
||||
```
|
||||
|
||||
### 优势
|
||||
|
||||
- ✅ **开发模式直接支持**:无需构建,直接 dev 即可
|
||||
- ✅ **完整的热更新**:修改 shared 代码自动更新到 platform
|
||||
- ✅ **自动路径处理**:无需关心 shared 内部的 `@/` 路径
|
||||
- ✅ **依赖去重**:vue、element-plus 等只加载一次
|
||||
- ✅ **企业级稳定**:官方维护,支持 Vite 6
|
||||
|
||||
## 🎯 开发工作流
|
||||
|
||||
### 日常开发
|
||||
|
||||
1. **启动服务**(每天开始)
|
||||
```bash
|
||||
# 使用脚本一键启动
|
||||
start-all.bat
|
||||
|
||||
# 或手动启动
|
||||
cd packages/shared && pnpm run dev # 终端1
|
||||
cd packages/platform && pnpm run dev # 终端2
|
||||
```
|
||||
|
||||
2. **修改代码**
|
||||
- 修改 shared 组件 → 保存 → 自动热更新 ✨
|
||||
- 修改 platform 页面 → 保存 → 自动热更新 ✨
|
||||
|
||||
3. **结束开发**
|
||||
- 按 Ctrl+C 停止所有服务
|
||||
|
||||
### 添加新的共享模块
|
||||
|
||||
1. **在 shared 中创建组件**
|
||||
```bash
|
||||
# 例如创建新组件
|
||||
packages/shared/src/components/mynew/MyNewComponent.vue
|
||||
```
|
||||
|
||||
2. **在 shared/vite.config.ts 中暴露**
|
||||
```typescript
|
||||
exposes: {
|
||||
'./MyNewComponent': './src/components/mynew/MyNewComponent.vue'
|
||||
}
|
||||
```
|
||||
|
||||
3. **在 platform 中使用**
|
||||
```typescript
|
||||
import MyNewComponent from 'shared/MyNewComponent'
|
||||
```
|
||||
|
||||
### API 代理
|
||||
|
||||
所有应用的 `/api` 请求会代理到后端服务 `http://localhost:8080`
|
||||
@@ -101,7 +226,8 @@ npm run build:workcase
|
||||
|
||||
## 🔥 注意事项
|
||||
|
||||
1. **启动顺序**: 建议先启动 `shared` 服务,再启动其他应用
|
||||
2. **端口占用**: 确保 5000-5003 端口未被占用
|
||||
3. **Node 版本**: 建议使用 Node.js 18+
|
||||
4. **依赖安装**: 首次运行前必须执行 `npm run install:all`
|
||||
1. **包管理器**: 必须使用 pnpm(项目使用 pnpm workspace)
|
||||
2. **启动顺序**: 必须先启动 `shared` 服务(5000端口),再启动其他应用
|
||||
3. **端口占用**: 确保 5000-5003 端口未被占用,shared 使用 strictPort 模式
|
||||
4. **Node 版本**: 建议使用 Node.js 18+
|
||||
5. **Module Federation**: remoteEntry.js 路径为 `http://localhost:5000/remoteEntry.js`(不是 /assets/)
|
||||
|
||||
1408
urbanLifelineWeb/package-lock.json
generated
1408
urbanLifelineWeb/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,21 @@ const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue(), vueJsx()],
|
||||
plugins: [
|
||||
vue({
|
||||
script: {
|
||||
defineModel: true,
|
||||
propsDestructure: true
|
||||
}
|
||||
}),
|
||||
vueJsx()
|
||||
],
|
||||
|
||||
define: {
|
||||
__VUE_OPTIONS_API__: true,
|
||||
__VUE_PROD_DEVTOOLS__: true,
|
||||
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: true
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
|
||||
// Windows temporarily needs this file, https://github.com/module-federation/vite/issues/68
|
||||
|
||||
import {loadShare} from "@module-federation/runtime";
|
||||
const importMap = {
|
||||
|
||||
"element-plus": async () => {
|
||||
let pkg = await import("__mf__virtual/platform__prebuild__element_mf_2_plus__prebuild__.js");
|
||||
return pkg;
|
||||
}
|
||||
,
|
||||
"vue": async () => {
|
||||
let pkg = await import("__mf__virtual/platform__prebuild__vue__prebuild__.js");
|
||||
return pkg;
|
||||
}
|
||||
,
|
||||
"vue-router": async () => {
|
||||
let pkg = await import("__mf__virtual/platform__prebuild__vue_mf_2_router__prebuild__.js");
|
||||
return pkg;
|
||||
}
|
||||
|
||||
}
|
||||
const usedShared = {
|
||||
|
||||
"element-plus": {
|
||||
name: "element-plus",
|
||||
version: "2.12.0",
|
||||
scope: ["default"],
|
||||
loaded: false,
|
||||
from: "platform",
|
||||
async get () {
|
||||
if (false) {
|
||||
throw new Error(`Shared module '${"element-plus"}' must be provided by host`);
|
||||
}
|
||||
usedShared["element-plus"].loaded = true
|
||||
const {"element-plus": pkgDynamicImport} = importMap
|
||||
const res = await pkgDynamicImport()
|
||||
const exportModule = {...res}
|
||||
// All npm packages pre-built by vite will be converted to esm
|
||||
Object.defineProperty(exportModule, "__esModule", {
|
||||
value: true,
|
||||
enumerable: false
|
||||
})
|
||||
return function () {
|
||||
return exportModule
|
||||
}
|
||||
},
|
||||
shareConfig: {
|
||||
singleton: false,
|
||||
requiredVersion: "^2.12.0",
|
||||
|
||||
}
|
||||
}
|
||||
,
|
||||
"vue": {
|
||||
name: "vue",
|
||||
version: "3.5.25",
|
||||
scope: ["default"],
|
||||
loaded: false,
|
||||
from: "platform",
|
||||
async get () {
|
||||
if (false) {
|
||||
throw new Error(`Shared module '${"vue"}' must be provided by host`);
|
||||
}
|
||||
usedShared["vue"].loaded = true
|
||||
const {"vue": pkgDynamicImport} = importMap
|
||||
const res = await pkgDynamicImport()
|
||||
const exportModule = {...res}
|
||||
// All npm packages pre-built by vite will be converted to esm
|
||||
Object.defineProperty(exportModule, "__esModule", {
|
||||
value: true,
|
||||
enumerable: false
|
||||
})
|
||||
return function () {
|
||||
return exportModule
|
||||
}
|
||||
},
|
||||
shareConfig: {
|
||||
singleton: false,
|
||||
requiredVersion: "^3.5.25",
|
||||
|
||||
}
|
||||
}
|
||||
,
|
||||
"vue-router": {
|
||||
name: "vue-router",
|
||||
version: "4.6.3",
|
||||
scope: ["default"],
|
||||
loaded: false,
|
||||
from: "platform",
|
||||
async get () {
|
||||
if (false) {
|
||||
throw new Error(`Shared module '${"vue-router"}' must be provided by host`);
|
||||
}
|
||||
usedShared["vue-router"].loaded = true
|
||||
const {"vue-router": pkgDynamicImport} = importMap
|
||||
const res = await pkgDynamicImport()
|
||||
const exportModule = {...res}
|
||||
// All npm packages pre-built by vite will be converted to esm
|
||||
Object.defineProperty(exportModule, "__esModule", {
|
||||
value: true,
|
||||
enumerable: false
|
||||
})
|
||||
return function () {
|
||||
return exportModule
|
||||
}
|
||||
},
|
||||
shareConfig: {
|
||||
singleton: false,
|
||||
requiredVersion: "^4.6.3",
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
const usedRemotes = [
|
||||
{
|
||||
entryGlobalName: "http://localhost:5000/remoteEntry.js",
|
||||
name: "shared",
|
||||
type: "var",
|
||||
entry: "http://localhost:5000/remoteEntry.js",
|
||||
shareScope: "default",
|
||||
}
|
||||
|
||||
]
|
||||
export {
|
||||
usedShared,
|
||||
usedRemotes
|
||||
}
|
||||
|
||||
9
urbanLifelineWeb/packages/platform/.env.example
Normal file
9
urbanLifelineWeb/packages/platform/.env.example
Normal file
@@ -0,0 +1,9 @@
|
||||
# Platform 应用环境变量示例
|
||||
# 复制此文件为 .env.local 或 .env.production 使用
|
||||
|
||||
# API 基础地址(Gateway 地址)
|
||||
VITE_API_BASE_URL=http://localhost:8180
|
||||
|
||||
# AES 加密密钥(可选,默认使用配置文件中的密钥)
|
||||
# 生产环境建议通过环境变量配置
|
||||
# VUE_APP_AES_SECRET_KEY=1234567890qwer
|
||||
@@ -9,22 +9,8 @@
|
||||
<!-- 加载运行时配置(必须在其他脚本之前加载) -->
|
||||
<script src="/app-config.js"></script>
|
||||
|
||||
<!-- Import Maps 配置 - 只配置共享模块 -->
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"@shared/components": "/shared/components.js",
|
||||
"@shared/utils": "/shared/utils.js",
|
||||
"@shared/api": "/shared/api.js",
|
||||
"@shared/composables": "/shared/composables.js",
|
||||
"@shared/types": "/shared/types.js"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- 预加载关键模块 -->
|
||||
<link rel="modulepreload" href="/shared/components.js">
|
||||
<link rel="modulepreload" href="/shared/utils.js">
|
||||
<!-- Module Federation - 预加载远程入口 -->
|
||||
<link rel="modulepreload" href="http://localhost:5000/remoteEntry.js">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@types/node": "^22.0.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||
"@module-federation/vite": "^1.9.3",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.0.3",
|
||||
"vue-tsc": "^2.2.0"
|
||||
|
||||
62
urbanLifelineWeb/packages/platform/pnpm-lock.yaml
generated
62
urbanLifelineWeb/packages/platform/pnpm-lock.yaml
generated
@@ -28,6 +28,9 @@ dependencies:
|
||||
version: 4.6.3(vue@3.5.25)
|
||||
|
||||
devDependencies:
|
||||
'@module-federation/vite':
|
||||
specifier: ^1.9.3
|
||||
version: 1.9.3
|
||||
'@types/node':
|
||||
specifier: ^22.0.0
|
||||
version: 22.19.1
|
||||
@@ -596,10 +599,61 @@ packages:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
dev: true
|
||||
|
||||
/@module-federation/error-codes@0.21.6:
|
||||
resolution: {integrity: sha512-MLJUCQ05KnoVl8xd6xs9a5g2/8U+eWmVxg7xiBMeR0+7OjdWUbHwcwgVFatRIwSZvFgKHfWEiI7wsU1q1XbTRQ==}
|
||||
dev: true
|
||||
|
||||
/@module-federation/runtime-core@0.21.6:
|
||||
resolution: {integrity: sha512-5Hd1Y5qp5lU/aTiK66lidMlM/4ji2gr3EXAtJdreJzkY+bKcI5+21GRcliZ4RAkICmvdxQU5PHPL71XmNc7Lsw==}
|
||||
dependencies:
|
||||
'@module-federation/error-codes': 0.21.6
|
||||
'@module-federation/sdk': 0.21.6
|
||||
dev: true
|
||||
|
||||
/@module-federation/runtime@0.21.6:
|
||||
resolution: {integrity: sha512-+caXwaQqwTNh+CQqyb4mZmXq7iEemRDrTZQGD+zyeH454JAYnJ3s/3oDFizdH6245pk+NiqDyOOkHzzFQorKhQ==}
|
||||
dependencies:
|
||||
'@module-federation/error-codes': 0.21.6
|
||||
'@module-federation/runtime-core': 0.21.6
|
||||
'@module-federation/sdk': 0.21.6
|
||||
dev: true
|
||||
|
||||
/@module-federation/sdk@0.21.6:
|
||||
resolution: {integrity: sha512-x6hARETb8iqHVhEsQBysuWpznNZViUh84qV2yE7AD+g7uIzHKiYdoWqj10posbo5XKf/147qgWDzKZoKoEP2dw==}
|
||||
dev: true
|
||||
|
||||
/@module-federation/vite@1.9.3:
|
||||
resolution: {integrity: sha512-MV6XI3FX6okEMJ7FdmvFmYuu7DygRoLljKT8atrBwFhlttsgBbswpqMj4P4Fs/X+pFmbIi/ntFzVhsrG0qQnGQ==}
|
||||
dependencies:
|
||||
'@module-federation/runtime': 0.21.6
|
||||
'@module-federation/sdk': 0.21.6
|
||||
'@rollup/pluginutils': 5.3.0
|
||||
defu: 6.1.4
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.21
|
||||
pathe: 1.1.2
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
dev: true
|
||||
|
||||
/@rolldown/pluginutils@1.0.0-beta.53:
|
||||
resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==}
|
||||
dev: true
|
||||
|
||||
/@rollup/pluginutils@5.3.0:
|
||||
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
estree-walker: 2.0.2
|
||||
picomatch: 4.0.3
|
||||
dev: true
|
||||
|
||||
/@rollup/rollup-android-arm-eabi@4.53.3:
|
||||
resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==}
|
||||
cpu: [arm]
|
||||
@@ -1146,6 +1200,10 @@ packages:
|
||||
ms: 2.1.3
|
||||
dev: true
|
||||
|
||||
/defu@6.1.4:
|
||||
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
||||
dev: true
|
||||
|
||||
/delayed-stream@1.0.0:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
@@ -1464,6 +1522,10 @@ packages:
|
||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||
dev: true
|
||||
|
||||
/pathe@1.1.2:
|
||||
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
|
||||
dev: true
|
||||
|
||||
/picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
|
||||
8
urbanLifelineWeb/packages/platform/public/avatar.svg
Normal file
8
urbanLifelineWeb/packages/platform/public/avatar.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<circle cx="50" cy="50" r="50" fill="#ffe4c4"/>
|
||||
<circle cx="50" cy="38" r="18" fill="#ffd1a1"/>
|
||||
<ellipse cx="50" cy="75" rx="28" ry="20" fill="#ff6b6b"/>
|
||||
<circle cx="38" cy="35" r="3" fill="#333"/>
|
||||
<circle cx="62" cy="35" r="3" fill="#333"/>
|
||||
<path d="M42 48 Q50 55 58 48" stroke="#333" stroke-width="2" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 399 B |
5
urbanLifelineWeb/packages/platform/public/favicon.svg
Normal file
5
urbanLifelineWeb/packages/platform/public/favicon.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<rect width="100" height="100" rx="20" fill="#7c3aed"/>
|
||||
<path d="M30 70V40h10v30H30zm15-30h10v30H45V40zm15 0h10v30H60V40z" fill="white"/>
|
||||
<rect x="25" y="30" width="50" height="5" rx="2" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 278 B |
BIN
urbanLifelineWeb/packages/platform/public/logo.jpg
Normal file
BIN
urbanLifelineWeb/packages/platform/public/logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
27
urbanLifelineWeb/packages/platform/src/App.vue
Normal file
27
urbanLifelineWeb/packages/platform/src/App.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
onMounted(() => {
|
||||
console.log('Platform App Mounted')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
</style>
|
||||
254
urbanLifelineWeb/packages/platform/src/config/README.md
Normal file
254
urbanLifelineWeb/packages/platform/src/config/README.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# Platform 应用配置说明
|
||||
|
||||
## AES 加密配置
|
||||
|
||||
### 密钥配置
|
||||
|
||||
**配置文件**:`src/config/index.ts`
|
||||
|
||||
```typescript
|
||||
export const AES_SECRET_KEY = '1234567890qwer'
|
||||
```
|
||||
|
||||
**注意事项**:
|
||||
1. ✅ 密钥已配置为 `1234567890qwer`,与后端保持一致
|
||||
2. ⚠️ 该密钥与后端 `application.yml` 中的 `security.aes.secret-key` 必须相同
|
||||
3. 🔒 生产环境应从环境变量或配置中心获取,不要硬编码
|
||||
|
||||
### 对应后端配置
|
||||
|
||||
**Gateway** (`gateway/src/main/resources/application.yml`):
|
||||
```yaml
|
||||
security:
|
||||
aes:
|
||||
secret-key: 1234567890qwer
|
||||
```
|
||||
|
||||
**Auth Service** (`auth/src/main/resources/application.yml`):
|
||||
```yaml
|
||||
security:
|
||||
aes:
|
||||
secret-key: 1234567890qwer
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 登录时加密密码
|
||||
|
||||
```typescript
|
||||
import { authAPI } from '@shared/api/auth'
|
||||
import { getAesInstance } from '@shared/utils'
|
||||
|
||||
async function handleLogin(username: string, password: string) {
|
||||
try {
|
||||
// 1. 获取 AES 加密实例
|
||||
const aes = getAesInstance()
|
||||
|
||||
// 2. 加密密码
|
||||
const encryptedPassword = await aes.encryptPassword(password)
|
||||
|
||||
// 3. 发送登录请求
|
||||
const response = await authAPI.login({
|
||||
username,
|
||||
password: encryptedPassword, // 使用加密后的密码
|
||||
loginType: 'password'
|
||||
})
|
||||
|
||||
if (response.data.success) {
|
||||
console.log('登录成功')
|
||||
// 保存 token 等操作
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 注册时加密手机号和密码
|
||||
|
||||
```typescript
|
||||
import { authAPI } from '@shared/api/auth'
|
||||
import { getAesInstance } from '@shared/utils'
|
||||
|
||||
async function handleRegister(phone: string, password: string, smsCode: string, sessionId: string) {
|
||||
try {
|
||||
const aes = getAesInstance()
|
||||
|
||||
// 加密敏感信息
|
||||
const encryptedPhone = await aes.encryptPhone(phone)
|
||||
const encryptedPassword = await aes.encryptPassword(password)
|
||||
|
||||
// 发送注册请求
|
||||
const response = await authAPI.register({
|
||||
registerType: 'phone',
|
||||
phone: encryptedPhone,
|
||||
password: encryptedPassword,
|
||||
confirmPassword: encryptedPassword,
|
||||
smsCode,
|
||||
smsSessionId: sessionId
|
||||
})
|
||||
|
||||
if (response.data.success) {
|
||||
console.log('注册成功')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('注册失败:', error)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 数据脱敏显示
|
||||
|
||||
```typescript
|
||||
import { AesUtils } from '@shared/utils'
|
||||
|
||||
// 显示脱敏手机号
|
||||
const phone = '13812345678'
|
||||
const maskedPhone = AesUtils.maskPhone(phone)
|
||||
console.log(maskedPhone) // 输出:138****5678
|
||||
|
||||
// 显示脱敏身份证号
|
||||
const idCard = '110101199001011234'
|
||||
const maskedIdCard = AesUtils.maskIdCard(idCard)
|
||||
console.log(maskedIdCard) // 输出:110101********1234
|
||||
```
|
||||
|
||||
## 初始化流程
|
||||
|
||||
### 应用启动时自动初始化
|
||||
|
||||
**文件**:`src/main.ts`
|
||||
|
||||
```typescript
|
||||
import { AES_SECRET_KEY } from './config'
|
||||
import { initAesEncrypt } from '@shared/utils'
|
||||
|
||||
async function initApp() {
|
||||
// 初始化 AES 加密工具
|
||||
await initAesEncrypt(AES_SECRET_KEY)
|
||||
|
||||
// ... 其他初始化操作
|
||||
}
|
||||
|
||||
initApp()
|
||||
```
|
||||
|
||||
### 初始化状态检查
|
||||
|
||||
```typescript
|
||||
import { getAesInstance } from '@shared/utils'
|
||||
|
||||
try {
|
||||
const aes = getAesInstance()
|
||||
console.log('✅ AES 加密工具已初始化')
|
||||
} catch (error) {
|
||||
console.error('❌ AES 加密工具未初始化:', error)
|
||||
}
|
||||
```
|
||||
|
||||
## 加密流程图
|
||||
|
||||
```
|
||||
用户输入密码
|
||||
↓
|
||||
前端 AES 加密 (1234567890qwer)
|
||||
↓
|
||||
发送加密后的密码
|
||||
↓
|
||||
Gateway (不解密,直接转发)
|
||||
↓
|
||||
Auth Service 接收
|
||||
↓
|
||||
AES 解密 (1234567890qwer)
|
||||
↓
|
||||
BCrypt 再次加密
|
||||
↓
|
||||
存入数据库
|
||||
```
|
||||
|
||||
## 安全建议
|
||||
|
||||
### 开发环境
|
||||
- ✅ 使用固定密钥 `1234567890qwer`
|
||||
- ✅ 密钥在代码中配置
|
||||
|
||||
### 生产环境
|
||||
- 🔒 从环境变量获取密钥
|
||||
- 🔒 使用配置中心(Nacos)
|
||||
- 🔒 定期轮换密钥
|
||||
- 🔒 密钥长度至少 32 字符
|
||||
|
||||
### 示例:从环境变量获取
|
||||
|
||||
```typescript
|
||||
// 生产环境配置
|
||||
export const AES_SECRET_KEY = process.env.VUE_APP_AES_SECRET_KEY || '1234567890qwer'
|
||||
```
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 问题:登录时提示"密码错误"
|
||||
|
||||
**可能原因**:前后端密钥不一致
|
||||
|
||||
**排查步骤**:
|
||||
1. 检查前端配置:`src/config/index.ts` 中的 `AES_SECRET_KEY`
|
||||
2. 检查后端配置:`application.yml` 中的 `security.aes.secret-key`
|
||||
3. 确保两者完全一致
|
||||
|
||||
**解决方案**:
|
||||
```bash
|
||||
# 前端
|
||||
export const AES_SECRET_KEY = '1234567890qwer'
|
||||
|
||||
# 后端
|
||||
security:
|
||||
aes:
|
||||
secret-key: 1234567890qwer
|
||||
```
|
||||
|
||||
### 问题:"AES 加密工具未初始化"
|
||||
|
||||
**原因**:`initAesEncrypt()` 未被调用
|
||||
|
||||
**解决**:检查 `main.ts` 中是否正确调用初始化函数
|
||||
|
||||
### 问题:加密后的数据无法解密
|
||||
|
||||
**可能原因**:
|
||||
1. 密钥不正确
|
||||
2. 数据被篡改
|
||||
3. Base64 编码问题
|
||||
|
||||
**调试方法**:
|
||||
```typescript
|
||||
const aes = getAesInstance()
|
||||
const original = 'test123'
|
||||
const encrypted = await aes.encrypt(original)
|
||||
const decrypted = await aes.decrypt(encrypted)
|
||||
console.log(original === decrypted) // 应该输出 true
|
||||
```
|
||||
|
||||
## API 参考
|
||||
|
||||
### 配置项
|
||||
|
||||
| 配置项 | 类型 | 默认值 | 说明 |
|
||||
|--------|------|--------|------|
|
||||
| `AES_SECRET_KEY` | `string` | `'1234567890qwer'` | AES 加密密钥 |
|
||||
| `API_BASE_URL` | `string` | `'http://localhost:8180'` | API 基础地址 |
|
||||
| `APP_CONFIG.name` | `string` | `'泰豪电源 AI 数智化平台'` | 应用名称 |
|
||||
| `APP_CONFIG.version` | `string` | `'1.0.0'` | 应用版本 |
|
||||
|
||||
### 环境变量
|
||||
|
||||
| 变量名 | 说明 | 示例 |
|
||||
|--------|------|------|
|
||||
| `VITE_API_BASE_URL` | API 基础地址 | `https://api.example.com` |
|
||||
| `VUE_APP_AES_SECRET_KEY` | AES 密钥(生产) | `your-secret-key-32-chars-long` |
|
||||
|
||||
## 更多信息
|
||||
|
||||
- AES 加密工具详细文档:`@shared/utils/crypto/README.md`
|
||||
- Auth API 文档:`@shared/api/auth/auth.ts`
|
||||
- 后端 AES 实现:`urbanLifelineServ/common/common-utils/src/main/java/org/xyzh/common/utils/crypto/AesEncryptUtil.java`
|
||||
24
urbanLifelineWeb/packages/platform/src/config/index.ts
Normal file
24
urbanLifelineWeb/packages/platform/src/config/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Platform 应用配置
|
||||
*/
|
||||
|
||||
/**
|
||||
* AES 加密密钥(与后端保持一致)
|
||||
* 对应后端配置:security.aes.secret-key
|
||||
* Base64 编码的 32 字节密钥(256 位)
|
||||
*/
|
||||
export const AES_SECRET_KEY = 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=' // Base64 编码,解码后是 "12345678901234567890123456789012" (32字节)
|
||||
|
||||
/**
|
||||
* API 基础地址
|
||||
*/
|
||||
export const API_BASE_URL = (import.meta as any).env?.VITE_API_BASE_URL || 'http://localhost:8180'
|
||||
|
||||
/**
|
||||
* 应用配置
|
||||
*/
|
||||
export const APP_CONFIG = {
|
||||
name: '泰豪电源 AI 数智化平台',
|
||||
version: '1.0.0',
|
||||
copyright: '泰豪电源'
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
# SidebarLayout 侧边栏布局组件
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 🎯 核心功能
|
||||
- **侧边栏菜单导航**:左侧固定侧边栏,支持折叠/展开
|
||||
- **双模式内容区**:
|
||||
- **路由模式**:普通路由页面渲染
|
||||
- **iframe 模式**:嵌入外部应用(招标助手、泰豪小电、智能体编排)
|
||||
- **用户信息展示**:底部用户头像和下拉菜单
|
||||
- **响应式设计**:支持移动端适配
|
||||
|
||||
### 📱 菜单配置
|
||||
|
||||
```typescript
|
||||
const menuItems: MenuItem[] = [
|
||||
{
|
||||
key: 'home',
|
||||
label: '工作台',
|
||||
icon: 'Grid',
|
||||
path: '/home',
|
||||
type: 'route'
|
||||
},
|
||||
{
|
||||
key: 'bidding',
|
||||
label: '招标助手',
|
||||
icon: 'Document',
|
||||
iframeUrl: 'http://localhost:5002',
|
||||
type: 'iframe'
|
||||
},
|
||||
{
|
||||
key: 'service',
|
||||
label: '泰豪小电',
|
||||
icon: 'Service',
|
||||
iframeUrl: 'http://localhost:5003',
|
||||
type: 'iframe'
|
||||
},
|
||||
{
|
||||
key: 'workflow',
|
||||
label: '智能体编排',
|
||||
icon: 'Connection',
|
||||
iframeUrl: 'http://localhost:3000', // Dify 地址
|
||||
type: 'iframe'
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 在路由中使用
|
||||
|
||||
```typescript
|
||||
// router/index.ts
|
||||
import { SidebarLayout } from '@/layouts'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
component: SidebarLayout,
|
||||
children: [
|
||||
{
|
||||
path: '/home',
|
||||
component: () => import('@/views/Home.vue')
|
||||
},
|
||||
{
|
||||
path: '/chat',
|
||||
component: () => import('@/views/Chat.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 在 App.vue 中使用
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<SidebarLayout />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SidebarLayout } from '@/layouts'
|
||||
</script>
|
||||
```
|
||||
|
||||
## 菜单项类型
|
||||
|
||||
```typescript
|
||||
interface MenuItem {
|
||||
key: string // 唯一标识
|
||||
label: string // 显示名称
|
||||
icon: string // Element Plus 图标组件名
|
||||
path?: string // 路由路径(route 类型必需)
|
||||
iframeUrl?: string // iframe URL(iframe 类型必需)
|
||||
type: 'route' | 'iframe' // 菜单类型
|
||||
}
|
||||
```
|
||||
|
||||
## iframe 应用说明
|
||||
|
||||
### 1. 招标助手 (Bidding)
|
||||
- **端口**:5002
|
||||
- **URL**:http://localhost:5002
|
||||
- **说明**:招投标业务管理系统
|
||||
|
||||
### 2. 泰豪小电 (Service)
|
||||
- **端口**:5003
|
||||
- **URL**:http://localhost:5003
|
||||
- **说明**:智能客服工单管理系统
|
||||
|
||||
### 3. 智能体编排 (Workflow)
|
||||
- **端口**:3000
|
||||
- **URL**:http://localhost:3000
|
||||
- **说明**:Dify 智能体编排界面
|
||||
|
||||
## 样式自定义
|
||||
|
||||
### 主题色调整
|
||||
|
||||
```scss
|
||||
// 修改侧边栏背景色
|
||||
.sidebar {
|
||||
background: #F0EAF4; // 当前淡紫色背景
|
||||
}
|
||||
|
||||
// 修改激活项颜色
|
||||
.nav-item.active {
|
||||
background: rgba(124, 58, 237, 0.15);
|
||||
color: #7c3aed;
|
||||
}
|
||||
```
|
||||
|
||||
### 侧边栏宽度
|
||||
|
||||
```scss
|
||||
.sidebar {
|
||||
width: 220px; // 展开宽度
|
||||
|
||||
&.collapsed {
|
||||
width: 64px; // 折叠宽度
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 功能说明
|
||||
|
||||
### 侧边栏折叠
|
||||
- 点击头部箭头图标可切换折叠/展开状态
|
||||
- 折叠后只显示图标,展开后显示图标+文字
|
||||
|
||||
### iframe 加载
|
||||
- 自动显示加载中状态
|
||||
- 支持刷新按钮重新加载
|
||||
- 显示当前应用标题
|
||||
|
||||
### 用户操作
|
||||
- **个人中心**:跳转到 /profile
|
||||
- **系统设置**:跳转到 /settings
|
||||
- **退出登录**:跳转到 /login
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **跨域问题**:确保 iframe 应用允许被嵌入
|
||||
```nginx
|
||||
# nginx 配置
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
# 或者
|
||||
add_header Content-Security-Policy "frame-ancestors 'self' http://localhost:5001";
|
||||
```
|
||||
|
||||
2. **端口配置**:确保对应服务已启动
|
||||
- platform: 5001
|
||||
- bidding: 5002
|
||||
- workcase: 5003
|
||||
- dify: 3000
|
||||
|
||||
3. **路由同步**:iframe 模式不会改变浏览器 URL
|
||||
|
||||
4. **通信机制**:如需与 iframe 通信,使用 postMessage API
|
||||
|
||||
## 扩展建议
|
||||
|
||||
### 添加新菜单项
|
||||
|
||||
```typescript
|
||||
// 在 menuItems 数组中添加
|
||||
{
|
||||
key: 'new-app',
|
||||
label: '新应用',
|
||||
icon: 'Plus',
|
||||
iframeUrl: 'http://localhost:5004',
|
||||
type: 'iframe'
|
||||
}
|
||||
```
|
||||
|
||||
### 动态菜单加载
|
||||
|
||||
```typescript
|
||||
// 从 API 获取菜单配置
|
||||
const loadMenus = async () => {
|
||||
const response = await fetch('/api/menus')
|
||||
menuItems.value = await response.json()
|
||||
}
|
||||
```
|
||||
|
||||
### 权限控制
|
||||
|
||||
```typescript
|
||||
const menuItems = computed(() => {
|
||||
return allMenuItems.filter(item =>
|
||||
hasPermission(item.key)
|
||||
)
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,264 @@
|
||||
.sidebar-layout {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// ==================== 侧边栏 ====================
|
||||
.sidebar {
|
||||
width: 220px;
|
||||
height: 100%;
|
||||
background: #F0EAF4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #333;
|
||||
flex-shrink: 0;
|
||||
transition: width 0.3s ease;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.08);
|
||||
|
||||
&.collapsed {
|
||||
width: 64px;
|
||||
|
||||
.sidebar-header {
|
||||
padding: 16px 12px;
|
||||
justify-content: center;
|
||||
|
||||
.logo {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.collapse-btn {
|
||||
position: static;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.user-section {
|
||||
justify-content: center;
|
||||
padding: 16px 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 侧边栏头部
|
||||
.sidebar-header {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.collapse-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
color: #888;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(124, 58, 237, 0.1);
|
||||
color: #7c3aed;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
.logo-img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 6px;
|
||||
object-fit: contain;
|
||||
background: #fff;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
// 导航菜单
|
||||
.nav-menu {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 12px 0;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-section {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 14px 20px;
|
||||
margin-bottom: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: #555;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(124, 58, 237, 0.1);
|
||||
color: #7c3aed;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: rgba(124, 58, 237, 0.15);
|
||||
color: #7c3aed;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
// 用户信息
|
||||
.user-section {
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(124, 58, 237, 0.05);
|
||||
}
|
||||
|
||||
.user-info-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 主内容区 ====================
|
||||
.main-content {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// iframe 容器
|
||||
.iframe-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.iframe-header {
|
||||
height: 56px;
|
||||
padding: 0 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
background: #fafafa;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.iframe-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.content-iframe {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.iframe-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
color: #7c3aed;
|
||||
font-size: 14px;
|
||||
z-index: 10;
|
||||
|
||||
.el-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 响应式 ====================
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
width: 64px;
|
||||
|
||||
&:not(.collapsed) {
|
||||
width: 220px;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.iframe-header {
|
||||
padding: 0 16px;
|
||||
|
||||
.iframe-title {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<div class="sidebar-layout">
|
||||
<!-- 侧边栏 -->
|
||||
<aside class="sidebar" :class="{ collapsed: collapsed }">
|
||||
<div class="sidebar-header">
|
||||
<div class="logo">
|
||||
<img src="/logo.jpg" alt="Logo" class="logo-img" />
|
||||
<span v-if="!collapsed" class="logo-text">城市生命线</span>
|
||||
</div>
|
||||
<div class="collapse-btn" @click="toggleSidebar">
|
||||
<el-icon>
|
||||
<DArrowLeft v-if="!collapsed" />
|
||||
<DArrowRight v-else />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="nav-menu">
|
||||
<div class="nav-section">
|
||||
<div
|
||||
v-for="item in menuItems"
|
||||
:key="item.key"
|
||||
class="nav-item"
|
||||
:class="{ active: activeMenu === item.key }"
|
||||
@click="handleMenuClick(item)"
|
||||
>
|
||||
<el-icon><component :is="item.icon" /></el-icon>
|
||||
<span v-if="!collapsed">{{ item.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 用户信息 -->
|
||||
<el-dropdown class="user-section" trigger="click" @command="handleUserCommand">
|
||||
<div class="user-info-wrapper">
|
||||
<div class="user-avatar">
|
||||
<el-avatar :size="36" src="/avatar.svg" @error="handleAvatarError" />
|
||||
</div>
|
||||
<span v-if="!collapsed" class="user-name">{{ userName }}</span>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="profile">
|
||||
<el-icon><User /></el-icon>
|
||||
个人中心
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="settings" divided>
|
||||
<el-icon><Setting /></el-icon>
|
||||
系统设置
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="logout" divided>
|
||||
<el-icon><SwitchButton /></el-icon>
|
||||
退出登录
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<main class="main-content">
|
||||
<!-- iframe 模式 -->
|
||||
<div v-if="currentIframeUrl" class="iframe-container">
|
||||
<div class="iframe-header">
|
||||
<span class="iframe-title">{{ currentMenuItem?.label }}</span>
|
||||
<el-button
|
||||
text
|
||||
@click="handleRefreshIframe"
|
||||
:icon="Refresh"
|
||||
>
|
||||
刷新
|
||||
</el-button>
|
||||
</div>
|
||||
<iframe
|
||||
ref="iframeRef"
|
||||
:src="currentIframeUrl"
|
||||
class="content-iframe"
|
||||
frameborder="0"
|
||||
@load="handleIframeLoad"
|
||||
/>
|
||||
<div v-if="iframeLoading" class="iframe-loading">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 路由模式 -->
|
||||
<router-view v-else />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import {
|
||||
ChatDotRound,
|
||||
Grid,
|
||||
Connection,
|
||||
Document,
|
||||
Service,
|
||||
DArrowLeft,
|
||||
DArrowRight,
|
||||
User,
|
||||
Setting,
|
||||
SwitchButton,
|
||||
Refresh,
|
||||
Loading
|
||||
} from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
interface MenuItem {
|
||||
key: string
|
||||
label: string
|
||||
icon: string
|
||||
path?: string
|
||||
iframeUrl?: string
|
||||
type: 'route' | 'iframe'
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
// 状态管理
|
||||
const collapsed = ref(false)
|
||||
const activeMenu = ref('home')
|
||||
const iframeLoading = ref(false)
|
||||
const iframeRef = ref<HTMLIFrameElement>()
|
||||
const userName = ref('管理员')
|
||||
|
||||
// 菜单配置
|
||||
const menuItems: MenuItem[] = [
|
||||
{
|
||||
key: 'home',
|
||||
label: '工作台',
|
||||
icon: 'Grid',
|
||||
path: '/home',
|
||||
type: 'route'
|
||||
},
|
||||
{
|
||||
key: 'bidding',
|
||||
label: '招标助手',
|
||||
icon: 'Document',
|
||||
iframeUrl: 'http://localhost:5002',
|
||||
type: 'iframe'
|
||||
},
|
||||
{
|
||||
key: 'service',
|
||||
label: '泰豪小电',
|
||||
icon: 'Service',
|
||||
iframeUrl: 'http://localhost:5003',
|
||||
type: 'iframe'
|
||||
},
|
||||
{
|
||||
key: 'workflow',
|
||||
label: '智能体编排',
|
||||
icon: 'Connection',
|
||||
iframeUrl: 'http://localhost:3000',
|
||||
type: 'iframe'
|
||||
},
|
||||
{
|
||||
key: 'chat',
|
||||
label: 'AI助手',
|
||||
icon: 'ChatDotRound',
|
||||
path: '/chat',
|
||||
type: 'route'
|
||||
}
|
||||
]
|
||||
|
||||
// 当前菜单项
|
||||
const currentMenuItem = computed(() => {
|
||||
return menuItems.find(item => item.key === activeMenu.value)
|
||||
})
|
||||
|
||||
// 当前 iframe URL
|
||||
const currentIframeUrl = computed(() => {
|
||||
return currentMenuItem.value?.type === 'iframe'
|
||||
? currentMenuItem.value.iframeUrl
|
||||
: null
|
||||
})
|
||||
|
||||
// 切换侧边栏
|
||||
const toggleSidebar = () => {
|
||||
collapsed.value = !collapsed.value
|
||||
}
|
||||
|
||||
// 处理菜单点击
|
||||
const handleMenuClick = (item: MenuItem) => {
|
||||
activeMenu.value = item.key
|
||||
|
||||
if (item.type === 'route' && item.path) {
|
||||
router.push(item.path)
|
||||
} else if (item.type === 'iframe') {
|
||||
iframeLoading.value = true
|
||||
// iframe 模式不需要路由跳转
|
||||
}
|
||||
}
|
||||
|
||||
// iframe 加载完成
|
||||
const handleIframeLoad = () => {
|
||||
iframeLoading.value = false
|
||||
}
|
||||
|
||||
// 刷新 iframe
|
||||
const handleRefreshIframe = () => {
|
||||
if (iframeRef.value) {
|
||||
iframeLoading.value = true
|
||||
iframeRef.value.src = iframeRef.value.src
|
||||
}
|
||||
}
|
||||
|
||||
// 用户头像加载错误
|
||||
const handleAvatarError = () => {
|
||||
return true
|
||||
}
|
||||
|
||||
// 用户操作
|
||||
const handleUserCommand = (command: string) => {
|
||||
switch (command) {
|
||||
case 'profile':
|
||||
router.push('/profile')
|
||||
break
|
||||
case 'settings':
|
||||
router.push('/settings')
|
||||
break
|
||||
case 'logout':
|
||||
ElMessage.success('退出成功')
|
||||
router.push('/login')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 监听路由变化,同步激活菜单
|
||||
watch(
|
||||
() => route.path,
|
||||
(newPath) => {
|
||||
const menuItem = menuItems.find(item => item.path === newPath)
|
||||
if (menuItem) {
|
||||
activeMenu.value = menuItem.key
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import url("./SidebarLayout.scss");
|
||||
</style>
|
||||
1
urbanLifelineWeb/packages/platform/src/layouts/index.ts
Normal file
1
urbanLifelineWeb/packages/platform/src/layouts/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as SidebarLayout } from "./SidebarLayout/SidebarLayout.vue";
|
||||
47
urbanLifelineWeb/packages/platform/src/main.ts
Normal file
47
urbanLifelineWeb/packages/platform/src/main.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import ElementPlus from 'element-plus'
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
import 'element-plus/dist/index.css'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router/'
|
||||
import { AES_SECRET_KEY } from './config'
|
||||
import { initAesEncrypt } from 'shared/utils'
|
||||
|
||||
// 异步初始化应用
|
||||
async function initApp() {
|
||||
// 1. 初始化 AES 加密工具
|
||||
try {
|
||||
await initAesEncrypt(AES_SECRET_KEY)
|
||||
console.log('✅ AES 加密工具初始化成功')
|
||||
} catch (error) {
|
||||
console.error('❌ AES 加密工具初始化失败:', error)
|
||||
}
|
||||
|
||||
// 2. 创建 Vue 应用
|
||||
const app = createApp(App)
|
||||
|
||||
// 3. 注册 Pinia
|
||||
const pinia = createPinia()
|
||||
app.use(pinia)
|
||||
|
||||
// 4. 注册 Element Plus
|
||||
app.use(ElementPlus)
|
||||
|
||||
// 5. 注册所有 Element Plus 图标
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component)
|
||||
}
|
||||
|
||||
// 6. 注册路由
|
||||
app.use(router)
|
||||
|
||||
// 7. 挂载应用
|
||||
app.mount('#app')
|
||||
|
||||
console.log('✅ Platform 应用启动成功')
|
||||
}
|
||||
|
||||
// 启动应用
|
||||
initApp()
|
||||
60
urbanLifelineWeb/packages/platform/src/router/index.ts
Normal file
60
urbanLifelineWeb/packages/platform/src/router/index.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
|
||||
import { SidebarLayout } from '../layouts'
|
||||
import { TokenManager } from 'shared/api'
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/home'
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('../views/public/Login.vue'),
|
||||
meta: {
|
||||
title: '登录',
|
||||
requiresAuth: false // 不需要登录
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
name: 'Home',
|
||||
component: SidebarLayout,
|
||||
meta: {
|
||||
title: '首页',
|
||||
requiresAuth: true // 需要登录
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
// 路由守卫
|
||||
router.beforeEach((to, from, next) => {
|
||||
// 设置页面标题
|
||||
if (to.meta.title) {
|
||||
document.title = `${to.meta.title} - 泰豪电源 AI 数智化平台`
|
||||
}
|
||||
|
||||
// 检查是否需要登录
|
||||
const requiresAuth = to.meta.requiresAuth !== false // 默认需要登录
|
||||
const hasToken = TokenManager.hasToken()
|
||||
|
||||
if (requiresAuth && !hasToken) {
|
||||
// 需要登录但未登录,跳转到登录页
|
||||
next({
|
||||
path: '/login',
|
||||
query: { redirect: to.fullPath } // 保存原始路径
|
||||
})
|
||||
} else if (to.path === '/login' && hasToken) {
|
||||
// 已登录但访问登录页,跳转到首页
|
||||
next('/home')
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
54
urbanLifelineWeb/packages/platform/src/types/shared.d.ts
vendored
Normal file
54
urbanLifelineWeb/packages/platform/src/types/shared.d.ts
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Shared Module Federation 类型声明
|
||||
* 用于 TypeScript 识别远程模块
|
||||
*/
|
||||
|
||||
|
||||
declare module 'shared/FileUpload' {
|
||||
import { DefineComponent } from 'vue'
|
||||
const FileUpload: DefineComponent<{}, {}, any>
|
||||
export default FileUpload
|
||||
}
|
||||
|
||||
declare module 'shared/DynamicFormItem' {
|
||||
import { DefineComponent } from 'vue'
|
||||
const DynamicFormItem: DefineComponent<{}, {}, any>
|
||||
export default DynamicFormItem
|
||||
}
|
||||
|
||||
declare module 'shared/api' {
|
||||
export const api: any
|
||||
export const TokenManager: any
|
||||
}
|
||||
|
||||
declare module 'shared/authAPI' {
|
||||
export const authAPI: any
|
||||
}
|
||||
|
||||
declare module 'shared/fileAPI' {
|
||||
export const fileAPI: any
|
||||
}
|
||||
|
||||
declare module 'shared/utils' {
|
||||
export const initAesEncrypt: any
|
||||
export const getAesInstance: any
|
||||
export const formatFileSize: any
|
||||
export const isImageFile: any
|
||||
export const getFileTypeIcon: any
|
||||
export const isValidFileType: any
|
||||
export const getFilePreviewUrl: any
|
||||
}
|
||||
|
||||
declare module 'shared/types' {
|
||||
export type LoginParam = any
|
||||
export type LoginDomain = any
|
||||
export type SysUserVO = any
|
||||
export type TbSysFileDTO = any
|
||||
export type SysConfigVO = any
|
||||
export type ResultDomain<T = any> = any
|
||||
}
|
||||
|
||||
declare module 'shared/components' {
|
||||
export const FileUpload: any
|
||||
export const DynamicFormItem: any
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* Import Maps 使用示例
|
||||
*
|
||||
* 通过 HTML 中的 <script type="importmap"> 配置,
|
||||
* 可以直接从 HTTP URL 导入共享组件
|
||||
*/
|
||||
|
||||
// ✅ 直接导入!浏览器会自动从 http://localhost/shared/components.js 加载
|
||||
import { UlTable } from '@shared/components'
|
||||
import { http } from '@shared/utils'
|
||||
import { authApi } from '@shared/api'
|
||||
import { useTable } from '@shared/composables'
|
||||
|
||||
// 类型定义
|
||||
interface User {
|
||||
id: string
|
||||
username: string
|
||||
email: string
|
||||
createTime: string
|
||||
}
|
||||
|
||||
// 使用共享的 useTable 组合式函数
|
||||
const {
|
||||
loading,
|
||||
tableData,
|
||||
pagination,
|
||||
handlePageChange,
|
||||
handleSizeChange,
|
||||
refresh
|
||||
} = useTable<User>({
|
||||
fetchData: async (params) => {
|
||||
// 使用共享的 API 函数
|
||||
return await authApi.getUserList(params)
|
||||
}
|
||||
})
|
||||
|
||||
// 表格列配置
|
||||
const columns = [
|
||||
{ prop: 'username', label: '用户名', minWidth: 150 },
|
||||
{ prop: 'email', label: '邮箱', minWidth: 200 },
|
||||
{ prop: 'createTime', label: '创建时间', width: 180 }
|
||||
]
|
||||
|
||||
// 测试 HTTP 请求
|
||||
const testRequest = async () => {
|
||||
try {
|
||||
// 使用共享的 http 工具
|
||||
const result = await http.get('/api/test')
|
||||
console.log('请求结果:', result)
|
||||
} catch (error) {
|
||||
console.error('请求失败:', error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="import-maps-example">
|
||||
<div class="header">
|
||||
<h1>Import Maps 示例</h1>
|
||||
<p class="description">
|
||||
✅ 共享组件从 <code>http://localhost/shared/</code> 动态加载<br>
|
||||
✅ 无需打包,浏览器原生 ES Module 支持<br>
|
||||
✅ 真正的运行时共享,所有应用使用同一份代码
|
||||
</p>
|
||||
<el-button type="primary" @click="testRequest">
|
||||
测试 HTTP 请求
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 使用从 HTTP 加载的共享组件 -->
|
||||
<UlTable
|
||||
:data="tableData"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@page-change="handlePageChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
|
||||
<div class="info">
|
||||
<h3>📦 当前加载的模块:</h3>
|
||||
<ul>
|
||||
<li><code>@shared/components</code> → http://localhost/shared/components.js</li>
|
||||
<li><code>@shared/utils</code> → http://localhost/shared/utils.js</li>
|
||||
<li><code>@shared/api</code> → http://localhost/shared/api.js</li>
|
||||
<li><code>@shared/composables</code> → http://localhost/shared/composables.js</li>
|
||||
</ul>
|
||||
|
||||
<h3>🔍 如何查看?</h3>
|
||||
<ol>
|
||||
<li>打开浏览器开发者工具 (F12)</li>
|
||||
<li>切换到 Network 标签页</li>
|
||||
<li>筛选 JS 类型</li>
|
||||
<li>刷新页面,可以看到从 /shared/ 加载的模块</li>
|
||||
</ol>
|
||||
|
||||
<h3>✨ 优势:</h3>
|
||||
<ul>
|
||||
<li>✅ 真正的代码共享(所有应用共用一份)</li>
|
||||
<li>✅ 支持热更新(修改共享组件,所有应用自动更新)</li>
|
||||
<li>✅ 减小构建体积(共享代码不打包到业务应用)</li>
|
||||
<li>✅ 浏览器缓存(共享模块只下载一次)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.import-maps-example {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.description code {
|
||||
background: #f0f0f0;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-top: 32px;
|
||||
padding: 20px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.info h3 {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 12px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.info ul, .info ol {
|
||||
line-height: 2;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.info code {
|
||||
background: #fff;
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #e6a23c;
|
||||
}
|
||||
</style>
|
||||
@@ -1,24 +1,256 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="login-container">
|
||||
<div class="login-card">
|
||||
<div class="login-header">
|
||||
<h1 class="login-title">泰豪电源 AI 数智化平台</h1>
|
||||
<p class="login-subtitle">Urban Lifeline Platform</p>
|
||||
</div>
|
||||
|
||||
<el-form
|
||||
ref="loginFormRef"
|
||||
:model="loginForm"
|
||||
:rules="loginRules"
|
||||
class="login-form"
|
||||
@submit.prevent="handleLogin"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
placeholder="请输入用户名"
|
||||
size="large"
|
||||
prefix-icon="User"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
size="large"
|
||||
prefix-icon="Lock"
|
||||
show-password
|
||||
@keyup.enter="handleLogin"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="loginForm.rememberMe">
|
||||
记住我
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
:loading="loading"
|
||||
class="login-button"
|
||||
@click="handleLogin"
|
||||
>
|
||||
{{ loading ? '登录中...' : '登录' }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="login-footer">
|
||||
<span class="footer-link">忘记密码?</span>
|
||||
<span class="footer-divider">|</span>
|
||||
<span class="footer-link">注册账号</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from "vue"
|
||||
import type { LoginParam} from "@shared/types/auth"
|
||||
import { authAPI } from "@shared/api/auth/auth"
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
import type { LoginParam } from 'shared/types'
|
||||
import { authAPI } from 'shared/authAPI'
|
||||
import { getAesInstance } from 'shared/utils'
|
||||
import { TokenManager } from 'shared/api'
|
||||
|
||||
// 路由
|
||||
const router = useRouter()
|
||||
|
||||
const loginParam = reactive<LoginParam>({
|
||||
loginType: "password"
|
||||
// 表单引用
|
||||
const loginFormRef = ref<FormInstance>()
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
|
||||
// 登录表单
|
||||
const loginForm = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
rememberMe: false
|
||||
})
|
||||
|
||||
async function login(){
|
||||
// const result = await authAPI.login(loginParam)
|
||||
// 表单验证规则
|
||||
const loginRules: FormRules = {
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ min: 3, max: 20, message: '用户名长度为3-20个字符', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码至少6个字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理登录
|
||||
*/
|
||||
async function handleLogin() {
|
||||
if (!loginFormRef.value) return
|
||||
|
||||
try {
|
||||
// 1. 表单验证
|
||||
await loginFormRef.value.validate()
|
||||
|
||||
// 2. 显示加载状态
|
||||
loading.value = true
|
||||
|
||||
// 3. 获取 AES 加密实例
|
||||
const aes = getAesInstance()
|
||||
|
||||
// 4. 加密密码
|
||||
const encryptedPassword = await aes.encryptPassword(loginForm.password)
|
||||
|
||||
// 5. 构建登录参数
|
||||
const loginParam: LoginParam = {
|
||||
username: loginForm.username,
|
||||
password: encryptedPassword, // 使用加密后的密码
|
||||
loginType: 'password'
|
||||
}
|
||||
|
||||
// 6. 调用登录接口
|
||||
const response = await authAPI.login(loginParam)
|
||||
|
||||
// 7. 检查登录结果
|
||||
if (response.data.success && response.data.data) {
|
||||
const loginData = response.data.data
|
||||
|
||||
// 8. 保存 Token
|
||||
if (loginData.token) {
|
||||
TokenManager.setToken(loginData.token, loginForm.rememberMe)
|
||||
}
|
||||
|
||||
// 9. 显示成功消息
|
||||
ElMessage.success('登录成功!')
|
||||
|
||||
// 10. 跳转到首页
|
||||
router.push('/home')
|
||||
} else {
|
||||
// 登录失败
|
||||
ElMessage.error(response.data.message || '登录失败,请检查用户名和密码')
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('登录失败:', error)
|
||||
|
||||
// 显示错误消息
|
||||
if (error.response) {
|
||||
// HTTP 错误
|
||||
ElMessage.error(error.response.data?.message || '登录失败,请稍后重试')
|
||||
} else if (error.message) {
|
||||
// 其他错误
|
||||
ElMessage.error(error.message)
|
||||
} else {
|
||||
ElMessage.error('登录失败,请检查网络连接')
|
||||
}
|
||||
} finally {
|
||||
// 隐藏加载状态
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
padding: 40px;
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 24px;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.footer-link {
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.footer-divider {
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 480px) {
|
||||
.login-card {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -16,8 +16,7 @@
|
||||
"noEmit": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"],
|
||||
"@shared/*": ["../shared/src/*"]
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
|
||||
@@ -1,14 +1,44 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import { federation } from '@module-federation/vite'
|
||||
import { resolve, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue(), vueJsx()],
|
||||
plugins: [
|
||||
vue({
|
||||
script: {
|
||||
defineModel: true,
|
||||
propsDestructure: true
|
||||
}
|
||||
}),
|
||||
vueJsx(),
|
||||
federation({
|
||||
name: 'platform',
|
||||
remotes: {
|
||||
shared: {
|
||||
type: 'module',
|
||||
name: 'shared',
|
||||
entry: 'http://localhost:5000/remoteEntry.js'
|
||||
}
|
||||
},
|
||||
shared: {
|
||||
vue: {},
|
||||
'vue-router': {},
|
||||
'element-plus': {},
|
||||
axios: {}
|
||||
}
|
||||
})
|
||||
],
|
||||
|
||||
define: {
|
||||
__VUE_OPTIONS_API__: true,
|
||||
__VUE_PROD_DEVTOOLS__: true,
|
||||
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: true
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
@@ -25,11 +55,6 @@ export default defineConfig({
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
rewrite: (path: string) => path.replace(/^\/api/, '')
|
||||
},
|
||||
// 代理共享模块请求到 shared 服务
|
||||
'/shared': {
|
||||
target: 'http://localhost:5000',
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
|
||||
// Windows temporarily needs this file, https://github.com/module-federation/vite/issues/68
|
||||
|
||||
import {loadShare} from "@module-federation/runtime";
|
||||
const importMap = {
|
||||
|
||||
"@element-plus/icons-vue": async () => {
|
||||
let pkg = await import("__mf__virtual/shared__prebuild___mf_0_element_mf_2_plus_mf_1_icons_mf_2_vue__prebuild__.js");
|
||||
return pkg;
|
||||
}
|
||||
,
|
||||
"axios": async () => {
|
||||
let pkg = await import("__mf__virtual/shared__prebuild__axios__prebuild__.js");
|
||||
return pkg;
|
||||
}
|
||||
,
|
||||
"element-plus": async () => {
|
||||
let pkg = await import("__mf__virtual/shared__prebuild__element_mf_2_plus__prebuild__.js");
|
||||
return pkg;
|
||||
}
|
||||
,
|
||||
"vue": async () => {
|
||||
let pkg = await import("__mf__virtual/shared__prebuild__vue__prebuild__.js");
|
||||
return pkg;
|
||||
}
|
||||
,
|
||||
"vue-router": async () => {
|
||||
let pkg = await import("__mf__virtual/shared__prebuild__vue_mf_2_router__prebuild__.js");
|
||||
return pkg;
|
||||
}
|
||||
|
||||
}
|
||||
const usedShared = {
|
||||
|
||||
"@element-plus/icons-vue": {
|
||||
name: "@element-plus/icons-vue",
|
||||
version: "2.3.2",
|
||||
scope: ["default"],
|
||||
loaded: false,
|
||||
from: "shared",
|
||||
async get () {
|
||||
if (false) {
|
||||
throw new Error(`Shared module '${"@element-plus/icons-vue"}' must be provided by host`);
|
||||
}
|
||||
usedShared["@element-plus/icons-vue"].loaded = true
|
||||
const {"@element-plus/icons-vue": pkgDynamicImport} = importMap
|
||||
const res = await pkgDynamicImport()
|
||||
const exportModule = {...res}
|
||||
// All npm packages pre-built by vite will be converted to esm
|
||||
Object.defineProperty(exportModule, "__esModule", {
|
||||
value: true,
|
||||
enumerable: false
|
||||
})
|
||||
return function () {
|
||||
return exportModule
|
||||
}
|
||||
},
|
||||
shareConfig: {
|
||||
singleton: false,
|
||||
requiredVersion: "^2.3.2",
|
||||
|
||||
}
|
||||
}
|
||||
,
|
||||
"axios": {
|
||||
name: "axios",
|
||||
version: "1.13.2",
|
||||
scope: ["default"],
|
||||
loaded: false,
|
||||
from: "shared",
|
||||
async get () {
|
||||
if (false) {
|
||||
throw new Error(`Shared module '${"axios"}' must be provided by host`);
|
||||
}
|
||||
usedShared["axios"].loaded = true
|
||||
const {"axios": pkgDynamicImport} = importMap
|
||||
const res = await pkgDynamicImport()
|
||||
const exportModule = {...res}
|
||||
// All npm packages pre-built by vite will be converted to esm
|
||||
Object.defineProperty(exportModule, "__esModule", {
|
||||
value: true,
|
||||
enumerable: false
|
||||
})
|
||||
return function () {
|
||||
return exportModule
|
||||
}
|
||||
},
|
||||
shareConfig: {
|
||||
singleton: false,
|
||||
requiredVersion: "^1.13.2",
|
||||
|
||||
}
|
||||
}
|
||||
,
|
||||
"element-plus": {
|
||||
name: "element-plus",
|
||||
version: "2.12.0",
|
||||
scope: ["default"],
|
||||
loaded: false,
|
||||
from: "shared",
|
||||
async get () {
|
||||
if (false) {
|
||||
throw new Error(`Shared module '${"element-plus"}' must be provided by host`);
|
||||
}
|
||||
usedShared["element-plus"].loaded = true
|
||||
const {"element-plus": pkgDynamicImport} = importMap
|
||||
const res = await pkgDynamicImport()
|
||||
const exportModule = {...res}
|
||||
// All npm packages pre-built by vite will be converted to esm
|
||||
Object.defineProperty(exportModule, "__esModule", {
|
||||
value: true,
|
||||
enumerable: false
|
||||
})
|
||||
return function () {
|
||||
return exportModule
|
||||
}
|
||||
},
|
||||
shareConfig: {
|
||||
singleton: false,
|
||||
requiredVersion: "^2.12.0",
|
||||
|
||||
}
|
||||
}
|
||||
,
|
||||
"vue": {
|
||||
name: "vue",
|
||||
version: "3.5.25",
|
||||
scope: ["default"],
|
||||
loaded: false,
|
||||
from: "shared",
|
||||
async get () {
|
||||
if (false) {
|
||||
throw new Error(`Shared module '${"vue"}' must be provided by host`);
|
||||
}
|
||||
usedShared["vue"].loaded = true
|
||||
const {"vue": pkgDynamicImport} = importMap
|
||||
const res = await pkgDynamicImport()
|
||||
const exportModule = {...res}
|
||||
// All npm packages pre-built by vite will be converted to esm
|
||||
Object.defineProperty(exportModule, "__esModule", {
|
||||
value: true,
|
||||
enumerable: false
|
||||
})
|
||||
return function () {
|
||||
return exportModule
|
||||
}
|
||||
},
|
||||
shareConfig: {
|
||||
singleton: false,
|
||||
requiredVersion: "^3.5.25",
|
||||
|
||||
}
|
||||
}
|
||||
,
|
||||
"vue-router": {
|
||||
name: "vue-router",
|
||||
version: "4.6.3",
|
||||
scope: ["default"],
|
||||
loaded: false,
|
||||
from: "shared",
|
||||
async get () {
|
||||
if (false) {
|
||||
throw new Error(`Shared module '${"vue-router"}' must be provided by host`);
|
||||
}
|
||||
usedShared["vue-router"].loaded = true
|
||||
const {"vue-router": pkgDynamicImport} = importMap
|
||||
const res = await pkgDynamicImport()
|
||||
const exportModule = {...res}
|
||||
// All npm packages pre-built by vite will be converted to esm
|
||||
Object.defineProperty(exportModule, "__esModule", {
|
||||
value: true,
|
||||
enumerable: false
|
||||
})
|
||||
return function () {
|
||||
return exportModule
|
||||
}
|
||||
},
|
||||
shareConfig: {
|
||||
singleton: false,
|
||||
requiredVersion: "^4.6.3",
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
const usedRemotes = [
|
||||
]
|
||||
export {
|
||||
usedShared,
|
||||
usedRemotes
|
||||
}
|
||||
|
||||
@@ -5,24 +5,24 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev:demo": "vite",
|
||||
"build": "run-p build:*",
|
||||
"build:esm": "vite build --mode esm",
|
||||
"build:federation": "vite build --mode federation",
|
||||
"preview": "vite preview"
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"serve": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.5.13",
|
||||
"ofetch": "^1.4.1",
|
||||
"vue-router": "^4.5.0",
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"cors": "^2.8.5",
|
||||
"element-plus": "^2.12.0",
|
||||
"@element-plus/icons-vue": "^2.3.2"
|
||||
"express": "^4.18.2",
|
||||
"ofetch": "^1.4.1",
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@module-federation/vite": "^1.9.3",
|
||||
"@types/node": "^20.10.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||
"@originjs/vite-plugin-federation": "^1.3.6",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"sass": "^1.80.6",
|
||||
"sass-embedded": "^1.80.6",
|
||||
@@ -30,7 +30,7 @@
|
||||
"vite": "^6.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.13",
|
||||
"typescript": ">=5.0.0"
|
||||
"typescript": ">=5.0.0",
|
||||
"vue": "^3.5.13"
|
||||
}
|
||||
}
|
||||
509
urbanLifelineWeb/packages/shared/pnpm-lock.yaml
generated
509
urbanLifelineWeb/packages/shared/pnpm-lock.yaml
generated
@@ -8,9 +8,15 @@ dependencies:
|
||||
'@element-plus/icons-vue':
|
||||
specifier: ^2.3.2
|
||||
version: 2.3.2(vue@3.5.25)
|
||||
cors:
|
||||
specifier: ^2.8.5
|
||||
version: 2.8.5
|
||||
element-plus:
|
||||
specifier: ^2.12.0
|
||||
version: 2.12.0(vue@3.5.25)
|
||||
express:
|
||||
specifier: ^4.18.2
|
||||
version: 4.22.1
|
||||
ofetch:
|
||||
specifier: ^1.4.1
|
||||
version: 1.5.1
|
||||
@@ -22,9 +28,9 @@ dependencies:
|
||||
version: 4.6.3(vue@3.5.25)
|
||||
|
||||
devDependencies:
|
||||
'@originjs/vite-plugin-federation':
|
||||
specifier: ^1.3.6
|
||||
version: 1.4.1
|
||||
'@module-federation/vite':
|
||||
specifier: ^1.9.3
|
||||
version: 1.9.3
|
||||
'@types/node':
|
||||
specifier: ^20.10.0
|
||||
version: 20.19.25
|
||||
@@ -603,12 +609,41 @@ packages:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
dev: true
|
||||
|
||||
/@originjs/vite-plugin-federation@1.4.1:
|
||||
resolution: {integrity: sha512-Uo08jW5pj1t58OUKuZNkmzcfTN2pqeVuAWCCiKf/75/oll4Efq4cHOqSE1FXMlvwZNGDziNdDyBbQ5IANem3CQ==}
|
||||
engines: {node: '>=14.0.0', pnpm: '>=7.0.1'}
|
||||
/@module-federation/error-codes@0.21.6:
|
||||
resolution: {integrity: sha512-MLJUCQ05KnoVl8xd6xs9a5g2/8U+eWmVxg7xiBMeR0+7OjdWUbHwcwgVFatRIwSZvFgKHfWEiI7wsU1q1XbTRQ==}
|
||||
dev: true
|
||||
|
||||
/@module-federation/runtime-core@0.21.6:
|
||||
resolution: {integrity: sha512-5Hd1Y5qp5lU/aTiK66lidMlM/4ji2gr3EXAtJdreJzkY+bKcI5+21GRcliZ4RAkICmvdxQU5PHPL71XmNc7Lsw==}
|
||||
dependencies:
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.27.0
|
||||
'@module-federation/error-codes': 0.21.6
|
||||
'@module-federation/sdk': 0.21.6
|
||||
dev: true
|
||||
|
||||
/@module-federation/runtime@0.21.6:
|
||||
resolution: {integrity: sha512-+caXwaQqwTNh+CQqyb4mZmXq7iEemRDrTZQGD+zyeH454JAYnJ3s/3oDFizdH6245pk+NiqDyOOkHzzFQorKhQ==}
|
||||
dependencies:
|
||||
'@module-federation/error-codes': 0.21.6
|
||||
'@module-federation/runtime-core': 0.21.6
|
||||
'@module-federation/sdk': 0.21.6
|
||||
dev: true
|
||||
|
||||
/@module-federation/sdk@0.21.6:
|
||||
resolution: {integrity: sha512-x6hARETb8iqHVhEsQBysuWpznNZViUh84qV2yE7AD+g7uIzHKiYdoWqj10posbo5XKf/147qgWDzKZoKoEP2dw==}
|
||||
dev: true
|
||||
|
||||
/@module-federation/vite@1.9.3:
|
||||
resolution: {integrity: sha512-MV6XI3FX6okEMJ7FdmvFmYuu7DygRoLljKT8atrBwFhlttsgBbswpqMj4P4Fs/X+pFmbIi/ntFzVhsrG0qQnGQ==}
|
||||
dependencies:
|
||||
'@module-federation/runtime': 0.21.6
|
||||
'@module-federation/sdk': 0.21.6
|
||||
'@rollup/pluginutils': 5.3.0
|
||||
defu: 6.1.4
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.21
|
||||
pathe: 1.1.2
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
dev: true
|
||||
|
||||
/@parcel/watcher-android-arm64@2.5.1:
|
||||
@@ -764,6 +799,20 @@ packages:
|
||||
resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==}
|
||||
dev: true
|
||||
|
||||
/@rollup/pluginutils@5.3.0:
|
||||
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
|
||||
peerDependenciesMeta:
|
||||
rollup:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
estree-walker: 2.0.2
|
||||
picomatch: 4.0.3
|
||||
dev: true
|
||||
|
||||
/@rollup/rollup-android-arm-eabi@4.53.3:
|
||||
resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==}
|
||||
cpu: [arm]
|
||||
@@ -1142,6 +1191,14 @@ packages:
|
||||
- vue
|
||||
dev: false
|
||||
|
||||
/accepts@1.3.8:
|
||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
mime-types: 2.1.35
|
||||
negotiator: 0.6.3
|
||||
dev: false
|
||||
|
||||
/ansi-styles@3.2.1:
|
||||
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -1157,6 +1214,10 @@ packages:
|
||||
is-array-buffer: 3.0.5
|
||||
dev: true
|
||||
|
||||
/array-flatten@1.1.1:
|
||||
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
|
||||
dev: false
|
||||
|
||||
/arraybuffer.prototype.slice@1.0.4:
|
||||
resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1195,6 +1256,26 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/body-parser@1.20.4:
|
||||
resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==}
|
||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||
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.1
|
||||
iconv-lite: 0.4.24
|
||||
on-finished: 2.4.1
|
||||
qs: 6.14.0
|
||||
raw-body: 2.5.3
|
||||
type-is: 1.6.18
|
||||
unpipe: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/brace-expansion@1.1.12:
|
||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
||||
dependencies:
|
||||
@@ -1227,13 +1308,17 @@ packages:
|
||||
resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==}
|
||||
dev: true
|
||||
|
||||
/bytes@3.1.2:
|
||||
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/call-bind-apply-helpers@1.0.2:
|
||||
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
function-bind: 1.1.2
|
||||
dev: true
|
||||
|
||||
/call-bind@1.0.8:
|
||||
resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
|
||||
@@ -1251,7 +1336,6 @@ packages:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
get-intrinsic: 1.3.0
|
||||
dev: true
|
||||
|
||||
/caniuse-lite@1.0.30001759:
|
||||
resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==}
|
||||
@@ -1291,10 +1375,39 @@ packages:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
dev: true
|
||||
|
||||
/content-disposition@0.5.4:
|
||||
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
dev: false
|
||||
|
||||
/content-type@1.0.5:
|
||||
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
dev: true
|
||||
|
||||
/cookie-signature@1.0.7:
|
||||
resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==}
|
||||
dev: false
|
||||
|
||||
/cookie@0.7.2:
|
||||
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/cors@2.8.5:
|
||||
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
dependencies:
|
||||
object-assign: 4.1.1
|
||||
vary: 1.1.2
|
||||
dev: false
|
||||
|
||||
/cross-spawn@6.0.6:
|
||||
resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==}
|
||||
engines: {node: '>=4.8'}
|
||||
@@ -1340,6 +1453,17 @@ packages:
|
||||
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
|
||||
dev: false
|
||||
|
||||
/debug@2.6.9:
|
||||
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.0.0
|
||||
dev: false
|
||||
|
||||
/debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -1370,10 +1494,24 @@ packages:
|
||||
object-keys: 1.1.1
|
||||
dev: true
|
||||
|
||||
/defu@6.1.4:
|
||||
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
||||
dev: true
|
||||
|
||||
/depd@2.0.0:
|
||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/destr@2.0.5:
|
||||
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
|
||||
dev: false
|
||||
|
||||
/destroy@1.2.0:
|
||||
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
|
||||
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
|
||||
dev: false
|
||||
|
||||
/detect-libc@1.0.3:
|
||||
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
|
||||
engines: {node: '>=0.10'}
|
||||
@@ -1389,7 +1527,10 @@ packages:
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
dev: true
|
||||
|
||||
/ee-first@1.1.1:
|
||||
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
|
||||
dev: false
|
||||
|
||||
/electron-to-chromium@1.5.266:
|
||||
resolution: {integrity: sha512-kgWEglXvkEfMH7rxP5OSZZwnaDWT7J9EoZCujhnpLbfi0bbNtRkgdX2E3gt0Uer11c61qCYktB3hwkAS325sJg==}
|
||||
@@ -1419,6 +1560,16 @@ packages:
|
||||
- '@vue/composition-api'
|
||||
dev: false
|
||||
|
||||
/encodeurl@1.0.2:
|
||||
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/encodeurl@2.0.0:
|
||||
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
@@ -1492,19 +1643,16 @@ packages:
|
||||
/es-define-property@1.0.1:
|
||||
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/es-errors@1.3.0:
|
||||
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/es-object-atoms@1.1.1:
|
||||
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
dev: true
|
||||
|
||||
/es-set-tostringtag@2.1.0:
|
||||
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||
@@ -1564,6 +1712,10 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/escape-html@1.0.3:
|
||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||
dev: false
|
||||
|
||||
/escape-string-regexp@1.0.5:
|
||||
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
@@ -1572,11 +1724,49 @@ packages:
|
||||
/estree-walker@2.0.2:
|
||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||
|
||||
/estree-walker@3.0.3:
|
||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||
/etag@1.8.1:
|
||||
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/express@4.22.1:
|
||||
resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
dev: true
|
||||
accepts: 1.3.8
|
||||
array-flatten: 1.1.1
|
||||
body-parser: 1.20.4
|
||||
content-disposition: 0.5.4
|
||||
content-type: 1.0.5
|
||||
cookie: 0.7.2
|
||||
cookie-signature: 1.0.7
|
||||
debug: 2.6.9
|
||||
depd: 2.0.0
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
finalhandler: 1.3.2
|
||||
fresh: 0.5.2
|
||||
http-errors: 2.0.1
|
||||
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.14.0
|
||||
range-parser: 1.2.1
|
||||
safe-buffer: 5.2.1
|
||||
send: 0.19.1
|
||||
serve-static: 1.16.2
|
||||
setprototypeof: 1.2.0
|
||||
statuses: 2.0.2
|
||||
type-is: 1.6.18
|
||||
utils-merge: 1.0.1
|
||||
vary: 1.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/fdir@6.5.0(picomatch@4.0.3):
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
@@ -1599,6 +1789,21 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/finalhandler@1.3.2:
|
||||
resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
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.2
|
||||
unpipe: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/for-each@0.3.5:
|
||||
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1606,6 +1811,16 @@ packages:
|
||||
is-callable: 1.2.7
|
||||
dev: true
|
||||
|
||||
/forwarded@0.2.0:
|
||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/fresh@0.5.2:
|
||||
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
@@ -1616,7 +1831,6 @@ packages:
|
||||
|
||||
/function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
dev: true
|
||||
|
||||
/function.prototype.name@1.1.8:
|
||||
resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
|
||||
@@ -1658,7 +1872,6 @@ packages:
|
||||
has-symbols: 1.1.0
|
||||
hasown: 2.0.2
|
||||
math-intrinsics: 1.1.0
|
||||
dev: true
|
||||
|
||||
/get-proto@1.0.1:
|
||||
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||
@@ -1666,7 +1879,6 @@ packages:
|
||||
dependencies:
|
||||
dunder-proto: 1.0.1
|
||||
es-object-atoms: 1.1.1
|
||||
dev: true
|
||||
|
||||
/get-symbol-description@1.1.0:
|
||||
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
|
||||
@@ -1688,7 +1900,6 @@ packages:
|
||||
/gopd@1.2.0:
|
||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
@@ -1725,7 +1936,6 @@ packages:
|
||||
/has-symbols@1.1.0:
|
||||
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/has-tostringtag@1.0.2:
|
||||
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||
@@ -1739,16 +1949,48 @@ packages:
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
dev: true
|
||||
|
||||
/hosted-git-info@2.8.9:
|
||||
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
||||
dev: true
|
||||
|
||||
/http-errors@2.0.0:
|
||||
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
depd: 2.0.0
|
||||
inherits: 2.0.4
|
||||
setprototypeof: 1.2.0
|
||||
statuses: 2.0.1
|
||||
toidentifier: 1.0.1
|
||||
dev: false
|
||||
|
||||
/http-errors@2.0.1:
|
||||
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
depd: 2.0.0
|
||||
inherits: 2.0.4
|
||||
setprototypeof: 1.2.0
|
||||
statuses: 2.0.2
|
||||
toidentifier: 1.0.1
|
||||
dev: false
|
||||
|
||||
/iconv-lite@0.4.24:
|
||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
dev: false
|
||||
|
||||
/immutable@5.1.4:
|
||||
resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==}
|
||||
dev: true
|
||||
|
||||
/inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
dev: false
|
||||
|
||||
/internal-slot@1.1.0:
|
||||
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -1758,6 +2000,11 @@ packages:
|
||||
side-channel: 1.1.0
|
||||
dev: true
|
||||
|
||||
/ipaddr.js@1.9.1:
|
||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
dev: false
|
||||
|
||||
/is-array-buffer@3.0.5:
|
||||
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2015,13 +2262,6 @@ packages:
|
||||
yallist: 3.1.1
|
||||
dev: true
|
||||
|
||||
/magic-string@0.27.0:
|
||||
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
dev: true
|
||||
|
||||
/magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
dependencies:
|
||||
@@ -2030,7 +2270,11 @@ packages:
|
||||
/math-intrinsics@1.1.0:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/media-typer@0.3.0:
|
||||
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/memoize-one@6.0.0:
|
||||
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
|
||||
@@ -2041,6 +2285,15 @@ packages:
|
||||
engines: {node: '>= 0.10.0'}
|
||||
dev: true
|
||||
|
||||
/merge-descriptors@1.0.3:
|
||||
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
|
||||
dev: false
|
||||
|
||||
/methods@1.1.2:
|
||||
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/micromatch@4.0.8:
|
||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
engines: {node: '>=8.6'}
|
||||
@@ -2051,21 +2304,47 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/mime-db@1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/mime-types@2.1.35:
|
||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
dev: false
|
||||
|
||||
/mime@1.6.0:
|
||||
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/minimatch@3.1.2:
|
||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
dependencies:
|
||||
brace-expansion: 1.1.12
|
||||
dev: true
|
||||
|
||||
/ms@2.0.0:
|
||||
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
||||
dev: false
|
||||
|
||||
/ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
dev: true
|
||||
|
||||
/nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
/negotiator@0.6.3:
|
||||
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/nice-try@1.0.5:
|
||||
resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==}
|
||||
dev: true
|
||||
@@ -2113,10 +2392,14 @@ packages:
|
||||
string.prototype.padend: 3.1.6
|
||||
dev: true
|
||||
|
||||
/object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/object-inspect@1.13.4:
|
||||
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/object-keys@1.1.1:
|
||||
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
|
||||
@@ -2143,6 +2426,13 @@ packages:
|
||||
ufo: 1.6.1
|
||||
dev: false
|
||||
|
||||
/on-finished@2.4.1:
|
||||
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
ee-first: 1.1.1
|
||||
dev: false
|
||||
|
||||
/own-keys@1.0.1:
|
||||
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2160,6 +2450,11 @@ packages:
|
||||
json-parse-better-errors: 1.0.2
|
||||
dev: true
|
||||
|
||||
/parseurl@1.3.3:
|
||||
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/path-key@2.0.1:
|
||||
resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -2169,6 +2464,10 @@ packages:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
dev: true
|
||||
|
||||
/path-to-regexp@0.1.12:
|
||||
resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==}
|
||||
dev: false
|
||||
|
||||
/path-type@3.0.0:
|
||||
resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -2176,6 +2475,10 @@ packages:
|
||||
pify: 3.0.0
|
||||
dev: true
|
||||
|
||||
/pathe@1.1.2:
|
||||
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
|
||||
dev: true
|
||||
|
||||
/picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
@@ -2215,6 +2518,36 @@ packages:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
/proxy-addr@2.0.7:
|
||||
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
|
||||
engines: {node: '>= 0.10'}
|
||||
dependencies:
|
||||
forwarded: 0.2.0
|
||||
ipaddr.js: 1.9.1
|
||||
dev: false
|
||||
|
||||
/qs@6.14.0:
|
||||
resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==}
|
||||
engines: {node: '>=0.6'}
|
||||
dependencies:
|
||||
side-channel: 1.1.0
|
||||
dev: false
|
||||
|
||||
/range-parser@1.2.1:
|
||||
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dev: false
|
||||
|
||||
/raw-body@2.5.3:
|
||||
resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
http-errors: 2.0.1
|
||||
iconv-lite: 0.4.24
|
||||
unpipe: 1.0.0
|
||||
dev: false
|
||||
|
||||
/read-pkg@3.0.0:
|
||||
resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -2314,6 +2647,10 @@ packages:
|
||||
isarray: 2.0.5
|
||||
dev: true
|
||||
|
||||
/safe-buffer@5.2.1:
|
||||
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||
dev: false
|
||||
|
||||
/safe-push-apply@1.0.0:
|
||||
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2331,6 +2668,10 @@ packages:
|
||||
is-regex: 1.2.1
|
||||
dev: true
|
||||
|
||||
/safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
dev: false
|
||||
|
||||
/sass-embedded-all-unknown@1.93.3:
|
||||
resolution: {integrity: sha512-3okGgnE41eg+CPLtAPletu6nQ4N0ij7AeW+Sl5Km4j29XcmqZQeFwYjHe1AlKTEgLi/UAONk1O8i8/lupeKMbw==}
|
||||
cpu: ['!arm', '!arm64', '!riscv64', '!x64']
|
||||
@@ -2571,6 +2912,60 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/send@0.19.0:
|
||||
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
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
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/send@0.19.1:
|
||||
resolution: {integrity: sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
dependencies:
|
||||
debug: 2.6.9
|
||||
depd: 2.0.0
|
||||
destroy: 1.2.0
|
||||
encodeurl: 2.0.0
|
||||
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
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/serve-static@1.16.2:
|
||||
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
dependencies:
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
parseurl: 1.3.3
|
||||
send: 0.19.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/set-function-length@1.2.2:
|
||||
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2602,6 +2997,10 @@ packages:
|
||||
es-object-atoms: 1.1.1
|
||||
dev: true
|
||||
|
||||
/setprototypeof@1.2.0:
|
||||
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
|
||||
dev: false
|
||||
|
||||
/shebang-command@1.2.0:
|
||||
resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -2625,7 +3024,6 @@ packages:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
dev: true
|
||||
|
||||
/side-channel-map@1.0.1:
|
||||
resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
|
||||
@@ -2635,7 +3033,6 @@ packages:
|
||||
es-errors: 1.3.0
|
||||
get-intrinsic: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
dev: true
|
||||
|
||||
/side-channel-weakmap@1.0.2:
|
||||
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
|
||||
@@ -2646,7 +3043,6 @@ packages:
|
||||
get-intrinsic: 1.3.0
|
||||
object-inspect: 1.13.4
|
||||
side-channel-map: 1.0.1
|
||||
dev: true
|
||||
|
||||
/side-channel@1.1.0:
|
||||
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
|
||||
@@ -2657,7 +3053,6 @@ packages:
|
||||
side-channel-list: 1.0.0
|
||||
side-channel-map: 1.0.1
|
||||
side-channel-weakmap: 1.0.2
|
||||
dev: true
|
||||
|
||||
/source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
@@ -2685,6 +3080,16 @@ packages:
|
||||
resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==}
|
||||
dev: true
|
||||
|
||||
/statuses@2.0.1:
|
||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/statuses@2.0.2:
|
||||
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/stop-iteration-iterator@1.1.0:
|
||||
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2788,10 +3193,23 @@ packages:
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/toidentifier@1.0.1:
|
||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||
engines: {node: '>=0.6'}
|
||||
dev: false
|
||||
|
||||
/tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
dev: true
|
||||
|
||||
/type-is@1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
dependencies:
|
||||
media-typer: 0.3.0
|
||||
mime-types: 2.1.35
|
||||
dev: false
|
||||
|
||||
/typed-array-buffer@1.0.3:
|
||||
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -2860,6 +3278,11 @@ packages:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
dev: true
|
||||
|
||||
/unpipe@1.0.0:
|
||||
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/update-browserslist-db@1.2.2(browserslist@4.28.1):
|
||||
resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==}
|
||||
hasBin: true
|
||||
@@ -2871,6 +3294,11 @@ packages:
|
||||
picocolors: 1.1.1
|
||||
dev: true
|
||||
|
||||
/utils-merge@1.0.1:
|
||||
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
dev: false
|
||||
|
||||
/validate-npm-package-license@3.0.4:
|
||||
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
||||
dependencies:
|
||||
@@ -2882,6 +3310,11 @@ packages:
|
||||
resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==}
|
||||
dev: true
|
||||
|
||||
/vary@1.1.2:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
dev: false
|
||||
|
||||
/vite@6.4.1(@types/node@20.19.25)(sass-embedded@1.93.3)(sass@1.94.2):
|
||||
resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
|
||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||
|
||||
73
urbanLifelineWeb/packages/shared/server.js
Normal file
73
urbanLifelineWeb/packages/shared/server.js
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Shared 模块静态文件服务器
|
||||
* 提供构建后的 ES Module 文件供其他应用使用
|
||||
*
|
||||
* 使用方式:
|
||||
* 1. npm run build:esm # 先构建
|
||||
* 2. node server.js # 启动服务器
|
||||
*/
|
||||
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
const app = express()
|
||||
const PORT = 5000
|
||||
|
||||
// 启用 CORS
|
||||
app.use(cors())
|
||||
|
||||
// 静态文件服务:/shared/* -> dist/esm/*
|
||||
app.use('/shared', express.static(path.join(__dirname, 'dist/esm'), {
|
||||
setHeaders: (res, filepath) => {
|
||||
// 设置正确的 MIME 类型
|
||||
if (filepath.endsWith('.js')) {
|
||||
res.setHeader('Content-Type', 'application/javascript; charset=utf-8')
|
||||
}
|
||||
// 允许跨域
|
||||
res.setHeader('Access-Control-Allow-Origin', '*')
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS')
|
||||
}
|
||||
}))
|
||||
|
||||
// 健康检查
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
port: PORT,
|
||||
modules: ['components', 'utils', 'api', 'composables', 'types']
|
||||
})
|
||||
})
|
||||
|
||||
// 模块列表
|
||||
app.get('/modules', (req, res) => {
|
||||
res.json({
|
||||
modules: {
|
||||
components: '/shared/components.js',
|
||||
utils: '/shared/utils.js',
|
||||
api: '/shared/api.js',
|
||||
composables: '/shared/composables.js',
|
||||
types: '/shared/types.js'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 启动服务器
|
||||
app.listen(PORT, () => {
|
||||
console.log(`\n🚀 Shared 模块服务器已启动!`)
|
||||
console.log(``)
|
||||
console.log(`📦 提供以下模块:`)
|
||||
console.log(` - http://localhost:${PORT}/shared/components.js`)
|
||||
console.log(` - http://localhost:${PORT}/shared/utils.js`)
|
||||
console.log(` - http://localhost:${PORT}/shared/api.js`)
|
||||
console.log(` - http://localhost:${PORT}/shared/composables.js`)
|
||||
console.log(` - http://localhost:${PORT}/shared/types.js`)
|
||||
console.log(``)
|
||||
console.log(`🔍 健康检查:http://localhost:${PORT}/health`)
|
||||
console.log(`📋 模块列表:http://localhost:${PORT}/modules`)
|
||||
console.log(``)
|
||||
})
|
||||
@@ -1,6 +1,92 @@
|
||||
import { api } from '@/api/index'
|
||||
import type { LoginParam, LoginDomain } from '@/types'
|
||||
|
||||
/**
|
||||
* 认证 API
|
||||
* 通过 Gateway (8180) 访问 Auth Service (8181)
|
||||
* 路由规则:/urban-lifeline/auth/** → auth-service/urban-lifeline/auth/**
|
||||
*/
|
||||
export const authAPI = {
|
||||
baseUrl: "/auth",
|
||||
baseUrl: "/urban-lifeline/auth",
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param loginParam 登录参数
|
||||
* @returns 登录结果(包含 token 和用户信息)
|
||||
*/
|
||||
login(loginParam: LoginParam) {
|
||||
return api.post<LoginDomain>(`${this.baseUrl}/login`, loginParam)
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户登出
|
||||
* @returns 登出结果
|
||||
*/
|
||||
logout() {
|
||||
return api.post<LoginDomain>(`${this.baseUrl}/logout`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取验证码(统一接口)
|
||||
* @param loginParam 登录参数(包含验证码类型)
|
||||
* @returns 验证码结果
|
||||
*/
|
||||
getCaptcha(loginParam: LoginParam) {
|
||||
return api.post<LoginDomain>(`${this.baseUrl}/captcha`, loginParam)
|
||||
},
|
||||
|
||||
/**
|
||||
* 刷新 Token
|
||||
* @returns 新的登录信息
|
||||
*/
|
||||
refreshToken() {
|
||||
return api.post<LoginDomain>(`${this.baseUrl}/refresh`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 发送邮箱验证码
|
||||
* @param email 邮箱地址
|
||||
* @returns 发送结果
|
||||
*/
|
||||
sendEmailCode(email: string) {
|
||||
return api.post<LoginDomain>(`${this.baseUrl}/send-email-code`, { email })
|
||||
},
|
||||
|
||||
/**
|
||||
* 发送短信验证码
|
||||
* @param phone 手机号
|
||||
* @returns 发送结果
|
||||
*/
|
||||
sendSmsCode(phone: string) {
|
||||
return api.post<LoginDomain>(`${this.baseUrl}/send-sms-code`, { phone })
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
* @param registerData 注册数据
|
||||
* @returns 注册结果(成功后自动登录,返回 token)
|
||||
*/
|
||||
register(registerData: {
|
||||
registerType: 'username' | 'phone' | 'email'
|
||||
username?: string
|
||||
phone?: string
|
||||
email?: string
|
||||
password: string
|
||||
confirmPassword: string
|
||||
smsCode?: string
|
||||
emailCode?: string
|
||||
smsSessionId?: string
|
||||
emailSessionId?: string
|
||||
studentId?: string
|
||||
}) {
|
||||
return api.post<LoginDomain>(`${this.baseUrl}/register`, registerData)
|
||||
},
|
||||
|
||||
/**
|
||||
* 健康检查
|
||||
* @returns 健康状态
|
||||
*/
|
||||
health() {
|
||||
return api.get<string>(`${this.baseUrl}/health`)
|
||||
}
|
||||
}
|
||||
|
||||
274
urbanLifelineWeb/packages/shared/src/utils/crypto/README.md
Normal file
274
urbanLifelineWeb/packages/shared/src/utils/crypto/README.md
Normal file
@@ -0,0 +1,274 @@
|
||||
# 加密工具
|
||||
|
||||
## AES-256-GCM 加密
|
||||
|
||||
### 概述
|
||||
|
||||
前端 AES 加密工具,与后端 `AesEncryptUtil` 保持一致,用于敏感信息传输加密。
|
||||
|
||||
### 使用场景
|
||||
|
||||
1. **密码传输**:登录时加密密码
|
||||
2. **敏感信息**:加密手机号、身份证号等
|
||||
|
||||
### 快速开始
|
||||
|
||||
#### 1. 初始化(应用启动时)
|
||||
|
||||
```typescript
|
||||
import { initAesEncrypt } from '@/utils/crypto'
|
||||
|
||||
// 在 main.ts 中初始化
|
||||
const AES_SECRET_KEY = '1234567890qwer' // 与后端配置保持一致
|
||||
await initAesEncrypt(AES_SECRET_KEY)
|
||||
```
|
||||
|
||||
#### 2. 使用加密
|
||||
|
||||
```typescript
|
||||
import { getAesInstance } from '@/utils/crypto'
|
||||
|
||||
// 获取加密实例
|
||||
const aes = getAesInstance()
|
||||
|
||||
// 加密密码
|
||||
const encryptedPassword = await aes.encryptPassword('myPassword123')
|
||||
|
||||
// 加密手机号
|
||||
const encryptedPhone = await aes.encryptPhone('13812345678')
|
||||
```
|
||||
|
||||
### 完整示例
|
||||
|
||||
#### 登录时加密密码
|
||||
|
||||
```typescript
|
||||
import { authAPI } from '@/api/auth'
|
||||
import { getAesInstance } from '@/utils/crypto'
|
||||
|
||||
async function login(username: string, password: string) {
|
||||
try {
|
||||
// 加密密码
|
||||
const aes = getAesInstance()
|
||||
const encryptedPassword = await aes.encryptPassword(password)
|
||||
|
||||
// 发送登录请求
|
||||
const response = await authAPI.login({
|
||||
username,
|
||||
password: encryptedPassword,
|
||||
loginType: 'password'
|
||||
})
|
||||
|
||||
return response.data
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 手机号注册
|
||||
|
||||
```typescript
|
||||
import { authAPI } from '@/api/auth'
|
||||
import { getAesInstance } from '@/utils/crypto'
|
||||
|
||||
async function register(phone: string, password: string, smsCode: string) {
|
||||
try {
|
||||
const aes = getAesInstance()
|
||||
|
||||
// 加密密码
|
||||
const encryptedPassword = await aes.encryptPassword(password)
|
||||
|
||||
// 加密手机号
|
||||
const encryptedPhone = await aes.encryptPhone(phone)
|
||||
|
||||
// 发送注册请求
|
||||
const response = await authAPI.register({
|
||||
registerType: 'phone',
|
||||
phone: encryptedPhone,
|
||||
password: encryptedPassword,
|
||||
confirmPassword: encryptedPassword,
|
||||
smsCode,
|
||||
smsSessionId: 'session-id-from-captcha'
|
||||
})
|
||||
|
||||
return response.data
|
||||
} catch (error) {
|
||||
console.error('注册失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 工具函数
|
||||
|
||||
#### 数据脱敏
|
||||
|
||||
```typescript
|
||||
import { AesUtils } from '@/utils/crypto'
|
||||
|
||||
// 脱敏手机号
|
||||
const masked = AesUtils.maskPhone('13812345678')
|
||||
// 输出:138****5678
|
||||
|
||||
// 脱敏身份证号
|
||||
const maskedId = AesUtils.maskIdCard('110101199001011234')
|
||||
// 输出:110101********1234
|
||||
|
||||
// 脱敏邮箱
|
||||
const maskedEmail = AesUtils.maskEmail('test@example.com')
|
||||
// 输出:t***@example.com
|
||||
```
|
||||
|
||||
### API 参考
|
||||
|
||||
#### AesEncryptUtil 类
|
||||
|
||||
| 方法 | 参数 | 返回值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `init()` | - | `Promise<void>` | 初始化密钥(必须先调用) |
|
||||
| `encrypt(plaintext)` | `string` | `Promise<string>` | 加密字符串 |
|
||||
| `decrypt(ciphertext)` | `string` | `Promise<string>` | 解密字符串 |
|
||||
| `encryptPassword(password)` | `string` | `Promise<string>` | 加密密码 |
|
||||
| `encryptPhone(phone)` | `string` | `Promise<string>` | 加密手机号 |
|
||||
| `decryptPhone(encrypted)` | `string` | `Promise<string>` | 解密手机号 |
|
||||
| `encryptIdCard(idCard)` | `string` | `Promise<string>` | 加密身份证号 |
|
||||
| `decryptIdCard(encrypted)` | `string` | `Promise<string>` | 解密身份证号 |
|
||||
|
||||
#### AesUtils 静态方法
|
||||
|
||||
| 方法 | 参数 | 返回值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `maskPhone(phone)` | `string` | `string` | 脱敏手机号 |
|
||||
| `maskIdCard(idCard)` | `string` | `string` | 脱敏身份证号 |
|
||||
| `maskEmail(email)` | `string` | `string` | 脱敏邮箱 |
|
||||
|
||||
#### 全局函数
|
||||
|
||||
| 函数 | 参数 | 返回值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `initAesEncrypt(secretKey)` | `string` | `Promise<void>` | 初始化 AES 加密(应用启动时调用) |
|
||||
| `getAesInstance()` | - | `AesEncryptUtil` | 获取 AES 加密实例 |
|
||||
| `createAesEncrypt(secretKey)` | `string` | `Promise<AesEncryptUtil>` | 创建新的 AES 实例 |
|
||||
|
||||
### 配置说明
|
||||
|
||||
#### 密钥配置
|
||||
|
||||
前端密钥必须与后端配置保持一致:
|
||||
|
||||
**后端配置(application.yml)**:
|
||||
```yaml
|
||||
security:
|
||||
aes:
|
||||
secret-key: 1234567890qwer
|
||||
```
|
||||
|
||||
**前端配置**:
|
||||
```typescript
|
||||
const AES_SECRET_KEY = '1234567890qwer' // 与后端保持一致
|
||||
await initAesEncrypt(AES_SECRET_KEY)
|
||||
```
|
||||
|
||||
#### 算法参数
|
||||
|
||||
| 参数 | 值 | 说明 |
|
||||
|------|-----|------|
|
||||
| 算法 | AES-256-GCM | 高强度加密算法 |
|
||||
| 密钥长度 | 256 bits | AES-256 |
|
||||
| IV 长度 | 12 bytes | GCM 推荐长度 |
|
||||
| Tag 长度 | 128 bits | GCM 认证标签 |
|
||||
| 编码 | Base64 | 密文编码格式 |
|
||||
|
||||
### 安全注意事项
|
||||
|
||||
1. **密钥管理**
|
||||
- 密钥不要硬编码在代码中
|
||||
- 生产环境从配置中心或环境变量获取
|
||||
- 定期轮换密钥
|
||||
|
||||
2. **HTTPS 传输**
|
||||
- 生产环境必须使用 HTTPS
|
||||
- 加密只是额外保障,不能替代 HTTPS
|
||||
|
||||
3. **密码安全**
|
||||
- 密码传输前加密
|
||||
- 后端再次使用 BCrypt 等算法加密存储
|
||||
- 前端加密防止明文传输被截获
|
||||
|
||||
4. **错误处理**
|
||||
- 捕获加密失败异常
|
||||
- 不要在错误信息中暴露敏感信息
|
||||
|
||||
### 浏览器兼容性
|
||||
|
||||
使用 Web Crypto API,支持以下浏览器:
|
||||
|
||||
- Chrome 37+
|
||||
- Firefox 34+
|
||||
- Safari 11+
|
||||
- Edge 79+
|
||||
|
||||
不支持 IE 浏览器。
|
||||
|
||||
### 与后端对接
|
||||
|
||||
#### 数据流程
|
||||
|
||||
```
|
||||
前端 ----[加密数据]----> Gateway -----> Auth Service
|
||||
(AES-256-GCM) (AES-256-GCM)
|
||||
[解密] → [BCrypt] → 数据库
|
||||
```
|
||||
|
||||
#### 示例对比
|
||||
|
||||
**前端加密**:
|
||||
```typescript
|
||||
const encrypted = await aes.encrypt('13812345678')
|
||||
// 输出:Base64([IV(12字节)][密文])
|
||||
```
|
||||
|
||||
**后端解密**:
|
||||
```java
|
||||
String decrypted = aesEncryptUtil.decrypt(encrypted)
|
||||
// 输出:'13812345678'
|
||||
```
|
||||
|
||||
### 故障排查
|
||||
|
||||
#### 1. "AES 密钥未初始化"
|
||||
|
||||
**原因**:未调用 `initAesEncrypt()`
|
||||
|
||||
**解决**:在 `main.ts` 中初始化
|
||||
```typescript
|
||||
await initAesEncrypt(AES_SECRET_KEY)
|
||||
```
|
||||
|
||||
#### 2. "AES 解密失败"
|
||||
|
||||
**原因**:前后端密钥不一致
|
||||
|
||||
**解决**:检查前后端密钥配置是否相同
|
||||
|
||||
#### 3. 类型错误
|
||||
|
||||
**原因**:TypeScript 类型问题
|
||||
|
||||
**解决**:确保使用最新版本的工具类
|
||||
|
||||
### 性能优化
|
||||
|
||||
1. **密钥复用**:使用单例模式,避免重复初始化
|
||||
2. **异步处理**:加密操作是异步的,注意使用 `await`
|
||||
3. **批量加密**:如需加密多个字段,可并行处理
|
||||
|
||||
```typescript
|
||||
// 并行加密
|
||||
const [encryptedPhone, encryptedPassword] = await Promise.all([
|
||||
aes.encryptPhone(phone),
|
||||
aes.encryptPassword(password)
|
||||
])
|
||||
```
|
||||
322
urbanLifelineWeb/packages/shared/src/utils/crypto/aes.ts
Normal file
322
urbanLifelineWeb/packages/shared/src/utils/crypto/aes.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* AES-256-GCM 加密工具(兼容 H5/小程序/App)
|
||||
* 与后端 AesEncryptUtil 保持一致
|
||||
*
|
||||
* 使用场景:
|
||||
* - 密码传输前加密
|
||||
* - 敏感信息(手机号、身份证号)加密
|
||||
*
|
||||
* @author yslg
|
||||
* @since 2025-12-10
|
||||
*/
|
||||
|
||||
/**
|
||||
* AES 加密配置
|
||||
*/
|
||||
interface AesConfig {
|
||||
algorithm: string
|
||||
keySize: number
|
||||
ivLength: number
|
||||
tagLength: number
|
||||
}
|
||||
|
||||
const AES_CONFIG: AesConfig = {
|
||||
algorithm: 'AES-GCM',
|
||||
keySize: 256,
|
||||
ivLength: 12, // GCM 推荐 IV 长度
|
||||
tagLength: 128 // GCM 认证标签长度
|
||||
}
|
||||
|
||||
const getCrypto = (): Crypto => {
|
||||
return window.crypto
|
||||
}
|
||||
|
||||
/**
|
||||
* AES 加密工具类
|
||||
*/
|
||||
export class AesEncryptUtil {
|
||||
private secretKey: CryptoKey | null = null
|
||||
private secretKeyString: string
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param secretKeyString Base64 编码的密钥(32字节,AES-256)
|
||||
*/
|
||||
constructor(secretKeyString: string) {
|
||||
this.secretKeyString = secretKeyString
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化密钥(异步)
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
if (!this.secretKeyString) {
|
||||
throw new Error('AES 密钥未配置')
|
||||
}
|
||||
|
||||
try {
|
||||
// Base64 解码密钥
|
||||
const keyData = this.base64ToArrayBuffer(this.secretKeyString)
|
||||
|
||||
// 校验密钥长度(AES-256 必须是 32 字节)
|
||||
if (keyData.byteLength !== 32) {
|
||||
throw new Error(`AES 密钥长度错误:需32字节,实际${keyData.byteLength}字节`)
|
||||
}
|
||||
|
||||
// 导入密钥(跨端兼容)
|
||||
this.secretKey = await getCrypto().subtle.importKey(
|
||||
'raw',
|
||||
keyData,
|
||||
{ name: AES_CONFIG.algorithm },
|
||||
false,
|
||||
['encrypt', 'decrypt']
|
||||
)
|
||||
} catch (error) {
|
||||
throw new Error(`AES 密钥初始化失败: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密字符串
|
||||
* @param plaintext 明文
|
||||
* @returns Base64 编码的密文(包含 IV)
|
||||
*/
|
||||
async encrypt(plaintext: string): Promise<string> {
|
||||
if (!plaintext) {
|
||||
return plaintext
|
||||
}
|
||||
|
||||
if (!this.secretKey) {
|
||||
throw new Error('AES 密钥未初始化,请先调用 init()')
|
||||
}
|
||||
|
||||
try {
|
||||
// 生成随机 IV(跨端兼容)
|
||||
const iv = getCrypto().getRandomValues(new Uint8Array(AES_CONFIG.ivLength))
|
||||
|
||||
// 将明文转为 ArrayBuffer
|
||||
const encoder = new TextEncoder()
|
||||
const data = encoder.encode(plaintext)
|
||||
|
||||
// 加密
|
||||
const ciphertext = await getCrypto().subtle.encrypt(
|
||||
{
|
||||
name: AES_CONFIG.algorithm,
|
||||
iv: iv,
|
||||
tagLength: AES_CONFIG.tagLength
|
||||
},
|
||||
this.secretKey,
|
||||
data
|
||||
)
|
||||
|
||||
// 将 IV 和密文组合:[IV(12字节)][密文]
|
||||
const combined = new Uint8Array(iv.length + ciphertext.byteLength)
|
||||
combined.set(iv, 0)
|
||||
combined.set(new Uint8Array(ciphertext), iv.length)
|
||||
|
||||
// Base64 编码
|
||||
return this.arrayBufferToBase64(combined)
|
||||
} catch (error) {
|
||||
throw new Error(`AES 加密失败: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密字符串
|
||||
* @param ciphertext Base64 编码的密文(包含 IV)
|
||||
* @returns 明文
|
||||
*/
|
||||
async decrypt(ciphertext: string): Promise<string> {
|
||||
if (!ciphertext) {
|
||||
return ciphertext
|
||||
}
|
||||
|
||||
if (!this.secretKey) {
|
||||
throw new Error('AES 密钥未初始化,请先调用 init()')
|
||||
}
|
||||
|
||||
try {
|
||||
// Base64 解码
|
||||
const combinedBuffer = this.base64ToArrayBuffer(ciphertext)
|
||||
const combined = new Uint8Array(combinedBuffer)
|
||||
|
||||
// 校验 IV 长度
|
||||
if (combined.length < AES_CONFIG.ivLength) {
|
||||
throw new Error('密文格式错误:IV 长度不足')
|
||||
}
|
||||
|
||||
// 提取 IV 和密文
|
||||
const iv = combined.slice(0, AES_CONFIG.ivLength)
|
||||
const data = combined.slice(AES_CONFIG.ivLength)
|
||||
|
||||
// 解密
|
||||
const plaintext = await getCrypto().subtle.decrypt(
|
||||
{
|
||||
name: AES_CONFIG.algorithm,
|
||||
iv: iv,
|
||||
tagLength: AES_CONFIG.tagLength
|
||||
},
|
||||
this.secretKey,
|
||||
data
|
||||
)
|
||||
|
||||
// 将 ArrayBuffer 转为字符串
|
||||
const decoder = new TextDecoder()
|
||||
return decoder.decode(plaintext)
|
||||
} catch (error) {
|
||||
throw new Error(`AES 解密失败: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密密码(用于登录等场景)
|
||||
*/
|
||||
async encryptPassword(password: string): Promise<string> {
|
||||
return this.encrypt(password)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密手机号
|
||||
*/
|
||||
async encryptPhone(phone: string): Promise<string> {
|
||||
return this.encrypt(phone)
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密手机号
|
||||
*/
|
||||
async decryptPhone(encryptedPhone: string): Promise<string> {
|
||||
return this.decrypt(encryptedPhone)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密身份证号
|
||||
*/
|
||||
async encryptIdCard(idCard: string): Promise<string> {
|
||||
return this.encrypt(idCard)
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密身份证号
|
||||
*/
|
||||
async decryptIdCard(encryptedIdCard: string): Promise<string> {
|
||||
return this.decrypt(encryptedIdCard)
|
||||
}
|
||||
|
||||
// ============ 工具方法 ============
|
||||
|
||||
/**
|
||||
* Base64 转 ArrayBuffer
|
||||
*/
|
||||
private base64ToArrayBuffer(base64: string): ArrayBuffer {
|
||||
// 处理 Base64 填充字符
|
||||
base64 = base64.replace(/-/g, '+').replace(/_/g, '/')
|
||||
const padLength = (4 - (base64.length % 4)) % 4
|
||||
base64 += '='.repeat(padLength)
|
||||
|
||||
const binaryString = atob(base64)
|
||||
const bytes = new Uint8Array(binaryString.length)
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i)
|
||||
}
|
||||
return bytes.buffer
|
||||
}
|
||||
|
||||
/**
|
||||
* ArrayBuffer 转 Base64
|
||||
*/
|
||||
private arrayBufferToBase64(buffer: Uint8Array): string {
|
||||
let binary = ''
|
||||
const len = buffer.byteLength
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(buffer[i])
|
||||
}
|
||||
return btoa(binary)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态工具方法
|
||||
*/
|
||||
export class AesUtils {
|
||||
/**
|
||||
* 脱敏显示手机号
|
||||
* 例如:13812345678 -> 138****5678
|
||||
*/
|
||||
static maskPhone(phone: string): string {
|
||||
if (!phone || phone.length < 11) {
|
||||
return phone
|
||||
}
|
||||
return phone.substring(0, 3) + '****' + phone.substring(7)
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏显示身份证号
|
||||
* 例如:110101199001011234 -> 110101********1234
|
||||
*/
|
||||
static maskIdCard(idCard: string): string {
|
||||
if (!idCard || idCard.length < 18) {
|
||||
return idCard
|
||||
}
|
||||
return idCard.substring(0, 6) + '********' + idCard.substring(14)
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏显示邮箱
|
||||
* 例如:test@example.com -> t***@example.com
|
||||
*/
|
||||
static maskEmail(email: string): string {
|
||||
if (!email || !email.includes('@')) {
|
||||
return email
|
||||
}
|
||||
const [username, domain] = email.split('@')
|
||||
if (username.length <= 1) {
|
||||
return email
|
||||
}
|
||||
return username[0] + '***@' + domain
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 AES-256 Base64 密钥(工具方法,用于后端/测试)
|
||||
*/
|
||||
static generateAes256KeyBase64(): string {
|
||||
const crypto = getCrypto()
|
||||
const keyBytes = crypto.getRandomValues(new Uint8Array(32)) // 32字节=256位
|
||||
let binary = ''
|
||||
for (let i = 0; i < keyBytes.length; i++) {
|
||||
binary += String.fromCharCode(keyBytes[i])
|
||||
}
|
||||
return btoa(binary)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 AES 加密实例的工厂函数
|
||||
* @param secretKey Base64 编码的密钥(32字节)
|
||||
*/
|
||||
export async function createAesEncrypt(secretKey: string): Promise<AesEncryptUtil> {
|
||||
const aes = new AesEncryptUtil(secretKey)
|
||||
await aes.init()
|
||||
return aes
|
||||
}
|
||||
|
||||
// 导出单例(需要在应用启动时初始化)
|
||||
let aesInstance: AesEncryptUtil | null = null
|
||||
|
||||
/**
|
||||
* 获取 AES 加密实例
|
||||
*/
|
||||
export function getAesInstance(): AesEncryptUtil {
|
||||
if (!aesInstance) {
|
||||
throw new Error('AES 加密工具未初始化,请先调用 initAesEncrypt()')
|
||||
}
|
||||
return aesInstance
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 AES 加密工具(在应用启动时调用)
|
||||
* @param secretKey Base64 编码的密钥(32字节,与后端配置保持一致)
|
||||
*/
|
||||
export async function initAesEncrypt(secretKey: string): Promise<void> {
|
||||
aesInstance = await createAesEncrypt(secretKey)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './aes'
|
||||
@@ -3,3 +3,4 @@
|
||||
*/
|
||||
|
||||
export * from './file'
|
||||
export * from './crypto'
|
||||
|
||||
@@ -1,108 +1,111 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import { federation } from '@module-federation/vite'
|
||||
import { resolve, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
// ES 模块中获取 __dirname
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
/**
|
||||
* ES Module 构建配置
|
||||
* 用于 Import Maps 方案
|
||||
* Module Federation 构建配置(@module-federation/vite)
|
||||
* 官方维护版本,支持 Vite 6 + 开发模式热更新
|
||||
*
|
||||
* 策略:将 Vue、Element Plus 等依赖打包进共享模块
|
||||
* 业务应用只需引入 @shared/*,无需关心底层依赖
|
||||
* 优势:
|
||||
* - ✅ 完整支持 Vite 开发模式
|
||||
* - ✅ dev 模式能生成 remoteEntry.js
|
||||
* - ✅ 自动处理内部路径别名 (@/)
|
||||
* - ✅ 真正的生产可用版本
|
||||
*/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), vueJsx()],
|
||||
define: {
|
||||
__VUE_PROD_DEVTOOLS__: true,
|
||||
// __VUE_OPTIONS_API__: true, // 确保启用 Options API
|
||||
// __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: true
|
||||
plugins: [
|
||||
vue({
|
||||
script: {
|
||||
defineModel: true,
|
||||
propsDestructure: true
|
||||
}
|
||||
}),
|
||||
vueJsx(),
|
||||
federation({
|
||||
name: 'shared',
|
||||
filename: 'remoteEntry.js',
|
||||
// 暴露的模块
|
||||
exposes: {
|
||||
// 通用组件
|
||||
'./FileUpload': './src/components/fileupload/FileUpload.vue',
|
||||
'./DynamicFormItem': './src/components/dynamicFormItem/DynamicFormItem.vue',
|
||||
|
||||
// API 模块
|
||||
'./api': './src/api/index.ts',
|
||||
'./authAPI': './src/api/auth/auth.ts',
|
||||
'./fileAPI': './src/api/file/file.ts',
|
||||
|
||||
// Utils 模块
|
||||
'./utils': './src/utils/index.ts',
|
||||
|
||||
// Types 模块
|
||||
'./types': './src/types/index.ts',
|
||||
|
||||
// 整体导出
|
||||
'./components': './src/components/index.ts'
|
||||
},
|
||||
resolve: {
|
||||
alias: [
|
||||
{ find: '@', replacement: resolve(__dirname, 'src') }
|
||||
// 共享依赖(重要:避免重复加载)
|
||||
shared: {
|
||||
vue: {},
|
||||
'vue-router': {},
|
||||
'element-plus': {},
|
||||
'@element-plus/icons-vue': {},
|
||||
axios: {}
|
||||
}
|
||||
})
|
||||
],
|
||||
|
||||
define: {
|
||||
__VUE_OPTIONS_API__: true,
|
||||
__VUE_PROD_DEVTOOLS__: true,
|
||||
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: true
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src')
|
||||
},
|
||||
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
|
||||
},
|
||||
|
||||
build: {
|
||||
lib: {
|
||||
entry: {
|
||||
components: resolve(__dirname, 'src/components/index.ts'),
|
||||
utils: resolve(__dirname, 'src/utils/index.ts'),
|
||||
api: resolve(__dirname, 'src/api/index.ts'),
|
||||
composables: resolve(__dirname, 'src/composables/index.ts'),
|
||||
types: resolve(__dirname, 'src/types/index.ts')
|
||||
},
|
||||
formats: ['es'], // 仅构建 ES Module
|
||||
fileName: (format, entryName) => `${entryName}.js`
|
||||
},
|
||||
|
||||
rollupOptions: {
|
||||
// ⚠️ 不外部化依赖,将它们打包进共享模块
|
||||
// 这样业务应用只需引入 @shared/* 即可
|
||||
external: [],
|
||||
|
||||
output: {
|
||||
// 保持 ES Module 格式
|
||||
format: 'es',
|
||||
// 导出命名导出
|
||||
exports: 'named',
|
||||
// 生成 sourcemap
|
||||
sourcemap: true,
|
||||
// 分块策略:将大的依赖分离出来
|
||||
manualChunks(id) {
|
||||
// Vue 核心
|
||||
if (id.includes('node_modules/vue/') ||
|
||||
id.includes('node_modules/@vue/')) {
|
||||
return 'vue-core'
|
||||
}
|
||||
// Vue Router
|
||||
if (id.includes('node_modules/vue-router/')) {
|
||||
return 'vue-router'
|
||||
}
|
||||
// Pinia
|
||||
if (id.includes('node_modules/pinia/')) {
|
||||
return 'pinia'
|
||||
}
|
||||
// Element Plus
|
||||
if (id.includes('node_modules/element-plus/')) {
|
||||
return 'element-plus'
|
||||
}
|
||||
// VueUse
|
||||
if (id.includes('node_modules/@vueuse/')) {
|
||||
return 'vueuse'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 输出目录
|
||||
outDir: 'dist/esm',
|
||||
emptyOutDir: true,
|
||||
|
||||
// 目标浏览器
|
||||
target: 'esnext',
|
||||
|
||||
// 不压缩(开发环境)
|
||||
minify: false,
|
||||
|
||||
// 启用代码分割
|
||||
cssCodeSplit: true
|
||||
cssCodeSplit: false,
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
format: 'es'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 开发服务器配置
|
||||
|
||||
server: {
|
||||
port: 5000,
|
||||
strictPort: true,
|
||||
host: true,
|
||||
cors: true,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
||||
}
|
||||
},
|
||||
|
||||
preview: {
|
||||
port: 5000,
|
||||
host: true,
|
||||
cors: true,
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
||||
// 'Content-Type': 'application/javascript; charset=utf-8'
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,7 +8,21 @@ const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue(), vueJsx()],
|
||||
plugins: [
|
||||
vue({
|
||||
script: {
|
||||
defineModel: true,
|
||||
propsDestructure: true
|
||||
}
|
||||
}),
|
||||
vueJsx()
|
||||
],
|
||||
|
||||
define: {
|
||||
__VUE_OPTIONS_API__: true,
|
||||
__VUE_PROD_DEVTOOLS__: true,
|
||||
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: true
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
|
||||
@@ -13,4 +13,4 @@ echo 按 Ctrl+C 停止所有服务
|
||||
echo ====================================
|
||||
echo.
|
||||
|
||||
npm run dev:all
|
||||
pnpm run dev
|
||||
|
||||
Reference in New Issue
Block a user