# front **Repository Path**: WHLNoTenant/front ## Basic Information - **Project Name**: front - **Description**: No description available - **Primary Language**: HTML/CSS - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-29 - **Last Updated**: 2026-07-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # whl-admin (WHL.AI 前端) > WHL.AI 全栈业务平台 — 前端(vue-pure-admin 中后台模板) > > 后端:`../backend/`(.NET 10 + SqlSugar + WHL 框架 + 插件化) > 插件:`../whl.plugins/`(7 业务 plugin DLL) > 远端:gitee.com/whllong/whl.admin.front ## 简介 WHL.Admin 前端基于 vue-pure-admin 模板,**所有业务 plugin 的视图统一收口**到 `src/views/plugins//` 子目录,通过 `import.meta.glob` 自动发现 + 动态 addRoute 注入。 **核心特性**: - 7 大业务模块(sys / app / file / log / pluginmanager / platform / oauth)+ home / login - 完整 v-auth 按钮级权限(258 处 `authRequest` + 25 view 覆盖) - 鉴权 3 道闸:`v-auth` 指令 / `authRequest.isAuthCheck` opt-in / 后端 `JwtPermissionAttribute` 兜底 - 多端识别:`X-Device-Code` + `X-App-Code` header(2026-07-14) - 多租户兼容:既支持无 prefix 单租户,又支持 `/t/{slug}/` 显式 prefix(2026-07-16) - File plugin 全集成:头像 / 插件 / 日志 / **应用 Logo (FC_0005)** - GTable `@dataLoad` 事件批量反查 FileNo → URL - iframe 隐藏打印(替代 `window.open` 新 tab) ## 截图占位 | 模块 | 路径 | 说明 | |------|------|------| | Login | `docs/screenshots/login.png` | 8 主题 + 4 主色 + 验证码 + 记住密码 | | Home | `docs/screenshots/home.png` | 6 张统计卡 + 部署趋势图(接 `/platform/stats/*`) | | 分配租户 | `docs/screenshots/app-tenant.png` | 弹窗租户下拉 + 应用 logo 上传(FC_0005) | | OAuth 字典 | `docs/screenshots/oauth-dict.png` | WTree 1 级 + 树表联动 | ## 技术栈 | 类别 | 选型 | 版本 | |------|------|------| | 构建 | Vite | ^5.1.6 | | 语言 | TypeScript | ^5.4.3 | | 框架 | Vue | ^3.4.21 | | UI | Element Plus | ^2.6.1 | | 路由 | Vue Router | ^4.3.0 | | 状态 | Pinia | ^2.1.7 | | HTTP | Axios | ^1.6.8 | | TFM | .NET(后端)| 10.0 LTS | ## 启动 ```bash npm install npm run dev # 开发服 npm run build # 生产构建 npm run preview # 预览构建产物 npm run type-check # TS 类型检查 ``` 后端地址由 `public/static/appsetting.json` 的 `baseUrl` 字段控制,**dev/prod 一致**,无 vite proxy,无 `.env` 配后端地址。 ### 切换后端端口 ```bash # 编辑 public/static/appsetting.json { "baseUrl": "http://localhost:4000", # 改这里 "version": 2.0 } # 重启 vite dev / 刷新浏览器 ``` `appsetting.json` **不** 在 `.gitignore` 内,跟随代码提交,部署期修改即可切换后端地址。 ### 多租户前缀(2026-07-16 启用) 后端 `SaaSOption.Mode: "Both"` + `RewritePath: true`,支持两种 URL: - **无 prefix**(单租户/开发):`/platform/...` - **带 prefix**(多租户/SaaS):`/t/{slug}/platform/...` 两种都路由到同一 controller,前端 URL 写法不变(继续无 prefix),后端自动按 `DefaultTenant=master` fallback。 ## 目录结构 ``` src/ ├── api/ # 通用 API(占位,业务 API 全部收口到 view 同目录) ├── assets/ # 静态资源 ├── components/ │ ├── common/ # 通用组件(WTree / ImageCropper) │ ├── gTable/ # 通用表格 + 打印/导出(纯前端,无 print-js) │ ├── layout/ # Layout 子组件(Sidebar / Topbar / TagsView) │ ├── platform/ # 平台通用组件(Cache 诊断) │ └── regionPicker/ # 4 级行政区域选择器 ├── composables/ │ ├── useFilePlugin.ts # File plugin 集成(uploadImage / resolveUrl / resolveUrls) │ └── useDataDictionary*.ts # 数据字典(各 plugin 独立 composable) ├── directives/ │ └── vAuth.ts # v-auth 指令(el-button / el-link 用) ├── models/ │ ├── apiResult.ts # 通用 ApiResult 包装 │ └── sys/ # sys 域共享 model ├── router/ │ ├── index.ts # 主路由(static + 动态 addRoute) │ └── allowedPagesToRoutes.ts # allowedPages → RouteRecordRaw[] ├── stores/ │ ├── auth.ts # token + userInfo + allowedPages │ ├── layout.ts # 侧边栏折叠 / 多页签 │ ├── plugin.ts # 插件菜单 │ └── tenantInfo.ts # 当前租户信息 ├── utils/ │ ├── authRequest.ts # 统一 HTTP 入口(258 调用点) │ ├── request.ts # Axios 实例 + token 刷新 + X-Tenant + X-Device-Code │ ├── authCheck.ts # hasAuthRequest + createHasAuthFn │ ├── authRequestByEvent.ts # 事件总线版 authRequest │ ├── device.ts # X-Device-Code / X-App-Code 生成 │ ├── enumLabels.ts # 枚举 label 字典(plugin 域业务枚举) │ ├── theme.ts # 8 主题 + 4 主色 │ ├── loadSetting.ts # appsetting.json 加载 │ └── buildRoutesFromAllowedPages.ts └── views/ ├── home/ # 首页(6 张卡 + 趋势图) ├── login/ # 登录 / 注册 / 忘记密码 ├── layout/ # Layout 模板 ├── error/ # 404 / 401 整页 ├── sys/ # Sys 域 ├── log/ # Log 域 ├── plugins/ │ ├── app/ # App plugin │ ├── platform/ # Platform plugin │ ├── file/ # File plugin │ ├── oauth/ # OAuth plugin(2026-07-15 完整实现) │ └── pluginmanager/ # PluginManager plugin └── preview/ # 原型预览(不 commit) ``` ## 已迁移模块 | 插件 | 视图 | 后端 plugin | 关键功能 | |------|------|------------|----------| | Sys | user / role / permission / datadictionary / page / codingrule | `whl.plugins/Sys/` | RBAC + 菜单同步 | | App | application / server / serverdeployment / database / tenantapp / scriptrun | `whl.plugins/App/` | v0.5 部署 + 分配租户 + Logo 上传 | | Platform | tenant / codingrule / dbtype / region / cache | `whl.plugins/Platform/` | Stats 仪表盘 + DB 类型 enum | | File | info / category / contenttype / physical | `whl.plugins/File/` | 物理浏览 + FC_0005 应用 Logo | | OAuth | client / provider / server / thirdpartyuser / providertenant / datadictionary | `whl.plugins/OAuth/` | 8 套字典 + 独立表 | | PluginManager | info / loadlog / update | `whl.plugins/PluginManager/` | 版本管理 + 升级 | | Log | sqllog / httplog | `whl.plugins/Log/` | 日志查询 | ## 鉴权三道闸(2026-07-06 收口) ``` ┌─────────────────────────────────────────────────────────┐ │ ① v-auth 指令(el-button / el-link) │ │ 受 authCheckDisabled 开关控制(开发态可临时关) │ │ 通过则按钮 / 链接显示 │ ├─────────────────────────────────────────────────────────┤ │ ② authRequest.isAuthCheck = true(opt-in URL 预检) │ │ 不受开关影响,显式 opt-in 永远生效 │ │ 命中 allowedPages 放行;未命中 throw 静默拦截 │ ├─────────────────────────────────────────────────────────┤ │ ③ 后端 JwtPermissionAttribute(终极兜底) │ │ 3 字段合并:IsAuthCheck 替代老 AllowAnonymous/IsIgnore │ │ DB 存 RouteUrl 无 prefix,匹配 Request.Path(改写后) │ └─────────────────────────────────────────────────────────┘ ``` **`v-auth` vs `v-if + hasAuthFn`**: | 元素 | 推荐 | 原因 | |------|------|------| | `el-button` / `el-link` / 原生 | `v-auth="{url, method}"` | 指令靠 `el.style.display='none'` 隐藏 | | `el-dropdown-item` / popper 浮层 | `v-if="hasAuthFn(url)"` | Fragment 组件,指令不生效 | | `el-menu-item` | 同上 | popper 浮层 | 工厂:`const hasAuthFn = createHasAuthFn(auth.user as any)` ## 多端识别(2026-07-14) 每个请求自动加 2 个 header: - `X-Device-Code`:UUID v4,存 localStorage,**设备级稳定** - `X-App-Code`:`VITE_APP_CODE` 编译时确定,子项目级(admin / mobile / portal) 后端 `HttpRequestMiddleware` 读这俩 header,做 `IsNewDevice` / 多端策略判定。 ## Login 流 - 8 主题 + 4 主色,从 `theme.ts` 单一数据源派生 - 图形验证码(看后端 `EnableCaptcha` 开关) - 记住密码 7 天 - 登录前调 `/platform/tenant/findAllActiveProxy?enabled=true` 拉租户列表 ## 头像 / Logo 接 File plugin | CategoryCode | 用途 | |--------------|------| | `FC_0001` | 头像(已有) | | `FC_0002` | 插件(已有) | | `FC_0003` | 日志(已有) | | `FC_0004` | (跳号预留) | | `FC_0005` | **应用 Logo**(2026-07-15 新增) | 使用方式(`composables/useFilePlugin.ts`): ```ts const { uploadImage, resolveUrl, resolveUrls } = useFilePlugin() // 上传 const fileNo = await uploadImage(file, 'FC_0005') // 列表批量反查 const urlMap = await resolveUrls([fileNo1, fileNo2]) // 渲染 ``` ## GTable `@dataLoad` 事件 ```vue ``` - `@refresh`:无参,只触发刷新 - `@dataLoad="onGridDataLoad(data, total)"`:带 `(data, total)`,**批量反查 FileNo 用这个** ## 打印(2026-07-16 改造) 不再 `window.open` 新 tab,改用**隐藏 iframe**: - `iframe` 0×0 挂 `document.body`,`visibility:hidden` - 在 iframe 内 `document.write` HTML,`contentWindow.print()` - 打印完 1s 后 `iframe.remove()` ✅ 不开新页面,浏览器拦截弹窗的 `ElMessage.error` 也不需要了 ## 开发规范 - **业务 API 收口** 放 view 同目录(`xxxModel.ts` + `useXxx.ts`) - **view 目录名 = 后端 controller 名 lowercase**(`file/info` ↔ `FileInfoController`) - **type-check**: 提交前跑 `npm run type-check` 确保 0 新错 - **Git 提交**: `(): ` 中文描述 ## License Private — WHL.Admin 内部项目