init
199
.cursor/rules/admin-framework-routing-store.mdc
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
---
|
||||||
|
description: Admin Framework 动态路由与 Vuex 使用约束(详尽版,对照 admin_core 源码)
|
||||||
|
globs: admin/src/**/*.{vue,js},admin_core/src/**/*.{vue,js}
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Admin 路由与状态约束(详尽)
|
||||||
|
|
||||||
|
适用于 `admin` 端动态菜单、路由与 Vuex;**实现以 `admin_core` 源码为准**,模板目录 `admin/src` 与之对齐。涉及文件主要包括:`src/index.js`、`src/router/index.js`、`src/utils/uiTool.js`、`src/utils/http.js`、`src/store/user.js`、`src/store/app.js`、`src/store/index.js`、`src/views/index.js`(`setupComponentMap`)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. `createApp` 生命周期与单例
|
||||||
|
|
||||||
|
### 1.1 执行顺序(与 `index.js` 中 `createApp` 一致)
|
||||||
|
|
||||||
|
1. 若未传 `uploadUrl` 且存在 `apiUrl`:按 `apiUrl` 是否以 `/` 结尾拼接 `upload` 或 `/upload`。
|
||||||
|
2. 若 `config.HomePage` 存在:写入框架实例 `this.HomePage`。
|
||||||
|
3. `this.config = config`。
|
||||||
|
4. `Vue.use(ViewUI)`、`Vue.use(VueRouter)`、`Vue.use(Vuex)`。
|
||||||
|
5. 原型注入:`$config`、`$http`、`$tools`、`$uiTool`、`$framework`。
|
||||||
|
6. `registerGlobalComponents(Vue)`:注册布局、表格、上传等全局组件。
|
||||||
|
7. `setupComponentMap(config.componentMap || {}, uiTool)`:合并内置系统页映射与业务映射(见第 3 节)。
|
||||||
|
8. **Store**:若 `!this.store`,则 `createStore(Vuex, {}, null)` 并 `http.init(config, this.store)`;若已有 `store` 则**不重建**、不重复 `http.init`。
|
||||||
|
9. **Router**:若 `!this.router`,则 `getRoutes` + `createRouter`(hash 模式)并挂守卫;若已有 `router` 则**不重建**。
|
||||||
|
10. 根 Vue:`render: h => h('router-view')`,`created` 内:`uiTool.setRem()`、`resize` 监听;若有 `token` + `localStorage.authorityMenus` 则 `dispatch('user/setAuthorityMenus', { Main, ParentView, Page404, HomePage, authorityMenus })` 再 `dispatch('app/getSysTitle', { defaultTitle, defaultLogo })`;否则 `document.title = config.title`;最后调用 `config.onReady`(若存在)。
|
||||||
|
|
||||||
|
### 1.2 单例与全局
|
||||||
|
|
||||||
|
- 默认导出为**同一** `AdminFramework` 实例;`createApp` 前后均可能在浏览器挂载 `window.framework`。
|
||||||
|
- **`store` / `router` 一旦创建即复用**:业务不得在页面里 `new Vuex.Store` 或 `new VueRouter` 替换框架主链路。
|
||||||
|
- `window.framework`、`window.rootVue` **仅供调试**;业务代码不得依赖其存在(SSR、单测、微前端场景可能无 `window` 或未挂载)。
|
||||||
|
|
||||||
|
### 1.3 根实例恢复菜单与标题
|
||||||
|
|
||||||
|
- 刷新后:读 `this.$store.state.user.token` 与 `localStorage.getItem('authorityMenus')`;二者齐全才恢复动态路由并拉标题。
|
||||||
|
- `authorityMenus` 在 `user` 模块里经 `setAuthorityMenus` mutation 写入 **`localStorage.authorityMenus`**(值为 **JSON 字符串**)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 路由模式、基础路由与守卫
|
||||||
|
|
||||||
|
### 2.1 模式与基础表
|
||||||
|
|
||||||
|
- `createRouter` 使用 **`mode: 'hash'`**(`router/index.js`)。
|
||||||
|
- `createBaseRoutes`:`/login`、`/401`、`/404`、`/500`、通配 `*` → 404。
|
||||||
|
|
||||||
|
### 2.2 `setupRouterGuards` 行为摘要
|
||||||
|
|
||||||
|
- 每次导航:`ViewUI.LoadingBar.start()`;`afterEach`:`LoadingBar.finish()` + `window.scrollTo(0,0)`。
|
||||||
|
- **`name === 'view_log'`**:直接 `next()`,不做登录校验(操作日志类独立入口)。
|
||||||
|
- **无 token 且目标非 `login`** → `next({ name: 'login' })`。
|
||||||
|
- **无 token 且目标为 `login`** → `next()`。
|
||||||
|
- **有 token 且目标为 `login`**:若 `from.name === 'home'` 则 `next(false)` 避免重复导航;否则 `next({ name: 'home' })`。
|
||||||
|
- **有 token、`to.matched.length === 0` 且 `to.path !== '/'`**:认为可能动态路由尚未就绪,**150ms** 后 `router.resolve` 再试;仍无匹配则 `next({ name: 'home', replace: true })`。
|
||||||
|
|
||||||
|
### 2.3 动态主路由与 `redirect`
|
||||||
|
|
||||||
|
- 初始 `customRoutes` 来自 `getRoutes`,内部为 `uiTool.getRoutes(Main, ParentView, Page404, HomePage)`,得到 `path: '/'` 的 **主路由** 对象(含 `children`、`redirect`)。
|
||||||
|
- `setAuthorityMenus` 成功生成 `mainMenu` 后:在 `router.options.routes` 中 **删除** 原 `path === '/'` 的项,再 **`router.addRoute(mainMenu)`**;并 `await` 约 **100ms** 等待路由表更新。
|
||||||
|
- **`mainMenu.redirect`**:`getRoutes` 内若存在权限菜单,会 `findFirstRoute`:优先带 `/home` 或 `home` 的项;否则取第一个非「菜单」类型且有 `path` 的节点(菜单类型会递归子节点)。用于登录后首屏落地路径。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 菜单数据、类型与组件映射
|
||||||
|
|
||||||
|
### 3.1 菜单项 `type` 与 `menuToRoute`(`uiTool.js`)
|
||||||
|
|
||||||
|
| `type` | 行为 |
|
||||||
|
|--------|------|
|
||||||
|
| `菜单` | `component` 固定为 **`ParentView`**(布局容器) |
|
||||||
|
| `页面` / `功能` | 用 `item.component` 调 **`uiTool.getComponent`**;命中则替换为真实组件;未命中则渲染占位 `Alert`(提示路径并保留 `item.componentPath`);`catch` 时用 **`Page404`** |
|
||||||
|
| 其他 | 视为 `ParentView` |
|
||||||
|
| 任意 | 设置 `meta`:`icon`、`isMenu`、`type`、`title`(来自 `name`);有 `children` 则递归 |
|
||||||
|
|
||||||
|
### 3.2 `getComponent` 与 `setupComponentMap`
|
||||||
|
|
||||||
|
- `getComponent`:去掉路径尾 `.vue` 后与表内键匹配;也支持直接用带 `.vue` 的键。
|
||||||
|
- `setupComponentMap`:先合并默认 `home/index` 与系统页,再展开 **`customMap`**;对每个键生成 **`cleanPath`** 与 **`cleanPath + '.vue'`** 两条映射指向同一组件。
|
||||||
|
- 后端菜单 **`component` 字符串必须与映射键一致**(或仅差 `.vue`,由双键覆盖)。
|
||||||
|
|
||||||
|
### 3.3 首页 `home` 与权限树合并(`getRoutes`)
|
||||||
|
|
||||||
|
- 默认子路由含 **`/home`**(`HomePage` 或内置欢迎 render)。
|
||||||
|
- 若 `localStorage.authorityMenus` 解析后非空:先 `transformTree` 再 `menuToRoute` 得 `curRoutes`。
|
||||||
|
- **`hasHomeInRoutes`**:任意层出现 `path === '/home'` 或 `'home'` 即视为权限自带首页。
|
||||||
|
- **有 home**:`mainRoute.children = curRoutes`(权限树完全替换子级),`redirect` 先置 `/home` 再由 `findFirstRoute` 覆盖。
|
||||||
|
- **无 home**:`mainRoute.children = [homeRoute, ...curRoutes]`,保证仍有框架默认首页。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Vuex:`user` 模块(鉴权、菜单、租户)
|
||||||
|
|
||||||
|
### 4.1 `state` 与持久化
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `token` | 初始 `getToken()`(Cookie `token`,见 `tools.js` 中 `TOKEN_KEY`) |
|
||||||
|
| `authorityMenus` | 内存;实际持久化由 mutation 写 **localStorage `authorityMenus`** |
|
||||||
|
| `menuList` | 默认从 **localStorage `menuList`** JSON 解析 |
|
||||||
|
| `currentTenant` | 登录租户;**localStorage `currentTenant`** |
|
||||||
|
| `userName` / `avatorImgPath` | 展示用;`userName` 可与 **localStorage.userName** 同步 |
|
||||||
|
|
||||||
|
### 4.2 `setAuthorityMenus` 入参(action)
|
||||||
|
|
||||||
|
- **`Main`, `ParentView`, `Page404`**:必填语义;用于 `uiTool.getRoutes` / 动态 `addRoute`。
|
||||||
|
- **`HomePage`**:可选;缺省时用 `window.framework.HomePage`(框架不推荐业务依赖 `window`,调用方应显式传入与 `createApp` 一致的 `HomePage`)。
|
||||||
|
- **`authorityMenus`**:可选;若传入则不再请求接口;可为 **已 JSON.stringify 的树** 或对象数组(内部会 `JSON.parse` 字符串)。
|
||||||
|
- **`menuIds`**:可选;仅在 **未传入 `authorityMenus` 且 `authorityMenus()` 接口失败** 时,用于从 **`defaultMenus`**(`menuConfig.js`)过滤。
|
||||||
|
|
||||||
|
### 4.3 `setAuthorityMenus` 数据流摘要
|
||||||
|
|
||||||
|
1. 无 `authorityMenus` → `userServer.authorityMenus()`;`code === 0` 取 `data`;失败则用 `menuIds` + `defaultMenus` 或全量 `defaultMenus`。
|
||||||
|
2. 字符串则 `JSON.parse`;失败或非法数组则回退 `defaultMenus`。
|
||||||
|
3. `commit('setAuthorityMenus', JSON.stringify(menus))`。
|
||||||
|
4. `mainMenu = uiTool.getRoutes(...)`;若有 `children`:`commit('setMenuList', mainMenu.children)`,再按第 2.3 节替换 `/` 路由。
|
||||||
|
|
||||||
|
### 4.4 `handleLogin`(与登录页配合)
|
||||||
|
|
||||||
|
- 期望 **`userServer.login`** 返回体:`{ code, message, data }`,且 `data` 含 **`token`**、**`user.name`**、可选 **`authorityMenus`**(菜单 ID 列表,字符串或数组)、可选 **`tenant`**。
|
||||||
|
- 成功后:`setUserName`、`setToken`、`setCurrentTenant`;解析 `menuIds`;`dispatch('setAuthorityMenus', { Main, ParentView, Page404, HomePage, menuIds })`;再 **`dispatch('app/getSysTitle', {}, { root: true })`**。
|
||||||
|
- 失败:抛错,由页面 `try/catch` 处理。
|
||||||
|
|
||||||
|
### 4.5 `handleLogOut`
|
||||||
|
|
||||||
|
- `setToken('')`、`setAuthorityMenus('[]')`、`setMenuList([])`、`setCurrentTenant(null)`、移除 **`menuList`** 的 localStorage、`location.reload()`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Vuex:`app` 模块(面包屑、站点标题)
|
||||||
|
|
||||||
|
### 5.1 `getSysTitle`(**命名空间 `app`**)
|
||||||
|
|
||||||
|
- 调用:**`this.$store.dispatch('app/getSysTitle', { defaultTitle, defaultLogo })`**;从其他 namespaced 模块内需 **`{ root: true }`**。
|
||||||
|
- 无 `defaultTitle` 时尝试 `window.framework.config.title`,再回退文案。
|
||||||
|
- 有 token:请求 **`paramSetupServer.getOne('sys_title')`**、**`getOne('sys_logo')`**(与参数表约定一致);更新 `document.title` 与 `commit('setSysTitle', formModel)`。
|
||||||
|
- 无 token:仅用默认标题写 `document.title`。
|
||||||
|
|
||||||
|
### 5.2 面包屑
|
||||||
|
|
||||||
|
- `setBreadCrumb` / `setHomeRoute` 由布局与路由变化驱动;业务侧改菜单后应通过 **`setAuthorityMenus`** 刷新路由与 `menuList`,而不是手写面包屑数组。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. `createStore` 扩展
|
||||||
|
|
||||||
|
```js
|
||||||
|
createStore(Vuex, customModules, createPersistedState)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **模块合并**:`{ user, app, ...customModules }`。
|
||||||
|
- **第三个参数**:传入 **`vuex-persistedstate` 工厂** 则启用 `localStorage` 插件;**`null`**(框架默认)表示不启用持久化插件。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. HTTP 与全局 Loading(`http.js`)
|
||||||
|
|
||||||
|
### 7.1 实例与 Token
|
||||||
|
|
||||||
|
- **`getHttpInstance`**:`baseURL = config.apiUrl`,合并 `timeout`(默认 300000);若存在 `store.state.user`,设置请求头 **`admin-token`** 为 **`store.state.user.token`**。
|
||||||
|
- Token 存 Cookie(`tools.setToken`),请求头由 HTTP 层统一加;**不要**在业务里手写 `admin-token`。
|
||||||
|
|
||||||
|
### 7.2 响应拦截
|
||||||
|
|
||||||
|
- **HTTP 200** 且 **`response.data.code === 0`**:resolve 整个 `response`(注意:`get`/`post` 的 Promise 里再取 **`response.data`** 作为业务返回值)。
|
||||||
|
- **`code !== 0`**:`hideLoad`、`Message.error(message)`、`reject(message)`(字符串)。
|
||||||
|
- **`401`**:`commit('user/setToken','')`;若存在 **`window.framework.router`** 则 `push({ path: '/login' })`。
|
||||||
|
- 其他网络错误:统一文案映射后 `showError`、`reject`。
|
||||||
|
|
||||||
|
### 7.3 各方法与 Loading
|
||||||
|
|
||||||
|
| 方法 | Loading |
|
||||||
|
|------|---------|
|
||||||
|
| `get` | 默认 `showLoad`;**`config.hideLoad === true`** 时不展示 |
|
||||||
|
| `post` | **始终** `showLoad`(无 `hideLoad` 分支) |
|
||||||
|
| `postFormData` | 无单独 `showLoad`/`hideLoad`(与 `post` 不同,实现上依赖实例 post 行为,以源码为准联调) |
|
||||||
|
| `fileExport` | 独立 `axios.post` + `blob`;默认 `is_down=true` 时调 **`window.framework.uiTool.downloadFile`** |
|
||||||
|
|
||||||
|
### 7.4 参数预处理 `formatParamete`
|
||||||
|
|
||||||
|
- 深拷贝后对**对象**递归删除值为 **`''`** 的键;`Date` 格式化为 **`YYYY-MM-DD HH:mm:ss`**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 组件复用优先级(与框架导出一致)
|
||||||
|
|
||||||
|
- 表单弹窗:**`editModal`**、**`AsyncModal`**;字段:**`FieldRenderer`**、**`fieldItem`**。
|
||||||
|
- 表格/树/上传:**`Tables`**、**`TreeGrid`**、**`UploadSingle`** / **`UploadMultiple`**。
|
||||||
|
- 布局:**`Main`**、**`ParentView`**;勿复制一套侧边栏 + 多页签逻辑。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 联调检查清单(路由 / Store)
|
||||||
|
|
||||||
|
- [ ] 菜单里「页面/功能」的 `component` 路径是否能在映射表命中?
|
||||||
|
- [ ] 改菜单后是否调用 **`user/setAuthorityMenus`**(并传入布局组件),而非只改 DB?
|
||||||
|
- [ ] 刷新后标题是否仍走 **`app/getSysTitle`**?
|
||||||
|
- [ ] 新增无感轮询是否用 **`get` + `hideLoad`**,而非 `post`?
|
||||||
|
- [ ] 是否避免在业务逻辑里依赖 **`window.framework`** 完成核心流程?
|
||||||
114
.cursor/rules/admin-framework-strict.mdc
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
---
|
||||||
|
description: Admin 前端 admin-framework 严格约束(详尽版,对照 admin_core)
|
||||||
|
globs: admin/src/**/*.{vue,js},admin_core/src/**/*.{vue,js}
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Admin Framework 严格约束(详尽)
|
||||||
|
|
||||||
|
本规则用于 `admin` / `admin_core` 业务页面与入口,**优先级高于建议类文档**。与 `admin_core` 中 `http.js`、`store/user.js`、`router/index.js`、`index.js` 行为冲突的写法一律禁止。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 禁止项(违反即应重构)
|
||||||
|
|
||||||
|
### 1.1 HTTP 与 axios
|
||||||
|
|
||||||
|
- **禁止**在业务 `vue/js` 中 `import axios` 或 `axios.create`,用于替代 `this.$http`(`http.js` 已集中处理拦截器、loading、`admin-token`)。
|
||||||
|
- **禁止**手工设置请求头 **`admin-token`**(或复制一套与 `getHttpInstance` 等价的拦截器);例外仅存在于框架内部的 **`fileExport`**(独立 `axios.post`),业务仍应调用 **`this.$http.fileExport`**。
|
||||||
|
- **禁止**在页面内维护第二套 **`apiUrl` / baseURL** 常量拼接 URL;统一 `createApp({ apiUrl })` 与 **`this.$http`**。
|
||||||
|
|
||||||
|
### 1.2 与框架重复的基础设施
|
||||||
|
|
||||||
|
- **禁止**复制 **`formatParamete`**(去空串、日期格式化)、全局 Loading DOM(`spin-box-one`)、401 跳转登录等与 **`http`** 重复的逻辑。
|
||||||
|
- **禁止**在已有 **`editModal` / `AsyncModal` / `FieldRenderer`** 能满足需求时,再写一套通用弹窗/动态表单渲染(除非产品明确要求完全不同的交互且与框架无关)。
|
||||||
|
|
||||||
|
### 1.3 路由与菜单
|
||||||
|
|
||||||
|
- **禁止**直接写 **`localStorage.authorityMenus`**、**`localStorage.menuList`** 替代 **`user` 模块 mutation/action**(除非你在做迁移脚本且不在运行时页面)。
|
||||||
|
- **禁止**绕过 **`setAuthorityMenus`** 直接 **`router.addRoutes`** 拼装主布局(会与框架内 **`router.options.routes` splice + addRoute** 逻辑冲突)。
|
||||||
|
|
||||||
|
### 1.4 命名与兼容
|
||||||
|
|
||||||
|
- **禁止**接口/表单字段 **`snake_case` 与 `camelCase` 双读兜底**(如 `row.user_id ?? row.userId`),除非后端合同明文双写且规范文档特许。
|
||||||
|
- **禁止**对框架已约定 API 的 **`ref`** 写 **`typeof ref.show === 'function'`**、**`if (!this.$refs.modal) return`** 等防御式分支(规范用法下 ref 由模板绑定保证;若条件渲染导致 ref 不稳定,应调整 `v-if` 与调用时机,而非堆兼容分支)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 必须项(代码审查硬标准)
|
||||||
|
|
||||||
|
### 2.1 请求与错误
|
||||||
|
|
||||||
|
- **必须**使用 **`this.$http`**(组件)或 **`AdminFramework.http`**(非组件);二者指向同一封装实例。
|
||||||
|
- **必须**对 `await this.$http.*` 使用 **`try/catch`**(或等价的 `.catch`);原因:后端 **`code !== 0`**、网络错误、401 均可能 **reject**;拦截器已 `Message.error` 时,catch 内可避免重复弹窗,但**不可**空 catch 吞掉需上报的逻辑。
|
||||||
|
- **必须**对**写库、改状态**类接口优先使用 **`POST`**,路径 **`snake_case`** 分段(与后端控制器风格一致)。
|
||||||
|
|
||||||
|
### 2.2 Loading 策略
|
||||||
|
|
||||||
|
- **必须**用 **`this.$http.get(url, params, { hideLoad: true })`** 实现无感轮询/预加载;**不得**为绕过 `post` 的强制 loading 而引入 axios。
|
||||||
|
- 若业务确需「POST 且无 loading」,**正确做法**是扩展 **`admin_core` 的 `http.post`**(例如增加可选 `config.hideLoad`)并升版框架,**禁止**仅在业务仓库私改 axios。
|
||||||
|
|
||||||
|
### 2.3 动态路由与 `componentMap`
|
||||||
|
|
||||||
|
- **必须**保证后端菜单「页面/功能」行的 **`component`** 在 **`setupComponentMap` / `addComponentMap`** 合并后的表可解析。
|
||||||
|
- **必须**在新菜单页合并前本地验证:**`uiTool.getComponent(菜单路径)`**(或通过实际登录 + 点击菜单)无「组件未找到」占位。
|
||||||
|
- 映射键:**建议**与菜单字符串完全一致;框架虽自动补 **无 `.vue` / 有 `.vue`** 双键,但**路径前缀、大小写、子目录**仍须一致。
|
||||||
|
|
||||||
|
### 2.4 刷新菜单与标题
|
||||||
|
|
||||||
|
- 菜单 CRUD 后刷新侧栏:**必须** `dispatch('user/setAuthorityMenus', { Main, ParentView, Page404, HomePage })`(参数与 **`sys_menu.vue`** 等处一致,使用 **`this.$framework.components`** 与 **`this.$framework.pages`** / **`HomePage`**)。
|
||||||
|
- 修改站点标题/Logo 后:**必须** `dispatch('app/getSysTitle', { defaultTitle: this.$config.title, defaultLogo: '' })`(**带 `app/` 命名空间**)。
|
||||||
|
|
||||||
|
### 2.5 框架组件 `ref` 调用
|
||||||
|
|
||||||
|
- **`AsyncModal`、`FloatPanel`、`editModal`**:**直接** `this.$refs.xxx.show()` / `hide()` / 文档约定方法;**禁止**多套 hide 回退、反射内部 state。
|
||||||
|
- 若组件为 `v-if` 控制,**必须**在 **`$nextTick`** 后再调 `ref` 方法,而不是写「若不存在则静默 return」掩盖时序 bug。
|
||||||
|
|
||||||
|
### 2.6 子组件内聚
|
||||||
|
|
||||||
|
- 表单 **`data` / `rules` / `loading` / `visible`**、校验、重置:**必须**放在子组件内;父组件只传 **`id`**、**只读上下文**、**`@success`** 等;**禁止**父组件 `v-model` 绑十几项内部字段。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. `createApp` 入口(硬约束)
|
||||||
|
|
||||||
|
- **必须** `AdminFramework.createApp(config)` 得到根实例后再 **`$mount`**。
|
||||||
|
- **`config.apiUrl` 必填**;`title`、`componentMap`、`HomePage`、`onReady` 按项目需要。
|
||||||
|
- **不得**在业务页重复 `Vue.use(VueRouter)` / `Vuex` / `ViewUI` 以替换框架已注册插件(二次 `createApp` 也不会卸载已 `Vue.use` 的插件,易脏状态)。
|
||||||
|
|
||||||
|
### 3.1 运行时补映射
|
||||||
|
|
||||||
|
- **必须**使用 **`AdminFramework.addComponentMap(map)`**;**禁止**直接改 **`uiTool`** 内部 `componentMap` 对象引用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Token 与 Cookie(与 `tools.js` 一致)
|
||||||
|
|
||||||
|
- Cookie 键 **`TOKEN_KEY === 'token'`**(不是 `admin-token`);**HTTP 请求头**才是 **`admin-token`**,值来自 **`store.state.user.token`**。
|
||||||
|
- 登录/登出须走 **`user`** 模块的 **`setToken` / `handleLogOut`**,保证 Cookie 与 store 一致。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 反例与正例(摘要)
|
||||||
|
|
||||||
|
| 反例 | 正例 |
|
||||||
|
|------|------|
|
||||||
|
| `import axios from 'axios'; axios.get(...)` | `await this.$http.get(...)` |
|
||||||
|
| `headers: { 'admin-token': xxx }` | 使用 `this.$http`,token 由 store 注入 |
|
||||||
|
| `dispatch('getSysTitle')` | `dispatch('app/getSysTitle', { ... })` |
|
||||||
|
| `post` 轮询且无 loading | `get` + `{ hideLoad: true }` 或扩展框架 |
|
||||||
|
| `receiving_id ?? receivingId` | 只使用合同字段名(如 `receiving_id`) |
|
||||||
|
| `if (this.$refs.m && this.$refs.m.show) this.$refs.m.show()` | `this.$nextTick(() => this.$refs.m.show())` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 提交前自检(详尽清单)
|
||||||
|
|
||||||
|
- [ ] 全文搜索 **`import axios`** / **`axios.create`**(业务目录应为 0)。
|
||||||
|
- [ ] 全文搜索 **`admin-token`**(除注释外应为 0,框架内 `fileExport` 除外若复制代码)。
|
||||||
|
- [ ] 变更接口是否 **`POST`** + **`snake_case`** URL。
|
||||||
|
- [ ] 新页面是否已写入 **`componentMap`**(或运行时 **`addComponentMap`**)。
|
||||||
|
- [ ] 标题刷新是否 **`app/getSysTitle`**。
|
||||||
|
- [ ] 所有 **`await this.$http`** 是否处于 **`try/catch`** 或链式 **`catch`**。
|
||||||
|
- [ ] 是否存在 **`??` 双字段名**、**`typeof === 'function'` ref 防御**(应删除或改为规范时序)。
|
||||||
|
- [ ] 子组件是否仍接收大量「仅表单内部使用」的 props(应内聚)。
|
||||||
290
.cursor/rules/admin-framework-usage.mdc
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
---
|
||||||
|
description: Admin 前端 admin-framework 使用约定(详尽版,对照 admin_core 与 README)
|
||||||
|
globs: admin/src/**/*.{vue,js},admin_core/src/**/*.{vue,js}
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Admin Framework 使用约定(详尽)
|
||||||
|
|
||||||
|
适用于 **Vue 2 + Vue Router 3 + Vuex 3 + View Design** 的后台;约定与 **`admin_core` 源码**、根目录 **`README.md`**、分发 **`admin-framework.md`** 一致。默认构建产物为 **`dist/admin-framework.js`(UMD)**,浏览器全局为 **`window.AdminFramework`** / **`window.framework`**(单例)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 依赖与引入
|
||||||
|
|
||||||
|
### 1.1 Peer 依赖(须由宿主安装)
|
||||||
|
|
||||||
|
`vue`、`vue-router`、`vuex`、`view-design`、`axios` 等须满足 **`package.json` 中 peerDependencies**;打包时将框架作 externals 或正常解析均可,须与 UMD externals 一致。
|
||||||
|
|
||||||
|
### 1.2 入口最小示例
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import AdminFramework from 'admin-framework' // 或 dist 路径
|
||||||
|
import componentMap from './router/component-map.js'
|
||||||
|
|
||||||
|
const app = AdminFramework.createApp({
|
||||||
|
title: '某某管理系统',
|
||||||
|
apiUrl: 'https://api.example.com/admin_api/',
|
||||||
|
componentMap,
|
||||||
|
HomePage: null, // 可选:自定义首页组件
|
||||||
|
onReady() {
|
||||||
|
// this 为根 Vue 实例
|
||||||
|
}
|
||||||
|
})
|
||||||
|
app.$mount('#app')
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. `createApp(config)` 配置项
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `apiUrl` | string | **必填**;`http` 的 `baseURL` |
|
||||||
|
| `uploadUrl` | string | 可选;不传则按 `apiUrl` 自动拼 `upload` 或 `/upload` |
|
||||||
|
| `title` | string | 默认 `document.title`、未登录或 `getSysTitle` 失败时的标题基线 |
|
||||||
|
| `componentMap` | object | 业务页组件路径 → 组件构造器;与菜单 `component` 字段对应 |
|
||||||
|
| `HomePage` | Component | 可选;覆盖默认欢迎首页 |
|
||||||
|
| `onReady` | function | 可选;根实例 `created` 末尾、`this` 指向根实例 |
|
||||||
|
|
||||||
|
**执行后**可用:`AdminFramework.store`、`AdminFramework.router`、`AdminFramework.config`、`AdminFramework.http`、`AdminFramework.uiTool`、`AdminFramework.tools` 等(与单例上字段一致)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 框架单例 API 速查(`index.js` / README)
|
||||||
|
|
||||||
|
| API | 用途 |
|
||||||
|
|-----|------|
|
||||||
|
| `createApp(config)` | 一键创建 store、router、根实例 |
|
||||||
|
| `addComponentMap(customMap)` | 运行时合并组件映射(内部 `setupComponentMap`) |
|
||||||
|
| `initHttp(config, store)` | 非 `createApp` 场景手动绑定 http 与 store |
|
||||||
|
| `version` | 版本字符串 |
|
||||||
|
| `pages` | 内置登录、错误页、部分系统页导出 |
|
||||||
|
| `components` | **Main**、**ParentView**、**Tables**、**AsyncModal**、**editModal**、**FieldRenderer** 等 |
|
||||||
|
| `systemApi` | `src/api/system` 聚合(框架自带系统接口封装) |
|
||||||
|
| `storeModules` | 默认 `user`/`app` 模块引用,扩展 store 时可参考 |
|
||||||
|
| `createBaseRoutes` / `setupRouterGuards` / `createRouter` / `getRoutes` | 高级自定义路由时使用 |
|
||||||
|
| `registerComponents` / `registerGlobalComponents` | 高级注册组件 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 全局注入(`createApp` 内)
|
||||||
|
|
||||||
|
- **`Vue.prototype.$config`**:`createApp` 的 config。
|
||||||
|
- **`$http`**、`$tools`**、`$uiTool`**、`$framework`**:与单例一致。
|
||||||
|
|
||||||
|
组件内推荐 **`this.$http`** / **`this.$uiTool`**;单元测试或非组件模块可用 **`AdminFramework.http`**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. HTTP 模块(`src/utils/http.js`)
|
||||||
|
|
||||||
|
### 5.1 成功约定
|
||||||
|
|
||||||
|
- 后端 JSON:**`code === 0`** 为业务成功;否则拦截器 **`Message.error`** 并 **reject**。
|
||||||
|
- `get`/`post` 返回的 Promise **resolve 值为 `response.data`**(即整包 `{ code, message, data }`),业务通常再判断 **`res.code === 0`** 后使用 **`res.data`**。
|
||||||
|
|
||||||
|
### 5.2 方法一览
|
||||||
|
|
||||||
|
| 方法 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `get(url, param, config)` | Query;**`config.hideLoad`** 为 true 时不显示全局 loading |
|
||||||
|
| `post(url, param, config)` | JSON body;**始终**显示 loading |
|
||||||
|
| `postFormData(url, data)` | `application/x-www-form-urlencoded` |
|
||||||
|
| `fileExport(url, param, filename, is_down)` | `blob` 下载;默认 **`is_down=true`** 时内部 **`uiTool.downloadFile`** |
|
||||||
|
| `baseUrl()` | 当前 `apiUrl` |
|
||||||
|
| `ImgSrc(src)` | `baseUrl() + src` |
|
||||||
|
|
||||||
|
### 5.3 401 行为
|
||||||
|
|
||||||
|
- 清空 **`user.token`**;依赖 **`window.framework.router`** 跳转 **`/login`**(布局代码应避免在非浏览器环境假设 router 已挂 window)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. uiTool 模块(README §6 整段,`src/utils/uiTool.js`)
|
||||||
|
|
||||||
|
通过 **`this.$uiTool`** 或 **`AdminFramework.uiTool`** 使用(**类静态方法**)。
|
||||||
|
|
||||||
|
| 方法 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `setComponentMap(map)` | 合并组件映射表 |
|
||||||
|
| `getComponent(path)` | 按路径取组件,`path` 可带或不带 `.vue` |
|
||||||
|
| `downloadFile(res, fileName)` | Blob 下载 |
|
||||||
|
| `setRem()` | 根据屏宽设置根字体(自适应) |
|
||||||
|
| `getImgSrc(src)` | 图片地址(同 http 规则) |
|
||||||
|
| `getBtn(h, options)` | Render 函数里生成操作按钮组 |
|
||||||
|
| `getDropdown(h, items)` | 下拉更多菜单 |
|
||||||
|
| `delConfirm(callback)` | 删除确认框 |
|
||||||
|
| `showConfirm({ title, content }, callback)` | 通用确认框 |
|
||||||
|
| `transformTree(list, cb)` | 扁平列表转树 |
|
||||||
|
| `subTree` / `menuToRoute` / `getRoutes` | 菜单转路由(框架内部与权限菜单配合) |
|
||||||
|
|
||||||
|
**树表示例:**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const tree = this.$uiTool.transformTree(flatListFromApi)
|
||||||
|
```
|
||||||
|
|
||||||
|
**与路由、菜单的衔接**:`menuToRoute`、`getRoutes` 与动态权限、`componentMap` 的配合见 **`admin-framework-routing-store.mdc`**;`downloadFile` 与 **`this.$http.fileExport`** 配合见本文 §5。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6.2 通用工具 `tools`(README §6.2,`src/utils/tools.js`)
|
||||||
|
|
||||||
|
通过 **`this.$tools`** 使用(具体以源码为准)。
|
||||||
|
|
||||||
|
常用包括:
|
||||||
|
|
||||||
|
- **Cookie / Token**:`getToken`、`setToken`、`TOKEN_KEY`
|
||||||
|
- **日期**:`formatDate(val, fmt)`(dayjs)
|
||||||
|
- **数组**:`forEach`、`hasOneOf`、`getUnion`、`getIntersection`
|
||||||
|
- **对象**:`objEqual`、`removeEmptyObject`、`isNullorEmpty`
|
||||||
|
- **路由/菜单**:`getBreadCrumbList`、`getHomeRoute`、`getMenuByRouter`、`filterMenu`
|
||||||
|
- **本地存储**:`localSave`、`localRead`
|
||||||
|
- **其它**:`generateUUID`、`getUrlParam`、`downStream`、`scrollTop` 等
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (this.$tools.getToken()) { /* 已登录 */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6.3 表格组件 `Tables`(README §12.2)
|
||||||
|
|
||||||
|
基于 View Design **`Table`** 封装,支持分页条、导出、列配置、可编辑列等。
|
||||||
|
|
||||||
|
| Props | 说明 |
|
||||||
|
|-------|------|
|
||||||
|
| `value` | 表格数据数组 |
|
||||||
|
| `columns` | 列配置(同 iView Table `columns`,框架内会做对齐等默认处理) |
|
||||||
|
| `title` / `tip` | 表格标题与灰色提示 |
|
||||||
|
| `isDown` | 为 true 且 `value` 非空时显示「下载」链接触发导出 |
|
||||||
|
| `pageOption` | 分页:`{ page, pageSize, total }`,存在则显示 `Page` |
|
||||||
|
| `width` / `height` | 传给内部 Table |
|
||||||
|
| `maxHeightOffset` | 表格外层最大高度:`auto`(默认,按视口计算)或数字,用于 `calc(100vh - offset)` |
|
||||||
|
|
||||||
|
| 插槽 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `header` | 标题行右侧区域 |
|
||||||
|
| 默认 | 透传给内部 Table(如 `slot-scope` 列) |
|
||||||
|
| `footer` / `loading` | 透传 Table 同名插槽 |
|
||||||
|
|
||||||
|
| 事件 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `changePage` | 分页变更(内部已更新 `pageOption.page`) |
|
||||||
|
| `on-select` | 多选变化 |
|
||||||
|
| `downExecl` | 点击下载时,`{ value, columns }` |
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<Tables
|
||||||
|
:value="tableData"
|
||||||
|
:columns="columns"
|
||||||
|
:page-option="{ page: 1, pageSize: 10, total: 100 }"
|
||||||
|
title="用户列表"
|
||||||
|
@changePage="loadData"
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 系统 API(`systemApi`,README §8 整段)
|
||||||
|
|
||||||
|
`AdminFramework.systemApi` 聚合导出 **`admin_core/src/api/system/index.js`** 中的服务(与源码 `export { default as ... }` 一致;以下为当前版本完整导出)。
|
||||||
|
|
||||||
|
| 导出名 | 模块文件 | 典型用途 |
|
||||||
|
|--------|----------|----------|
|
||||||
|
| `fileServe` | `fileServe.js` | 文件上传下载 |
|
||||||
|
| `plaAccountServer` | `pla_account_server.js` | 平台账号管理 |
|
||||||
|
| `rolePermissionServer` | `rolePermissionServer.js` | 角色权限 |
|
||||||
|
| `roleServer` | `roleServer.js` | 角色管理 |
|
||||||
|
| `sysAddressServer` | `sysAddressServer.js` | 地址字典 |
|
||||||
|
| `sysModuleServer` | `sysModuleServer.js` | 模块管理 |
|
||||||
|
| `sysLogServe` | `sys_log_serve.js` | 系统日志 |
|
||||||
|
| `systemTypeServer` | `systemType_server.js` | 系统类型配置 |
|
||||||
|
| `tableServer` | `tableServer.js` | 表结构相关 |
|
||||||
|
| `userServer` | `userServer.js` | 登录、用户 CRUD、权限菜单 |
|
||||||
|
| `formFieldServer` | `formFieldServer.js` | 表单字段 |
|
||||||
|
| `formServer` | `formServer.js` | 表单模型 |
|
||||||
|
| `menuServer` | `menuServer.js` | 菜单管理 |
|
||||||
|
| `modelFieldServer` | `modelFieldServer.js` | 数据模型字段 |
|
||||||
|
| `modelServer` | `modelServer.js` | 数据模型 |
|
||||||
|
| `paramSetupServer` | `paramSetupServer.js` | 系统参数(标题、Logo) |
|
||||||
|
| `sysControlTypeServer` | `sysControlTypeServer.js` | 控件类型 |
|
||||||
|
| `sysTenantServer` | `sysTenantServer.js` | 租户管理 |
|
||||||
|
|
||||||
|
**调用示例:**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { userServer } = AdminFramework.systemApi
|
||||||
|
|
||||||
|
async function login() {
|
||||||
|
const res = await userServer.login({ username: 'admin', password: '***' })
|
||||||
|
if (res.code === 0) {
|
||||||
|
// 写入 token、拉菜单等由登录页与 store 配合完成
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
实际请求仍走全局配置的 **`http`**(`apiUrl`、`admin-token` 头)。
|
||||||
|
|
||||||
|
> **说明**:`src/api/system` 中若存在**未**在 **`index.js`** 聚合导出的文件(例如实验性模块),**不会**出现在 **`AdminFramework.systemApi`**;新增系统接口时须同时 **`export`** 才能在框架聚合对象上访问。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Vuex 内置模块
|
||||||
|
|
||||||
|
### 8.1 `user`(namespaced)
|
||||||
|
|
||||||
|
- **state**:`token`、`userName`、`authorityMenus`、`menuList`、`currentTenant`、`avatorImgPath` 等。
|
||||||
|
- **常用 action**:**`handleLogin`**、**`handleLogOut`**、**`setAuthorityMenus`**。
|
||||||
|
- **租户**:**`currentTenant`** 与 **`localStorage.currentTenant`** 由 **`setCurrentTenant`** 同步。
|
||||||
|
|
||||||
|
### 8.2 `app`(namespaced)
|
||||||
|
|
||||||
|
- **state**:**`sysFormModel`**(标题、Logo)、**`breadCrumbList`**、**`homeRoute`**。
|
||||||
|
- **action**:**`getSysTitle`** — 必须使用 **`app/getSysTitle`** 形式 dispatch。
|
||||||
|
|
||||||
|
### 8.3 扩展 Store
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { createStore } from 'admin-framework/src/store' // 按实际包路径调整
|
||||||
|
import createPersistedState from 'vuex-persistedstate'
|
||||||
|
|
||||||
|
// 第三个参数传工厂函数则启用持久化
|
||||||
|
const store = createStore(Vuex, { myModule }, createPersistedState)
|
||||||
|
```
|
||||||
|
|
||||||
|
默认 **`createApp`** 内为 **`createStore(Vuex, {}, null)`**,即**无** persistedstate 插件。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 路由
|
||||||
|
|
||||||
|
- **Hash 模式**(`router/index.js`)。
|
||||||
|
- **动态菜单**:依赖 **`localStorage.authorityMenus`** + **`setAuthorityMenus`** 与 **`router.addRoute`**;详见 routing-store 文档。
|
||||||
|
- **守卫**:未登录跳转登录、已登录访问登录重定向 **`home`**、动态路由未命中延迟重试等,见 routing-store 文档。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 与后端协作约定
|
||||||
|
|
||||||
|
- **管理端 API** 路径通常带 **`/admin_api/`** 前缀(以项目 `apiUrl` 为准)。
|
||||||
|
- **请求体/查询**:与 Node 端 **`snake_case`** 字段对齐;前端展示层若用驼峰,应在边界单次映射,避免全页双写。
|
||||||
|
- **分页**:若后端使用框架 **`ctx.getPageSize`**,则与 **`pageOption`** JSON 约定一致(见 node-core 规范)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 调试与排错
|
||||||
|
|
||||||
|
- **`window.framework`**:单例、router、store、config。
|
||||||
|
- **`window.rootVue`**:`createApp` 返回的根实例。
|
||||||
|
- 动态页空白:控制台搜 **「组件未找到」**;检查菜单 **`component`** 与 **`componentMap`**。
|
||||||
|
- 刷新后丢菜单:检查 **`token`** 与 **`localStorage.authorityMenus`** 是否仍存在、是否执行了 **`setAuthorityMenus`**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 版本与构建
|
||||||
|
|
||||||
|
- 框架版本见 **`AdminFramework.version`** 与 **`README`**;发版时注意 **`admin-framework.js`** 与 **`admin-framework.md`** 同步分发。
|
||||||
262
.cursor/rules/node-core-framework-strict.mdc
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
---
|
||||||
|
description: Node Core(Koa2+Sequelize)启动、路由、鉴权与 Context 严格约束(详尽版,对照 node_core)
|
||||||
|
globs: "{app.js,api/**/*.js,config/**/*.js,middleware/**/*.js,services/**/*.js,models/**/*.js,node_core/**/*.js}"
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# Node Core 严格约束(详尽)
|
||||||
|
|
||||||
|
适用于基于 **`node_core`** 的业务后端;行为以 **`node_core/framework.js`**、**`middleware/baseRequest.js`**、**`router/baseController.js`** 及分发 **`node-core-framework.md`** 为准。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 静态入口 `Framework.init(config)` 执行顺序
|
||||||
|
|
||||||
|
模块导出 **`init`**(`framework.js` 末尾),顺序**固定**:
|
||||||
|
|
||||||
|
1. **`new Framework(config)`**
|
||||||
|
- 构造内 **`_validateConfig(config)`**:缺 `db` 必填子项、`apiPaths` 非法等会 **抛错终止**;`baseUrl` 缺失、`allowUrls` 非数组、`redis` 不完整等多为 **警告**。
|
||||||
|
- 创建 **`ServiceManager`**、**`RequestManager`**;挂载 **`initDb`**、**`initApi`** 方法引用。
|
||||||
|
|
||||||
|
2. **`_validatePackages()`**
|
||||||
|
- 若 **`config.skipPackageValidation === true`** 则跳过。
|
||||||
|
- 否则 **`PackageValidator.validate`**;若 **`strictPackageValidation === true`** 且失败则 **抛错**;否则仅 **warn**。
|
||||||
|
|
||||||
|
3. **`await initDb(config.modelPaths, config.businessAssociations)`** → 内部 **`_initDb`**:
|
||||||
|
- **`await _validateLicense(licensePath)`**:无授权码会 **`process.exit(1)`**(硬退出)。
|
||||||
|
- **`ModelManager`** 初始化库、**`initSysModels`**、**`_loadBusinessModels`**(目录下 `*.js`,函数则 `(db) => model(db)`)、**`mergeModels`**、**`setupBusinessModelAssociations`**(若传入)。
|
||||||
|
- 设置 **`dbInitialized = true`**。
|
||||||
|
|
||||||
|
4. **`await initServices()`**
|
||||||
|
- **`serviceManager.initServices()`**(含 Redis 等异步);默认 **`servicesRequired !== false`** 时失败 **抛错**;若为 **`false`** 则 warn 并继续。
|
||||||
|
|
||||||
|
5. **`await initApi(config.beforeInitApi)`** → 内部 **`_initApi`**:
|
||||||
|
- 若 **`apiInitialized`** 已 true:warn 并 return。
|
||||||
|
- 若 **`!dbInitialized`**:**抛错** `Database must be initialized before API`。
|
||||||
|
- 然后进入 **§2** 管线。
|
||||||
|
|
||||||
|
6. **`return global_framework`**。
|
||||||
|
|
||||||
|
**业务侧**:`const framework = await Framework.init({ ... }); await framework.start(port);`;不得在 **`init` resolve 之前** 假设 DB/API 已就绪。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. `_initApi` 内部顺序(中间件与路由)
|
||||||
|
|
||||||
|
1. **`_setupMiddleware()`**(顺序即 Koa 洋葱外层 → 内层)
|
||||||
|
- **`koa-static`**:`path.resolve(__dirname, "../upload")`(相对 **`node_core` 包内位置**),`extensions: ["html"]`。
|
||||||
|
- **`@koa/cors`**:`exposeHeaders: ["*"]`。
|
||||||
|
- **`koa-body`**:`multipart: true`,**`maxFileSize: 200 * 1024 * 1024`**(200MB)。
|
||||||
|
- **`this.requestManager.getBaseRequest()`**:Token 校验、**`ctx.*` 扩展**、日志等(见 §4)。
|
||||||
|
|
||||||
|
2. **`await beforeInitApi(this)`**(若存在)
|
||||||
|
- **仅此阶段**应调用 **`framework.addRoutes`**、**`framework.use`** 等,保证在 **Swagger 与路由表注册之前** 插入中间件或收集自定义路由。
|
||||||
|
|
||||||
|
3. **`_setupSwagger()`**(当构造时存在 `apiPaths` 即会调用;与 `_initApi` 内注释一致)
|
||||||
|
- 使用 **`modelManager.getAllModels()`** 等创建 Swagger 服务并挂载文档路由。
|
||||||
|
|
||||||
|
4. **`_setupRoutes(apiPaths)`**
|
||||||
|
- **`_registerSystemControllers`**:固定加载 **`controller/admin`** 下 **sys_*** 模块,前缀 **`/admin_api`**(见 §3)。
|
||||||
|
- **`baseController.loadControllers(apiPaths)`**:扫描业务目录。
|
||||||
|
- 将 **`this.customRoutes`**(由 **`addRoutes`** 累积)**push** 到 **`baseController.routes`**。
|
||||||
|
- **`baseController.init(router)`** 注册到 **koa-router**,再 **`app.use(router.routes())`**、**`router.allowedMethods()`**。
|
||||||
|
|
||||||
|
5. **`apiInitialized = true`**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 系统控制器与业务控制器
|
||||||
|
|
||||||
|
### 3.1 系统模块(`_registerSystemControllers`)
|
||||||
|
|
||||||
|
手动 `require` 并注册(避免 webpack 动态路径问题):
|
||||||
|
|
||||||
|
- **`sys_user`**、**`sys_menu`**、**`sys_role`**、**`sys_tenant`**、**`sys_parameter`**、**`sys_log`**、**`sys_control_type`**
|
||||||
|
|
||||||
|
路由前缀:**`/admin_api`** + 各导出键路径。
|
||||||
|
|
||||||
|
### 3.2 业务 `apiPaths`
|
||||||
|
|
||||||
|
- 元素为 **字符串**:仅 **`path`**(目录),**无前缀**。
|
||||||
|
- 元素为 **对象**:**`path`**(必填),**`prefix`**(可选),**`authType`**(可选,见 §5)。
|
||||||
|
|
||||||
|
### 3.3 `loadControllers` 文件规则
|
||||||
|
|
||||||
|
- 目录内 **`*.js`** 或 **`*_controller.js`** 会加载;其他文件忽略。
|
||||||
|
- 每个文件 `require` 后的导出对象由 **`_parseController`** 解析。
|
||||||
|
|
||||||
|
### 3.4 导出键与处理器
|
||||||
|
|
||||||
|
- 键必须匹配 **`/^(GET|POST)\s+(.+)$/`**;否则忽略。
|
||||||
|
- 处理器须为 **function**;签名 **`async (ctx, next) => {}`**,按需 **`await next()`**(系统控制器多为 **`async (ctx) =>`**)。
|
||||||
|
- 注册路径:**`routePrefix`(来自 apiPaths) + 键中 path 段**;系统侧 **`routePrefix` 为 `/admin_api`**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. `addRoutes(prefix, routes)`(仅 `beforeInitApi` 内)
|
||||||
|
|
||||||
|
- **`routes`** 须为对象;否则 warn 并 return。
|
||||||
|
- 对每个键:非 function 跳过;正则提取 **method** 与 **path**;最终 **`path: prefix ? prefix + match[2] : match[2]`**,**`module: 'custom'`**,push 到 **`this.customRoutes`**。
|
||||||
|
- **`prefix` 为空字符串**时路径为键内裸 path(常用于与文档路径对齐)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 认证、白名单与 `apiPaths` 匹配(`baseRequest.js`)
|
||||||
|
|
||||||
|
### 5.1 日志
|
||||||
|
|
||||||
|
- 进入中间件后:**`console.log('Process API', method, path)`** — **仅 path**,**不打印 query**(防敏感信息)。
|
||||||
|
|
||||||
|
### 5.2 白名单
|
||||||
|
|
||||||
|
- **`defaultAllowUrls`**:`/login`、`/register`、`/health`、`/docs`、`/swagger.json` 等(以源码为准)。
|
||||||
|
- **`allowUrls`**:`[...defaultAllowUrls, ...(config.allowUrls || [])]`。
|
||||||
|
- 匹配:**`ctx.request.path.indexOf(url) > -1`**(**子串**)。**禁止**配置过宽片段;新增白名单须为**明确路径片段**。
|
||||||
|
|
||||||
|
### 5.3 需鉴权时的 `apiPaths` 匹配
|
||||||
|
|
||||||
|
- 遍历 **`config.apiPaths`**:取 **`prefix`**(对象)或空(字符串形式无 prefix)。
|
||||||
|
- 条件:**`ctx.request.path.indexOf(prefix + '/') > -1`**。
|
||||||
|
- **注意**:`prefix` 建议带前导 **`/`** 且**无尾斜杠**;否则与业务 path 拼接易不匹配。
|
||||||
|
- 命中后:**`authType === 'admin'`** → **`ctx.getAdminUserId()`** 非 0 视为通过;否则 **`ctx.getPappletUserId()`**。
|
||||||
|
- 任一条 `apiPaths` 命中且 userId 有效 → **`next()`**;否则 **`ctx.tokenFail()`**。
|
||||||
|
|
||||||
|
### 5.4 `ctx.getPappletUserId`(小程序)
|
||||||
|
|
||||||
|
- 测试:**`ctx.request.query.userIdTest45`** 优先。
|
||||||
|
- 否则解析头 **`applet-token`**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Context 扩展详解
|
||||||
|
|
||||||
|
### 6.1 `ctx.get(id)`
|
||||||
|
|
||||||
|
- 合并 **`ctx.request.query`** 与 **`ctx.request.body`** 到一对象再取值。
|
||||||
|
- 若合并结果为 **string**,会 **`JSON.parse`**(异常未在片段中捕获时需知悉风险)。
|
||||||
|
- 取值:**`obj[id] === undefined ? "" : obj[id]`**(缺省为 **空字符串**)。
|
||||||
|
|
||||||
|
### 6.2 `ctx.getBody()`
|
||||||
|
|
||||||
|
- **`Object.assign({}, ctx.body, ctx.request.body)`**;若整体为 string 则 **`JSON.parse`**。
|
||||||
|
- **不含 query**;写操作、JSON 体优先。
|
||||||
|
|
||||||
|
### 6.3 `ctx.getQuery()`
|
||||||
|
|
||||||
|
- 返回 **`ctx.request.query`**。
|
||||||
|
|
||||||
|
### 6.4 `ctx.getAdminUserId()` / `ctx.getAdminTenantId()`
|
||||||
|
|
||||||
|
- 读头 **`admin-token`**,**`tokenService.parse`**。
|
||||||
|
- **租户**:若 payload 无 **`tenant_id`**(或 null/空串)则返回 **默认 `1`**;解析失败或无效为 **`0`**(与「未登录」区分以业务为准,源码为数字返回值)。
|
||||||
|
|
||||||
|
### 6.5 `ctx.getPageSize()`
|
||||||
|
|
||||||
|
- 读 **`ctx.get("pageOption")`**;若为字符串则 **`JSON.parse`**。
|
||||||
|
- 返回 **`{ limit: pageSize || 20, offset: pageSize * (page - 1) }`**(**`page` 默认 1**)。
|
||||||
|
|
||||||
|
### 6.6 `ctx.getOrder(key)`
|
||||||
|
|
||||||
|
- **`key`** 默认 **`"order"`**;先 **`ctx.get(key)`**。
|
||||||
|
- 若已是数组则直接返回;否则 **`eval("(" + order + ")")`** — **有注入风险**;新代码应避免把不可信字符串传入,或改为 JSON.parse + 校验。
|
||||||
|
|
||||||
|
### 6.7 `ctx.getIp()`
|
||||||
|
|
||||||
|
- **`x-forwarded-for`** 取第一段;否则 socket 地址。
|
||||||
|
|
||||||
|
### 6.8 响应辅助
|
||||||
|
|
||||||
|
| 方法 | 行为 |
|
||||||
|
|------|------|
|
||||||
|
| **`ctx.json(code, message, data)`** | `type: application/json`,`body: { code, message, data }` |
|
||||||
|
| **`ctx.success(data, msg)`** | **`ctx.json(0, msg \|\| '请求成功', data)`** |
|
||||||
|
| **`ctx.fail(msg)`** | **`ctx.json(-1, message, {})`**;若存在 **`ctx.errorCallback`** 则先调用 |
|
||||||
|
| **`ctx.tokenFail(data)`** | **`ctx.json(-2, '非法请求,或登录已超时', data)`** |
|
||||||
|
| **`ctx.downFile({ title, rows, cols })`** | CSV,**UTF-8 BOM**,`Content-Type: text/csv; charset=utf-8` |
|
||||||
|
|
||||||
|
### 6.9 中间件错误
|
||||||
|
|
||||||
|
- **`try/catch`** 包裹鉴权逻辑:异常时 **`logsService.ctxError`**,**`return ctx.fail(e.message)`**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. `start(port)`
|
||||||
|
|
||||||
|
- 若 **`!apiInitialized`**:**抛错** `API not initialized. Call initApi() first.`
|
||||||
|
- **`this.app.listen(port)`** 返回 server。
|
||||||
|
- **定时任务、后台循环**等须在 **`start` 成功之后**再启动(与框架规则一致)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 静态资源与上传目录
|
||||||
|
|
||||||
|
- 默认静态目录指向包内 **`../upload`**;业务部署若需对外提供文件,应明确该目录在磁盘上的位置与权限,而非在控制器里随意 `sendFile` 到任意路径。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 数据模型:`DatabaseManager.define` 自动注入字段(`node_core/database/db.js`)
|
||||||
|
|
||||||
|
业务在 **`db.define(name, attributes)`**(或经 `ModelManager` 等价入口)里**只写业务字段**即可;框架会在 **`define()` 内统一合并**下列字段与行为,**无需、也不应在每个模型文件里重复声明** `create_time` / `last_modify_time` / `is_delete`(若 `attributes` 里写了同名键,会被框架定义**覆盖**)。
|
||||||
|
|
||||||
|
### 9.1 自动追加的三列
|
||||||
|
|
||||||
|
| 字段 | 类型与默认 | 说明 |
|
||||||
|
|------|------------|------|
|
||||||
|
| **`create_time`** | `DATE`,库默认 `CURRENT_TIMESTAMP`,`allowNull: false` | **创建时间**。`getter` 用 dayjs 格式化为 **`YYYY-MM-DD HH:mm:ss`** 字符串返回;`setter` 接受 `Date` 或可解析字符串。 |
|
||||||
|
| **`last_modify_time`** | `DATE`,库默认 `CURRENT_TIMESTAMP`,`allowNull: false` | **最后修改时间**。读写格式与 `create_time` 相同。 |
|
||||||
|
| **`is_delete`** | `INTEGER(1)`,默认 **`0`**,`allowNull: false` | **软删标记**:`0` 正常,`1` 已删;`is_swagger: false`(可按生成器策略处理)。 |
|
||||||
|
|
||||||
|
### 9.2 `define` 选项与默认作用域
|
||||||
|
|
||||||
|
- **`tableName`**:与模型名 **`name`** 一致(即表名与 `define` 第一个参数相同)。
|
||||||
|
- **`timestamps: false`**:不使用 Sequelize 内置 `createdAt`/`updatedAt`,改由上述 **`create_time` / `last_modify_time`** 维护。
|
||||||
|
- **`defaultScope`**:
|
||||||
|
- **`where: { is_delete: 0 }`** — 普通 **`findAll` / `findOne` / `findByPk`** 等**默认只查未删除行**。
|
||||||
|
- **`attributes: { exclude: ['is_delete'] }`** — 默认查询结果里**不选出 `is_delete` 列**(行仍受 `where` 约束)。
|
||||||
|
- 需要查已删数据或显式带出 **`is_delete`** 时,须使用 **Sequelize 作用域 API**(如 **`Model.unscoped()`** / **`Model.scope(null)`** 等,以项目所用 Sequelize 版本文档为准)**临时去掉 `defaultScope`**,再自行写 **`where`**;业务仓库内若未封装,不要随意全表无 `where` 查询。
|
||||||
|
|
||||||
|
### 9.3 模型钩子(与操作日志、时间戳)
|
||||||
|
|
||||||
|
- **`beforeCreate`**:若传入的 `create_time` / `last_modify_time` 无效则删除让库/默认值处理;未设置则填 **`new Date()`**。
|
||||||
|
- **`afterCreate`**:在排除表之外写 **`sys_log`**「新增」日志;必要时补全空的时间字段。
|
||||||
|
- **`beforeUpdate`**:对非 **`sys_log`** 表强制 **`last_modify_time = new Date()`**;并取更新前一行与当前值对比写 **`sys_log`**「修改」日志(字段 diff 时**忽略** `create_time` / `last_modify_time` 的变化展示规则见 `_logEditField`)。
|
||||||
|
- **`afterDestroy`**:写 **`sys_log`**「删除」日志(若业务走物理 `destroy`);**业务侧软删推荐**与系统控制器一致:使用 **`update({ is_delete: 1 }, { where: { id, is_delete: 0 } })`**,而不是依赖 `destroy()`。
|
||||||
|
|
||||||
|
### 9.4 列表与统计查询约定
|
||||||
|
|
||||||
|
- **列表/详情**:在 `defaultScope` 下已隐含 **`is_delete: 0`**;若手写 **`raw: true`** 或复杂 **`include`**,仍应显式带上 **`is_delete: 0`**(或与产品确认的可删数据范围),避免误查出已删行。
|
||||||
|
- **Swagger / 文档**:框架侧会把 **`create_time` / `last_modify_time` / `is_delete`** 纳入 schema 相关约定(见 **`services/swagger.js`** 等),与表结构保持一致即可。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 控制器编写规范(与既有严格条目一致)
|
||||||
|
|
||||||
|
- **入参**:**`snake_case`**;**禁止** `body.a ?? body.b` 双字段兼容。
|
||||||
|
- **异常**:**不要**在控制器外层包一层「万能 try/catch」吞栈;业务分支用 **`ctx.fail`**;未捕获异常交给全局(若项目有统一错误中间件)。
|
||||||
|
- **`if`**:**必须**使用大括号,**禁止** `if (cond) return ctx.fail(...);` 单行无括号写法。
|
||||||
|
- **模型/服务**:**`Framework.getModels()`**、**`Framework.getServices()`**(或 **`Framework.getInstance()`**)须在 **`init` 完成之后**调用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 配置校验摘要(`_validateConfig`)
|
||||||
|
|
||||||
|
| 配置 | 严重级别 |
|
||||||
|
|------|-----------|
|
||||||
|
| 缺 `db` / 缺 `host|username|password|database|dialect` | **error** |
|
||||||
|
| `apiPaths` 非非空数组 / 项缺 `path` | **error** |
|
||||||
|
| 缺 `baseUrl` | **warning**(Swagger 等可能异常) |
|
||||||
|
| `allowUrls` 非数组 | **warning** |
|
||||||
|
| `redis` 缺 host/port | **warning** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 提交前自检(后端)
|
||||||
|
|
||||||
|
- [ ] 新增路由是否在 **`beforeInitApi`** 内 **`addRoutes`** 或业务控制器导出 **`GET`/`POST`** 键?
|
||||||
|
- [ ] **`apiPaths`** 的 **`prefix`** 是否与 **`indexOf(prefix + '/')`** 匹配方式一致?
|
||||||
|
- [ ] **`allowUrls`** 是否尽量短且明确、避免误放行?
|
||||||
|
- [ ] 读 body 是否优先 **`ctx.getBody()`**,避免与 query 混用?
|
||||||
|
- [ ] 是否避免 **`ctx.getOrder`** 处理不可信字符串?
|
||||||
|
- [ ] 响应是否统一 **`ctx.success` / `ctx.fail` / `ctx.tokenFail`**?
|
||||||
|
- [ ] 控制器是否无万能 try/catch、且 **if 均带大括号**?
|
||||||
|
- [ ] 列表/更新/删除是否默认带上 **`is_delete: 0`**(或与 `defaultScope` 一致),软删是否用 **`update({ is_delete: 1 })`**?
|
||||||
17
.gitattributes
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
约球设计稿/*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
|
||||||
|
# LocalHistory in vscode
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
|
app/*
|
||||||
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
node_modules/
|
||||||
|
logs/
|
||||||
|
node_modules.*
|
||||||
|
dist.zip
|
||||||
|
dist/
|
||||||
|
coverage/*
|
||||||
|
admin/node_modules/
|
||||||
|
|
||||||
|
# 临时文件目录(保留目录但忽略文件)
|
||||||
|
temp/*
|
||||||
|
!temp/.gitkeep
|
||||||
34
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
// 使用 IntelliSense 了解相关属性。
|
||||||
|
// 悬停以查看现有属性的描述。
|
||||||
|
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "启动后端",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
|
"program": "${workspaceFolder}\\app.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "启动前端",
|
||||||
|
"runtimeExecutable": "npm",
|
||||||
|
"runtimeArgs": ["run", "serve"],
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "启动Pad",
|
||||||
|
"runtimeExecutable": "npm",
|
||||||
|
"runtimeArgs": ["run", "dev"],
|
||||||
|
"cwd": "${workspaceFolder}\\pda_uniAppV2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 选择官方 Node 镜像
|
||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
# 复制 package.json 和 package-lock.json
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
RUN npm install --production
|
||||||
|
|
||||||
|
# 复制应用代码
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# 暴露端口(根据你应用实际端口)
|
||||||
|
EXPOSE 9080
|
||||||
|
# 启动应用
|
||||||
|
CMD ["npm", "start"]
|
||||||
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 light
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
28
README.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# 前后端项目模板
|
||||||
|
|
||||||
|
基于 **admin-framework**(管理端 Vue2 + ViewUI)与 **node-core-framework**(Koa2 + Sequelize)的最小可运行骨架,已去掉原 WMS 业务,仅保留一条演示业务链与系统接口。
|
||||||
|
|
||||||
|
## 结构
|
||||||
|
|
||||||
|
- `framework/`:node-core-framework 核心(勿与业务混写)
|
||||||
|
- `admin/src/framework/`:admin-framework UMD
|
||||||
|
- `api/controller_admin/`:管理端业务控制器(模板保留 `tpl_demo`、`sys_file`、`sys_parameter`、`sys_tenant`)
|
||||||
|
- `api/model/`:业务模型(模板保留 `tpl_demo`、`sys_tenant`)
|
||||||
|
- `config/`:`config.js` 白名单、`framework.config.js` 框架项、环境库表连接
|
||||||
|
|
||||||
|
## 本地运行
|
||||||
|
|
||||||
|
1. 安装依赖:项目根目录 `npm install`,`admin` 目录 `npm install`
|
||||||
|
2. 在 MySQL 中创建库,导入 node-core 自带的系统表;再执行 `sql/tpl_demo.sql` 创建演示表
|
||||||
|
3. 修改 `config/config.development.js` 中的数据库账号等
|
||||||
|
4. 根目录 `npm run api` 启动后端(默认端口见 `config/config.js`)
|
||||||
|
5. `npm run dev` 启动管理端,浏览器访问 webpack 提示的端口
|
||||||
|
|
||||||
|
## 菜单与演示页
|
||||||
|
|
||||||
|
在 `sys_menu` 中增加一条菜单:`component` 填 **`demo/tpl_demo`**(与 `admin/src/router/component-map.js` 一致),分配权限后即可打开「演示数据」列表页。
|
||||||
|
|
||||||
|
## 从模板扩展
|
||||||
|
|
||||||
|
- 后端:在 `api/model` 增加模型、在 `api/controller_admin` 增加控制器导出 `GET/POST /xxx/...`;若有关联,写在 `config/model.associations.js`
|
||||||
|
- 前端:在 `admin/src/views` 增加页面,并在 `component-map.js` 注册;列表可参考 `views/demo/tpl_demo.vue` + `tableMixin`
|
||||||
1
_license/license.lic
Normal file
@@ -0,0 +1 @@
|
|||||||
|
eyJ2ZXJzaW9uIjoiMy4wIiwiZGF0YSI6eyJyZWdpc3Rlcl9jb2RlIjoiZXlKMklqb2lNeTR4SWl3aWFDSTZJakpsTm1abVpURmhaVEU1WmpnelpXSXlNbVk0T0dFNFpHVm1NVE0zT0Rrd1pERXpZamcwWkdNd05EYzNaall5TURkbU5tSmxNalk1TkRJME1qQTJaVFlpTENKeklqb2lPVEkyT0dRNU1EY3paV0UwTWpnNVpHVXdNRFF6TldOaU5EazBOVFU1TnpraUxDSjBJam94TnpVNU5ERTJNRFUyTENKdUlqb2lNbU00WkRVME1ETTJNR0k1T1RoaE5DSXNJbXNpT2lJMk5EUmpaR0ZoTWlJc0ltTWlPaUptTVdNek5tVmpaREptWmpjMFpXUmpOVFJrTlRrNE5HTTNaV1k1TjJJNFpUQTJaVFV6WVRsbU1Ua3hOV0ZrTURRME5tUTFNRFk0WldSbE5ETmpZMkV6SWl3aWJTSTZJbVF4TVROaFptSXlOREE1T1RsaE5qWWlmUT09IiwidmVyc2lvbiI6IjMuMCIsInJlbWFyayI6IiIsImlzc3VlciI6IlBsYXRmb3JtV2ViIExpY2Vuc2UgU2VydmVyIiwiZW5jcnlwdGVkX2RhdGEiOiJMcXQ1MnpTT2dFdlR6dFA5T0FCTlU1NmY2L3JIMlpNR2puQnA5UXgxb2FkcW1YMXBISThkdEVVWWVET29IQ0Z5Q0N2b2p3TkVpV0R4YmN2RCtMMDAwSitOMXF5WjBNZGdPanZoSGRFbG0wVWxlYVNuNjR6QWU4M1FBelNOUGlIRWJWbEcvSk4ySUpHdVg2anhFaktsZCtCNGVVWXdoa1VVakFsUnVQL0lOVUFZZXplYW1sM0x2RmRTQW4xRnV2QXMiLCJhZXNfaXYiOiJpRkQ0VGhrdEFBcTY3emdsWlR1VStRPT0iLCJub25jZSI6ImRiYjM2ZTg0NThiNTEzMDJkYzNhYTU4NDI1MTY1M2JlIiwia2V5X2lkIjoiNjQ0Y2RhYTItZmRmNy00MjEzLTgyNWItZDkxOWU4YjQ0Mzg4IiwiZGF0YV9oYXNoIjoiYmUzZmFjZDkyNWVjM2FmMjQ3ZjQ3NTZjMzUzMWJlYTM1ZTMyZmZjYTA5MTg0ZDM2Y2E3ZmFmOWJkMWRmYzI3NiIsImV4cGlyZV90aW1lX3B1YmxpYyI6IjIwMjYtMTItMDVUMDg6NTk6MzQuNjQ0WiIsImNyZWF0ZWRfdGltZV9wdWJsaWMiOiIyMDI1LTEyLTA1VDA4OjU5OjM0LjY0NVoifSwiZW5jcnlwdGVkX2tleSI6IlVGZEpzUE5DVW9pb3hNTHE1eFNvaklveHR1c3FkMXhsY0hLODZRS01tT0hkRzZpY3dYd0tDNEx4dDYxTHNMVHY5VEZqN0V5NldGNkVtVjZibDMvK21DKzZsdmRBZnNPL2ZTR0xkcDdmMUhXcC9mUVlqSGJIMEhnZUhjd3ZxUWppajRha2hxdkJsRVN6dWhUZTF2cmxzRDhOZk9aaUlvb0llTHp6aTBoVVpHSVFlTC85ek1ZTjhNQXlkczRkZk9VaGRtTGh0QkVYcHlFR2lkcVRkT28zODh2Z0pKamlLdm9FdU9JNm9tN0NPWFdPNkRvQmxsMkllU0EvVmJzZjE3cm1wbGVyYmtscXlKYktJQXVpZDlFKzNvYms4ZVhHaldpNHVWKzZXNW13anhNMzR5YTBkTEtrcGtlR2J6cXpBTjJmTFJLaWJ3Y0kvU3F2VkU5WnFhdzB6YnlhNHJZczFhVGVWdGp0NXcvQkxwUm9RbzJ2TE9Xdnd1N1R0Nko5U1VRUVNqK1JhVnZzTkQrUmZOdHlmbmd3VlM1ZUdhcEpaRHJOSVlUOHVHZW9qZDRaQjFTdnJyVnh2Ym10YW5EcHYrYkxnZTVPZVNXWCt4M2ErME03Wmd3NE02aTBXRmNoTyticnVFSU9RSkZ4K3g0ZVFoK25oaFFJcnNRaHZoRkpBRXRFTjdhUXludU5yeFNDU1FqdGpnZnhuSnBEOXpTc2w5VmNxanNlZDF5SUJPM3hsUkh4MUdFenFNbG1iTHlLNkpBNlBUcUIwWUJoN0FmM2pkaHZhSDc0b3NRV3RZcjg3dC9oK1dwT2VLL1pDTDJ3WFdKM1Fiam9SVXRMYTFWc3dxZnQweTd4VXBBNzZJMnlaYzdDTHpwdzR0MFl0cjhrQzZLemdhUzhxbkV1cG1vPSIsInNpZ25hdHVyZSI6IlVTNEFCVWwvSm9vZng4MjZzTVBSd2FDOTdscnhaeHJqOWEwT1g3Wm0xSkg0T2lQai9JY1BKVCtUZngyNzZpZkpNRGVwV2pCSFdFbVBWLzRHbDkxZnF3MTAwdk9EejZMQlBMY3doWkFiMzdGZWp0aEwwbnJBZERhZ2NqNUJOYkt6NTREYVJaZzlxNS9EMDlpVlltM2Y1YVFGckJxdS9oRHdFSGFrZGVBelF3eW5KOGxYWVcvYkZLN0ZieU9xakFlbDNMcjNBSTZ1Yzd6dDdmZDNKNHNGTkdqVjdMOWdmSi96eE1tRHdVV3BqdEN3d3RmUmwzNkFkbzl4MzNJbVNERkRvVWlJWWpWeXBUSVhERFlUZGJWSjNhMkEySzgxMUZteG4rRGhqVEJHMm80ZVpkbFJWZnRZcmVwREtUNS9PcjdRK2lxdG0rTEUyVTBNbDBDMXEzeFpaMitKM1RVM3JXTE9LTldIS3hCZk9seFJib0NxclRWcHpoZnhrOCthanRhSGMrWVU1UCsxYUxXN2ZaOEN6dCs5Z1ZMak1MQUI5SEZyRVAyNnFlOTRydDNIOGZtUlR6T3I3bUZsMUZic2dtRkRENkdkdmRuQmUrU09PSGdVQWJQYkI0TTBQcTRlSHJRTWZQSFpRSTRnd3JXY1p4QjgydFd2ZGdWTldHbVlWT0VxVVAxazVhS29wcFI2L2RGZnVmNmZOc3JKc1lWTVhCVWdPODJDWmQ0RzBDUTRDdllFQTVYa0xQUFpKSWlMdHEzQ2Y5eTdxazdidnVSejNMemVMWGhtMUk1ZHVoU2xENjZXNVFLWm9pZlpmYzJGYkwyMVpkV1Y0aGc0UllDRVRidk5MQkpOM092QlpQbTRYZUJmSlZnTGtkc2pXckFocWNVY0dxS243TGttc1U0PSIsInRpbWVzdGFtcCI6MTc2NDkyNTE3NH0=
|
||||||
14
_license/public_key.pem
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5qcNLrwgngGJMqkHhY4b
|
||||||
|
KCeS1HZegxM744fRtdrnWNVA3JwYASc52aokSQh0ig9SKN9k1zRs3L7N4cF4i9kE
|
||||||
|
AfW/2c+yiaMmbX6LW3Wi+yRH2jvTbpj1GkB/9Lsa+OEvdqYaeMiBEVoHS7FZUEaV
|
||||||
|
dzqTqrikUfql3htEhGCI9CGqZFoi8dz0GGKqDpqX7380pbST5Qgi9N4ZQLRVcmOP
|
||||||
|
596xYMoXdfufJ4em+FftYT5Q1rDt42lJhO+UrENORwTrGwCJVmLtIWiHfiCxeUzx
|
||||||
|
5Ft/xOKacndR86L4CmKLekVjejQSo+4Ge8j/BEdVUWY1tMlUFTC8aUTeFE2yA6dt
|
||||||
|
FkX3dQzgEOlRUifLjalXxLmxPY77N+mcuDzjaRomdHdxoGZsRYlS8yHL74rixSRa
|
||||||
|
U9JOVL9i8csLmJarzmYx6jsl4sSDbcDdZHxC2AbdGdDHV5/Zr+a8m8B6PW2nArgB
|
||||||
|
bTNKVx9g8aj4n3jf3NGzRqW/TwNifY4xb6BrbeNTlhXl/9+RCvvmCZZYK8JKus55
|
||||||
|
3cvBvrLUBQdpkk9JwIzmEQZoitD8g4CB/2tKsKvfiwlQUK44HNfWE+cxiqtyXL+I
|
||||||
|
shRJkwYbt0CQsXmU5F5j/prOPiJZjjlk7jqSLZLyJ99vMMm0+Iw7kozweGs3zUct
|
||||||
|
dOvKFUYgxdSaMjTiMOXdcN0CAwEAAQ==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
1
_license/registration.code
Normal file
@@ -0,0 +1 @@
|
|||||||
|
eyJ2IjoiMy4xIiwiaCI6IjJlNmZmZTFhZTE5ZjgzZWIyMmY4OGE4ZGVmMTM3ODkwZDEzYjg0ZGMwNDc3ZjYyMDdmNmJlMjY5NDI0MjA2ZTYiLCJzIjoiOTI2OGQ5MDczZWE0Mjg5ZGUwMDQzNWNiNDk0NTU5NzkiLCJ0IjoxNzU5NDE2MDU2LCJuIjoiMmM4ZDU0MDM2MGI5OThhNCIsImsiOiI2NDRjZGFhMiIsImMiOiJmMWMzNmVjZDJmZjc0ZWRjNTRkNTk4NGM3ZWY5N2I4ZTA2ZTUzYTlmMTkxNWFkMDQ0NmQ1MDY4ZWRlNDNjY2EzIiwibSI6ImQxMTNhZmIyNDA5OTlhNjYifQ==
|
||||||
14
admin/.babelrc
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
[
|
||||||
|
"@babel/preset-env",
|
||||||
|
{
|
||||||
|
"modules": false,
|
||||||
|
"targets": {
|
||||||
|
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
2
admin/.env.prod
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# 生产环境(youchang 接口)
|
||||||
|
BUILD_ENV=prod
|
||||||
2
admin/.env.sit
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# SIT 环境(tennis 接口)
|
||||||
|
BUILD_ENV=sit
|
||||||
24
admin/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# 依赖
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# 构建输出
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# 日志
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# 编辑器
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
# 系统文件
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
251
admin/README.md
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
# Admin Framework Demo
|
||||||
|
|
||||||
|
本目录包含 Admin Framework 的使用示例,提供两种使用方式:
|
||||||
|
|
||||||
|
## 📁 文件说明
|
||||||
|
|
||||||
|
### 🌐 CDN 版本(快速体验)
|
||||||
|
- **index.html** - 基础示例(CDN)
|
||||||
|
- **advanced.html** - 高级示例(CDN)
|
||||||
|
|
||||||
|
适合快速体验,所有依赖从 CDN 加载,无需安装。
|
||||||
|
|
||||||
|
### 💻 本地开发版本(推荐开发使用)
|
||||||
|
- **src/** - 源代码目录
|
||||||
|
- **main.js** - 基础示例入口
|
||||||
|
- **main-advanced.js** - 高级示例入口
|
||||||
|
- **components/** - 自定义组件
|
||||||
|
- **package.json** - 依赖配置
|
||||||
|
- **webpack.config.js** - 构建配置
|
||||||
|
|
||||||
|
所有依赖本地安装,支持热更新,适合开发调试。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 使用方式
|
||||||
|
|
||||||
|
### 方式一:CDN 版本(快速体验)
|
||||||
|
|
||||||
|
#### index.html - 基础示例
|
||||||
|
最简单的使用示例,展示如何:
|
||||||
|
- 引入必要的依赖
|
||||||
|
- 初始化框架
|
||||||
|
- 创建基本应用
|
||||||
|
|
||||||
|
#### advanced.html - 高级示例
|
||||||
|
完整的使用示例,展示如何:
|
||||||
|
- 添加自定义页面组件
|
||||||
|
- 注册自定义 Vuex 模块
|
||||||
|
- 添加自定义路由
|
||||||
|
- 配置路由守卫
|
||||||
|
- 配置 Axios 拦截器
|
||||||
|
- 使用组件映射
|
||||||
|
|
||||||
|
### 方式二:本地开发版本(推荐)
|
||||||
|
|
||||||
|
查看详细文档:[README-LOCAL.md](./README-LOCAL.md)
|
||||||
|
|
||||||
|
快速开始:
|
||||||
|
```bash
|
||||||
|
# 1. 构建框架(在项目根目录)
|
||||||
|
cd ..
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 2. 安装 demo 依赖
|
||||||
|
cd demo
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 3. 启动开发服务器
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用步骤
|
||||||
|
|
||||||
|
### 1. 构建框架
|
||||||
|
首先需要构建 admin-framework:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 生产构建(压缩,无 sourcemap)
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 开发构建(不压缩,有 sourcemap)
|
||||||
|
npm run build:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 启动示例
|
||||||
|
|
||||||
|
有以下几种方式启动示例:
|
||||||
|
|
||||||
|
#### 方式一:使用 Live Server(推荐)
|
||||||
|
1. 安装 VS Code 的 Live Server 插件
|
||||||
|
2. 右键 `index.html` 或 `advanced.html`
|
||||||
|
3. 选择 "Open with Live Server"
|
||||||
|
|
||||||
|
#### 方式二:使用 HTTP 服务器
|
||||||
|
```bash
|
||||||
|
# 安装 http-server
|
||||||
|
npm install -g http-server
|
||||||
|
|
||||||
|
# 在项目根目录运行
|
||||||
|
http-server
|
||||||
|
|
||||||
|
# 访问
|
||||||
|
# http://localhost:8080/demo/index.html
|
||||||
|
# http://localhost:8080/demo/advanced.html
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 方式三:直接打开
|
||||||
|
- 双击 HTML 文件在浏览器中打开
|
||||||
|
- 注意:某些功能可能因跨域限制无法使用
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
### 基本配置
|
||||||
|
```javascript
|
||||||
|
const config = {
|
||||||
|
title: '系统标题',
|
||||||
|
apiUrl: 'http://your-api.com/api/', // API 基础地址
|
||||||
|
uploadUrl: 'http://your-api.com/api/upload' // 上传接口地址
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 初始化框架
|
||||||
|
```javascript
|
||||||
|
framework.install(Vue, {
|
||||||
|
config: config, // 配置对象
|
||||||
|
ViewUI: iview, // iView 实例
|
||||||
|
VueRouter: VueRouter, // Vue Router
|
||||||
|
Vuex: Vuex, // Vuex
|
||||||
|
createPersistedState: null, // Vuex 持久化插件(可选)
|
||||||
|
componentMap: {} // 自定义组件映射
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 内置功能
|
||||||
|
|
||||||
|
### 1. 系统页面
|
||||||
|
- **登录页面**: `/login`
|
||||||
|
- **首页**: `/home`
|
||||||
|
- **错误页面**: `/401`, `/404`, `/500`
|
||||||
|
|
||||||
|
### 2. 系统管理
|
||||||
|
- **用户管理**: 系统用户的增删改查
|
||||||
|
- **角色管理**: 角色权限管理
|
||||||
|
- **菜单管理**: 动态菜单配置
|
||||||
|
- **日志管理**: 系统操作日志
|
||||||
|
|
||||||
|
### 3. 高级功能
|
||||||
|
- **动态表单**: 基于配置生成表单
|
||||||
|
- **动态表格**: 可配置的数据表格
|
||||||
|
- **文件上传**: 单文件/多文件上传
|
||||||
|
- **富文本编辑器**: WangEditor
|
||||||
|
- **代码编辑器**: Ace Editor
|
||||||
|
|
||||||
|
## API 使用
|
||||||
|
|
||||||
|
### HTTP 请求
|
||||||
|
```javascript
|
||||||
|
// GET 请求
|
||||||
|
framework.http.get('/api/users').then(res => {
|
||||||
|
console.log(res.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
// POST 请求
|
||||||
|
framework.http.post('/api/users', {
|
||||||
|
name: '张三',
|
||||||
|
age: 25
|
||||||
|
}).then(res => {
|
||||||
|
console.log(res.data)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 在组件中使用
|
||||||
|
this.$http.get('/api/users').then(res => {
|
||||||
|
console.log(res.data)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 工具函数
|
||||||
|
```javascript
|
||||||
|
// 使用框架提供的工具函数
|
||||||
|
const tools = framework.tools
|
||||||
|
|
||||||
|
// 日期格式化
|
||||||
|
tools.formatDate(new Date(), 'yyyy-MM-dd HH:mm:ss')
|
||||||
|
|
||||||
|
// 深拷贝
|
||||||
|
tools.deepClone(obj)
|
||||||
|
|
||||||
|
// 防抖
|
||||||
|
tools.debounce(fn, 500)
|
||||||
|
|
||||||
|
// 节流
|
||||||
|
tools.throttle(fn, 500)
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI 工具
|
||||||
|
```javascript
|
||||||
|
// 使用 UI 工具
|
||||||
|
const uiTool = framework.uiTool
|
||||||
|
|
||||||
|
// 成功提示
|
||||||
|
window.framework.uiTool.success('操作成功')
|
||||||
|
|
||||||
|
// 错误提示
|
||||||
|
window.framework.uiTool.error('操作失败')
|
||||||
|
|
||||||
|
// 确认对话框
|
||||||
|
window.framework.uiTool.confirm('确定删除吗?').then(() => {
|
||||||
|
// 确认后的操作
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. 依赖库版本
|
||||||
|
确保使用以下版本的依赖库:
|
||||||
|
- Vue: 2.6.x
|
||||||
|
- Vue Router: 3.x
|
||||||
|
- Vuex: 3.x
|
||||||
|
- iView (view-design): 4.x
|
||||||
|
- Axios: 0.21.x+
|
||||||
|
|
||||||
|
### 2. 路径问题
|
||||||
|
如果无法加载 admin-framework.js,检查路径是否正确:
|
||||||
|
```html
|
||||||
|
<!-- 确保路径指向正确的文件 -->
|
||||||
|
<script src="../dist/admin-framework.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. API 地址
|
||||||
|
记得修改配置中的 API 地址为实际的后端地址:
|
||||||
|
```javascript
|
||||||
|
const config = {
|
||||||
|
apiUrl: 'http://your-real-api.com/api/',
|
||||||
|
uploadUrl: 'http://your-real-api.com/api/upload'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 跨域问题
|
||||||
|
如果遇到跨域问题,需要配置后端 CORS 或使用代理。
|
||||||
|
|
||||||
|
## 开发建议
|
||||||
|
|
||||||
|
1. **开发时使用 build:dev**
|
||||||
|
- 生成 sourcemap,方便调试
|
||||||
|
- 代码不压缩,易读
|
||||||
|
|
||||||
|
2. **生产时使用 build**
|
||||||
|
- 代码压缩,体积小
|
||||||
|
- 无 sourcemap,安全
|
||||||
|
|
||||||
|
3. **使用浏览器调试工具**
|
||||||
|
```javascript
|
||||||
|
// 所有实例都挂载到 window 上,方便调试
|
||||||
|
window.app // Vue 实例
|
||||||
|
window.framework // 框架实例
|
||||||
|
```
|
||||||
|
|
||||||
|
## 更多信息
|
||||||
|
|
||||||
|
查看完整文档:`../_doc/完整使用文档.md`
|
||||||
|
|
||||||
248
admin/config/README.md
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
# Admin 前端配置说明
|
||||||
|
|
||||||
|
## 📁 配置文件结构
|
||||||
|
|
||||||
|
```
|
||||||
|
admin/
|
||||||
|
├── config/
|
||||||
|
│ ├── index.js # 主配置文件(支持多环境)
|
||||||
|
│ └── README.md # 配置说明文档(本文件)
|
||||||
|
├── env.development # 开发环境变量
|
||||||
|
├── env.test # 测试环境变量
|
||||||
|
└── env.production # 生产环境变量
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ 配置项说明
|
||||||
|
|
||||||
|
### 基础配置
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `title` | String | '仓库管理系统' | 系统标题 |
|
||||||
|
| `apiUrl` | String | - | 后端 API 地址 |
|
||||||
|
| `uploadUrl` | String | - | 文件上传地址 |
|
||||||
|
| `showSettings` | Boolean | true | 是否显示设置按钮 |
|
||||||
|
| `showTagsView` | Boolean | true | 是否显示标签栏 |
|
||||||
|
| `fixedHeader` | Boolean | true | 是否固定头部 |
|
||||||
|
| `sidebarLogo` | Boolean | true | 是否显示侧边栏 Logo |
|
||||||
|
| `cookieExpires` | Number | 1 | Token 在 Cookie 中存储的天数 |
|
||||||
|
| `themeColor` | String | '#2d8cf0' | 系统主题色 |
|
||||||
|
| `debug` | Boolean | false | 是否开启调试模式 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🌍 环境配置
|
||||||
|
|
||||||
|
### 开发环境(development)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
apiUrl: 'http://localhost:9098/admin_api/',
|
||||||
|
uploadUrl: 'http://localhost:9098/admin_api/upload',
|
||||||
|
debug: true // 开发环境显示调试信息
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**启动命令**:
|
||||||
|
```bash
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试环境(test)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
apiUrl: 'http://test.yourdomain.com/admin_api/',
|
||||||
|
uploadUrl: 'http://test.yourdomain.com/admin_api/upload',
|
||||||
|
debug: false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**启动命令**:
|
||||||
|
```bash
|
||||||
|
npm run build:test
|
||||||
|
```
|
||||||
|
|
||||||
|
### 生产环境(production)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
apiUrl: 'https://api.yourdomain.com/admin_api/',
|
||||||
|
uploadUrl: 'https://api.yourdomain.com/admin_api/upload',
|
||||||
|
debug: false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**启动命令**:
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 使用方法
|
||||||
|
|
||||||
|
### 在组件中使用配置
|
||||||
|
|
||||||
|
框架已将配置挂载到 `Vue.prototype.$config`,可以在任何组件中使用:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>{{ $config.title }}</h1>
|
||||||
|
<p>API 地址: {{ $config.apiUrl }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
mounted() {
|
||||||
|
console.log('系统标题:', this.$config.title)
|
||||||
|
console.log('API 地址:', this.$config.apiUrl)
|
||||||
|
console.log('是否调试模式:', this.$config.debug)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 在 API 服务中使用配置
|
||||||
|
|
||||||
|
HTTP 工具已自动使用 `apiUrl` 作为基础路径:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 无需手动拼接 apiUrl,框架会自动处理
|
||||||
|
this.$http.get('/user/list')
|
||||||
|
// 实际请求: http://localhost:9098/admin_api/user/list
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 修改配置
|
||||||
|
|
||||||
|
### 修改基础配置
|
||||||
|
|
||||||
|
编辑 `config/index.js` 中的 `baseConfig`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const baseConfig = {
|
||||||
|
title: '你的系统名称', // 修改系统标题
|
||||||
|
themeColor: '#409EFF', // 修改主题色
|
||||||
|
// ... 其他配置
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 修改环境配置
|
||||||
|
|
||||||
|
编辑对应环境的配置对象:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 开发环境配置
|
||||||
|
const developmentConfig = {
|
||||||
|
...baseConfig,
|
||||||
|
apiUrl: 'http://localhost:9098/admin_api/', // 修改开发环境 API 地址
|
||||||
|
uploadUrl: 'http://localhost:9098/admin_api/upload',
|
||||||
|
debug: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生产环境配置
|
||||||
|
const productionConfig = {
|
||||||
|
...baseConfig,
|
||||||
|
apiUrl: 'https://api.yourdomain.com/admin_api/', // 修改生产环境 API 地址
|
||||||
|
uploadUrl: 'https://api.yourdomain.com/admin_api/upload',
|
||||||
|
debug: false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 添加自定义配置
|
||||||
|
|
||||||
|
可以在任何环境配置中添加自定义字段:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const developmentConfig = {
|
||||||
|
...baseConfig,
|
||||||
|
apiUrl: 'http://localhost:9098/admin_api/',
|
||||||
|
uploadUrl: 'http://localhost:9098/admin_api/upload',
|
||||||
|
|
||||||
|
// 自定义配置
|
||||||
|
enableMock: true,
|
||||||
|
websocketUrl: 'ws://localhost:9099',
|
||||||
|
maxFileSize: 10 * 1024 * 1024, // 10MB
|
||||||
|
allowedFileTypes: ['image/jpeg', 'image/png', 'application/pdf']
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在组件中使用:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (this.$config.enableMock) {
|
||||||
|
console.log('启用 Mock 数据')
|
||||||
|
}
|
||||||
|
|
||||||
|
const ws = new WebSocket(this.$config.websocketUrl)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 部署说明
|
||||||
|
|
||||||
|
### 开发环境部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动开发服务器
|
||||||
|
npm run serve
|
||||||
|
|
||||||
|
# 或使用 yarn
|
||||||
|
yarn serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试环境部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建测试环境代码
|
||||||
|
npm run build:test
|
||||||
|
|
||||||
|
# 将 dist 目录部署到测试服务器
|
||||||
|
```
|
||||||
|
|
||||||
|
### 生产环境部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建生产环境代码
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 将 dist 目录部署到生产服务器
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 注意事项
|
||||||
|
|
||||||
|
1. **不要将敏感信息写入配置文件**
|
||||||
|
- API 密钥、数据库密码等敏感信息应通过环境变量传递
|
||||||
|
- 使用 `process.env.VUE_APP_*` 格式定义环境变量
|
||||||
|
|
||||||
|
2. **修改配置后需要重启**
|
||||||
|
- 修改配置文件后,需要重启开发服务器才能生效
|
||||||
|
- `Ctrl + C` 停止服务器,然后重新运行 `npm run serve`
|
||||||
|
|
||||||
|
3. **生产环境配置检查**
|
||||||
|
- 部署前务必检查生产环境的 `apiUrl` 是否正确
|
||||||
|
- 确保关闭 `debug` 模式
|
||||||
|
|
||||||
|
4. **跨域问题**
|
||||||
|
- 开发环境如遇到跨域问题,可以在 `vue.config.js` 中配置代理
|
||||||
|
- 生产环境需要后端配置 CORS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 相关文档
|
||||||
|
|
||||||
|
- [Vue CLI 环境变量和模式](https://cli.vuejs.org/zh/guide/mode-and-env.html)
|
||||||
|
- [AdminFramework 完整文档](../../_doc/admin_core完整使用文档.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**最后更新**: 2025-10-10
|
||||||
|
|
||||||
60
admin/config/index.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Admin 前端配置文件
|
||||||
|
* 支持多环境:development、sit、prod
|
||||||
|
* 打包时由 webpack DefinePlugin 注入 __APP_BUILD_ENV__(.env.sit / .env.prod)
|
||||||
|
*/
|
||||||
|
const buildEnv = (typeof __APP_BUILD_ENV__ !== 'undefined' ? __APP_BUILD_ENV__ : (typeof process !== 'undefined' && process.env && process.env.BUILD_ENV)) || (typeof process !== 'undefined' && process.env && process.env.NODE_ENV) || 'development'
|
||||||
|
|
||||||
|
// 基础配置
|
||||||
|
const baseConfig = {
|
||||||
|
title: '沁羿物流 · 仓库管理系统',
|
||||||
|
showSettings: true,
|
||||||
|
showTagsView: true,
|
||||||
|
fixedHeader: true,
|
||||||
|
sidebarLogo: true,
|
||||||
|
cookieExpires: 1,
|
||||||
|
themeColor: '#2d8cf0'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开发环境
|
||||||
|
const developmentConfig = {
|
||||||
|
...baseConfig,
|
||||||
|
apiUrl: 'http://localhost:9098/admin_api/',
|
||||||
|
uploadUrl: 'http://localhost:9098/admin_api/upload',
|
||||||
|
debug: true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SIT 环境(build:sit)- tennis 接口
|
||||||
|
const sitConfig = {
|
||||||
|
...baseConfig,
|
||||||
|
apiUrl: 'http://*/admin_api/',
|
||||||
|
uploadUrl: 'http://*/admin_api/upload',
|
||||||
|
debug: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生产环境(build:prod)— 与线上 Node 同域
|
||||||
|
const productionConfig = {
|
||||||
|
...baseConfig,
|
||||||
|
apiUrl: 'http://*/admin_api/',
|
||||||
|
uploadUrl: 'http://*/admin_api/upload',
|
||||||
|
debug: false
|
||||||
|
}
|
||||||
|
|
||||||
|
const configMap = {
|
||||||
|
development: developmentConfig,
|
||||||
|
sit: sitConfig,
|
||||||
|
production: productionConfig,
|
||||||
|
prod: productionConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = configMap[buildEnv] || developmentConfig
|
||||||
|
|
||||||
|
export default config
|
||||||
|
|
||||||
|
export {
|
||||||
|
baseConfig,
|
||||||
|
developmentConfig,
|
||||||
|
sitConfig,
|
||||||
|
productionConfig
|
||||||
|
}
|
||||||
|
|
||||||
7704
admin/package-lock.json
generated
Normal file
41
admin/package.json
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"name": "admin-framework-demo",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Admin Framework 本地示例",
|
||||||
|
"scripts": {
|
||||||
|
"install:deps": "npm install",
|
||||||
|
"dev": "webpack serve --mode development --open",
|
||||||
|
"build": "webpack --mode production",
|
||||||
|
"build:sit": "webpack --mode production --env env_file=.env.sit",
|
||||||
|
"build:prod": "webpack --mode production --env env_file=.env.prod",
|
||||||
|
"build:test": "webpack --mode test"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.27.2",
|
||||||
|
"jsbarcode": "^3.11.6",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
|
"view-design": "^4.7.0",
|
||||||
|
"vue": "^2.6.14",
|
||||||
|
"vue-router": "^3.5.3",
|
||||||
|
"vuex": "^3.6.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.12.0",
|
||||||
|
"@babel/preset-env": "^7.12.0",
|
||||||
|
"babel-loader": "^8.2.0",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"css-loader": "^5.0.0",
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
|
"file-loader": "^6.2.0",
|
||||||
|
"html-webpack-plugin": "^5.5.0",
|
||||||
|
"less": "^4.4.2",
|
||||||
|
"less-loader": "^12.3.0",
|
||||||
|
"style-loader": "^2.0.0",
|
||||||
|
"vue-loader": "^15.9.0",
|
||||||
|
"vue-style-loader": "^4.1.0",
|
||||||
|
"vue-template-compiler": "^2.6.14",
|
||||||
|
"webpack": "^5.0.0",
|
||||||
|
"webpack-cli": "^4.0.0",
|
||||||
|
"webpack-dev-server": "^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
admin/public/index.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>沁羿物流 · 仓库管理系统</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
31
admin/src/api/demo/tplDemoServer.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
class TplDemoServer {
|
||||||
|
async page(row) {
|
||||||
|
return await window.framework.http.post("/tpl_demo/page", row);
|
||||||
|
}
|
||||||
|
|
||||||
|
async all(param) {
|
||||||
|
return await window.framework.http.get("/tpl_demo/all", param || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
async detail(param) {
|
||||||
|
return await window.framework.http.get("/tpl_demo/detail", param);
|
||||||
|
}
|
||||||
|
|
||||||
|
async add(row) {
|
||||||
|
return await window.framework.http.post("/tpl_demo/add", row);
|
||||||
|
}
|
||||||
|
|
||||||
|
async edit(row) {
|
||||||
|
return await window.framework.http.post("/tpl_demo/edit", row);
|
||||||
|
}
|
||||||
|
|
||||||
|
async del(row) {
|
||||||
|
return await window.framework.http.post("/tpl_demo/del", row);
|
||||||
|
}
|
||||||
|
|
||||||
|
exportCsv(params) {
|
||||||
|
return window.framework.http.fileExport("/tpl_demo/export", params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new TplDemoServer();
|
||||||
4
admin/src/assets/css/index.less
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.ivu-menu-submenu .ivu-menu-item{
|
||||||
|
padding-top: 12px !important;
|
||||||
|
padding-bottom: 12px !important;
|
||||||
|
}
|
||||||
66
admin/src/components/CustomTabPane.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<div v-show="isActive" class="tab-pane">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'CustomTabPane',
|
||||||
|
props: {
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isActive: false,
|
||||||
|
parentTabs: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.findParentTabs()
|
||||||
|
if (this.parentTabs) {
|
||||||
|
this.parentTabs.registerTab({
|
||||||
|
label: this.label,
|
||||||
|
name: this.name
|
||||||
|
})
|
||||||
|
this.updateActiveState(this.parentTabs.activeTab)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
if (this.parentTabs) {
|
||||||
|
this.parentTabs.unregisterTab(this.name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
findParentTabs() {
|
||||||
|
let parent = this.$parent
|
||||||
|
while (parent) {
|
||||||
|
if (parent.$options.name === 'CustomTabs') {
|
||||||
|
this.parentTabs = parent
|
||||||
|
break
|
||||||
|
}
|
||||||
|
parent = parent.$parent
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateActiveState(activeTab) {
|
||||||
|
this.isActive = activeTab === this.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.tab-pane {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: visible;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
118
admin/src/components/CustomTabs.vue
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<template>
|
||||||
|
<div class="custom-tabs">
|
||||||
|
<div class="tabs-header">
|
||||||
|
<div
|
||||||
|
v-for="tab in tabs"
|
||||||
|
:key="tab.name"
|
||||||
|
:class="['tab-item', { active: activeTab === tab.name }]"
|
||||||
|
@click="handleTabClick(tab.name)">
|
||||||
|
{{ tab.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tabs-content">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'CustomTabs',
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeTab: this.value || '',
|
||||||
|
tabs: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(newVal) {
|
||||||
|
this.activeTab = newVal
|
||||||
|
this.updateChildren()
|
||||||
|
},
|
||||||
|
activeTab(newVal) {
|
||||||
|
this.updateChildren()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateChildren()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleTabClick(name) {
|
||||||
|
this.activeTab = name
|
||||||
|
this.$emit('input', name)
|
||||||
|
this.$emit('change', name)
|
||||||
|
this.updateChildren()
|
||||||
|
},
|
||||||
|
registerTab(tab) {
|
||||||
|
if (!this.tabs.find(t => t.name === tab.name)) {
|
||||||
|
this.tabs.push(tab)
|
||||||
|
// 如果没有激活的tab,设置第一个为激活
|
||||||
|
if (!this.activeTab && this.tabs.length > 0) {
|
||||||
|
this.activeTab = this.tabs[0].name
|
||||||
|
this.$emit('input', this.activeTab)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unregisterTab(tabName) {
|
||||||
|
const index = this.tabs.findIndex(t => t.name === tabName)
|
||||||
|
if (index > -1) {
|
||||||
|
this.tabs.splice(index, 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateChildren() {
|
||||||
|
this.$children.forEach(child => {
|
||||||
|
if (child.$options.name === 'CustomTabPane') {
|
||||||
|
child.updateActiveState(this.activeTab)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.custom-tabs {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-header {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid #e8eaec;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
padding: 12px 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
transition: all 0.3s;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.active {
|
||||||
|
color: #333;
|
||||||
|
border-bottom-color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-content {
|
||||||
|
padding: 20px 0;
|
||||||
|
min-height: 400px;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: visible;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
32
admin/src/framework/admin-framework.js
Normal file
1106
admin/src/framework/admin-framework.md
Normal file
29
admin/src/main.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// 引入 Admin Framework(框架内部已包含所有依赖和样式)
|
||||||
|
import AdminFramework from "./framework/admin-framework.js";
|
||||||
|
import Vue from "vue";
|
||||||
|
import componentMap from "./router/component-map.js";
|
||||||
|
|
||||||
|
import CustomTabs from "./components/CustomTabs.vue";
|
||||||
|
import CustomTabPane from "./components/CustomTabPane.vue";
|
||||||
|
|
||||||
|
import config from "../config/index.js";
|
||||||
|
|
||||||
|
import "./assets/css/index.less";
|
||||||
|
|
||||||
|
const apiUrl = config.apiUrl;
|
||||||
|
|
||||||
|
const app = AdminFramework.createApp({
|
||||||
|
title: "前后端模板",
|
||||||
|
apiUrl: apiUrl,
|
||||||
|
componentMap: componentMap,
|
||||||
|
onReady() {
|
||||||
|
console.log("应用已准备就绪");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
AdminFramework.registerComponents(Vue, {
|
||||||
|
CustomTabs: CustomTabs,
|
||||||
|
CustomTabPane: CustomTabPane,
|
||||||
|
});
|
||||||
|
|
||||||
|
app.$mount("#app");
|
||||||
461
admin/src/mixins/tableMixin.js
Normal file
@@ -0,0 +1,461 @@
|
|||||||
|
// 通用表格页面混入
|
||||||
|
import dynamicModelApi from '@/api/entity/dynamicModelApi.js';
|
||||||
|
import { mapFormFieldsToEditColumns } from '@/utils/formEditColumns.js';
|
||||||
|
import { getForeignRowLabel, rowsToIdOptions } from '@/utils/foreignRowLabel.js';
|
||||||
|
import { orderActionBtnsViewEditFirst } from '@/utils/actionBtnOrder.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
created() {
|
||||||
|
this.ensure_meta_shape();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/** 操作列:查看、编辑排在最左侧相邻,其余按钮顺序在后 */
|
||||||
|
renderRowActionBtns(h, btns) {
|
||||||
|
return window.framework.uiTool.getBtn(h, orderActionBtnsViewEditFirst(btns));
|
||||||
|
},
|
||||||
|
|
||||||
|
field_display_name(f) {
|
||||||
|
if (!f) return "";
|
||||||
|
return f.label || f.title || f.key || "";
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按 meta.formFields 整理行数据,供 editModal 绑定(InputNumber 不能接收 "")。
|
||||||
|
* 外键数字字段空值用 undefined;其它 number 空值或非数字用 0。
|
||||||
|
*/
|
||||||
|
normalizeRowForEditModal(row) {
|
||||||
|
if (!row || typeof row !== 'object') return row;
|
||||||
|
const fields =
|
||||||
|
this.meta && Array.isArray(this.meta.formFields) ? this.meta.formFields : [];
|
||||||
|
if (!fields.length) return { ...row };
|
||||||
|
const o = { ...row };
|
||||||
|
for (const f of fields) {
|
||||||
|
if (!f || f.form === false || f.type !== 'number') continue;
|
||||||
|
const k = f.key;
|
||||||
|
const v = o[k];
|
||||||
|
if (/_id$/.test(k)) {
|
||||||
|
if (v === '' || v === null || v === undefined) {
|
||||||
|
o[k] = undefined;
|
||||||
|
} else {
|
||||||
|
const n = Number(v);
|
||||||
|
o[k] = Number.isNaN(n) ? undefined : n;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (v === '' || v === null || v === undefined) {
|
||||||
|
o[k] = 0;
|
||||||
|
} else {
|
||||||
|
const n = Number(v);
|
||||||
|
o[k] = Number.isNaN(n) ? 0 : n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 兼容页面只定义 fields 的场景,统一补齐 formFields / columns
|
||||||
|
ensure_meta_shape() {
|
||||||
|
if (!this.meta || typeof this.meta !== 'object') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = Array.isArray(this.meta.formFields)
|
||||||
|
? this.meta.formFields
|
||||||
|
: (Array.isArray(this.meta.fields) ? this.meta.fields : []);
|
||||||
|
|
||||||
|
// code 字段:后端自动生成,表单中不显示
|
||||||
|
fields.forEach((f) => {
|
||||||
|
if (f && f.key === 'code') {
|
||||||
|
f.form = false;
|
||||||
|
f.required = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!Array.isArray(this.meta.formFields)) {
|
||||||
|
const formFields = fields.filter((f) => f && f.form !== false);
|
||||||
|
this.$set(this.meta, 'formFields', formFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(this.meta.columns)) {
|
||||||
|
const columns = fields
|
||||||
|
.filter((f) => f && f.list)
|
||||||
|
.map((f) => ({
|
||||||
|
key: f.key,
|
||||||
|
title: f.label || f.title,
|
||||||
|
minWidth: f.minWidth,
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.$set(this.meta, 'columns', columns);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 通用的编辑列配置生成方法(实现见 utils/formEditColumns.js)
|
||||||
|
generateEditColumns(formFields, selectSources) {
|
||||||
|
return mapFormFieldsToEditColumns(formFields, selectSources);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标准列表列:外键列显示关联名称(select_sources)、布尔、日期等
|
||||||
|
* meta 需含 columns,以及 fields 或 formFields(带 type)
|
||||||
|
*/
|
||||||
|
buildListColumnsFromMeta(meta, selectSources) {
|
||||||
|
if (!meta || !Array.isArray(meta.columns)) return [];
|
||||||
|
const allFields = meta.fields || meta.formFields || [];
|
||||||
|
return this.generateListColumns(meta.columns, allFields, selectSources);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 通用的列表列配置生成方法
|
||||||
|
generateListColumns(columns, allFields, selectSources) {
|
||||||
|
if (!columns || !Array.isArray(columns)) return [];
|
||||||
|
|
||||||
|
const fields = Array.isArray(allFields) ? allFields : [];
|
||||||
|
|
||||||
|
/** 系统统一时间字段(与后端 create_time / last_modify_time 一致),排在业务列之后、操作列之前 */
|
||||||
|
const colList = [...columns];
|
||||||
|
const keySet = new Set(colList.map((c) => c && c.key).filter(Boolean));
|
||||||
|
if (!keySet.has('create_time')) {
|
||||||
|
colList.push({ key: 'create_time', title: '创建时间', minWidth: 160 });
|
||||||
|
keySet.add('create_time');
|
||||||
|
}
|
||||||
|
if (!keySet.has('last_modify_time')) {
|
||||||
|
colList.push({ key: 'last_modify_time', title: '最后修改时间', minWidth: 160 });
|
||||||
|
keySet.add('last_modify_time');
|
||||||
|
}
|
||||||
|
|
||||||
|
const boolKeys = new Set(fields.filter((f) => f.type === 'bool').map((f) => f.key));
|
||||||
|
const selectKeys = new Set(fields.filter((f) => f.type === 'select').map((f) => f.key));
|
||||||
|
|
||||||
|
return colList.map((c) => {
|
||||||
|
// 外键:已加载下拉数据时显示名称(含仓库/库区/巷道及 mat_sku 等)
|
||||||
|
if (
|
||||||
|
selectSources &&
|
||||||
|
selectSources[c.key] &&
|
||||||
|
Array.isArray(selectSources[c.key]) &&
|
||||||
|
selectSources[c.key].length &&
|
||||||
|
/_id$/.test(c.key)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...c,
|
||||||
|
render: (h, params) => {
|
||||||
|
const id = params.row[c.key];
|
||||||
|
const source = selectSources[c.key];
|
||||||
|
const item = source && source.find((w) => String(w.key) === String(id));
|
||||||
|
return h('span', item ? item.value : id != null && id !== '' ? String(id) : '-');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理选择字段显示(status 按字段 source_key)
|
||||||
|
if (selectKeys.has(c.key)) {
|
||||||
|
const fieldDef = fields.find((f) => f.key === c.key);
|
||||||
|
const source_key = fieldDef && fieldDef.source_key;
|
||||||
|
return {
|
||||||
|
...c,
|
||||||
|
render: (h, params) => {
|
||||||
|
const value = params.row[c.key];
|
||||||
|
let source;
|
||||||
|
if (source_key && selectSources && selectSources[source_key]) {
|
||||||
|
source = selectSources[source_key];
|
||||||
|
} else if (c.key === 'type') {
|
||||||
|
source = selectSources && selectSources.warehouse_type_options;
|
||||||
|
} else if (c.key === 'tray_type_id') {
|
||||||
|
source = selectSources && selectSources.tray_type_id;
|
||||||
|
} else if (c.key === 'lock_type') {
|
||||||
|
source = selectSources && selectSources.lock_type_options;
|
||||||
|
} else if (c.key === 'status' && source_key) {
|
||||||
|
source = selectSources && selectSources[source_key];
|
||||||
|
} else if (c.key === 'status') {
|
||||||
|
source = selectSources && selectSources.tray_status_options;
|
||||||
|
} else {
|
||||||
|
source = selectSources && selectSources.yes_no_options;
|
||||||
|
}
|
||||||
|
const option = source && source.find((opt) => String(opt.key) === String(value));
|
||||||
|
return h('span', option ? option.value : value);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理布尔字段显示
|
||||||
|
if (boolKeys.has(c.key)) {
|
||||||
|
return {
|
||||||
|
...c,
|
||||||
|
render: (h, params) => {
|
||||||
|
const v = params.row[c.key];
|
||||||
|
const on = v === true || v === 1 || v === '1';
|
||||||
|
return h('Tag', { props: { color: on ? 'green' : 'default' } }, on ? '是' : '否');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理日期字段显示
|
||||||
|
if (this.isDateLikeColumnKey && this.isDateLikeColumnKey(c.key)) {
|
||||||
|
return {
|
||||||
|
...c,
|
||||||
|
render: (h, params) => {
|
||||||
|
const raw =
|
||||||
|
c.key === 'create_time' || c.key === 'last_modify_time'
|
||||||
|
? this.pickRowTimeCell(params.row, c.key)
|
||||||
|
: params.row[c.key];
|
||||||
|
return h('span', this.formatCellDate(raw, c.key));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { ...c };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// 从 Vuex store (wms_dict) 获取字典选项
|
||||||
|
getDictOptions(key) {
|
||||||
|
if (!this.$store || !this.$store.state.wms_dict) return [];
|
||||||
|
return this.$store.state.wms_dict[key] || [];
|
||||||
|
},
|
||||||
|
|
||||||
|
// 通用的数据源加载方法(从 store 读取字典,不再请求 sys_parameter)
|
||||||
|
async loadSelectSources(formFields, idFieldToModelFn, getRowLabelFn) {
|
||||||
|
const sources = {};
|
||||||
|
|
||||||
|
sources.yes_no_options = this.getDictOptions('yes_no');
|
||||||
|
sources.warehouse_type_options = this.getDictOptions('warehouse_type');
|
||||||
|
sources.lock_type_options = this.getDictOptions('lock_type');
|
||||||
|
sources.tray_status_options = this.getDictOptions('tray_status');
|
||||||
|
sources.tray_type_status_options = this.getDictOptions('tray_type_status');
|
||||||
|
sources.receiving_status_options = this.getDictOptions('receiving_status');
|
||||||
|
sources.inbound_status_options = this.getDictOptions('inbound_status');
|
||||||
|
sources.send_status_options = this.getDictOptions('send_status');
|
||||||
|
sources.outbound_status_options = this.getDictOptions('outbound_status');
|
||||||
|
sources.move_status_options = this.getDictOptions('move_status');
|
||||||
|
sources.damage_status_options = this.getDictOptions('damage_status');
|
||||||
|
sources.count_status_options = this.getDictOptions('count_status');
|
||||||
|
sources.receiving_type_options = this.getDictOptions('receiving_type');
|
||||||
|
sources.send_type_options = this.getDictOptions('send_type');
|
||||||
|
sources.fee_charge_type_options = this.getDictOptions('fee_charge_type_options');
|
||||||
|
sources.fee_generate_type_options = this.getDictOptions('fee_generate_type_options');
|
||||||
|
sources.fee_collection_type_options = this.getDictOptions('fee_collection_type_options');
|
||||||
|
sources.fee_item_inbound_options = this.getDictOptions('fee_item_inbound_options');
|
||||||
|
sources.fee_item_outbound_options = this.getDictOptions('fee_item_outbound_options');
|
||||||
|
sources.packaging_status_options = this.getDictOptions('packaging_status_options');
|
||||||
|
|
||||||
|
// 加载外键数据
|
||||||
|
const idFields = formFields
|
||||||
|
.filter((f) => f.type === 'number' && /_id$/.test(f.key))
|
||||||
|
.map((f) => f.key);
|
||||||
|
|
||||||
|
for (const k of idFields) {
|
||||||
|
const model = idFieldToModelFn(k);
|
||||||
|
if (!model) {
|
||||||
|
sources[k] = [];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await dynamicModelApi.all(model);
|
||||||
|
const rows = res && res.code === 0 && res.data ? res.data : [];
|
||||||
|
sources[k] = rowsToIdOptions(rows, getRowLabelFn || getForeignRowLabel);
|
||||||
|
} catch (e) {
|
||||||
|
sources[k] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sources;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 通用的空表单构建方法
|
||||||
|
generateEmptyForm(formFields) {
|
||||||
|
const o = {};
|
||||||
|
if (!formFields) return o;
|
||||||
|
|
||||||
|
for (const f of formFields) {
|
||||||
|
if (f.type === 'bool') o[f.key] = false;
|
||||||
|
else if (f.type === 'number') {
|
||||||
|
// 外键用 Select 时,0 易被当成「已选」且无法匹配 Option,校验与展示都不直观
|
||||||
|
o[f.key] = /_id$/.test(f.key) ? undefined : 0;
|
||||||
|
} else if (f.type === 'select') {
|
||||||
|
if (f.key === 'lock_type') o[f.key] = 0;
|
||||||
|
else if (f.data_type === 'number' && /_id$/.test(f.key)) o[f.key] = undefined;
|
||||||
|
else o[f.key] = '';
|
||||||
|
} else o[f.key] = '';
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 通用的日期相关方法
|
||||||
|
isDateLikeColumnKey(key) {
|
||||||
|
if (!key) return false;
|
||||||
|
return (
|
||||||
|
key === 'stat_date' ||
|
||||||
|
key === 'create_time' ||
|
||||||
|
key === 'last_modify_time' ||
|
||||||
|
/_time$/.test(key) ||
|
||||||
|
/_date$/.test(key)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表「创建时间 / 最后修改时间」与后端 Sequelize 字段对齐:
|
||||||
|
|
||||||
|
*/
|
||||||
|
pickRowTimeCell(row, key) {
|
||||||
|
if (!row || !key) return undefined;
|
||||||
|
if (key === 'create_time') {
|
||||||
|
return row.create_time;
|
||||||
|
}
|
||||||
|
if (key === 'last_modify_time') {
|
||||||
|
return row.last_modify_time;
|
||||||
|
}
|
||||||
|
return row[key];
|
||||||
|
},
|
||||||
|
|
||||||
|
formatCellDate(v, key) {
|
||||||
|
if (v === null || v === undefined || v === '') return '-';
|
||||||
|
const d = v instanceof Date ? v : new Date(v);
|
||||||
|
if (Number.isNaN(d.getTime())) return String(v);
|
||||||
|
const pad = (n) => String(n).padStart(2, '0');
|
||||||
|
const y = d.getFullYear();
|
||||||
|
const m = pad(d.getMonth() + 1);
|
||||||
|
const day = pad(d.getDate());
|
||||||
|
const hh = pad(d.getHours());
|
||||||
|
const mm = pad(d.getMinutes());
|
||||||
|
const dateOnly = key === 'stat_date' || (key && /_date$/.test(key) && !/_time$/.test(key));
|
||||||
|
if (dateOnly) return y + '-' + m + '-' + day;
|
||||||
|
return y + '-' + m + '-' + day + ' ' + hh + ':' + mm;
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 外键行展示文案;实现见 @/utils/foreignRowLabel.js */
|
||||||
|
getRowLabel(row) {
|
||||||
|
return getForeignRowLabel(row);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 通用的数据规范化方法
|
||||||
|
generatePayload(raw, formFields) {
|
||||||
|
const o = { ...raw };
|
||||||
|
if (!formFields) return o;
|
||||||
|
|
||||||
|
const typeByKey = {};
|
||||||
|
const selectDataTypeByKey = {};
|
||||||
|
for (const f of formFields) {
|
||||||
|
if (!f || !f.key) continue;
|
||||||
|
typeByKey[f.key] = f.type;
|
||||||
|
if (f.data_type) selectDataTypeByKey[f.key] = f.data_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const k of Object.keys(o)) {
|
||||||
|
const t = typeByKey[k];
|
||||||
|
let v = o[k];
|
||||||
|
|
||||||
|
if (t === 'number') {
|
||||||
|
if (v === '' || v === null || v === undefined) {
|
||||||
|
o[k] = null;
|
||||||
|
} else {
|
||||||
|
const n = Number(v);
|
||||||
|
o[k] = Number.isNaN(n) ? v : n;
|
||||||
|
}
|
||||||
|
} else if (t === 'bool') {
|
||||||
|
o[k] = v === true || v === 1 || v === '1';
|
||||||
|
} else if (t === 'select') {
|
||||||
|
const numSelect = k === 'lock_type' || selectDataTypeByKey[k] === 'number';
|
||||||
|
if (numSelect) {
|
||||||
|
if (v === '' || v === null || v === undefined) {
|
||||||
|
o[k] = null;
|
||||||
|
} else {
|
||||||
|
const n = Number(v);
|
||||||
|
o[k] = Number.isNaN(n) ? v : n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (t === 'date' || t === 'datetime') {
|
||||||
|
if (v instanceof Date) {
|
||||||
|
const pad = (n) => String(n).padStart(2, '0');
|
||||||
|
if (t === 'date') {
|
||||||
|
o[k] = v.getFullYear() + '-' + pad(v.getMonth() + 1) + '-' + pad(v.getDate());
|
||||||
|
} else {
|
||||||
|
o[k] = v.getFullYear() + '-' + pad(v.getMonth() + 1) + '-' + pad(v.getDate()) +
|
||||||
|
' ' + pad(v.getHours()) + ':' + pad(v.getMinutes()) + ':' + pad(v.getSeconds());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 通用的表单验证规则生成方法(与 iView Form / async-validator 约定一致:输入类 blur,选择类 change)
|
||||||
|
generateEditRules(formFields) {
|
||||||
|
if (!formFields) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const rules = {};
|
||||||
|
for (const f of formFields) {
|
||||||
|
if (f.form === false) continue;
|
||||||
|
if (!f.required) continue;
|
||||||
|
if (f.type === 'bool') continue;
|
||||||
|
const fieldTitle = f.label || f.title || f.key;
|
||||||
|
|
||||||
|
if (f.type === 'date' || f.type === 'datetime') {
|
||||||
|
rules[f.key] = [
|
||||||
|
{ required: true, type: 'date', message: '请选择' + fieldTitle, trigger: 'change' }
|
||||||
|
];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数字 id 用 Select 渲染(如 tenant_id:type=select + data_type=number),Option 常为字符串;
|
||||||
|
// 仅用 async-validator required 易与 iView 绑定不一致导致「已选仍报错」
|
||||||
|
if (f.type === 'select' && f.data_type === 'number' && /_id$/.test(f.key)) {
|
||||||
|
rules[f.key] = [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
trigger: 'change',
|
||||||
|
validator(rule, value, callback) {
|
||||||
|
const n = value === '' || value === null || value === undefined ? NaN : Number(value);
|
||||||
|
if (Number.isNaN(n) || n === 0) {
|
||||||
|
callback(new Error('请选择' + fieldTitle));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f.type === 'select') {
|
||||||
|
rules[f.key] = [
|
||||||
|
{ required: true, message: '请选择' + fieldTitle, trigger: 'change' }
|
||||||
|
];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 外键:generateEditColumns 里用 Select,值为数字 id,但 Option 上可能是字符串;不能用 type:'number' 直接校验
|
||||||
|
if (f.type === 'number' && /_id$/.test(f.key)) {
|
||||||
|
rules[f.key] = [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
trigger: 'change',
|
||||||
|
validator(rule, value, callback) {
|
||||||
|
const n = value === '' || value === null || value === undefined ? NaN : Number(value);
|
||||||
|
if (Number.isNaN(n) || n === 0) {
|
||||||
|
callback(new Error('请选择' + fieldTitle));
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f.type === 'number') {
|
||||||
|
rules[f.key] = [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
type: 'number',
|
||||||
|
message: '请填写' + fieldTitle,
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
rules[f.key] = [{ required: true, message: '请填写' + fieldTitle, trigger: 'blur' }];
|
||||||
|
}
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
10
admin/src/router/component-map.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// 动态路由组件映射:key 与 sys_menu.component 一致(可带或不带 .vue)
|
||||||
|
|
||||||
|
import TplDemoPage from "../views/demo/tpl_demo.vue";
|
||||||
|
|
||||||
|
const componentMap = {
|
||||||
|
"demo/tpl_demo": TplDemoPage,
|
||||||
|
tpl_demo: TplDemoPage,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default componentMap;
|
||||||
17
admin/src/utils/actionBtnOrder.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* 列表操作列:将「查看」「编辑」排到最前且相邻(先查看后编辑),其余按钮保持原有相对顺序在后。
|
||||||
|
* @param {{ title?: string, type?: string, click?: Function }[]} btns
|
||||||
|
*/
|
||||||
|
export function orderActionBtnsViewEditFirst(btns) {
|
||||||
|
if (!Array.isArray(btns) || btns.length === 0) {
|
||||||
|
return btns || [];
|
||||||
|
}
|
||||||
|
const firstByTitle = (t) => btns.find((b) => b && b.title === t);
|
||||||
|
const v = firstByTitle('查看');
|
||||||
|
const e = firstByTitle('编辑');
|
||||||
|
const head = [];
|
||||||
|
if (v) head.push(v);
|
||||||
|
if (e) head.push(e);
|
||||||
|
const tail = btns.filter((b) => b && b !== v && b !== e);
|
||||||
|
return [...head, ...tail];
|
||||||
|
}
|
||||||
40
admin/src/utils/foreignRowLabel.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* 外键关联行 → 下拉/表格可读文案(物料、仓库、单号等)。
|
||||||
|
* 与 tableMixin.getRowLabel 语义一致;页面加载 select_sources 时请用 rowsToIdOptions,避免各处重复 map。
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getForeignRowLabel(row) {
|
||||||
|
if (!row) return '';
|
||||||
|
return (
|
||||||
|
row.sku_name ||
|
||||||
|
row.spu_name ||
|
||||||
|
row.category_name ||
|
||||||
|
row.name ||
|
||||||
|
row.code ||
|
||||||
|
row.spu_code ||
|
||||||
|
row.sku_code ||
|
||||||
|
row.title ||
|
||||||
|
row.label ||
|
||||||
|
String(row.id != null ? row.id : '')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 /model/all 等接口返回的行列表转为 Select 用的 { key, value }(key 与数字 id 对齐)
|
||||||
|
* @param {Array} rows
|
||||||
|
* @param {function(object): string} [getLabel=getForeignRowLabel] 自定义展示,例如收货明细「编码 - 名称」
|
||||||
|
*/
|
||||||
|
export function rowsToIdOptions(rows, getLabel = getForeignRowLabel) {
|
||||||
|
if (!Array.isArray(rows)) return [];
|
||||||
|
return rows.map((r) => {
|
||||||
|
const rawId = r.id;
|
||||||
|
const key =
|
||||||
|
rawId !== null && rawId !== undefined && rawId !== ''
|
||||||
|
? Number(rawId)
|
||||||
|
: rawId;
|
||||||
|
return {
|
||||||
|
key: Number.isFinite(key) ? key : rawId,
|
||||||
|
value: getLabel(r),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
82
admin/src/utils/formEditColumns.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* 将 meta.formFields / meta.fields 转为 editModal 用的列配置(与 tableMixin.generateEditColumns 一致)。
|
||||||
|
* 纯函数,便于单测;页面仍通过 mixin 的 this.generateEditColumns 调用。
|
||||||
|
*
|
||||||
|
* @param {Array<{ key, type, label?, title?, form?, required?, source_key? }>} formFields
|
||||||
|
* @param {Record<string, Array<{ key, value }>>} [selectSources]
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
export function mapFormFieldsToEditColumns(formFields, selectSources) {
|
||||||
|
if (!formFields || !Array.isArray(formFields)) return [];
|
||||||
|
const visible = formFields.filter((f) => f && f.form !== false);
|
||||||
|
|
||||||
|
return visible.map((f) => {
|
||||||
|
const fieldTitle = f.label || f.title || f.key;
|
||||||
|
const col = { key: f.key, title: fieldTitle, required: !!f.required };
|
||||||
|
|
||||||
|
switch (f.type) {
|
||||||
|
case 'number':
|
||||||
|
if (/_id$/.test(f.key)) {
|
||||||
|
col.com = 'Select';
|
||||||
|
col.data_type = 'number';
|
||||||
|
col.placeholder = '请选择' + fieldTitle;
|
||||||
|
col.source = (selectSources && selectSources[f.key]) || [];
|
||||||
|
} else {
|
||||||
|
col.com = 'InputNumber';
|
||||||
|
col.data_type = 'number';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'select':
|
||||||
|
col.com = 'Select';
|
||||||
|
// 与后端数字枚举一致时用 number,否则 Option 与 v-model 类型不一致无法显示已选
|
||||||
|
col.data_type = f.data_type === 'number' ? 'number' : 'string';
|
||||||
|
col.placeholder = '请选择' + fieldTitle;
|
||||||
|
col.source = (selectSources && selectSources[f.source_key]) || [];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'textarea':
|
||||||
|
col.com = 'TextArea';
|
||||||
|
col.rows = 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'password':
|
||||||
|
col.com = 'Input';
|
||||||
|
col.type = 'password';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'bool':
|
||||||
|
col.com = 'Switch';
|
||||||
|
col.data_type = 'boolean';
|
||||||
|
col.required = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'date':
|
||||||
|
col.com = 'DatePicker';
|
||||||
|
col.data_type = 'date';
|
||||||
|
col.type = 'date';
|
||||||
|
col.format = 'yyyy-MM-dd';
|
||||||
|
col.transfer = true;
|
||||||
|
col.placeholder = '请选择' + fieldTitle;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'datetime':
|
||||||
|
col.com = 'DatePicker';
|
||||||
|
col.data_type = 'date';
|
||||||
|
col.type = 'datetime';
|
||||||
|
col.format = 'yyyy-MM-dd HH:mm';
|
||||||
|
col.transfer = true;
|
||||||
|
col.placeholder = '请选择' + fieldTitle;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
col.com = 'Input';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f.disabled) {
|
||||||
|
col.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return col;
|
||||||
|
});
|
||||||
|
}
|
||||||
9
admin/src/utils/listColumn.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* 列表列简写:生成 { key, title, minWidth },供 meta.columns 使用。
|
||||||
|
* import { col } from '@/utils/listColumn.js' 与原先页面内 function col 等价。
|
||||||
|
*/
|
||||||
|
export function listCol(key, title, minWidth) {
|
||||||
|
return { key, title, minWidth };
|
||||||
|
}
|
||||||
|
|
||||||
|
export { listCol as col };
|
||||||
210
admin/src/views/demo/tpl_demo.vue
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
<template>
|
||||||
|
<div class="content-view" v-if="meta">
|
||||||
|
<div class="table-head-tool">
|
||||||
|
<div class="table-head-row">
|
||||||
|
<div class="table-head-actions">
|
||||||
|
<Button type="primary" @click="showAdd">新增</Button>
|
||||||
|
<Button type="default" @click="query(1)" class="ml10">刷新</Button>
|
||||||
|
<Button type="default" @click="exportCsv" class="ml10">导出</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Form ref="searchForm" :model="gridOption.param.seachOption" inline :label-width="80" class="search-form">
|
||||||
|
<FormItem :label-width="20" class="search-input-group">
|
||||||
|
<Select v-model="gridOption.param.seachOption.key" style="width: 180px" :placeholder="seachTypePlaceholder">
|
||||||
|
<Option v-for="item in meta.seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
|
||||||
|
</Select>
|
||||||
|
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 280px" search
|
||||||
|
placeholder="请输入关键字" @on-search="query(1)" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem class="search-action-group">
|
||||||
|
<Button type="primary" @click="query(1)">查询</Button>
|
||||||
|
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
<div class="table-body">
|
||||||
|
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
|
||||||
|
@changePage="query"></tables>
|
||||||
|
</div>
|
||||||
|
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules" width="640" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import tplDemoServer from "@/api/demo/tplDemoServer.js";
|
||||||
|
import tableMixin from "@/mixins/tableMixin.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "TplDemoPage",
|
||||||
|
mixins: [tableMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
select_sources: {},
|
||||||
|
gridOption: {
|
||||||
|
param: { seachOption: { key: "", value: "" }, pageOption: { page: 1, pageSize: 20, total: 0 } },
|
||||||
|
data: [],
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
modelName() {
|
||||||
|
return "tpl_demo";
|
||||||
|
},
|
||||||
|
meta() {
|
||||||
|
return {
|
||||||
|
title: "演示数据",
|
||||||
|
seachTypes: [
|
||||||
|
{ key: "title", value: "标题" },
|
||||||
|
{ key: "remark", value: "备注" },
|
||||||
|
],
|
||||||
|
fields: [
|
||||||
|
{ key: "id", title: "ID", type: "number", list: true, form: false, minWidth: 80 },
|
||||||
|
{ key: "title", title: "标题", type: "input", list: true, form: true, required: true, minWidth: 200 },
|
||||||
|
{ key: "remark", title: "备注", type: "input", list: true, form: true, required: false, minWidth: 220 },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
seachTypePlaceholder() {
|
||||||
|
const st = this.meta && this.meta.seachTypes;
|
||||||
|
if (!st || !st.length) {
|
||||||
|
return "请选择";
|
||||||
|
}
|
||||||
|
const k = this.gridOption.param.seachOption.key;
|
||||||
|
const f = st.find((x) => x.key === k);
|
||||||
|
return f ? f.value : "请选择搜索字段";
|
||||||
|
},
|
||||||
|
editColumns() {
|
||||||
|
if (!this.meta || !Array.isArray(this.meta.fields)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.generateEditColumns(this.meta.fields.filter((f) => f.form !== false), this.select_sources);
|
||||||
|
},
|
||||||
|
listColumns() {
|
||||||
|
if (!this.meta || !Array.isArray(this.meta.fields)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const listFields = this.meta.fields.filter((f) => f.list !== false);
|
||||||
|
const cols = this.generateListColumns(listFields, this.meta.fields, this.select_sources);
|
||||||
|
cols.push({
|
||||||
|
title: "操作",
|
||||||
|
key: "action",
|
||||||
|
width: 180,
|
||||||
|
type: "template",
|
||||||
|
render: (h, params) => {
|
||||||
|
return this.renderRowActionBtns(h, [
|
||||||
|
{ title: "编辑", type: "info", click: () => this.showEdit(params.row) },
|
||||||
|
{ title: "删除", type: "error", click: () => this.delConfirm(params.row) },
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return cols;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.load_select_sources();
|
||||||
|
this.resetSearchFromMeta();
|
||||||
|
this.syncEditRules();
|
||||||
|
this.query(1);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
id_field_to_model() {
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
async load_select_sources() {
|
||||||
|
this.select_sources = await this.loadSelectSources(
|
||||||
|
this.meta.fields,
|
||||||
|
this.id_field_to_model.bind(this),
|
||||||
|
this.getRowLabel.bind(this)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
syncEditRules() {
|
||||||
|
this.gridOption.rules = this.generateEditRules(this.meta.fields.filter((f) => f.form !== false));
|
||||||
|
},
|
||||||
|
resetSearchFromMeta() {
|
||||||
|
if (!this.meta) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const firstKey = (this.meta.seachTypes[0] && this.meta.seachTypes[0].key) || "title";
|
||||||
|
this.gridOption.param.seachOption = { key: firstKey, value: "" };
|
||||||
|
this.gridOption.param.pageOption = { page: 1, pageSize: 20, total: 0 };
|
||||||
|
},
|
||||||
|
buildEmptyForm() {
|
||||||
|
return this.generateEmptyForm(this.meta.fields.filter((f) => f.form !== false));
|
||||||
|
},
|
||||||
|
normalizePayload(raw) {
|
||||||
|
return this.generatePayload(raw, this.meta.fields.filter((f) => f.form !== false));
|
||||||
|
},
|
||||||
|
async query(page) {
|
||||||
|
if (!this.modelName || !this.meta) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (page) {
|
||||||
|
this.gridOption.param.pageOption.page = page;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await tplDemoServer.page(this.gridOption.param);
|
||||||
|
if (res && res.code === 0) {
|
||||||
|
this.gridOption.data = res.data.rows || [];
|
||||||
|
this.gridOption.param.pageOption.total = res.data.count || 0;
|
||||||
|
} else {
|
||||||
|
this.$Message.error((res && (res.message || res.Msg)) || "查询失败");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.$Message.error("查询失败: " + (e && e.message ? e.message : String(e)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetQuery() {
|
||||||
|
this.resetSearchFromMeta();
|
||||||
|
this.query(1);
|
||||||
|
},
|
||||||
|
showAdd() {
|
||||||
|
this.$refs.editModal.addShow(this.buildEmptyForm(), async (data) => {
|
||||||
|
await this.persist(data, false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showEdit(row) {
|
||||||
|
this.$refs.editModal.editShow(this.normalizeRowForEditModal({ ...row }), async (data) => {
|
||||||
|
await this.persist(data, true);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async persist(data, isEdit) {
|
||||||
|
const payload = this.normalizePayload(data);
|
||||||
|
const req = isEdit ? tplDemoServer.edit(payload) : tplDemoServer.add(payload);
|
||||||
|
const res = await req;
|
||||||
|
if (res && res.code === 0) {
|
||||||
|
this.$Message.success(isEdit ? "保存成功" : "新增成功");
|
||||||
|
this.query(1);
|
||||||
|
} else {
|
||||||
|
this.$Message.error((res && (res.message || res.Msg)) || "保存失败");
|
||||||
|
throw new Error("save failed");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
delConfirm(row) {
|
||||||
|
this.$Modal.confirm({
|
||||||
|
title: "确认删除",
|
||||||
|
content: "确定删除该条记录?",
|
||||||
|
onOk: async () => {
|
||||||
|
const res = await tplDemoServer.del({ id: row.id });
|
||||||
|
if (res && res.code === 0) {
|
||||||
|
this.$Message.success("已删除");
|
||||||
|
this.query(1);
|
||||||
|
} else {
|
||||||
|
this.$Message.error((res && (res.message || res.Msg)) || "删除失败");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
exportCsv() {
|
||||||
|
if (!this.modelName || !this.meta) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
tplDemoServer.exportCsv({ param: this.gridOption.param });
|
||||||
|
} catch (e) {
|
||||||
|
this.$Message.error("导出失败: " + (e && e.message ? e.message : String(e)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
118
admin/webpack.config.js
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const webpack = require('webpack')
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||||
|
const { VueLoaderPlugin } = require('vue-loader')
|
||||||
|
|
||||||
|
module.exports = (env, argv) => {
|
||||||
|
// 仅当传入 env_file 时加载 .env(build:sit / build:prod),否则 dev 用 development,build 用 prod
|
||||||
|
const envFile = env && env.env_file
|
||||||
|
if (envFile) {
|
||||||
|
require('dotenv').config({ path: path.resolve(__dirname, envFile) })
|
||||||
|
}
|
||||||
|
const buildEnv = process.env.BUILD_ENV || (argv.mode === 'production' ? 'prod' : 'development')
|
||||||
|
return {
|
||||||
|
entry: './src/main.js',
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
filename: 'js/[name].[contenthash:8].js',
|
||||||
|
chunkFilename: 'js/[name].[contenthash:8].chunk.js',
|
||||||
|
clean: true
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
splitChunks: {
|
||||||
|
chunks: 'all',
|
||||||
|
minSize: 20000,
|
||||||
|
maxSize: 244000,
|
||||||
|
cacheGroups: {
|
||||||
|
// Vue 相关库单独打包
|
||||||
|
vue: {
|
||||||
|
test: /[\\/]node_modules[\\/](vue|vue-router|vuex|vue-loader|vue-template-compiler)[\\/]/,
|
||||||
|
name: 'vue',
|
||||||
|
priority: 30,
|
||||||
|
reuseExistingChunk: true
|
||||||
|
},
|
||||||
|
// UI 库单独打包
|
||||||
|
ui: {
|
||||||
|
test: /[\\/]node_modules[\\/](view-design|iview)[\\/]/,
|
||||||
|
name: 'ui',
|
||||||
|
priority: 25,
|
||||||
|
reuseExistingChunk: true
|
||||||
|
},
|
||||||
|
// 其他第三方库打包
|
||||||
|
vendor: {
|
||||||
|
test: /[\\/]node_modules[\\/]/,
|
||||||
|
name: 'vendors',
|
||||||
|
priority: 10,
|
||||||
|
reuseExistingChunk: true
|
||||||
|
},
|
||||||
|
// 公共代码
|
||||||
|
common: {
|
||||||
|
name: 'common',
|
||||||
|
minChunks: 2,
|
||||||
|
priority: 5,
|
||||||
|
reuseExistingChunk: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 运行时代码单独打包
|
||||||
|
runtimeChunk: {
|
||||||
|
name: 'runtime'
|
||||||
|
},
|
||||||
|
// 生产环境启用压缩
|
||||||
|
minimize: process.env.NODE_ENV === 'production'
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.vue$/,
|
||||||
|
loader: 'vue-loader'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
loader: 'babel-loader',
|
||||||
|
exclude: /node_modules/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.css$/,
|
||||||
|
use: ['vue-style-loader', 'css-loader']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.less$/,
|
||||||
|
use: ['vue-style-loader', 'css-loader', 'less-loader']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf|otf)$/,
|
||||||
|
type: 'asset/resource',
|
||||||
|
generator: {
|
||||||
|
filename: 'assets/[name].[hash:8][ext]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
__APP_BUILD_ENV__: JSON.stringify(buildEnv),
|
||||||
|
'process.env.BUILD_ENV': JSON.stringify(buildEnv)
|
||||||
|
}),
|
||||||
|
new VueLoaderPlugin(),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: './public/index.html',
|
||||||
|
title: '沁羿物流 · 仓库管理系统'
|
||||||
|
})
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js', '.vue', '.json'],
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, 'src'),
|
||||||
|
'vue$': 'vue/dist/vue.esm.js'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
devServer: {
|
||||||
|
hot: true,
|
||||||
|
open: true,
|
||||||
|
port: 8080,
|
||||||
|
historyApiFallback: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
14
api/_license/public_key.pem
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5qcNLrwgngGJMqkHhY4b
|
||||||
|
KCeS1HZegxM744fRtdrnWNVA3JwYASc52aokSQh0ig9SKN9k1zRs3L7N4cF4i9kE
|
||||||
|
AfW/2c+yiaMmbX6LW3Wi+yRH2jvTbpj1GkB/9Lsa+OEvdqYaeMiBEVoHS7FZUEaV
|
||||||
|
dzqTqrikUfql3htEhGCI9CGqZFoi8dz0GGKqDpqX7380pbST5Qgi9N4ZQLRVcmOP
|
||||||
|
596xYMoXdfufJ4em+FftYT5Q1rDt42lJhO+UrENORwTrGwCJVmLtIWiHfiCxeUzx
|
||||||
|
5Ft/xOKacndR86L4CmKLekVjejQSo+4Ge8j/BEdVUWY1tMlUFTC8aUTeFE2yA6dt
|
||||||
|
FkX3dQzgEOlRUifLjalXxLmxPY77N+mcuDzjaRomdHdxoGZsRYlS8yHL74rixSRa
|
||||||
|
U9JOVL9i8csLmJarzmYx6jsl4sSDbcDdZHxC2AbdGdDHV5/Zr+a8m8B6PW2nArgB
|
||||||
|
bTNKVx9g8aj4n3jf3NGzRqW/TwNifY4xb6BrbeNTlhXl/9+RCvvmCZZYK8JKus55
|
||||||
|
3cvBvrLUBQdpkk9JwIzmEQZoitD8g4CB/2tKsKvfiwlQUK44HNfWE+cxiqtyXL+I
|
||||||
|
shRJkwYbt0CQsXmU5F5j/prOPiJZjjlk7jqSLZLyJ99vMMm0+Iw7kozweGs3zUct
|
||||||
|
dOvKFUYgxdSaMjTiMOXdcN0CAwEAAQ==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
1
api/_license/registration.code
Normal file
@@ -0,0 +1 @@
|
|||||||
|
eyJ2IjoiMy4xIiwiaCI6ImMwZjM0Yzg2ZDA2ZDAwZjQ3NTIzYTMwNjFhNjlmZmNlZmRkNzhjMWNlMDg4OTk1ZjE5MzQyOTE1ZjgyMmExNTgiLCJzIjoiZjY5MWIwYWUwYmQ2ODUwMTQ4NjA5YzBlMGYyNzZkZDQiLCJ0IjoxNzc1MjA3NDk1LCJuIjoiMjUxNjRiYmY0MjY5MzYwYiIsImsiOiI2NDRjZGFhMiIsImMiOiJiNmE4YmMzMzZkN2NlOTk2MDc0ODZhOGEzMjExNjlhN2YzNGYzNDFjZTUwMzgyMzA5ODBiOWYxN2I3NGYyZGZjIiwibSI6ImRkZTllMzc3Yzk2Mzg3ZDUifQ==
|
||||||
49
api/controller_admin/sys_file.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
var UUID = require("uuid");
|
||||||
|
var fs = require("fs");
|
||||||
|
var path = require("path");
|
||||||
|
const ossTool = require("../service/ossTool");
|
||||||
|
const funTool = require("../../tool/funTool");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
"POST /sys_file/upload_img": async (ctx, next) => {
|
||||||
|
const files = ctx.request.files; // 获取上传文件
|
||||||
|
let fileArray = [];
|
||||||
|
let rootPath = path.join(__dirname, "../../upload/imgs");
|
||||||
|
for (var key in files) {
|
||||||
|
fileArray.push(files[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
//创建文件夹
|
||||||
|
await funTool.mkdirsSync(rootPath);
|
||||||
|
|
||||||
|
let resArray = [];
|
||||||
|
fileArray.forEach((file) => {
|
||||||
|
// 创建可读流
|
||||||
|
const reader = fs.createReadStream(file.path);
|
||||||
|
|
||||||
|
let filePath = `/${UUID.v1() + "_" + file.name}`;
|
||||||
|
// 创建可写流
|
||||||
|
const upStream = fs.createWriteStream(path.join(rootPath, filePath));
|
||||||
|
// 可读流通过管道写入可写流
|
||||||
|
|
||||||
|
reader.pipe(upStream);
|
||||||
|
|
||||||
|
resArray.push({ name: file.name, path: path.join("/imgs", filePath) });
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.success(resArray);
|
||||||
|
},
|
||||||
|
"POST /sys_file/upload_oos_img": async (ctx, next) => {
|
||||||
|
let fileArray = [];
|
||||||
|
const files = ctx.request.files; // 获取上传文件
|
||||||
|
for (var key in files) {
|
||||||
|
fileArray.push(files[key]);
|
||||||
|
}
|
||||||
|
let data = await ossTool.putImg(fileArray[0]);
|
||||||
|
if (data.path) {
|
||||||
|
return ctx.success(data);
|
||||||
|
} else {
|
||||||
|
return ctx.fail();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
75
api/controller_admin/sys_parameter.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* 后台字典/系统参数管理
|
||||||
|
* Banner 配置存于 sys_parameter,key:bannerListImage、bannerDetailImage、bannerListIndex
|
||||||
|
*/
|
||||||
|
const { sys_parameter, op } = require('../../middleware/baseModel');
|
||||||
|
|
||||||
|
const BANNER_KEYS = ['bannerListImage', 'bannerDetailImage', 'bannerListIndex'];
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
// all 接口
|
||||||
|
'GET /sys_parameter/all': async (ctx, next) => {
|
||||||
|
const rows = await sys_parameter.findAll({
|
||||||
|
where: {},
|
||||||
|
attributes: ['key', 'value']
|
||||||
|
});
|
||||||
|
return ctx.success(rows);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 Banner 配置(三个 key 的 value)
|
||||||
|
*/
|
||||||
|
'GET /sys_parameter/banner_config': async (ctx, next) => {
|
||||||
|
const rows = await sys_parameter.findAll({
|
||||||
|
where: {
|
||||||
|
key: { [op.in]: ['bannerListImage', 'bannerDetailImage', 'bannerListIndex'] }
|
||||||
|
},
|
||||||
|
attributes: ['key', 'value']
|
||||||
|
});
|
||||||
|
const data = {
|
||||||
|
bannerListImage: '',
|
||||||
|
bannerDetailImage: '',
|
||||||
|
bannerListIndex: '0'
|
||||||
|
};
|
||||||
|
if (rows && rows.length) {
|
||||||
|
rows.forEach((row) => {
|
||||||
|
const key = row.key;
|
||||||
|
if (BANNER_KEYS.includes(key)) {
|
||||||
|
data[key] = row.value != null ? String(row.value).replace(/\n/g, '') : '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return ctx.success(data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存 Banner 配置
|
||||||
|
* body: { bannerListImage?, bannerDetailImage?, bannerListIndex? }
|
||||||
|
*/
|
||||||
|
'POST /sys_parameter/banner_config': async (ctx, next) => {
|
||||||
|
const body = ctx.getBody() || {};
|
||||||
|
const updates = [
|
||||||
|
{ key: 'bannerListImage', value: body.bannerListImage != null ? String(body.bannerListImage).trim() : '' },
|
||||||
|
{ key: 'bannerDetailImage', value: body.bannerDetailImage != null ? String(body.bannerDetailImage).trim() : '' },
|
||||||
|
{ key: 'bannerListIndex', value: body.bannerListIndex != null ? String(body.bannerListIndex).trim() : '0' }
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const item of updates) {
|
||||||
|
let row = await sys_parameter.findOne({ where: { key: item.key } });
|
||||||
|
if (row) {
|
||||||
|
await row.update({ value: item.value });
|
||||||
|
} else {
|
||||||
|
await sys_parameter.create({
|
||||||
|
key: item.key,
|
||||||
|
value: item.value,
|
||||||
|
remark: item.key === 'bannerListIndex' ? '列表下标或关闭(0关闭)' : 'Banner图片URL',
|
||||||
|
is_modified: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.success({}, '保存成功');
|
||||||
|
}
|
||||||
|
};
|
||||||
61
api/controller_admin/sys_tenant.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* 系统租户 sys_tenant:表单下拉(平台租户可看全部启用租户,否则仅本租户)
|
||||||
|
*/
|
||||||
|
const baseModel = require("../../middleware/baseModel");
|
||||||
|
const { getAdminTenantContext } = require("../service/tenant_scope");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
"GET /sys_tenant/form_options": async (ctx) => {
|
||||||
|
const sys_tenant = baseModel.sys_tenant;
|
||||||
|
if (!sys_tenant) {
|
||||||
|
return ctx.fail("sys_tenant 模型未加载");
|
||||||
|
}
|
||||||
|
const { tenantId: user_tenant_id, userId } = await getAdminTenantContext(ctx);
|
||||||
|
const uid = userId != null && userId !== "" ? Number(userId) : null;
|
||||||
|
const has_user = Number.isFinite(uid) && uid > 0;
|
||||||
|
if (!has_user) {
|
||||||
|
return ctx.fail("未登录或登录已失效");
|
||||||
|
}
|
||||||
|
const tid = user_tenant_id != null && user_tenant_id !== "" ? Number(user_tenant_id) : null;
|
||||||
|
const user_tenant_ok = Number.isFinite(tid) && tid > 0;
|
||||||
|
|
||||||
|
let is_platform = false;
|
||||||
|
if (user_tenant_ok) {
|
||||||
|
const row = await sys_tenant.findByPk(tid, {
|
||||||
|
attributes: ["id", "is_platform"],
|
||||||
|
raw: true,
|
||||||
|
});
|
||||||
|
if (row && Number(row.is_platform) === 1) {
|
||||||
|
is_platform = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rows;
|
||||||
|
if (is_platform) {
|
||||||
|
rows = await sys_tenant.findAll({
|
||||||
|
where: { status: 1 },
|
||||||
|
order: [["id", "ASC"]],
|
||||||
|
limit: 500,
|
||||||
|
raw: true,
|
||||||
|
});
|
||||||
|
} else if (user_tenant_ok) {
|
||||||
|
const one = await sys_tenant.findByPk(tid, { raw: true });
|
||||||
|
rows = one ? [one] : [];
|
||||||
|
} else {
|
||||||
|
rows = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = (rows || []).map((r) => {
|
||||||
|
const id = Number(r.id);
|
||||||
|
const name = r.name != null ? String(r.name).trim() : "";
|
||||||
|
const plat = Number(r.is_platform) === 1;
|
||||||
|
return {
|
||||||
|
key: id,
|
||||||
|
value: plat && name ? `${name}(平台)` : name || String(id),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const locked = !is_platform;
|
||||||
|
const defaultTenantId = user_tenant_ok ? tid : null;
|
||||||
|
return ctx.success({ list, locked, defaultTenantId });
|
||||||
|
},
|
||||||
|
};
|
||||||
114
api/controller_admin/tpl_demo.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
const { tpl_demo, op } = require("../../middleware/baseModel");
|
||||||
|
|
||||||
|
function build_search_where(seach_option) {
|
||||||
|
const key = seach_option && seach_option.key;
|
||||||
|
const raw = seach_option && seach_option.value;
|
||||||
|
if (!key || raw === undefined || raw === null) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const str = String(raw).trim();
|
||||||
|
if (str === "") {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const attr = tpl_demo.rawAttributes[key];
|
||||||
|
if (!attr) {
|
||||||
|
return { [key]: { [op.like]: `%${str}%` } };
|
||||||
|
}
|
||||||
|
const type_key = attr.type && attr.type.key;
|
||||||
|
if (type_key === "INTEGER" || type_key === "BIGINT") {
|
||||||
|
const n = Number(str);
|
||||||
|
if (!Number.isNaN(n)) {
|
||||||
|
return { [key]: n };
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return { [key]: { [op.like]: `%${str}%` } };
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
"POST /tpl_demo/page": async (ctx) => {
|
||||||
|
const body = ctx.getBody() || {};
|
||||||
|
const param = body.param || body;
|
||||||
|
const { limit, offset } = ctx.getPageSize();
|
||||||
|
const base_where = { is_delete: 0 };
|
||||||
|
const search_where = build_search_where(param.seachOption || {});
|
||||||
|
const where = { ...base_where, ...search_where };
|
||||||
|
const { count, rows } = await tpl_demo.findAndCountAll({
|
||||||
|
where,
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
order: [["id", "DESC"]],
|
||||||
|
});
|
||||||
|
return ctx.success({ rows, count });
|
||||||
|
},
|
||||||
|
|
||||||
|
"POST /tpl_demo/add": async (ctx) => {
|
||||||
|
const body = ctx.getBody() || {};
|
||||||
|
const title = body.title != null ? String(body.title).trim() : "";
|
||||||
|
if (!title) {
|
||||||
|
return ctx.fail("请填写标题");
|
||||||
|
}
|
||||||
|
const row = await tpl_demo.create({
|
||||||
|
title,
|
||||||
|
remark: body.remark != null ? String(body.remark).trim() : "",
|
||||||
|
});
|
||||||
|
return ctx.success(row);
|
||||||
|
},
|
||||||
|
|
||||||
|
"POST /tpl_demo/edit": async (ctx) => {
|
||||||
|
const body = ctx.getBody() || {};
|
||||||
|
const id = body.id;
|
||||||
|
if (!id) {
|
||||||
|
return ctx.fail("缺少 id");
|
||||||
|
}
|
||||||
|
const title = body.title != null ? String(body.title).trim() : "";
|
||||||
|
if (!title) {
|
||||||
|
return ctx.fail("请填写标题");
|
||||||
|
}
|
||||||
|
await tpl_demo.update(
|
||||||
|
{
|
||||||
|
title,
|
||||||
|
remark: body.remark != null ? String(body.remark).trim() : "",
|
||||||
|
last_modify_time: new Date(),
|
||||||
|
},
|
||||||
|
{ where: { id, is_delete: 0 } }
|
||||||
|
);
|
||||||
|
return ctx.success({});
|
||||||
|
},
|
||||||
|
|
||||||
|
"POST /tpl_demo/del": async (ctx) => {
|
||||||
|
const body = ctx.getBody() || {};
|
||||||
|
const id = body.id;
|
||||||
|
if (!id) {
|
||||||
|
return ctx.fail("缺少 id");
|
||||||
|
}
|
||||||
|
await tpl_demo.update(
|
||||||
|
{ is_delete: 1, last_modify_time: new Date() },
|
||||||
|
{ where: { id, is_delete: 0 } }
|
||||||
|
);
|
||||||
|
return ctx.success({});
|
||||||
|
},
|
||||||
|
|
||||||
|
"GET /tpl_demo/detail": async (ctx) => {
|
||||||
|
const id = ctx.get("id");
|
||||||
|
if (!id) {
|
||||||
|
return ctx.fail("缺少 id");
|
||||||
|
}
|
||||||
|
const row = await tpl_demo.findOne({ where: { id, is_delete: 0 } });
|
||||||
|
return ctx.success(row);
|
||||||
|
},
|
||||||
|
|
||||||
|
"GET /tpl_demo/all": async (ctx) => {
|
||||||
|
const rows = await tpl_demo.findAll({ limit: 500, order: [["id", "DESC"]] });
|
||||||
|
return ctx.success(rows);
|
||||||
|
},
|
||||||
|
|
||||||
|
"POST /tpl_demo/export": async (ctx) => {
|
||||||
|
const body = ctx.getBody() || {};
|
||||||
|
const param = body.param || body;
|
||||||
|
const search_where = build_search_where(param.seachOption || {});
|
||||||
|
const where = { is_delete: 0, ...search_where };
|
||||||
|
const rows = await tpl_demo.findAll({ where, limit: 10000, order: [["id", "DESC"]] });
|
||||||
|
return ctx.success({ rows });
|
||||||
|
},
|
||||||
|
};
|
||||||
40
api/model/sys_tenant.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
const Sequelize = require("sequelize");
|
||||||
|
|
||||||
|
/** 租户表:用户、角色按 tenant_id 隔离;is_platform=1 的租户可管理本表 */
|
||||||
|
module.exports = (db) => {
|
||||||
|
return db.define("sys_tenant", {
|
||||||
|
name: {
|
||||||
|
type: Sequelize.STRING(100),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: "",
|
||||||
|
comment: "租户名称",
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
type: Sequelize.STRING(64),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: "",
|
||||||
|
unique: true,
|
||||||
|
comment: "租户编码(登录时与账号一起使用)",
|
||||||
|
},
|
||||||
|
remark: {
|
||||||
|
type: Sequelize.STRING(255),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: "",
|
||||||
|
comment: "备注",
|
||||||
|
},
|
||||||
|
/** 1 启用 0 停用 */
|
||||||
|
status: {
|
||||||
|
type: Sequelize.INTEGER(1),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 1,
|
||||||
|
comment: "状态",
|
||||||
|
},
|
||||||
|
/** 1 平台租户:可维护租户表、可代建任意租户用户 */
|
||||||
|
is_platform: {
|
||||||
|
type: Sequelize.INTEGER(1),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0,
|
||||||
|
comment: "是否平台租户",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
60
api/model/tpl_demo.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
const Sequelize = require("sequelize");
|
||||||
|
|
||||||
|
/** 模板示例:与库表字段 create_time / last_modify_time / is_delete 一致(无 createdAt/updatedAt) */
|
||||||
|
module.exports = (db) => {
|
||||||
|
return db.define(
|
||||||
|
"tpl_demo",
|
||||||
|
{
|
||||||
|
title: {
|
||||||
|
type: Sequelize.STRING(200),
|
||||||
|
allowNull: false,
|
||||||
|
comment: "标题",
|
||||||
|
},
|
||||||
|
remark: {
|
||||||
|
type: Sequelize.STRING(500),
|
||||||
|
allowNull: true,
|
||||||
|
comment: "备注",
|
||||||
|
},
|
||||||
|
create_time: {
|
||||||
|
type: Sequelize.DATE,
|
||||||
|
allowNull: false,
|
||||||
|
comment: "创建时间",
|
||||||
|
},
|
||||||
|
last_modify_time: {
|
||||||
|
type: Sequelize.DATE,
|
||||||
|
allowNull: false,
|
||||||
|
comment: "最后修改时间",
|
||||||
|
},
|
||||||
|
is_delete: {
|
||||||
|
type: Sequelize.INTEGER(1),
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 0,
|
||||||
|
comment: "0 正常 1 已删除",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
comment: "前后端模板演示表",
|
||||||
|
timestamps: false,
|
||||||
|
defaultScope: {
|
||||||
|
where: { is_delete: 0 },
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
beforeCreate(instance) {
|
||||||
|
const now = new Date();
|
||||||
|
if (!instance.get("create_time")) {
|
||||||
|
instance.set("create_time", now);
|
||||||
|
}
|
||||||
|
if (!instance.get("last_modify_time")) {
|
||||||
|
instance.set("last_modify_time", now);
|
||||||
|
}
|
||||||
|
if (instance.get("is_delete") == null) {
|
||||||
|
instance.set("is_delete", 0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeUpdate(instance) {
|
||||||
|
instance.set("last_modify_time", new Date());
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
333
api/service/ossTool.js
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
const OSS = require('ali-oss')
|
||||||
|
const fs = require('fs')
|
||||||
|
const config = require('../../config/config')['aliyun']
|
||||||
|
const uuid = require('node-uuid')
|
||||||
|
const logs = require('../../tool/logs_proxy')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OSS 文件上传工具类
|
||||||
|
* 统一管理文件上传、存储路径、文件类型等
|
||||||
|
*/
|
||||||
|
class OSSTool {
|
||||||
|
constructor() {
|
||||||
|
this.client = new OSS({
|
||||||
|
region: 'oss-cn-shanghai',
|
||||||
|
accessKeyId: config.accessKeyId,
|
||||||
|
accessKeySecret: config.accessKeySecret,
|
||||||
|
bucket:config.bucket
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 基础存储路径前缀
|
||||||
|
this.basePrefix = 'front/ball'
|
||||||
|
|
||||||
|
// 文件类型映射
|
||||||
|
this.fileTypeMap = {
|
||||||
|
// 图片类型
|
||||||
|
'image/jpeg': 'jpg',
|
||||||
|
'image/jpg': 'jpg',
|
||||||
|
'image/png': 'png',
|
||||||
|
'image/gif': 'gif',
|
||||||
|
'image/webp': 'webp',
|
||||||
|
'image/svg+xml': 'svg',
|
||||||
|
|
||||||
|
// 视频类型
|
||||||
|
'video/mp4': 'mp4',
|
||||||
|
'video/avi': 'avi',
|
||||||
|
'video/mov': 'mov',
|
||||||
|
'video/wmv': 'wmv',
|
||||||
|
'video/flv': 'flv',
|
||||||
|
'video/webm': 'webm',
|
||||||
|
'video/mkv': 'mkv',
|
||||||
|
|
||||||
|
// 音频类型
|
||||||
|
'audio/mp3': 'mp3',
|
||||||
|
'audio/wav': 'wav',
|
||||||
|
'audio/aac': 'aac',
|
||||||
|
'audio/ogg': 'ogg',
|
||||||
|
'audio/flac': 'flac',
|
||||||
|
|
||||||
|
// 文档类型
|
||||||
|
'application/pdf': 'pdf',
|
||||||
|
'application/msword': 'doc',
|
||||||
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
|
||||||
|
'application/vnd.ms-excel': 'xls',
|
||||||
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
|
||||||
|
'application/vnd.ms-powerpoint': 'ppt',
|
||||||
|
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',
|
||||||
|
'text/plain': 'txt',
|
||||||
|
'text/html': 'html',
|
||||||
|
'text/css': 'css',
|
||||||
|
'application/javascript': 'js',
|
||||||
|
'application/json': 'json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件后缀名
|
||||||
|
* @param {Object} file - 文件对象(兼容 formidable 格式)
|
||||||
|
* @returns {string} 文件后缀名
|
||||||
|
*/
|
||||||
|
getFileSuffix(file) {
|
||||||
|
// 优先使用 MIME 类型判断(兼容 type 和 mimetype)
|
||||||
|
const mimeType = file.mimetype || file.type
|
||||||
|
if (mimeType && this.fileTypeMap[mimeType]) {
|
||||||
|
return this.fileTypeMap[mimeType]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 备用方案:从文件名获取(兼容 originalFilename 和 name)
|
||||||
|
const fileName = file.originalFilename || file.name
|
||||||
|
if (fileName) {
|
||||||
|
const lastIndex = fileName.lastIndexOf('.')
|
||||||
|
if (lastIndex > -1) {
|
||||||
|
return fileName.substring(lastIndex + 1).toLowerCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'bin'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件存储路径
|
||||||
|
* @param {Object} file - 文件对象(兼容 formidable 格式)
|
||||||
|
* @param {string} category - 存储分类
|
||||||
|
* @returns {string} 完整的存储路径
|
||||||
|
*/
|
||||||
|
getStoragePath(file, category = 'files') {
|
||||||
|
const suffix = this.getFileSuffix(file)
|
||||||
|
const uid = uuid.v4()
|
||||||
|
|
||||||
|
// 根据文件类型确定子路径(兼容 mimetype 和 type)
|
||||||
|
let subPath = category
|
||||||
|
const mimeType = file.mimetype || file.type
|
||||||
|
|
||||||
|
if (mimeType) {
|
||||||
|
if (mimeType.startsWith('image/')) {
|
||||||
|
subPath = 'images'
|
||||||
|
} else if (mimeType.startsWith('video/')) {
|
||||||
|
subPath = 'videos'
|
||||||
|
} else if (mimeType.startsWith('audio/')) {
|
||||||
|
subPath = 'audios'
|
||||||
|
} else if (mimeType.startsWith('application/') || mimeType.startsWith('text/')) {
|
||||||
|
subPath = 'documents'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完整路径:front/ball/{subPath}/{uid}.{suffix}
|
||||||
|
return `${this.basePrefix}/${subPath}/${uid}.${suffix}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心文件上传方法
|
||||||
|
* @param {Object} file - 文件对象(兼容 formidable 格式)
|
||||||
|
* @param {string} category - 存储分类
|
||||||
|
* @returns {Object} 上传结果
|
||||||
|
*/
|
||||||
|
async uploadFile(file, category = 'files') {
|
||||||
|
try {
|
||||||
|
// 兼容不同的文件对象格式(filepath 或 path)
|
||||||
|
const filePath = file.filepath || file.path
|
||||||
|
|
||||||
|
// 验证文件
|
||||||
|
if (!file || !filePath) {
|
||||||
|
return { success: false, error: '无效的文件对象' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = fs.createReadStream(filePath)
|
||||||
|
const storagePath = this.getStoragePath(file, category)
|
||||||
|
const suffix = this.getFileSuffix(file)
|
||||||
|
|
||||||
|
// 设置 content-type(兼容 mimetype 和 type)
|
||||||
|
const contentType = file.mimetype || file.type || 'application/octet-stream'
|
||||||
|
|
||||||
|
// 上传到 OSS
|
||||||
|
const result = await this.client.put(storagePath, stream, {
|
||||||
|
headers: {
|
||||||
|
'content-disposition': 'inline',
|
||||||
|
"content-type": contentType
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.res.status === 200) {
|
||||||
|
const ossPath = config.ossUrl + '/' + result.name
|
||||||
|
|
||||||
|
// 上传成功后删除临时文件
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
fs.unlinkSync(filePath)
|
||||||
|
}
|
||||||
|
} catch (unlinkError) {
|
||||||
|
logs.error('删除临时文件失败:', unlinkError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 ossPath(https)作为 path,确保返回 https 格式
|
||||||
|
const path = ossPath
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
name: result.name,
|
||||||
|
path: path,
|
||||||
|
ossPath,
|
||||||
|
fileType: file.mimetype || file.type,
|
||||||
|
fileSize: file.size,
|
||||||
|
originalName: file.originalFilename || file.name,
|
||||||
|
suffix: suffix,
|
||||||
|
storagePath: storagePath
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { success: false, error: 'OSS 上传失败' }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logs.error('文件上传错误:', error)
|
||||||
|
|
||||||
|
// 上传失败也要清理临时文件
|
||||||
|
try {
|
||||||
|
const filePath = file.filepath || file.path
|
||||||
|
if (filePath && fs.existsSync(filePath)) {
|
||||||
|
fs.unlinkSync(filePath)
|
||||||
|
}
|
||||||
|
} catch (unlinkError) {
|
||||||
|
logs.error('删除临时文件失败:', unlinkError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false, error: error.message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传流数据
|
||||||
|
* @param {Stream} stream - 文件流
|
||||||
|
* @param {string} contentType - 内容类型
|
||||||
|
* @param {string} suffix - 文件后缀
|
||||||
|
* @returns {Object} 上传结果
|
||||||
|
*/
|
||||||
|
async uploadStream(stream, contentType, suffix) {
|
||||||
|
try {
|
||||||
|
const uid = uuid.v4()
|
||||||
|
const storagePath = `${this.basePrefix}/files/${uid}.${suffix}`
|
||||||
|
|
||||||
|
const result = await this.client.put(storagePath, stream, {
|
||||||
|
headers: {
|
||||||
|
'content-disposition': 'inline',
|
||||||
|
"content-type": contentType
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (result.res.status === 200) {
|
||||||
|
const ossPath = config.ossUrl + '/' + result.name
|
||||||
|
// 使用 ossPath(https)作为 path,确保返回 https 格式
|
||||||
|
const path = ossPath
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
name: result.name,
|
||||||
|
path: path,
|
||||||
|
ossPath,
|
||||||
|
storagePath: storagePath
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { success: false, error: 'OSS 上传失败' }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logs.error('流上传错误:', error)
|
||||||
|
return { success: false, error: error.message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除文件
|
||||||
|
* @param {string} filePath - 文件路径
|
||||||
|
* @returns {Object} 删除结果
|
||||||
|
*/
|
||||||
|
async deleteFile(filePath) {
|
||||||
|
try {
|
||||||
|
if (!filePath) {
|
||||||
|
return { success: false, error: '文件路径不能为空' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从完整 URL 中提取相对路径
|
||||||
|
const relativePath = filePath.replace(config.ossUrl + '/', '')
|
||||||
|
const result = await this.client.delete(relativePath)
|
||||||
|
|
||||||
|
if (result.res.status === 204) {
|
||||||
|
return { success: true, message: '文件删除成功' }
|
||||||
|
} else {
|
||||||
|
return { success: false, error: '文件删除失败' }
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logs.error('文件删除错误:', error)
|
||||||
|
return { success: false, error: error.message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件信息
|
||||||
|
* @param {string} filePath - 文件路径
|
||||||
|
* @returns {Object} 文件信息
|
||||||
|
*/
|
||||||
|
async getFileInfo(filePath) {
|
||||||
|
try {
|
||||||
|
if (!filePath) {
|
||||||
|
return { success: false, error: '文件路径不能为空' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const relativePath = filePath.replace(config.ossUrl + '/', '')
|
||||||
|
const result = await this.client.head(relativePath)
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
size: result.res.headers['content-length'],
|
||||||
|
type: result.res.headers['content-type'],
|
||||||
|
lastModified: result.res.headers['last-modified'],
|
||||||
|
etag: result.res.headers['etag']
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logs.error('获取文件信息错误:', error)
|
||||||
|
return { success: false, error: error.message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 便捷方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传图片文件(保持向后兼容)
|
||||||
|
* @param {Object} file - 图片文件
|
||||||
|
* @returns {Object} 上传结果
|
||||||
|
*/
|
||||||
|
async putImg(file) {
|
||||||
|
return await this.uploadFile(file, 'images')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传视频文件
|
||||||
|
* @param {Object} file - 视频文件
|
||||||
|
* @returns {Object} 上传结果
|
||||||
|
*/
|
||||||
|
async uploadVideo(file) {
|
||||||
|
return await this.uploadFile(file, 'videos')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传音频文件
|
||||||
|
* @param {Object} file - 音频文件
|
||||||
|
* @returns {Object} 上传结果
|
||||||
|
*/
|
||||||
|
async uploadAudio(file) {
|
||||||
|
return await this.uploadFile(file, 'audios')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文档文件
|
||||||
|
* @param {Object} file - 文档文件
|
||||||
|
* @returns {Object} 上传结果
|
||||||
|
*/
|
||||||
|
async uploadDocument(file) {
|
||||||
|
return await this.uploadFile(file, 'documents')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建单例实例
|
||||||
|
const ossTool = new OSSTool()
|
||||||
|
|
||||||
|
// 导出实例(保持向后兼容)
|
||||||
|
module.exports = ossTool
|
||||||
89
api/service/tenant_scope.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* 管理端多租户:从当前登录 sys_user 解析 tenant_id
|
||||||
|
*/
|
||||||
|
const { sys_user } = require("../../middleware/baseModel");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('koa').Context} ctx
|
||||||
|
* @returns {Promise<{ tenantId: number|null, userId: number|null }>}
|
||||||
|
*/
|
||||||
|
async function getAdminTenantContext(ctx) {
|
||||||
|
let uid_raw = null;
|
||||||
|
try {
|
||||||
|
uid_raw = ctx.getAdminUserId && ctx.getAdminUserId();
|
||||||
|
} catch (e) {
|
||||||
|
uid_raw = null;
|
||||||
|
}
|
||||||
|
if (uid_raw === null || uid_raw === undefined || uid_raw === "") {
|
||||||
|
return { tenantId: null, userId: null };
|
||||||
|
}
|
||||||
|
const user_id = Number(uid_raw);
|
||||||
|
if (!Number.isFinite(user_id) || user_id <= 0) {
|
||||||
|
return { tenantId: null, userId: null };
|
||||||
|
}
|
||||||
|
if (!sys_user) {
|
||||||
|
return { tenantId: null, userId: user_id };
|
||||||
|
}
|
||||||
|
const row = await sys_user.findByPk(user_id, {
|
||||||
|
attributes: ["id", "tenant_id"],
|
||||||
|
raw: true,
|
||||||
|
});
|
||||||
|
const tid_raw = row && row.tenant_id;
|
||||||
|
const tenant_id = tid_raw != null && tid_raw !== "" ? Number(tid_raw) : null;
|
||||||
|
const tenant_ok = tenant_id != null && Number.isFinite(tenant_id) && tenant_id > 0;
|
||||||
|
return { tenantId: tenant_ok ? tenant_id : null, userId: user_id };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('sequelize').Model} model
|
||||||
|
* @param {object} where
|
||||||
|
* @param {number} tenant_id
|
||||||
|
*/
|
||||||
|
function mergeTenantWhere(model, where, tenant_id) {
|
||||||
|
if (tenant_id == null || tenant_id === "" || !model || !model.rawAttributes || !model.rawAttributes.tenant_id) {
|
||||||
|
return where || {};
|
||||||
|
}
|
||||||
|
const w = where && typeof where === "object" ? { ...where } : {};
|
||||||
|
w.tenant_id = tenant_id;
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeTenantWhereById(model, id, tenant_id) {
|
||||||
|
return mergeTenantWhere(model, { id }, tenant_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('koa').Context} ctx
|
||||||
|
* @returns {Promise<{ tenantId: number, userId: number }|null>}
|
||||||
|
*/
|
||||||
|
async function requireTenantContext(ctx) {
|
||||||
|
const { tenantId, userId } = await getAdminTenantContext(ctx);
|
||||||
|
if (!tenantId) {
|
||||||
|
ctx.fail("当前用户未绑定租户,无法操作业务数据");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!userId) {
|
||||||
|
ctx.fail("未登录或登录已失效");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return { tenantId, userId };
|
||||||
|
}
|
||||||
|
|
||||||
|
function stampTenantCreate(body, tenant_id, user_id, opts = {}) {
|
||||||
|
const out = body && typeof body === "object" ? { ...body } : {};
|
||||||
|
if (tenant_id != null) {
|
||||||
|
out.tenant_id = tenant_id;
|
||||||
|
}
|
||||||
|
if (opts.setCreateUser !== false && user_id != null) {
|
||||||
|
out.create_user_id = user_id;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getAdminTenantContext,
|
||||||
|
requireTenantContext,
|
||||||
|
stampTenantCreate,
|
||||||
|
mergeTenantWhere,
|
||||||
|
mergeTenantWhereById,
|
||||||
|
};
|
||||||
BIN
apidoc/assets/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
apidoc/assets/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
apidoc/assets/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
6
apidoc/assets/bootstrap.min.css
vendored
Normal file
1
apidoc/assets/bootstrap.min.css.map
Normal file
BIN
apidoc/assets/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 566 B |
BIN
apidoc/assets/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
apidoc/assets/favicon.ico
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
apidoc/assets/glyphicons-halflings-regular.eot
Normal file
288
apidoc/assets/glyphicons-halflings-regular.svg
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<metadata></metadata>
|
||||||
|
<defs>
|
||||||
|
<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
|
||||||
|
<font-face units-per-em="1200" ascent="960" descent="-240" />
|
||||||
|
<missing-glyph horiz-adv-x="500" />
|
||||||
|
<glyph horiz-adv-x="0" />
|
||||||
|
<glyph horiz-adv-x="400" />
|
||||||
|
<glyph unicode=" " />
|
||||||
|
<glyph unicode="*" d="M600 1100q15 0 34 -1.5t30 -3.5l11 -1q10 -2 17.5 -10.5t7.5 -18.5v-224l158 158q7 7 18 8t19 -6l106 -106q7 -8 6 -19t-8 -18l-158 -158h224q10 0 18.5 -7.5t10.5 -17.5q6 -41 6 -75q0 -15 -1.5 -34t-3.5 -30l-1 -11q-2 -10 -10.5 -17.5t-18.5 -7.5h-224l158 -158 q7 -7 8 -18t-6 -19l-106 -106q-8 -7 -19 -6t-18 8l-158 158v-224q0 -10 -7.5 -18.5t-17.5 -10.5q-41 -6 -75 -6q-15 0 -34 1.5t-30 3.5l-11 1q-10 2 -17.5 10.5t-7.5 18.5v224l-158 -158q-7 -7 -18 -8t-19 6l-106 106q-7 8 -6 19t8 18l158 158h-224q-10 0 -18.5 7.5 t-10.5 17.5q-6 41 -6 75q0 15 1.5 34t3.5 30l1 11q2 10 10.5 17.5t18.5 7.5h224l-158 158q-7 7 -8 18t6 19l106 106q8 7 19 6t18 -8l158 -158v224q0 10 7.5 18.5t17.5 10.5q41 6 75 6z" />
|
||||||
|
<glyph unicode="+" d="M450 1100h200q21 0 35.5 -14.5t14.5 -35.5v-350h350q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-350v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v350h-350q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5 h350v350q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode=" " />
|
||||||
|
<glyph unicode="¥" d="M825 1100h250q10 0 12.5 -5t-5.5 -13l-364 -364q-6 -6 -11 -18h268q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-100h275q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-174q0 -11 -7.5 -18.5t-18.5 -7.5h-148q-11 0 -18.5 7.5t-7.5 18.5v174 h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h125v100h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h118q-5 12 -11 18l-364 364q-8 8 -5.5 13t12.5 5h250q25 0 43 -18l164 -164q8 -8 18 -8t18 8l164 164q18 18 43 18z" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="650" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="1300" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="650" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="1300" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="433" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="325" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="216" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="216" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="162" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="260" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="72" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="260" />
|
||||||
|
<glyph unicode=" " horiz-adv-x="325" />
|
||||||
|
<glyph unicode="€" d="M744 1198q242 0 354 -189q60 -104 66 -209h-181q0 45 -17.5 82.5t-43.5 61.5t-58 40.5t-60.5 24t-51.5 7.5q-19 0 -40.5 -5.5t-49.5 -20.5t-53 -38t-49 -62.5t-39 -89.5h379l-100 -100h-300q-6 -50 -6 -100h406l-100 -100h-300q9 -74 33 -132t52.5 -91t61.5 -54.5t59 -29 t47 -7.5q22 0 50.5 7.5t60.5 24.5t58 41t43.5 61t17.5 80h174q-30 -171 -128 -278q-107 -117 -274 -117q-206 0 -324 158q-36 48 -69 133t-45 204h-217l100 100h112q1 47 6 100h-218l100 100h134q20 87 51 153.5t62 103.5q117 141 297 141z" />
|
||||||
|
<glyph unicode="₽" d="M428 1200h350q67 0 120 -13t86 -31t57 -49.5t35 -56.5t17 -64.5t6.5 -60.5t0.5 -57v-16.5v-16.5q0 -36 -0.5 -57t-6.5 -61t-17 -65t-35 -57t-57 -50.5t-86 -31.5t-120 -13h-178l-2 -100h288q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-138v-175q0 -11 -5.5 -18 t-15.5 -7h-149q-10 0 -17.5 7.5t-7.5 17.5v175h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v100h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v475q0 10 7.5 17.5t17.5 7.5zM600 1000v-300h203q64 0 86.5 33t22.5 119q0 84 -22.5 116t-86.5 32h-203z" />
|
||||||
|
<glyph unicode="−" d="M250 700h800q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="⌛" d="M1000 1200v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-50v-100q0 -91 -49.5 -165.5t-130.5 -109.5q81 -35 130.5 -109.5t49.5 -165.5v-150h50q21 0 35.5 -14.5t14.5 -35.5v-150h-800v150q0 21 14.5 35.5t35.5 14.5h50v150q0 91 49.5 165.5t130.5 109.5q-81 35 -130.5 109.5 t-49.5 165.5v100h-50q-21 0 -35.5 14.5t-14.5 35.5v150h800zM400 1000v-100q0 -60 32.5 -109.5t87.5 -73.5q28 -12 44 -37t16 -55t-16 -55t-44 -37q-55 -24 -87.5 -73.5t-32.5 -109.5v-150h400v150q0 60 -32.5 109.5t-87.5 73.5q-28 12 -44 37t-16 55t16 55t44 37 q55 24 87.5 73.5t32.5 109.5v100h-400z" />
|
||||||
|
<glyph unicode="◼" horiz-adv-x="500" d="M0 0z" />
|
||||||
|
<glyph unicode="☁" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -206.5q0 -121 -85 -207.5t-205 -86.5h-750q-79 0 -135.5 57t-56.5 137q0 69 42.5 122.5t108.5 67.5q-2 12 -2 37q0 153 108 260.5t260 107.5z" />
|
||||||
|
<glyph unicode="⛺" d="M774 1193.5q16 -9.5 20.5 -27t-5.5 -33.5l-136 -187l467 -746h30q20 0 35 -18.5t15 -39.5v-42h-1200v42q0 21 15 39.5t35 18.5h30l468 746l-135 183q-10 16 -5.5 34t20.5 28t34 5.5t28 -20.5l111 -148l112 150q9 16 27 20.5t34 -5zM600 200h377l-182 112l-195 534v-646z " />
|
||||||
|
<glyph unicode="✉" d="M25 1100h1150q10 0 12.5 -5t-5.5 -13l-564 -567q-8 -8 -18 -8t-18 8l-564 567q-8 8 -5.5 13t12.5 5zM18 882l264 -264q8 -8 8 -18t-8 -18l-264 -264q-8 -8 -13 -5.5t-5 12.5v550q0 10 5 12.5t13 -5.5zM918 618l264 264q8 8 13 5.5t5 -12.5v-550q0 -10 -5 -12.5t-13 5.5 l-264 264q-8 8 -8 18t8 18zM818 482l364 -364q8 -8 5.5 -13t-12.5 -5h-1150q-10 0 -12.5 5t5.5 13l364 364q8 8 18 8t18 -8l164 -164q8 -8 18 -8t18 8l164 164q8 8 18 8t18 -8z" />
|
||||||
|
<glyph unicode="✏" d="M1011 1210q19 0 33 -13l153 -153q13 -14 13 -33t-13 -33l-99 -92l-214 214l95 96q13 14 32 14zM1013 800l-615 -614l-214 214l614 614zM317 96l-333 -112l110 335z" />
|
||||||
|
<glyph unicode="" d="M700 650v-550h250q21 0 35.5 -14.5t14.5 -35.5v-50h-800v50q0 21 14.5 35.5t35.5 14.5h250v550l-500 550h1200z" />
|
||||||
|
<glyph unicode="" d="M368 1017l645 163q39 15 63 0t24 -49v-831q0 -55 -41.5 -95.5t-111.5 -63.5q-79 -25 -147 -4.5t-86 75t25.5 111.5t122.5 82q72 24 138 8v521l-600 -155v-606q0 -42 -44 -90t-109 -69q-79 -26 -147 -5.5t-86 75.5t25.5 111.5t122.5 82.5q72 24 138 7v639q0 38 14.5 59 t53.5 34z" />
|
||||||
|
<glyph unicode="" d="M500 1191q100 0 191 -39t156.5 -104.5t104.5 -156.5t39 -191l-1 -2l1 -5q0 -141 -78 -262l275 -274q23 -26 22.5 -44.5t-22.5 -42.5l-59 -58q-26 -20 -46.5 -20t-39.5 20l-275 274q-119 -77 -261 -77l-5 1l-2 -1q-100 0 -191 39t-156.5 104.5t-104.5 156.5t-39 191 t39 191t104.5 156.5t156.5 104.5t191 39zM500 1022q-88 0 -162 -43t-117 -117t-43 -162t43 -162t117 -117t162 -43t162 43t117 117t43 162t-43 162t-117 117t-162 43z" />
|
||||||
|
<glyph unicode="" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104z" />
|
||||||
|
<glyph unicode="" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429z" />
|
||||||
|
<glyph unicode="" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429zM477 700h-240l197 -142l-74 -226 l193 139l195 -140l-74 229l192 140h-234l-78 211z" />
|
||||||
|
<glyph unicode="" d="M600 1200q124 0 212 -88t88 -212v-250q0 -46 -31 -98t-69 -52v-75q0 -10 6 -21.5t15 -17.5l358 -230q9 -5 15 -16.5t6 -21.5v-93q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v93q0 10 6 21.5t15 16.5l358 230q9 6 15 17.5t6 21.5v75q-38 0 -69 52 t-31 98v250q0 124 88 212t212 88z" />
|
||||||
|
<glyph unicode="" d="M25 1100h1150q10 0 17.5 -7.5t7.5 -17.5v-1050q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v1050q0 10 7.5 17.5t17.5 7.5zM100 1000v-100h100v100h-100zM875 1000h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5t17.5 -7.5h550 q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM1000 1000v-100h100v100h-100zM100 800v-100h100v100h-100zM1000 800v-100h100v100h-100zM100 600v-100h100v100h-100zM1000 600v-100h100v100h-100zM875 500h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5 t17.5 -7.5h550q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM100 400v-100h100v100h-100zM1000 400v-100h100v100h-100zM100 200v-100h100v100h-100zM1000 200v-100h100v100h-100z" />
|
||||||
|
<glyph unicode="" d="M50 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM650 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5zM50 500h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM650 500h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M50 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5zM850 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM850 700h200q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM850 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5 t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M50 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h700q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M465 477l571 571q8 8 18 8t17 -8l177 -177q8 -7 8 -17t-8 -18l-783 -784q-7 -8 -17.5 -8t-17.5 8l-384 384q-8 8 -8 18t8 17l177 177q7 8 17 8t18 -8l171 -171q7 -7 18 -7t18 7z" />
|
||||||
|
<glyph unicode="" d="M904 1083l178 -179q8 -8 8 -18.5t-8 -17.5l-267 -268l267 -268q8 -7 8 -17.5t-8 -18.5l-178 -178q-8 -8 -18.5 -8t-17.5 8l-268 267l-268 -267q-7 -8 -17.5 -8t-18.5 8l-178 178q-8 8 -8 18.5t8 17.5l267 268l-267 268q-8 7 -8 17.5t8 18.5l178 178q8 8 18.5 8t17.5 -8 l268 -267l268 268q7 7 17.5 7t18.5 -7z" />
|
||||||
|
<glyph unicode="" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM425 900h150q10 0 17.5 -7.5t7.5 -17.5v-75h75q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5 t-17.5 -7.5h-75v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-75q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v75q0 10 7.5 17.5t17.5 7.5z" />
|
||||||
|
<glyph unicode="" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM325 800h350q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-350q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
|
||||||
|
<glyph unicode="" d="M550 1200h100q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM800 975v166q167 -62 272 -209.5t105 -331.5q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5 t-184.5 123t-123 184.5t-45.5 224q0 184 105 331.5t272 209.5v-166q-103 -55 -165 -155t-62 -220q0 -116 57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5q0 120 -62 220t-165 155z" />
|
||||||
|
<glyph unicode="" d="M1025 1200h150q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM725 800h150q10 0 17.5 -7.5t7.5 -17.5v-750q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v750 q0 10 7.5 17.5t17.5 7.5zM425 500h150q10 0 17.5 -7.5t7.5 -17.5v-450q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v450q0 10 7.5 17.5t17.5 7.5zM125 300h150q10 0 17.5 -7.5t7.5 -17.5v-250q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5 v250q0 10 7.5 17.5t17.5 7.5z" />
|
||||||
|
<glyph unicode="" d="M600 1174q33 0 74 -5l38 -152l5 -1q49 -14 94 -39l5 -2l134 80q61 -48 104 -105l-80 -134l3 -5q25 -44 39 -93l1 -6l152 -38q5 -43 5 -73q0 -34 -5 -74l-152 -38l-1 -6q-15 -49 -39 -93l-3 -5l80 -134q-48 -61 -104 -105l-134 81l-5 -3q-44 -25 -94 -39l-5 -2l-38 -151 q-43 -5 -74 -5q-33 0 -74 5l-38 151l-5 2q-49 14 -94 39l-5 3l-134 -81q-60 48 -104 105l80 134l-3 5q-25 45 -38 93l-2 6l-151 38q-6 42 -6 74q0 33 6 73l151 38l2 6q13 48 38 93l3 5l-80 134q47 61 105 105l133 -80l5 2q45 25 94 39l5 1l38 152q43 5 74 5zM600 815 q-89 0 -152 -63t-63 -151.5t63 -151.5t152 -63t152 63t63 151.5t-63 151.5t-152 63z" />
|
||||||
|
<glyph unicode="" d="M500 1300h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-75h-1100v75q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5zM500 1200v-100h300v100h-300zM1100 900v-800q0 -41 -29.5 -70.5t-70.5 -29.5h-700q-41 0 -70.5 29.5t-29.5 70.5 v800h900zM300 800v-700h100v700h-100zM500 800v-700h100v700h-100zM700 800v-700h100v700h-100zM900 800v-700h100v700h-100z" />
|
||||||
|
<glyph unicode="" d="M18 618l620 608q8 7 18.5 7t17.5 -7l608 -608q8 -8 5.5 -13t-12.5 -5h-175v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v375h-300v-375q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v575h-175q-10 0 -12.5 5t5.5 13z" />
|
||||||
|
<glyph unicode="" d="M600 1200v-400q0 -41 29.5 -70.5t70.5 -29.5h300v-650q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5h450zM1000 800h-250q-21 0 -35.5 14.5t-14.5 35.5v250z" />
|
||||||
|
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h50q10 0 17.5 -7.5t7.5 -17.5v-275h175q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5z" />
|
||||||
|
<glyph unicode="" d="M1300 0h-538l-41 400h-242l-41 -400h-538l431 1200h209l-21 -300h162l-20 300h208zM515 800l-27 -300h224l-27 300h-170z" />
|
||||||
|
<glyph unicode="" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-450h191q20 0 25.5 -11.5t-7.5 -27.5l-327 -400q-13 -16 -32 -16t-32 16l-327 400q-13 16 -7.5 27.5t25.5 11.5h191v450q0 21 14.5 35.5t35.5 14.5zM1125 400h50q10 0 17.5 -7.5t7.5 -17.5v-350q0 -10 -7.5 -17.5t-17.5 -7.5 h-1050q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h50q10 0 17.5 -7.5t7.5 -17.5v-175h900v175q0 10 7.5 17.5t17.5 7.5z" />
|
||||||
|
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -275q-13 -16 -32 -16t-32 16l-223 275q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z " />
|
||||||
|
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM632 914l223 -275q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5l223 275q13 16 32 16 t32 -16z" />
|
||||||
|
<glyph unicode="" d="M225 1200h750q10 0 19.5 -7t12.5 -17l186 -652q7 -24 7 -49v-425q0 -12 -4 -27t-9 -17q-12 -6 -37 -6h-1100q-12 0 -27 4t-17 8q-6 13 -6 38l1 425q0 25 7 49l185 652q3 10 12.5 17t19.5 7zM878 1000h-556q-10 0 -19 -7t-11 -18l-87 -450q-2 -11 4 -18t16 -7h150 q10 0 19.5 -7t11.5 -17l38 -152q2 -10 11.5 -17t19.5 -7h250q10 0 19.5 7t11.5 17l38 152q2 10 11.5 17t19.5 7h150q10 0 16 7t4 18l-87 450q-2 11 -11 18t-19 7z" />
|
||||||
|
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM540 820l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" />
|
||||||
|
<glyph unicode="" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-362q0 -10 -7.5 -17.5t-17.5 -7.5h-362q-11 0 -13 5.5t5 12.5l133 133q-109 76 -238 76q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5h150q0 -117 -45.5 -224 t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117z" />
|
||||||
|
<glyph unicode="" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-361q0 -11 -7.5 -18.5t-18.5 -7.5h-361q-11 0 -13 5.5t5 12.5l134 134q-110 75 -239 75q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5h-150q0 117 45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117zM1027 600h150 q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5q-192 0 -348 118l-134 -134q-7 -8 -12.5 -5.5t-5.5 12.5v360q0 11 7.5 18.5t18.5 7.5h360q10 0 12.5 -5.5t-5.5 -12.5l-133 -133q110 -76 240 -76q116 0 214.5 57t155.5 155.5t57 214.5z" />
|
||||||
|
<glyph unicode="" d="M125 1200h1050q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-1050q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM1075 1000h-850q-10 0 -17.5 -7.5t-7.5 -17.5v-850q0 -10 7.5 -17.5t17.5 -7.5h850q10 0 17.5 7.5t7.5 17.5v850 q0 10 -7.5 17.5t-17.5 7.5zM325 900h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 900h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 700h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 700h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 500h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 500h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 300h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 300h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5z" />
|
||||||
|
<glyph unicode="" d="M900 800v200q0 83 -58.5 141.5t-141.5 58.5h-300q-82 0 -141 -59t-59 -141v-200h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h900q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-100zM400 800v150q0 21 15 35.5t35 14.5h200 q20 0 35 -14.5t15 -35.5v-150h-300z" />
|
||||||
|
<glyph unicode="" d="M125 1100h50q10 0 17.5 -7.5t7.5 -17.5v-1075h-100v1075q0 10 7.5 17.5t17.5 7.5zM1075 1052q4 0 9 -2q16 -6 16 -23v-421q0 -6 -3 -12q-33 -59 -66.5 -99t-65.5 -58t-56.5 -24.5t-52.5 -6.5q-26 0 -57.5 6.5t-52.5 13.5t-60 21q-41 15 -63 22.5t-57.5 15t-65.5 7.5 q-85 0 -160 -57q-7 -5 -15 -5q-6 0 -11 3q-14 7 -14 22v438q22 55 82 98.5t119 46.5q23 2 43 0.5t43 -7t32.5 -8.5t38 -13t32.5 -11q41 -14 63.5 -21t57 -14t63.5 -7q103 0 183 87q7 8 18 8z" />
|
||||||
|
<glyph unicode="" d="M600 1175q116 0 227 -49.5t192.5 -131t131 -192.5t49.5 -227v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v300q0 127 -70.5 231.5t-184.5 161.5t-245 57t-245 -57t-184.5 -161.5t-70.5 -231.5v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50 q-10 0 -17.5 7.5t-7.5 17.5v300q0 116 49.5 227t131 192.5t192.5 131t227 49.5zM220 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460q0 8 6 14t14 6zM820 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460 q0 8 6 14t14 6z" />
|
||||||
|
<glyph unicode="" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM900 668l120 120q7 7 17 7t17 -7l34 -34q7 -7 7 -17t-7 -17l-120 -120l120 -120q7 -7 7 -17 t-7 -17l-34 -34q-7 -7 -17 -7t-17 7l-120 119l-120 -119q-7 -7 -17 -7t-17 7l-34 34q-7 7 -7 17t7 17l119 120l-119 120q-7 7 -7 17t7 17l34 34q7 8 17 8t17 -8z" />
|
||||||
|
<glyph unicode="" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6 l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238q-6 8 -4.5 18t9.5 17l29 22q7 5 15 5z" />
|
||||||
|
<glyph unicode="" d="M967 1004h3q11 -1 17 -10q135 -179 135 -396q0 -105 -34 -206.5t-98 -185.5q-7 -9 -17 -10h-3q-9 0 -16 6l-42 34q-8 6 -9 16t5 18q111 150 111 328q0 90 -29.5 176t-84.5 157q-6 9 -5 19t10 16l42 33q7 5 15 5zM321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5 t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238 q-6 8 -4.5 18.5t9.5 16.5l29 22q7 5 15 5z" />
|
||||||
|
<glyph unicode="" d="M500 900h100v-100h-100v-100h-400v-100h-100v600h500v-300zM1200 700h-200v-100h200v-200h-300v300h-200v300h-100v200h600v-500zM100 1100v-300h300v300h-300zM800 1100v-300h300v300h-300zM300 900h-100v100h100v-100zM1000 900h-100v100h100v-100zM300 500h200v-500 h-500v500h200v100h100v-100zM800 300h200v-100h-100v-100h-200v100h-100v100h100v200h-200v100h300v-300zM100 400v-300h300v300h-300zM300 200h-100v100h100v-100zM1200 200h-100v100h100v-100zM700 0h-100v100h100v-100zM1200 0h-300v100h300v-100z" />
|
||||||
|
<glyph unicode="" d="M100 200h-100v1000h100v-1000zM300 200h-100v1000h100v-1000zM700 200h-200v1000h200v-1000zM900 200h-100v1000h100v-1000zM1200 200h-200v1000h200v-1000zM400 0h-300v100h300v-100zM600 0h-100v91h100v-91zM800 0h-100v91h100v-91zM1100 0h-200v91h200v-91z" />
|
||||||
|
<glyph unicode="" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" />
|
||||||
|
<glyph unicode="" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM800 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-56 56l424 426l-700 700h150zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5 t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" />
|
||||||
|
<glyph unicode="" d="M300 1200h825q75 0 75 -75v-900q0 -25 -18 -43l-64 -64q-8 -8 -13 -5.5t-5 12.5v950q0 10 -7.5 17.5t-17.5 7.5h-700q-25 0 -43 -18l-64 -64q-8 -8 -5.5 -13t12.5 -5h700q10 0 17.5 -7.5t7.5 -17.5v-950q0 -10 -7.5 -17.5t-17.5 -7.5h-850q-10 0 -17.5 7.5t-7.5 17.5v975 q0 25 18 43l139 139q18 18 43 18z" />
|
||||||
|
<glyph unicode="" d="M250 1200h800q21 0 35.5 -14.5t14.5 -35.5v-1150l-450 444l-450 -445v1151q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M822 1200h-444q-11 0 -19 -7.5t-9 -17.5l-78 -301q-7 -24 7 -45l57 -108q6 -9 17.5 -15t21.5 -6h450q10 0 21.5 6t17.5 15l62 108q14 21 7 45l-83 301q-1 10 -9 17.5t-19 7.5zM1175 800h-150q-10 0 -21 -6.5t-15 -15.5l-78 -156q-4 -9 -15 -15.5t-21 -6.5h-550 q-10 0 -21 6.5t-15 15.5l-78 156q-4 9 -15 15.5t-21 6.5h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-650q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h750q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5 t7.5 17.5v650q0 10 -7.5 17.5t-17.5 7.5zM850 200h-500q-10 0 -19.5 -7t-11.5 -17l-38 -152q-2 -10 3.5 -17t15.5 -7h600q10 0 15.5 7t3.5 17l-38 152q-2 10 -11.5 17t-19.5 7z" />
|
||||||
|
<glyph unicode="" d="M500 1100h200q56 0 102.5 -20.5t72.5 -50t44 -59t25 -50.5l6 -20h150q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5h150q2 8 6.5 21.5t24 48t45 61t72 48t102.5 21.5zM900 800v-100 h100v100h-100zM600 730q-95 0 -162.5 -67.5t-67.5 -162.5t67.5 -162.5t162.5 -67.5t162.5 67.5t67.5 162.5t-67.5 162.5t-162.5 67.5zM600 603q43 0 73 -30t30 -73t-30 -73t-73 -30t-73 30t-30 73t30 73t73 30z" />
|
||||||
|
<glyph unicode="" d="M681 1199l385 -998q20 -50 60 -92q18 -19 36.5 -29.5t27.5 -11.5l10 -2v-66h-417v66q53 0 75 43.5t5 88.5l-82 222h-391q-58 -145 -92 -234q-11 -34 -6.5 -57t25.5 -37t46 -20t55 -6v-66h-365v66q56 24 84 52q12 12 25 30.5t20 31.5l7 13l399 1006h93zM416 521h340 l-162 457z" />
|
||||||
|
<glyph unicode="" d="M753 641q5 -1 14.5 -4.5t36 -15.5t50.5 -26.5t53.5 -40t50.5 -54.5t35.5 -70t14.5 -87q0 -67 -27.5 -125.5t-71.5 -97.5t-98.5 -66.5t-108.5 -40.5t-102 -13h-500v89q41 7 70.5 32.5t29.5 65.5v827q0 24 -0.5 34t-3.5 24t-8.5 19.5t-17 13.5t-28 12.5t-42.5 11.5v71 l471 -1q57 0 115.5 -20.5t108 -57t80.5 -94t31 -124.5q0 -51 -15.5 -96.5t-38 -74.5t-45 -50.5t-38.5 -30.5zM400 700h139q78 0 130.5 48.5t52.5 122.5q0 41 -8.5 70.5t-29.5 55.5t-62.5 39.5t-103.5 13.5h-118v-350zM400 200h216q80 0 121 50.5t41 130.5q0 90 -62.5 154.5 t-156.5 64.5h-159v-400z" />
|
||||||
|
<glyph unicode="" d="M877 1200l2 -57q-83 -19 -116 -45.5t-40 -66.5l-132 -839q-9 -49 13 -69t96 -26v-97h-500v97q186 16 200 98l173 832q3 17 3 30t-1.5 22.5t-9 17.5t-13.5 12.5t-21.5 10t-26 8.5t-33.5 10q-13 3 -19 5v57h425z" />
|
||||||
|
<glyph unicode="" d="M1300 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM175 1000h-75v-800h75l-125 -167l-125 167h75v800h-75l125 167z" />
|
||||||
|
<glyph unicode="" d="M1100 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-650q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v650h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM1167 50l-167 -125v75h-800v-75l-167 125l167 125v-75h800v75z" />
|
||||||
|
<glyph unicode="" d="M50 1100h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M250 1100h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM250 500h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000 q-21 0 -35.5 14.5t-14.5 35.5zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5zM0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5z" />
|
||||||
|
<glyph unicode="" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 1100h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 800h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 500h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 500h800q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 200h800 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M400 0h-100v1100h100v-1100zM550 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM267 550l-167 -125v75h-200v100h200v75zM550 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM900 0h-100v1100h100v-1100zM50 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM1100 600h200v-100h-200v-75l-167 125l167 125v-75zM50 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M75 1000h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53v650q0 31 22 53t53 22zM1200 300l-300 300l300 300v-600z" />
|
||||||
|
<glyph unicode="" d="M44 1100h1112q18 0 31 -13t13 -31v-1012q0 -18 -13 -31t-31 -13h-1112q-18 0 -31 13t-13 31v1012q0 18 13 31t31 13zM100 1000v-737l247 182l298 -131l-74 156l293 318l236 -288v500h-1000zM342 884q56 0 95 -39t39 -94.5t-39 -95t-95 -39.5t-95 39.5t-39 95t39 94.5 t95 39z" />
|
||||||
|
<glyph unicode="" d="M648 1169q117 0 216 -60t156.5 -161t57.5 -218q0 -115 -70 -258q-69 -109 -158 -225.5t-143 -179.5l-54 -62q-9 8 -25.5 24.5t-63.5 67.5t-91 103t-98.5 128t-95.5 148q-60 132 -60 249q0 88 34 169.5t91.5 142t137 96.5t166.5 36zM652.5 974q-91.5 0 -156.5 -65 t-65 -157t65 -156.5t156.5 -64.5t156.5 64.5t65 156.5t-65 157t-156.5 65z" />
|
||||||
|
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 173v854q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57z" />
|
||||||
|
<glyph unicode="" d="M554 1295q21 -72 57.5 -143.5t76 -130t83 -118t82.5 -117t70 -116t49.5 -126t18.5 -136.5q0 -71 -25.5 -135t-68.5 -111t-99 -82t-118.5 -54t-125.5 -23q-84 5 -161.5 34t-139.5 78.5t-99 125t-37 164.5q0 69 18 136.5t49.5 126.5t69.5 116.5t81.5 117.5t83.5 119 t76.5 131t58.5 143zM344 710q-23 -33 -43.5 -70.5t-40.5 -102.5t-17 -123q1 -37 14.5 -69.5t30 -52t41 -37t38.5 -24.5t33 -15q21 -7 32 -1t13 22l6 34q2 10 -2.5 22t-13.5 19q-5 4 -14 12t-29.5 40.5t-32.5 73.5q-26 89 6 271q2 11 -6 11q-8 1 -15 -10z" />
|
||||||
|
<glyph unicode="" d="M1000 1013l108 115q2 1 5 2t13 2t20.5 -1t25 -9.5t28.5 -21.5q22 -22 27 -43t0 -32l-6 -10l-108 -115zM350 1100h400q50 0 105 -13l-187 -187h-368q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v182l200 200v-332 q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM1009 803l-362 -362l-161 -50l55 170l355 355z" />
|
||||||
|
<glyph unicode="" d="M350 1100h361q-164 -146 -216 -200h-195q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-103q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M824 1073l339 -301q8 -7 8 -17.5t-8 -17.5l-340 -306q-7 -6 -12.5 -4t-6.5 11v203q-26 1 -54.5 0t-78.5 -7.5t-92 -17.5t-86 -35t-70 -57q10 59 33 108t51.5 81.5t65 58.5t68.5 40.5t67 24.5t56 13.5t40 4.5v210q1 10 6.5 12.5t13.5 -4.5z" />
|
||||||
|
<glyph unicode="" d="M350 1100h350q60 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-219q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M643 639l395 395q7 7 17.5 7t17.5 -7l101 -101q7 -7 7 -17.5t-7 -17.5l-531 -532q-7 -7 -17.5 -7t-17.5 7l-248 248q-7 7 -7 17.5t7 17.5l101 101q7 7 17.5 7t17.5 -7l111 -111q8 -7 18 -7t18 7z" />
|
||||||
|
<glyph unicode="" d="M318 918l264 264q8 8 18 8t18 -8l260 -264q7 -8 4.5 -13t-12.5 -5h-170v-200h200v173q0 10 5 12t13 -5l264 -260q8 -7 8 -17.5t-8 -17.5l-264 -265q-8 -7 -13 -5t-5 12v173h-200v-200h170q10 0 12.5 -5t-4.5 -13l-260 -264q-8 -8 -18 -8t-18 8l-264 264q-8 8 -5.5 13 t12.5 5h175v200h-200v-173q0 -10 -5 -12t-13 5l-264 265q-8 7 -8 17.5t8 17.5l264 260q8 7 13 5t5 -12v-173h200v200h-175q-10 0 -12.5 5t5.5 13z" />
|
||||||
|
<glyph unicode="" d="M250 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5 t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M1200 1050v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-492 480q-15 14 -15 35t15 35l492 480q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25z" />
|
||||||
|
<glyph unicode="" d="M243 1074l814 -498q18 -11 18 -26t-18 -26l-814 -498q-18 -11 -30.5 -4t-12.5 28v1000q0 21 12.5 28t30.5 -4z" />
|
||||||
|
<glyph unicode="" d="M250 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM650 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800 q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M1100 950v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5z" />
|
||||||
|
<glyph unicode="" d="M500 612v438q0 21 10.5 25t25.5 -10l492 -480q15 -14 15 -35t-15 -35l-492 -480q-15 -14 -25.5 -10t-10.5 25v438l-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10z" />
|
||||||
|
<glyph unicode="" d="M1048 1102l100 1q20 0 35 -14.5t15 -35.5l5 -1000q0 -21 -14.5 -35.5t-35.5 -14.5l-100 -1q-21 0 -35.5 14.5t-14.5 35.5l-2 437l-463 -454q-14 -15 -24.5 -10.5t-10.5 25.5l-2 437l-462 -455q-15 -14 -25.5 -9.5t-10.5 24.5l-5 1000q0 21 10.5 25.5t25.5 -10.5l466 -450 l-2 438q0 20 10.5 24.5t25.5 -9.5l466 -451l-2 438q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M850 1100h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10l464 -453v438q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M686 1081l501 -540q15 -15 10.5 -26t-26.5 -11h-1042q-22 0 -26.5 11t10.5 26l501 540q15 15 36 15t36 -15zM150 400h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M885 900l-352 -353l352 -353l-197 -198l-552 552l552 550z" />
|
||||||
|
<glyph unicode="" d="M1064 547l-551 -551l-198 198l353 353l-353 353l198 198z" />
|
||||||
|
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM650 900h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-150 q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5h150v-150q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v150h150q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-150v150q0 21 -14.5 35.5t-35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM850 700h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5 t35.5 -14.5h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM741.5 913q-12.5 0 -21.5 -9l-120 -120l-120 120q-9 9 -21.5 9 t-21.5 -9l-141 -141q-9 -9 -9 -21.5t9 -21.5l120 -120l-120 -120q-9 -9 -9 -21.5t9 -21.5l141 -141q9 -9 21.5 -9t21.5 9l120 120l120 -120q9 -9 21.5 -9t21.5 9l141 141q9 9 9 21.5t-9 21.5l-120 120l120 120q9 9 9 21.5t-9 21.5l-141 141q-9 9 -21.5 9z" />
|
||||||
|
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM546 623l-84 85q-7 7 -17.5 7t-18.5 -7l-139 -139q-7 -8 -7 -18t7 -18 l242 -241q7 -8 17.5 -8t17.5 8l375 375q7 7 7 17.5t-7 18.5l-139 139q-7 7 -17.5 7t-17.5 -7z" />
|
||||||
|
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM588 941q-29 0 -59 -5.5t-63 -20.5t-58 -38.5t-41.5 -63t-16.5 -89.5 q0 -25 20 -25h131q30 -5 35 11q6 20 20.5 28t45.5 8q20 0 31.5 -10.5t11.5 -28.5q0 -23 -7 -34t-26 -18q-1 0 -13.5 -4t-19.5 -7.5t-20 -10.5t-22 -17t-18.5 -24t-15.5 -35t-8 -46q-1 -8 5.5 -16.5t20.5 -8.5h173q7 0 22 8t35 28t37.5 48t29.5 74t12 100q0 47 -17 83 t-42.5 57t-59.5 34.5t-64 18t-59 4.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" />
|
||||||
|
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM675 1000h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5 t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5zM675 700h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h75v-200h-75q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h350q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5 t-17.5 7.5h-75v275q0 10 -7.5 17.5t-17.5 7.5z" />
|
||||||
|
<glyph unicode="" d="M525 1200h150q10 0 17.5 -7.5t7.5 -17.5v-194q103 -27 178.5 -102.5t102.5 -178.5h194q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-194q-27 -103 -102.5 -178.5t-178.5 -102.5v-194q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v194 q-103 27 -178.5 102.5t-102.5 178.5h-194q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h194q27 103 102.5 178.5t178.5 102.5v194q0 10 7.5 17.5t17.5 7.5zM700 893v-168q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v168q-68 -23 -119 -74 t-74 -119h168q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-168q23 -68 74 -119t119 -74v168q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-168q68 23 119 74t74 119h-168q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h168 q-23 68 -74 119t-119 74z" />
|
||||||
|
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM759 823l64 -64q7 -7 7 -17.5t-7 -17.5l-124 -124l124 -124q7 -7 7 -17.5t-7 -17.5l-64 -64q-7 -7 -17.5 -7t-17.5 7l-124 124l-124 -124q-7 -7 -17.5 -7t-17.5 7l-64 64 q-7 7 -7 17.5t7 17.5l124 124l-124 124q-7 7 -7 17.5t7 17.5l64 64q7 7 17.5 7t17.5 -7l124 -124l124 124q7 7 17.5 7t17.5 -7z" />
|
||||||
|
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM782 788l106 -106q7 -7 7 -17.5t-7 -17.5l-320 -321q-8 -7 -18 -7t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l197 197q7 7 17.5 7t17.5 -7z" />
|
||||||
|
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5q0 -120 65 -225 l587 587q-105 65 -225 65zM965 819l-584 -584q104 -62 219 -62q116 0 214.5 57t155.5 155.5t57 214.5q0 115 -62 219z" />
|
||||||
|
<glyph unicode="" d="M39 582l522 427q16 13 27.5 8t11.5 -26v-291h550q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-550v-291q0 -21 -11.5 -26t-27.5 8l-522 427q-16 13 -16 32t16 32z" />
|
||||||
|
<glyph unicode="" d="M639 1009l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291h-550q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h550v291q0 21 11.5 26t27.5 -8z" />
|
||||||
|
<glyph unicode="" d="M682 1161l427 -522q13 -16 8 -27.5t-26 -11.5h-291v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v550h-291q-21 0 -26 11.5t8 27.5l427 522q13 16 32 16t32 -16z" />
|
||||||
|
<glyph unicode="" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-550h291q21 0 26 -11.5t-8 -27.5l-427 -522q-13 -16 -32 -16t-32 16l-427 522q-13 16 -8 27.5t26 11.5h291v550q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M639 1109l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291q-94 -2 -182 -20t-170.5 -52t-147 -92.5t-100.5 -135.5q5 105 27 193.5t67.5 167t113 135t167 91.5t225.5 42v262q0 21 11.5 26t27.5 -8z" />
|
||||||
|
<glyph unicode="" d="M850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5zM350 0h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249 q8 7 18 7t18 -7l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5z" />
|
||||||
|
<glyph unicode="" d="M1014 1120l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249q8 7 18 7t18 -7zM250 600h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5z" />
|
||||||
|
<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM704 900h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5 t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" />
|
||||||
|
<glyph unicode="" d="M260 1200q9 0 19 -2t15 -4l5 -2q22 -10 44 -23l196 -118q21 -13 36 -24q29 -21 37 -12q11 13 49 35l196 118q22 13 45 23q17 7 38 7q23 0 47 -16.5t37 -33.5l13 -16q14 -21 18 -45l25 -123l8 -44q1 -9 8.5 -14.5t17.5 -5.5h61q10 0 17.5 -7.5t7.5 -17.5v-50 q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 -7.5t-7.5 -17.5v-175h-400v300h-200v-300h-400v175q0 10 -7.5 17.5t-17.5 7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5h61q11 0 18 3t7 8q0 4 9 52l25 128q5 25 19 45q2 3 5 7t13.5 15t21.5 19.5t26.5 15.5 t29.5 7zM915 1079l-166 -162q-7 -7 -5 -12t12 -5h219q10 0 15 7t2 17l-51 149q-3 10 -11 12t-15 -6zM463 917l-177 157q-8 7 -16 5t-11 -12l-51 -143q-3 -10 2 -17t15 -7h231q11 0 12.5 5t-5.5 12zM500 0h-375q-10 0 -17.5 7.5t-7.5 17.5v375h400v-400zM1100 400v-375 q0 -10 -7.5 -17.5t-17.5 -7.5h-375v400h400z" />
|
||||||
|
<glyph unicode="" d="M1165 1190q8 3 21 -6.5t13 -17.5q-2 -178 -24.5 -323.5t-55.5 -245.5t-87 -174.5t-102.5 -118.5t-118 -68.5t-118.5 -33t-120 -4.5t-105 9.5t-90 16.5q-61 12 -78 11q-4 1 -12.5 0t-34 -14.5t-52.5 -40.5l-153 -153q-26 -24 -37 -14.5t-11 43.5q0 64 42 102q8 8 50.5 45 t66.5 58q19 17 35 47t13 61q-9 55 -10 102.5t7 111t37 130t78 129.5q39 51 80 88t89.5 63.5t94.5 45t113.5 36t129 31t157.5 37t182 47.5zM1116 1098q-8 9 -22.5 -3t-45.5 -50q-38 -47 -119 -103.5t-142 -89.5l-62 -33q-56 -30 -102 -57t-104 -68t-102.5 -80.5t-85.5 -91 t-64 -104.5q-24 -56 -31 -86t2 -32t31.5 17.5t55.5 59.5q25 30 94 75.5t125.5 77.5t147.5 81q70 37 118.5 69t102 79.5t99 111t86.5 148.5q22 50 24 60t-6 19z" />
|
||||||
|
<glyph unicode="" d="M653 1231q-39 -67 -54.5 -131t-10.5 -114.5t24.5 -96.5t47.5 -80t63.5 -62.5t68.5 -46.5t65 -30q-4 7 -17.5 35t-18.5 39.5t-17 39.5t-17 43t-13 42t-9.5 44.5t-2 42t4 43t13.5 39t23 38.5q96 -42 165 -107.5t105 -138t52 -156t13 -159t-19 -149.5q-13 -55 -44 -106.5 t-68 -87t-78.5 -64.5t-72.5 -45t-53 -22q-72 -22 -127 -11q-31 6 -13 19q6 3 17 7q13 5 32.5 21t41 44t38.5 63.5t21.5 81.5t-6.5 94.5t-50 107t-104 115.5q10 -104 -0.5 -189t-37 -140.5t-65 -93t-84 -52t-93.5 -11t-95 24.5q-80 36 -131.5 114t-53.5 171q-2 23 0 49.5 t4.5 52.5t13.5 56t27.5 60t46 64.5t69.5 68.5q-8 -53 -5 -102.5t17.5 -90t34 -68.5t44.5 -39t49 -2q31 13 38.5 36t-4.5 55t-29 64.5t-36 75t-26 75.5q-15 85 2 161.5t53.5 128.5t85.5 92.5t93.5 61t81.5 25.5z" />
|
||||||
|
<glyph unicode="" d="M600 1094q82 0 160.5 -22.5t140 -59t116.5 -82.5t94.5 -95t68 -95t42.5 -82.5t14 -57.5t-14 -57.5t-43 -82.5t-68.5 -95t-94.5 -95t-116.5 -82.5t-140 -59t-159.5 -22.5t-159.5 22.5t-140 59t-116.5 82.5t-94.5 95t-68.5 95t-43 82.5t-14 57.5t14 57.5t42.5 82.5t68 95 t94.5 95t116.5 82.5t140 59t160.5 22.5zM888 829q-15 15 -18 12t5 -22q25 -57 25 -119q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 59 23 114q8 19 4.5 22t-17.5 -12q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q22 -36 47 -71t70 -82t92.5 -81t113 -58.5t133.5 -24.5 t133.5 24t113 58.5t92.5 81.5t70 81.5t47 70.5q11 18 9 42.5t-14 41.5q-90 117 -163 189zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l35 34q14 15 12.5 33.5t-16.5 33.5q-44 44 -89 117q-11 18 -28 20t-32 -12z" />
|
||||||
|
<glyph unicode="" d="M592 0h-148l31 120q-91 20 -175.5 68.5t-143.5 106.5t-103.5 119t-66.5 110t-22 76q0 21 14 57.5t42.5 82.5t68 95t94.5 95t116.5 82.5t140 59t160.5 22.5q61 0 126 -15l32 121h148zM944 770l47 181q108 -85 176.5 -192t68.5 -159q0 -26 -19.5 -71t-59.5 -102t-93 -112 t-129 -104.5t-158 -75.5l46 173q77 49 136 117t97 131q11 18 9 42.5t-14 41.5q-54 70 -107 130zM310 824q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q18 -30 39 -60t57 -70.5t74 -73t90 -61t105 -41.5l41 154q-107 18 -178.5 101.5t-71.5 193.5q0 59 23 114q8 19 4.5 22 t-17.5 -12zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l12 11l22 86l-3 4q-44 44 -89 117q-11 18 -28 20t-32 -12z" />
|
||||||
|
<glyph unicode="" d="M-90 100l642 1066q20 31 48 28.5t48 -35.5l642 -1056q21 -32 7.5 -67.5t-50.5 -35.5h-1294q-37 0 -50.5 34t7.5 66zM155 200h345v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h345l-445 723zM496 700h208q20 0 32 -14.5t8 -34.5l-58 -252 q-4 -20 -21.5 -34.5t-37.5 -14.5h-54q-20 0 -37.5 14.5t-21.5 34.5l-58 252q-4 20 8 34.5t32 14.5z" />
|
||||||
|
<glyph unicode="" d="M650 1200q62 0 106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -93 100 -113v-64q0 -21 -13 -29t-32 1l-205 128l-205 -128q-19 -9 -32 -1t-13 29v64q0 20 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5v41 q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44z" />
|
||||||
|
<glyph unicode="" d="M850 1200h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-150h-1100v150q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-50h500v50q0 21 14.5 35.5t35.5 14.5zM1100 800v-750q0 -21 -14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v750h1100zM100 600v-100h100v100h-100zM300 600v-100h100v100h-100zM500 600v-100h100v100h-100zM700 600v-100h100v100h-100zM900 600v-100h100v100h-100zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400 v-100h100v100h-100zM700 400v-100h100v100h-100zM900 400v-100h100v100h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100zM500 200v-100h100v100h-100zM700 200v-100h100v100h-100zM900 200v-100h100v100h-100z" />
|
||||||
|
<glyph unicode="" d="M1135 1165l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-159l-600 -600h-291q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h209l600 600h241v150q0 21 10.5 25t24.5 -10zM522 819l-141 -141l-122 122h-209q-21 0 -35.5 14.5 t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h291zM1135 565l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-241l-181 181l141 141l122 -122h159v150q0 21 10.5 25t24.5 -10z" />
|
||||||
|
<glyph unicode="" d="M100 1100h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5z" />
|
||||||
|
<glyph unicode="" d="M150 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM850 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM1100 800v-300q0 -41 -3 -77.5t-15 -89.5t-32 -96t-58 -89t-89 -77t-129 -51t-174 -20t-174 20 t-129 51t-89 77t-58 89t-32 96t-15 89.5t-3 77.5v300h300v-250v-27v-42.5t1.5 -41t5 -38t10 -35t16.5 -30t25.5 -24.5t35 -19t46.5 -12t60 -4t60 4.5t46.5 12.5t35 19.5t25 25.5t17 30.5t10 35t5 38t2 40.5t-0.5 42v25v250h300z" />
|
||||||
|
<glyph unicode="" d="M1100 411l-198 -199l-353 353l-353 -353l-197 199l551 551z" />
|
||||||
|
<glyph unicode="" d="M1101 789l-550 -551l-551 551l198 199l353 -353l353 353z" />
|
||||||
|
<glyph unicode="" d="M404 1000h746q21 0 35.5 -14.5t14.5 -35.5v-551h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v401h-381zM135 984l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-400h385l215 -200h-750q-21 0 -35.5 14.5 t-14.5 35.5v550h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
|
||||||
|
<glyph unicode="" d="M56 1200h94q17 0 31 -11t18 -27l38 -162h896q24 0 39 -18.5t10 -42.5l-100 -475q-5 -21 -27 -42.5t-55 -21.5h-633l48 -200h535q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-50q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-300v-50 q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-31q-18 0 -32.5 10t-20.5 19l-5 10l-201 961h-54q-20 0 -35 14.5t-15 35.5t15 35.5t35 14.5z" />
|
||||||
|
<glyph unicode="" d="M1200 1000v-100h-1200v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500zM0 800h1200v-800h-1200v800z" />
|
||||||
|
<glyph unicode="" d="M200 800l-200 -400v600h200q0 41 29.5 70.5t70.5 29.5h300q42 0 71 -29.5t29 -70.5h500v-200h-1000zM1500 700l-300 -700h-1200l300 700h1200z" />
|
||||||
|
<glyph unicode="" d="M635 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-601h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v601h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
|
||||||
|
<glyph unicode="" d="M936 864l249 -229q14 -15 14 -35.5t-14 -35.5l-249 -229q-15 -15 -25.5 -10.5t-10.5 24.5v151h-600v-151q0 -20 -10.5 -24.5t-25.5 10.5l-249 229q-14 15 -14 35.5t14 35.5l249 229q15 15 25.5 10.5t10.5 -25.5v-149h600v149q0 21 10.5 25.5t25.5 -10.5z" />
|
||||||
|
<glyph unicode="" d="M1169 400l-172 732q-5 23 -23 45.5t-38 22.5h-672q-20 0 -38 -20t-23 -41l-172 -739h1138zM1100 300h-1000q-41 0 -70.5 -29.5t-29.5 -70.5v-100q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v100q0 41 -29.5 70.5t-70.5 29.5zM800 100v100h100v-100h-100 zM1000 100v100h100v-100h-100z" />
|
||||||
|
<glyph unicode="" d="M1150 1100q21 0 35.5 -14.5t14.5 -35.5v-850q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v850q0 21 14.5 35.5t35.5 14.5zM1000 200l-675 200h-38l47 -276q3 -16 -5.5 -20t-29.5 -4h-7h-84q-20 0 -34.5 14t-18.5 35q-55 337 -55 351v250v6q0 16 1 23.5t6.5 14 t17.5 6.5h200l675 250v-850zM0 750v-250q-4 0 -11 0.5t-24 6t-30 15t-24 30t-11 48.5v50q0 26 10.5 46t25 30t29 16t25.5 7z" />
|
||||||
|
<glyph unicode="" d="M553 1200h94q20 0 29 -10.5t3 -29.5l-18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q19 0 33 -14.5t14 -35t-13 -40.5t-31 -27q-8 -4 -23 -9.5t-65 -19.5t-103 -25t-132.5 -20t-158.5 -9q-57 0 -115 5t-104 12t-88.5 15.5t-73.5 17.5t-54.5 16t-35.5 12l-11 4 q-18 8 -31 28t-13 40.5t14 35t33 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3.5 32t28.5 13zM498 110q50 -6 102 -6q53 0 102 6q-12 -49 -39.5 -79.5t-62.5 -30.5t-63 30.5t-39 79.5z" />
|
||||||
|
<glyph unicode="" d="M800 946l224 78l-78 -224l234 -45l-180 -155l180 -155l-234 -45l78 -224l-224 78l-45 -234l-155 180l-155 -180l-45 234l-224 -78l78 224l-234 45l180 155l-180 155l234 45l-78 224l224 -78l45 234l155 -180l155 180z" />
|
||||||
|
<glyph unicode="" d="M650 1200h50q40 0 70 -40.5t30 -84.5v-150l-28 -125h328q40 0 70 -40.5t30 -84.5v-100q0 -45 -29 -74l-238 -344q-16 -24 -38 -40.5t-45 -16.5h-250q-7 0 -42 25t-66 50l-31 25h-61q-45 0 -72.5 18t-27.5 57v400q0 36 20 63l145 196l96 198q13 28 37.5 48t51.5 20z M650 1100l-100 -212l-150 -213v-375h100l136 -100h214l250 375v125h-450l50 225v175h-50zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M600 1100h250q23 0 45 -16.5t38 -40.5l238 -344q29 -29 29 -74v-100q0 -44 -30 -84.5t-70 -40.5h-328q28 -118 28 -125v-150q0 -44 -30 -84.5t-70 -40.5h-50q-27 0 -51.5 20t-37.5 48l-96 198l-145 196q-20 27 -20 63v400q0 39 27.5 57t72.5 18h61q124 100 139 100z M50 1000h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM636 1000l-136 -100h-100v-375l150 -213l100 -212h50v175l-50 225h450v125l-250 375h-214z" />
|
||||||
|
<glyph unicode="" d="M356 873l363 230q31 16 53 -6l110 -112q13 -13 13.5 -32t-11.5 -34l-84 -121h302q84 0 138 -38t54 -110t-55 -111t-139 -39h-106l-131 -339q-6 -21 -19.5 -41t-28.5 -20h-342q-7 0 -90 81t-83 94v525q0 17 14 35.5t28 28.5zM400 792v-503l100 -89h293l131 339 q6 21 19.5 41t28.5 20h203q21 0 30.5 25t0.5 50t-31 25h-456h-7h-6h-5.5t-6 0.5t-5 1.5t-5 2t-4 2.5t-4 4t-2.5 4.5q-12 25 5 47l146 183l-86 83zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500 q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M475 1103l366 -230q2 -1 6 -3.5t14 -10.5t18 -16.5t14.5 -20t6.5 -22.5v-525q0 -13 -86 -94t-93 -81h-342q-15 0 -28.5 20t-19.5 41l-131 339h-106q-85 0 -139.5 39t-54.5 111t54 110t138 38h302l-85 121q-11 15 -10.5 34t13.5 32l110 112q22 22 53 6zM370 945l146 -183 q17 -22 5 -47q-2 -2 -3.5 -4.5t-4 -4t-4 -2.5t-5 -2t-5 -1.5t-6 -0.5h-6h-6.5h-6h-475v-100h221q15 0 29 -20t20 -41l130 -339h294l106 89v503l-342 236zM1050 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5 v500q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M550 1294q72 0 111 -55t39 -139v-106l339 -131q21 -6 41 -19.5t20 -28.5v-342q0 -7 -81 -90t-94 -83h-525q-17 0 -35.5 14t-28.5 28l-9 14l-230 363q-16 31 6 53l112 110q13 13 32 13.5t34 -11.5l121 -84v302q0 84 38 138t110 54zM600 972v203q0 21 -25 30.5t-50 0.5 t-25 -31v-456v-7v-6v-5.5t-0.5 -6t-1.5 -5t-2 -5t-2.5 -4t-4 -4t-4.5 -2.5q-25 -12 -47 5l-183 146l-83 -86l236 -339h503l89 100v293l-339 131q-21 6 -41 19.5t-20 28.5zM450 200h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M350 1100h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5zM600 306v-106q0 -84 -39 -139t-111 -55t-110 54t-38 138v302l-121 -84q-15 -12 -34 -11.5t-32 13.5l-112 110 q-22 22 -6 53l230 363q1 2 3.5 6t10.5 13.5t16.5 17t20 13.5t22.5 6h525q13 0 94 -83t81 -90v-342q0 -15 -20 -28.5t-41 -19.5zM308 900l-236 -339l83 -86l183 146q22 17 47 5q2 -1 4.5 -2.5t4 -4t2.5 -4t2 -5t1.5 -5t0.5 -6v-5.5v-6v-7v-456q0 -22 25 -31t50 0.5t25 30.5 v203q0 15 20 28.5t41 19.5l339 131v293l-89 100h-503z" />
|
||||||
|
<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM914 632l-275 223q-16 13 -27.5 8t-11.5 -26v-137h-275 q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h275v-137q0 -21 11.5 -26t27.5 8l275 223q16 13 16 32t-16 32z" />
|
||||||
|
<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM561 855l-275 -223q-16 -13 -16 -32t16 -32l275 -223q16 -13 27.5 -8 t11.5 26v137h275q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5h-275v137q0 21 -11.5 26t-27.5 -8z" />
|
||||||
|
<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM855 639l-223 275q-13 16 -32 16t-32 -16l-223 -275q-13 -16 -8 -27.5 t26 -11.5h137v-275q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v275h137q21 0 26 11.5t-8 27.5z" />
|
||||||
|
<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM675 900h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-275h-137q-21 0 -26 -11.5 t8 -27.5l223 -275q13 -16 32 -16t32 16l223 275q13 16 8 27.5t-26 11.5h-137v275q0 10 -7.5 17.5t-17.5 7.5z" />
|
||||||
|
<glyph unicode="" d="M600 1176q116 0 222.5 -46t184 -123.5t123.5 -184t46 -222.5t-46 -222.5t-123.5 -184t-184 -123.5t-222.5 -46t-222.5 46t-184 123.5t-123.5 184t-46 222.5t46 222.5t123.5 184t184 123.5t222.5 46zM627 1101q-15 -12 -36.5 -20.5t-35.5 -12t-43 -8t-39 -6.5 q-15 -3 -45.5 0t-45.5 -2q-20 -7 -51.5 -26.5t-34.5 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -91t-29.5 -79q-9 -34 5 -93t8 -87q0 -9 17 -44.5t16 -59.5q12 0 23 -5t23.5 -15t19.5 -14q16 -8 33 -15t40.5 -15t34.5 -12q21 -9 52.5 -32t60 -38t57.5 -11 q7 -15 -3 -34t-22.5 -40t-9.5 -38q13 -21 23 -34.5t27.5 -27.5t36.5 -18q0 -7 -3.5 -16t-3.5 -14t5 -17q104 -2 221 112q30 29 46.5 47t34.5 49t21 63q-13 8 -37 8.5t-36 7.5q-15 7 -49.5 15t-51.5 19q-18 0 -41 -0.5t-43 -1.5t-42 -6.5t-38 -16.5q-51 -35 -66 -12 q-4 1 -3.5 25.5t0.5 25.5q-6 13 -26.5 17.5t-24.5 6.5q1 15 -0.5 30.5t-7 28t-18.5 11.5t-31 -21q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q7 -12 18 -24t21.5 -20.5t20 -15t15.5 -10.5l5 -3q2 12 7.5 30.5t8 34.5t-0.5 32q-3 18 3.5 29 t18 22.5t15.5 24.5q6 14 10.5 35t8 31t15.5 22.5t34 22.5q-6 18 10 36q8 0 24 -1.5t24.5 -1.5t20 4.5t20.5 15.5q-10 23 -31 42.5t-37.5 29.5t-49 27t-43.5 23q0 1 2 8t3 11.5t1.5 10.5t-1 9.5t-4.5 4.5q31 -13 58.5 -14.5t38.5 2.5l12 5q5 28 -9.5 46t-36.5 24t-50 15 t-41 20q-18 -4 -37 0zM613 994q0 -17 8 -42t17 -45t9 -23q-8 1 -39.5 5.5t-52.5 10t-37 16.5q3 11 16 29.5t16 25.5q10 -10 19 -10t14 6t13.5 14.5t16.5 12.5z" />
|
||||||
|
<glyph unicode="" d="M756 1157q164 92 306 -9l-259 -138l145 -232l251 126q6 -89 -34 -156.5t-117 -110.5q-60 -34 -127 -39.5t-126 16.5l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5t15 37.5l600 599q-34 101 5.5 201.5t135.5 154.5z" />
|
||||||
|
<glyph unicode="" horiz-adv-x="1220" d="M100 1196h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 1096h-200v-100h200v100zM100 796h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 696h-500v-100h500v100zM100 396h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 296h-300v-100h300v100z " />
|
||||||
|
<glyph unicode="" d="M150 1200h900q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM700 500v-300l-200 -200v500l-350 500h900z" />
|
||||||
|
<glyph unicode="" d="M500 1200h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5zM500 1100v-100h200v100h-200zM1200 400v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v200h1200z" />
|
||||||
|
<glyph unicode="" d="M50 1200h300q21 0 25 -10.5t-10 -24.5l-94 -94l199 -199q7 -8 7 -18t-7 -18l-106 -106q-8 -7 -18 -7t-18 7l-199 199l-94 -94q-14 -14 -24.5 -10t-10.5 25v300q0 21 14.5 35.5t35.5 14.5zM850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-199 -199q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l199 199l-94 94q-14 14 -10 24.5t25 10.5zM364 470l106 -106q7 -8 7 -18t-7 -18l-199 -199l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l199 199 q8 7 18 7t18 -7zM1071 271l94 94q14 14 24.5 10t10.5 -25v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -25 10.5t10 24.5l94 94l-199 199q-7 8 -7 18t7 18l106 106q8 7 18 7t18 -7z" />
|
||||||
|
<glyph unicode="" d="M596 1192q121 0 231.5 -47.5t190 -127t127 -190t47.5 -231.5t-47.5 -231.5t-127 -190.5t-190 -127t-231.5 -47t-231.5 47t-190.5 127t-127 190.5t-47 231.5t47 231.5t127 190t190.5 127t231.5 47.5zM596 1010q-112 0 -207.5 -55.5t-151 -151t-55.5 -207.5t55.5 -207.5 t151 -151t207.5 -55.5t207.5 55.5t151 151t55.5 207.5t-55.5 207.5t-151 151t-207.5 55.5zM454.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38.5 -16.5t-38.5 16.5t-16 39t16 38.5t38.5 16zM754.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38 -16.5q-14 0 -29 10l-55 -145 q17 -23 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 23 16 39t38.5 16zM345.5 709q22.5 0 38.5 -16t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16zM854.5 709q22.5 0 38.5 -16 t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16z" />
|
||||||
|
<glyph unicode="" d="M546 173l469 470q91 91 99 192q7 98 -52 175.5t-154 94.5q-22 4 -47 4q-34 0 -66.5 -10t-56.5 -23t-55.5 -38t-48 -41.5t-48.5 -47.5q-376 -375 -391 -390q-30 -27 -45 -41.5t-37.5 -41t-32 -46.5t-16 -47.5t-1.5 -56.5q9 -62 53.5 -95t99.5 -33q74 0 125 51l548 548 q36 36 20 75q-7 16 -21.5 26t-32.5 10q-26 0 -50 -23q-13 -12 -39 -38l-341 -338q-15 -15 -35.5 -15.5t-34.5 13.5t-14 34.5t14 34.5q327 333 361 367q35 35 67.5 51.5t78.5 16.5q14 0 29 -1q44 -8 74.5 -35.5t43.5 -68.5q14 -47 2 -96.5t-47 -84.5q-12 -11 -32 -32 t-79.5 -81t-114.5 -115t-124.5 -123.5t-123 -119.5t-96.5 -89t-57 -45q-56 -27 -120 -27q-70 0 -129 32t-93 89q-48 78 -35 173t81 163l511 511q71 72 111 96q91 55 198 55q80 0 152 -33q78 -36 129.5 -103t66.5 -154q17 -93 -11 -183.5t-94 -156.5l-482 -476 q-15 -15 -36 -16t-37 14t-17.5 34t14.5 35z" />
|
||||||
|
<glyph unicode="" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104zM896 972q-33 0 -64.5 -19t-56.5 -46t-47.5 -53.5t-43.5 -45.5t-37.5 -19t-36 19t-40 45.5t-43 53.5t-54 46t-65.5 19q-67 0 -122.5 -55.5t-55.5 -132.5q0 -23 13.5 -51t46 -65t57.5 -63t76 -75l22 -22q15 -14 44 -44t50.5 -51t46 -44t41 -35t23 -12 t23.5 12t42.5 36t46 44t52.5 52t44 43q4 4 12 13q43 41 63.5 62t52 55t46 55t26 46t11.5 44q0 79 -53 133.5t-120 54.5z" />
|
||||||
|
<glyph unicode="" d="M776.5 1214q93.5 0 159.5 -66l141 -141q66 -66 66 -160q0 -42 -28 -95.5t-62 -87.5l-29 -29q-31 53 -77 99l-18 18l95 95l-247 248l-389 -389l212 -212l-105 -106l-19 18l-141 141q-66 66 -66 159t66 159l283 283q65 66 158.5 66zM600 706l105 105q10 -8 19 -17l141 -141 q66 -66 66 -159t-66 -159l-283 -283q-66 -66 -159 -66t-159 66l-141 141q-66 66 -66 159.5t66 159.5l55 55q29 -55 75 -102l18 -17l-95 -95l247 -248l389 389z" />
|
||||||
|
<glyph unicode="" d="M603 1200q85 0 162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5v953q0 21 30 46.5t81 48t129 37.5t163 15zM300 1000v-700h600v700h-600zM600 254q-43 0 -73.5 -30.5t-30.5 -73.5t30.5 -73.5t73.5 -30.5t73.5 30.5 t30.5 73.5t-30.5 73.5t-73.5 30.5z" />
|
||||||
|
<glyph unicode="" d="M902 1185l283 -282q15 -15 15 -36t-14.5 -35.5t-35.5 -14.5t-35 15l-36 35l-279 -267v-300l-212 210l-308 -307l-280 -203l203 280l307 308l-210 212h300l267 279l-35 36q-15 14 -15 35t14.5 35.5t35.5 14.5t35 -15z" />
|
||||||
|
<glyph unicode="" d="M700 1248v-78q38 -5 72.5 -14.5t75.5 -31.5t71 -53.5t52 -84t24 -118.5h-159q-4 36 -10.5 59t-21 45t-40 35.5t-64.5 20.5v-307l64 -13q34 -7 64 -16.5t70 -32t67.5 -52.5t47.5 -80t20 -112q0 -139 -89 -224t-244 -97v-77h-100v79q-150 16 -237 103q-40 40 -52.5 93.5 t-15.5 139.5h139q5 -77 48.5 -126t117.5 -65v335l-27 8q-46 14 -79 26.5t-72 36t-63 52t-40 72.5t-16 98q0 70 25 126t67.5 92t94.5 57t110 27v77h100zM600 754v274q-29 -4 -50 -11t-42 -21.5t-31.5 -41.5t-10.5 -65q0 -29 7 -50.5t16.5 -34t28.5 -22.5t31.5 -14t37.5 -10 q9 -3 13 -4zM700 547v-310q22 2 42.5 6.5t45 15.5t41.5 27t29 42t12 59.5t-12.5 59.5t-38 44.5t-53 31t-66.5 24.5z" />
|
||||||
|
<glyph unicode="" d="M561 1197q84 0 160.5 -40t123.5 -109.5t47 -147.5h-153q0 40 -19.5 71.5t-49.5 48.5t-59.5 26t-55.5 9q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -26 13.5 -63t26.5 -61t37 -66q6 -9 9 -14h241v-100h-197q8 -50 -2.5 -115t-31.5 -95q-45 -62 -99 -112 q34 10 83 17.5t71 7.5q32 1 102 -16t104 -17q83 0 136 30l50 -147q-31 -19 -58 -30.5t-55 -15.5t-42 -4.5t-46 -0.5q-23 0 -76 17t-111 32.5t-96 11.5q-39 -3 -82 -16t-67 -25l-23 -11l-55 145q4 3 16 11t15.5 10.5t13 9t15.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221v100h166q-23 47 -44 104q-7 20 -12 41.5t-6 55.5t6 66.5t29.5 70.5t58.5 71q97 88 263 88z" />
|
||||||
|
<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM935 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-900h-200v900h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" />
|
||||||
|
<glyph unicode="" d="M1000 700h-100v100h-100v-100h-100v500h300v-500zM400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM801 1100v-200h100v200h-100zM1000 350l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150z " />
|
||||||
|
<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 1050l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150zM1000 0h-100v100h-100v-100h-100v500h300v-500zM801 400v-200h100v200h-100z " />
|
||||||
|
<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 700h-100v400h-100v100h200v-500zM1100 0h-100v100h-200v400h300v-500zM901 400v-200h100v200h-100z" />
|
||||||
|
<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1100 700h-100v100h-200v400h300v-500zM901 1100v-200h100v200h-100zM1000 0h-100v400h-100v100h200v-500z" />
|
||||||
|
<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM900 1000h-200v200h200v-200zM1000 700h-300v200h300v-200zM1100 400h-400v200h400v-200zM1200 100h-500v200h500v-200z" />
|
||||||
|
<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1200 1000h-500v200h500v-200zM1100 700h-400v200h400v-200zM1000 400h-300v200h300v-200zM900 100h-200v200h200v-200z" />
|
||||||
|
<glyph unicode="" d="M350 1100h400q162 0 256 -93.5t94 -256.5v-400q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5z" />
|
||||||
|
<glyph unicode="" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-163 0 -256.5 92.5t-93.5 257.5v400q0 163 94 256.5t256 93.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM440 770l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" />
|
||||||
|
<glyph unicode="" d="M350 1100h400q163 0 256.5 -94t93.5 -256v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 163 92.5 256.5t257.5 93.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM350 700h400q21 0 26.5 -12t-6.5 -28l-190 -253q-12 -17 -30 -17t-30 17l-190 253q-12 16 -6.5 28t26.5 12z" />
|
||||||
|
<glyph unicode="" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -163 -92.5 -256.5t-257.5 -93.5h-400q-163 0 -256.5 94t-93.5 256v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM580 693l190 -253q12 -16 6.5 -28t-26.5 -12h-400q-21 0 -26.5 12t6.5 28l190 253q12 17 30 17t30 -17z" />
|
||||||
|
<glyph unicode="" d="M550 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h450q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-450q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM338 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" />
|
||||||
|
<glyph unicode="" d="M793 1182l9 -9q8 -10 5 -27q-3 -11 -79 -225.5t-78 -221.5l300 1q24 0 32.5 -17.5t-5.5 -35.5q-1 0 -133.5 -155t-267 -312.5t-138.5 -162.5q-12 -15 -26 -15h-9l-9 8q-9 11 -4 32q2 9 42 123.5t79 224.5l39 110h-302q-23 0 -31 19q-10 21 6 41q75 86 209.5 237.5 t228 257t98.5 111.5q9 16 25 16h9z" />
|
||||||
|
<glyph unicode="" d="M350 1100h400q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-450q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h450q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400 q0 165 92.5 257.5t257.5 92.5zM938 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" />
|
||||||
|
<glyph unicode="" d="M750 1200h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -10.5 -25t-24.5 10l-109 109l-312 -312q-15 -15 -35.5 -15t-35.5 15l-141 141q-15 15 -15 35.5t15 35.5l312 312l-109 109q-14 14 -10 24.5t25 10.5zM456 900h-156q-41 0 -70.5 -29.5t-29.5 -70.5v-500 q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v148l200 200v-298q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5h300z" />
|
||||||
|
<glyph unicode="" d="M600 1186q119 0 227.5 -46.5t187 -125t125 -187t46.5 -227.5t-46.5 -227.5t-125 -187t-187 -125t-227.5 -46.5t-227.5 46.5t-187 125t-125 187t-46.5 227.5t46.5 227.5t125 187t187 125t227.5 46.5zM600 1022q-115 0 -212 -56.5t-153.5 -153.5t-56.5 -212t56.5 -212 t153.5 -153.5t212 -56.5t212 56.5t153.5 153.5t56.5 212t-56.5 212t-153.5 153.5t-212 56.5zM600 794q80 0 137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137t57 137t137 57z" />
|
||||||
|
<glyph unicode="" d="M450 1200h200q21 0 35.5 -14.5t14.5 -35.5v-350h245q20 0 25 -11t-9 -26l-383 -426q-14 -15 -33.5 -15t-32.5 15l-379 426q-13 15 -8.5 26t25.5 11h250v350q0 21 14.5 35.5t35.5 14.5zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" />
|
||||||
|
<glyph unicode="" d="M583 1182l378 -435q14 -15 9 -31t-26 -16h-244v-250q0 -20 -17 -35t-39 -15h-200q-20 0 -32 14.5t-12 35.5v250h-250q-20 0 -25.5 16.5t8.5 31.5l383 431q14 16 33.5 17t33.5 -14zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" />
|
||||||
|
<glyph unicode="" d="M396 723l369 369q7 7 17.5 7t17.5 -7l139 -139q7 -8 7 -18.5t-7 -17.5l-525 -525q-7 -8 -17.5 -8t-17.5 8l-292 291q-7 8 -7 18t7 18l139 139q8 7 18.5 7t17.5 -7zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50 h-100z" />
|
||||||
|
<glyph unicode="" d="M135 1023l142 142q14 14 35 14t35 -14l77 -77l-212 -212l-77 76q-14 15 -14 36t14 35zM655 855l210 210q14 14 24.5 10t10.5 -25l-2 -599q-1 -20 -15.5 -35t-35.5 -15l-597 -1q-21 0 -25 10.5t10 24.5l208 208l-154 155l212 212zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5 v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" />
|
||||||
|
<glyph unicode="" d="M350 1200l599 -2q20 -1 35 -15.5t15 -35.5l1 -597q0 -21 -10.5 -25t-24.5 10l-208 208l-155 -154l-212 212l155 154l-210 210q-14 14 -10 24.5t25 10.5zM524 512l-76 -77q-15 -14 -36 -14t-35 14l-142 142q-14 14 -14 35t14 35l77 77zM50 300h1000q21 0 35.5 -14.5 t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" />
|
||||||
|
<glyph unicode="" d="M1200 103l-483 276l-314 -399v423h-399l1196 796v-1096zM483 424v-230l683 953z" />
|
||||||
|
<glyph unicode="" d="M1100 1000v-850q0 -21 -14.5 -35.5t-35.5 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200z" />
|
||||||
|
<glyph unicode="" d="M1100 1000l-2 -149l-299 -299l-95 95q-9 9 -21.5 9t-21.5 -9l-149 -147h-312v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1132 638l106 -106q7 -7 7 -17.5t-7 -17.5l-420 -421q-8 -7 -18 -7 t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l297 297q7 7 17.5 7t17.5 -7z" />
|
||||||
|
<glyph unicode="" d="M1100 1000v-269l-103 -103l-134 134q-15 15 -33.5 16.5t-34.5 -12.5l-266 -266h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1202 572l70 -70q15 -15 15 -35.5t-15 -35.5l-131 -131 l131 -131q15 -15 15 -35.5t-15 -35.5l-70 -70q-15 -15 -35.5 -15t-35.5 15l-131 131l-131 -131q-15 -15 -35.5 -15t-35.5 15l-70 70q-15 15 -15 35.5t15 35.5l131 131l-131 131q-15 15 -15 35.5t15 35.5l70 70q15 15 35.5 15t35.5 -15l131 -131l131 131q15 15 35.5 15 t35.5 -15z" />
|
||||||
|
<glyph unicode="" d="M1100 1000v-300h-350q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM850 600h100q21 0 35.5 -14.5t14.5 -35.5v-250h150q21 0 25 -10.5t-10 -24.5 l-230 -230q-14 -14 -35 -14t-35 14l-230 230q-14 14 -10 24.5t25 10.5h150v250q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M1100 1000v-400l-165 165q-14 15 -35 15t-35 -15l-263 -265h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM935 565l230 -229q14 -15 10 -25.5t-25 -10.5h-150v-250q0 -20 -14.5 -35 t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35v250h-150q-21 0 -25 10.5t10 25.5l230 229q14 15 35 15t35 -15z" />
|
||||||
|
<glyph unicode="" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-150h-1200v150q0 21 14.5 35.5t35.5 14.5zM1200 800v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v550h1200zM100 500v-200h400v200h-400z" />
|
||||||
|
<glyph unicode="" d="M935 1165l248 -230q14 -14 14 -35t-14 -35l-248 -230q-14 -14 -24.5 -10t-10.5 25v150h-400v200h400v150q0 21 10.5 25t24.5 -10zM200 800h-50q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v-200zM400 800h-100v200h100v-200zM18 435l247 230 q14 14 24.5 10t10.5 -25v-150h400v-200h-400v-150q0 -21 -10.5 -25t-24.5 10l-247 230q-15 14 -15 35t15 35zM900 300h-100v200h100v-200zM1000 500h51q20 0 34.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-34.5 -14.5h-51v200z" />
|
||||||
|
<glyph unicode="" d="M862 1073l276 116q25 18 43.5 8t18.5 -41v-1106q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v397q-4 1 -11 5t-24 17.5t-30 29t-24 42t-11 56.5v359q0 31 18.5 65t43.5 52zM550 1200q22 0 34.5 -12.5t14.5 -24.5l1 -13v-450q0 -28 -10.5 -59.5 t-25 -56t-29 -45t-25.5 -31.5l-10 -11v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447q-4 4 -11 11.5t-24 30.5t-30 46t-24 55t-11 60v450q0 2 0.5 5.5t4 12t8.5 15t14.5 12t22.5 5.5q20 0 32.5 -12.5t14.5 -24.5l3 -13v-350h100v350v5.5t2.5 12 t7 15t15 12t25.5 5.5q23 0 35.5 -12.5t13.5 -24.5l1 -13v-350h100v350q0 2 0.5 5.5t3 12t7 15t15 12t24.5 5.5z" />
|
||||||
|
<glyph unicode="" d="M1200 1100v-56q-4 0 -11 -0.5t-24 -3t-30 -7.5t-24 -15t-11 -24v-888q0 -22 25 -34.5t50 -13.5l25 -2v-56h-400v56q75 0 87.5 6.5t12.5 43.5v394h-500v-394q0 -37 12.5 -43.5t87.5 -6.5v-56h-400v56q4 0 11 0.5t24 3t30 7.5t24 15t11 24v888q0 22 -25 34.5t-50 13.5 l-25 2v56h400v-56q-75 0 -87.5 -6.5t-12.5 -43.5v-394h500v394q0 37 -12.5 43.5t-87.5 6.5v56h400z" />
|
||||||
|
<glyph unicode="" d="M675 1000h375q21 0 35.5 -14.5t14.5 -35.5v-150h-105l-295 -98v98l-200 200h-400l100 100h375zM100 900h300q41 0 70.5 -29.5t29.5 -70.5v-500q0 -41 -29.5 -70.5t-70.5 -29.5h-300q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5zM100 800v-200h300v200 h-300zM1100 535l-400 -133v163l400 133v-163zM100 500v-200h300v200h-300zM1100 398v-248q0 -21 -14.5 -35.5t-35.5 -14.5h-375l-100 -100h-375l-100 100h400l200 200h105z" />
|
||||||
|
<glyph unicode="" d="M17 1007l162 162q17 17 40 14t37 -22l139 -194q14 -20 11 -44.5t-20 -41.5l-119 -118q102 -142 228 -268t267 -227l119 118q17 17 42.5 19t44.5 -12l192 -136q19 -14 22.5 -37.5t-13.5 -40.5l-163 -162q-3 -1 -9.5 -1t-29.5 2t-47.5 6t-62.5 14.5t-77.5 26.5t-90 42.5 t-101.5 60t-111 83t-119 108.5q-74 74 -133.5 150.5t-94.5 138.5t-60 119.5t-34.5 100t-15 74.5t-4.5 48z" />
|
||||||
|
<glyph unicode="" d="M600 1100q92 0 175 -10.5t141.5 -27t108.5 -36.5t81.5 -40t53.5 -37t31 -27l9 -10v-200q0 -21 -14.5 -33t-34.5 -9l-202 34q-20 3 -34.5 20t-14.5 38v146q-141 24 -300 24t-300 -24v-146q0 -21 -14.5 -38t-34.5 -20l-202 -34q-20 -3 -34.5 9t-14.5 33v200q3 4 9.5 10.5 t31 26t54 37.5t80.5 39.5t109 37.5t141 26.5t175 10.5zM600 795q56 0 97 -9.5t60 -23.5t30 -28t12 -24l1 -10v-50l365 -303q14 -15 24.5 -40t10.5 -45v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v212q0 20 10.5 45t24.5 40l365 303v50 q0 4 1 10.5t12 23t30 29t60 22.5t97 10z" />
|
||||||
|
<glyph unicode="" d="M1100 700l-200 -200h-600l-200 200v500h200v-200h200v200h200v-200h200v200h200v-500zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5 t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M700 1100h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-1000h300v1000q0 41 -29.5 70.5t-70.5 29.5zM1100 800h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-700h300v700q0 41 -29.5 70.5t-70.5 29.5zM400 0h-300v400q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-400z " />
|
||||||
|
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" />
|
||||||
|
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 300h-100v200h-100v-200h-100v500h100v-200h100v200h100v-500zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" />
|
||||||
|
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-300h200v-100h-300v500h300v-100zM900 700h-200v-300h200v-100h-300v500h300v-100z" />
|
||||||
|
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 400l-300 150l300 150v-300zM900 550l-300 -150v300z" />
|
||||||
|
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM900 300h-700v500h700v-500zM800 700h-130q-38 0 -66.5 -43t-28.5 -108t27 -107t68 -42h130v300zM300 700v-300 h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130z" />
|
||||||
|
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 300h-100v400h-100v100h200v-500z M700 300h-100v100h100v-100z" />
|
||||||
|
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM300 700h200v-400h-300v500h100v-100zM900 300h-100v400h-100v100h200v-500zM300 600v-200h100v200h-100z M700 300h-100v100h100v-100z" />
|
||||||
|
<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 500l-199 -200h-100v50l199 200v150h-200v100h300v-300zM900 300h-100v400h-100v100h200v-500zM701 300h-100 v100h100v-100z" />
|
||||||
|
<glyph unicode="" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700h-300v-200h300v-100h-300l-100 100v200l100 100h300v-100z" />
|
||||||
|
<glyph unicode="" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700v-100l-50 -50l100 -100v-50h-100l-100 100h-150v-100h-100v400h300zM500 700v-100h200v100h-200z" />
|
||||||
|
<glyph unicode="" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -207t-85 -207t-205 -86.5h-128v250q0 21 -14.5 35.5t-35.5 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-250h-222q-80 0 -136 57.5t-56 136.5q0 69 43 122.5t108 67.5q-2 19 -2 37q0 100 49 185 t134 134t185 49zM525 500h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -244q-13 -16 -32 -16t-32 16l-223 244q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z" />
|
||||||
|
<glyph unicode="" d="M502 1089q110 0 201 -59.5t135 -156.5q43 15 89 15q121 0 206 -86.5t86 -206.5q0 -99 -60 -181t-150 -110l-378 360q-13 16 -31.5 16t-31.5 -16l-381 -365h-9q-79 0 -135.5 57.5t-56.5 136.5q0 69 43 122.5t108 67.5q-2 19 -2 38q0 100 49 184.5t133.5 134t184.5 49.5z M632 467l223 -228q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5q199 204 223 228q19 19 31.5 19t32.5 -19z" />
|
||||||
|
<glyph unicode="" d="M700 100v100h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170l-270 -300h400v-100h-50q-21 0 -35.5 -14.5t-14.5 -35.5v-50h400v50q0 21 -14.5 35.5t-35.5 14.5h-50z" />
|
||||||
|
<glyph unicode="" d="M600 1179q94 0 167.5 -56.5t99.5 -145.5q89 -6 150.5 -71.5t61.5 -155.5q0 -61 -29.5 -112.5t-79.5 -82.5q9 -29 9 -55q0 -74 -52.5 -126.5t-126.5 -52.5q-55 0 -100 30v-251q21 0 35.5 -14.5t14.5 -35.5v-50h-300v50q0 21 14.5 35.5t35.5 14.5v251q-45 -30 -100 -30 q-74 0 -126.5 52.5t-52.5 126.5q0 18 4 38q-47 21 -75.5 65t-28.5 97q0 74 52.5 126.5t126.5 52.5q5 0 23 -2q0 2 -1 10t-1 13q0 116 81.5 197.5t197.5 81.5z" />
|
||||||
|
<glyph unicode="" d="M1010 1010q111 -111 150.5 -260.5t0 -299t-150.5 -260.5q-83 -83 -191.5 -126.5t-218.5 -43.5t-218.5 43.5t-191.5 126.5q-111 111 -150.5 260.5t0 299t150.5 260.5q83 83 191.5 126.5t218.5 43.5t218.5 -43.5t191.5 -126.5zM476 1065q-4 0 -8 -1q-121 -34 -209.5 -122.5 t-122.5 -209.5q-4 -12 2.5 -23t18.5 -14l36 -9q3 -1 7 -1q23 0 29 22q27 96 98 166q70 71 166 98q11 3 17.5 13.5t3.5 22.5l-9 35q-3 13 -14 19q-7 4 -15 4zM512 920q-4 0 -9 -2q-80 -24 -138.5 -82.5t-82.5 -138.5q-4 -13 2 -24t19 -14l34 -9q4 -1 8 -1q22 0 28 21 q18 58 58.5 98.5t97.5 58.5q12 3 18 13.5t3 21.5l-9 35q-3 12 -14 19q-7 4 -15 4zM719.5 719.5q-49.5 49.5 -119.5 49.5t-119.5 -49.5t-49.5 -119.5t49.5 -119.5t119.5 -49.5t119.5 49.5t49.5 119.5t-49.5 119.5zM855 551q-22 0 -28 -21q-18 -58 -58.5 -98.5t-98.5 -57.5 q-11 -4 -17 -14.5t-3 -21.5l9 -35q3 -12 14 -19q7 -4 15 -4q4 0 9 2q80 24 138.5 82.5t82.5 138.5q4 13 -2.5 24t-18.5 14l-34 9q-4 1 -8 1zM1000 515q-23 0 -29 -22q-27 -96 -98 -166q-70 -71 -166 -98q-11 -3 -17.5 -13.5t-3.5 -22.5l9 -35q3 -13 14 -19q7 -4 15 -4 q4 0 8 1q121 34 209.5 122.5t122.5 209.5q4 12 -2.5 23t-18.5 14l-36 9q-3 1 -7 1z" />
|
||||||
|
<glyph unicode="" d="M700 800h300v-380h-180v200h-340v-200h-380v755q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM700 300h162l-212 -212l-212 212h162v200h100v-200zM520 0h-395q-10 0 -17.5 7.5t-7.5 17.5v395zM1000 220v-195q0 -10 -7.5 -17.5t-17.5 -7.5h-195z" />
|
||||||
|
<glyph unicode="" d="M700 800h300v-520l-350 350l-550 -550v1095q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM862 200h-162v-200h-100v200h-162l212 212zM480 0h-355q-10 0 -17.5 7.5t-7.5 17.5v55h380v-80zM1000 80v-55q0 -10 -7.5 -17.5t-17.5 -7.5h-155v80h180z" />
|
||||||
|
<glyph unicode="" d="M1162 800h-162v-200h100l100 -100h-300v300h-162l212 212zM200 800h200q27 0 40 -2t29.5 -10.5t23.5 -30t7 -57.5h300v-100h-600l-200 -350v450h100q0 36 7 57.5t23.5 30t29.5 10.5t40 2zM800 400h240l-240 -400h-800l300 500h500v-100z" />
|
||||||
|
<glyph unicode="" d="M650 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM1000 850v150q41 0 70.5 -29.5t29.5 -70.5v-800 q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-1 0 -20 4l246 246l-326 326v324q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM412 250l-212 -212v162h-200v100h200v162z" />
|
||||||
|
<glyph unicode="" d="M450 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM800 850v150q41 0 70.5 -29.5t29.5 -70.5v-500 h-200v-300h200q0 -36 -7 -57.5t-23.5 -30t-29.5 -10.5t-40 -2h-600q-41 0 -70.5 29.5t-29.5 70.5v800q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM1212 250l-212 -212v162h-200v100h200v162z" />
|
||||||
|
<glyph unicode="" d="M658 1197l637 -1104q23 -38 7 -65.5t-60 -27.5h-1276q-44 0 -60 27.5t7 65.5l637 1104q22 39 54 39t54 -39zM704 800h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM500 300v-100h200 v100h-200z" />
|
||||||
|
<glyph unicode="" d="M425 1100h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM825 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM25 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5zM425 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5 v150q0 10 7.5 17.5t17.5 7.5zM25 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
|
||||||
|
<glyph unicode="" d="M700 1200h100v-200h-100v-100h350q62 0 86.5 -39.5t-3.5 -94.5l-66 -132q-41 -83 -81 -134h-772q-40 51 -81 134l-66 132q-28 55 -3.5 94.5t86.5 39.5h350v100h-100v200h100v100h200v-100zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100 h-950l138 100h-13q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M600 1300q40 0 68.5 -29.5t28.5 -70.5h-194q0 41 28.5 70.5t68.5 29.5zM443 1100h314q18 -37 18 -75q0 -8 -3 -25h328q41 0 44.5 -16.5t-30.5 -38.5l-175 -145h-678l-178 145q-34 22 -29 38.5t46 16.5h328q-3 17 -3 25q0 38 18 75zM250 700h700q21 0 35.5 -14.5 t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-150v-200l275 -200h-950l275 200v200h-150q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M600 1181q75 0 128 -53t53 -128t-53 -128t-128 -53t-128 53t-53 128t53 128t128 53zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13 l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M600 1300q47 0 92.5 -53.5t71 -123t25.5 -123.5q0 -78 -55.5 -133.5t-133.5 -55.5t-133.5 55.5t-55.5 133.5q0 62 34 143l144 -143l111 111l-163 163q34 26 63 26zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45 zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M600 1200l300 -161v-139h-300q0 -57 18.5 -108t50 -91.5t63 -72t70 -67.5t57.5 -61h-530q-60 83 -90.5 177.5t-30.5 178.5t33 164.5t87.5 139.5t126 96.5t145.5 41.5v-98zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100 h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M600 1300q41 0 70.5 -29.5t29.5 -70.5v-78q46 -26 73 -72t27 -100v-50h-400v50q0 54 27 100t73 72v78q0 41 29.5 70.5t70.5 29.5zM400 800h400q54 0 100 -27t72 -73h-172v-100h200v-100h-200v-100h200v-100h-200v-100h200q0 -83 -58.5 -141.5t-141.5 -58.5h-400 q-83 0 -141.5 58.5t-58.5 141.5v400q0 83 58.5 141.5t141.5 58.5z" />
|
||||||
|
<glyph unicode="" d="M150 1100h900q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM125 400h950q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-283l224 -224q13 -13 13 -31.5t-13 -32 t-31.5 -13.5t-31.5 13l-88 88h-524l-87 -88q-13 -13 -32 -13t-32 13.5t-13 32t13 31.5l224 224h-289q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM541 300l-100 -100h324l-100 100h-124z" />
|
||||||
|
<glyph unicode="" d="M200 1100h800q83 0 141.5 -58.5t58.5 -141.5v-200h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100v200q0 83 58.5 141.5t141.5 58.5zM100 600h1000q41 0 70.5 -29.5 t29.5 -70.5v-300h-1200v300q0 41 29.5 70.5t70.5 29.5zM300 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200zM1100 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200z" />
|
||||||
|
<glyph unicode="" d="M480 1165l682 -683q31 -31 31 -75.5t-31 -75.5l-131 -131h-481l-517 518q-32 31 -32 75.5t32 75.5l295 296q31 31 75.5 31t76.5 -31zM108 794l342 -342l303 304l-341 341zM250 100h800q21 0 35.5 -14.5t14.5 -35.5v-50h-900v50q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M1057 647l-189 506q-8 19 -27.5 33t-40.5 14h-400q-21 0 -40.5 -14t-27.5 -33l-189 -506q-8 -19 1.5 -33t30.5 -14h625v-150q0 -21 14.5 -35.5t35.5 -14.5t35.5 14.5t14.5 35.5v150h125q21 0 30.5 14t1.5 33zM897 0h-595v50q0 21 14.5 35.5t35.5 14.5h50v50 q0 21 14.5 35.5t35.5 14.5h48v300h200v-300h47q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-50z" />
|
||||||
|
<glyph unicode="" d="M900 800h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-375v591l-300 300v84q0 10 7.5 17.5t17.5 7.5h375v-400zM1200 900h-200v200zM400 600h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-650q-10 0 -17.5 7.5t-7.5 17.5v950q0 10 7.5 17.5t17.5 7.5h375v-400zM700 700h-200v200z " />
|
||||||
|
<glyph unicode="" d="M484 1095h195q75 0 146 -32.5t124 -86t89.5 -122.5t48.5 -142q18 -14 35 -20q31 -10 64.5 6.5t43.5 48.5q10 34 -15 71q-19 27 -9 43q5 8 12.5 11t19 -1t23.5 -16q41 -44 39 -105q-3 -63 -46 -106.5t-104 -43.5h-62q-7 -55 -35 -117t-56 -100l-39 -234q-3 -20 -20 -34.5 t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l12 70q-49 -14 -91 -14h-195q-24 0 -65 8l-11 -64q-3 -20 -20 -34.5t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l26 157q-84 74 -128 175l-159 53q-19 7 -33 26t-14 40v50q0 21 14.5 35.5t35.5 14.5h124q11 87 56 166l-111 95 q-16 14 -12.5 23.5t24.5 9.5h203q116 101 250 101zM675 1000h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h250q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5t-17.5 7.5z" />
|
||||||
|
<glyph unicode="" d="M641 900l423 247q19 8 42 2.5t37 -21.5l32 -38q14 -15 12.5 -36t-17.5 -34l-139 -120h-390zM50 1100h106q67 0 103 -17t66 -71l102 -212h823q21 0 35.5 -14.5t14.5 -35.5v-50q0 -21 -14 -40t-33 -26l-737 -132q-23 -4 -40 6t-26 25q-42 67 -100 67h-300q-62 0 -106 44 t-44 106v200q0 62 44 106t106 44zM173 928h-80q-19 0 -28 -14t-9 -35v-56q0 -51 42 -51h134q16 0 21.5 8t5.5 24q0 11 -16 45t-27 51q-18 28 -43 28zM550 727q-32 0 -54.5 -22.5t-22.5 -54.5t22.5 -54.5t54.5 -22.5t54.5 22.5t22.5 54.5t-22.5 54.5t-54.5 22.5zM130 389 l152 130q18 19 34 24t31 -3.5t24.5 -17.5t25.5 -28q28 -35 50.5 -51t48.5 -13l63 5l48 -179q13 -61 -3.5 -97.5t-67.5 -79.5l-80 -69q-47 -40 -109 -35.5t-103 51.5l-130 151q-40 47 -35.5 109.5t51.5 102.5zM380 377l-102 -88q-31 -27 2 -65l37 -43q13 -15 27.5 -19.5 t31.5 6.5l61 53q19 16 14 49q-2 20 -12 56t-17 45q-11 12 -19 14t-23 -8z" />
|
||||||
|
<glyph unicode="" d="M625 1200h150q10 0 17.5 -7.5t7.5 -17.5v-109q79 -33 131 -87.5t53 -128.5q1 -46 -15 -84.5t-39 -61t-46 -38t-39 -21.5l-17 -6q6 0 15 -1.5t35 -9t50 -17.5t53 -30t50 -45t35.5 -64t14.5 -84q0 -59 -11.5 -105.5t-28.5 -76.5t-44 -51t-49.5 -31.5t-54.5 -16t-49.5 -6.5 t-43.5 -1v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-100v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-175q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v600h-75q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5h175v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h100v75q0 10 7.5 17.5t17.5 7.5zM400 900v-200h263q28 0 48.5 10.5t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-263zM400 500v-200h363q28 0 48.5 10.5 t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-363z" />
|
||||||
|
<glyph unicode="" d="M212 1198h780q86 0 147 -61t61 -147v-416q0 -51 -18 -142.5t-36 -157.5l-18 -66q-29 -87 -93.5 -146.5t-146.5 -59.5h-572q-82 0 -147 59t-93 147q-8 28 -20 73t-32 143.5t-20 149.5v416q0 86 61 147t147 61zM600 1045q-70 0 -132.5 -11.5t-105.5 -30.5t-78.5 -41.5 t-57 -45t-36 -41t-20.5 -30.5l-6 -12l156 -243h560l156 243q-2 5 -6 12.5t-20 29.5t-36.5 42t-57 44.5t-79 42t-105 29.5t-132.5 12zM762 703h-157l195 261z" />
|
||||||
|
<glyph unicode="" d="M475 1300h150q103 0 189 -86t86 -189v-500q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" />
|
||||||
|
<glyph unicode="" d="M475 1300h96q0 -150 89.5 -239.5t239.5 -89.5v-446q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" />
|
||||||
|
<glyph unicode="" d="M1294 767l-638 -283l-378 170l-78 -60v-224l100 -150v-199l-150 148l-150 -149v200l100 150v250q0 4 -0.5 10.5t0 9.5t1 8t3 8t6.5 6l47 40l-147 65l642 283zM1000 380l-350 -166l-350 166v147l350 -165l350 165v-147z" />
|
||||||
|
<glyph unicode="" d="M250 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM650 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM1050 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" />
|
||||||
|
<glyph unicode="" d="M550 1100q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 700q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 300q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" />
|
||||||
|
<glyph unicode="" d="M125 1100h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM125 700h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM125 300h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" />
|
||||||
|
<glyph unicode="" d="M350 1200h500q162 0 256 -93.5t94 -256.5v-500q0 -165 -93.5 -257.5t-256.5 -92.5h-500q-165 0 -257.5 92.5t-92.5 257.5v500q0 165 92.5 257.5t257.5 92.5zM900 1000h-600q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h600q41 0 70.5 29.5 t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5zM350 900h500q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-500q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 14.5 35.5t35.5 14.5zM400 800v-200h400v200h-400z" />
|
||||||
|
<glyph unicode="" d="M150 1100h1000q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M650 1187q87 -67 118.5 -156t0 -178t-118.5 -155q-87 66 -118.5 155t0 178t118.5 156zM300 800q124 0 212 -88t88 -212q-124 0 -212 88t-88 212zM1000 800q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM300 500q124 0 212 -88t88 -212q-124 0 -212 88t-88 212z M1000 500q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM700 199v-144q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v142q40 -4 43 -4q17 0 57 6z" />
|
||||||
|
<glyph unicode="" d="M745 878l69 19q25 6 45 -12l298 -295q11 -11 15 -26.5t-2 -30.5q-5 -14 -18 -23.5t-28 -9.5h-8q1 0 1 -13q0 -29 -2 -56t-8.5 -62t-20 -63t-33 -53t-51 -39t-72.5 -14h-146q-184 0 -184 288q0 24 10 47q-20 4 -62 4t-63 -4q11 -24 11 -47q0 -288 -184 -288h-142 q-48 0 -84.5 21t-56 51t-32 71.5t-16 75t-3.5 68.5q0 13 2 13h-7q-15 0 -27.5 9.5t-18.5 23.5q-6 15 -2 30.5t15 25.5l298 296q20 18 46 11l76 -19q20 -5 30.5 -22.5t5.5 -37.5t-22.5 -31t-37.5 -5l-51 12l-182 -193h891l-182 193l-44 -12q-20 -5 -37.5 6t-22.5 31t6 37.5 t31 22.5z" />
|
||||||
|
<glyph unicode="" d="M1200 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM500 450h-25q0 15 -4 24.5t-9 14.5t-17 7.5t-20 3t-25 0.5h-100v-425q0 -11 12.5 -17.5t25.5 -7.5h12v-50h-200v50q50 0 50 25v425h-100q-17 0 -25 -0.5t-20 -3t-17 -7.5t-9 -14.5t-4 -24.5h-25v150h500v-150z" />
|
||||||
|
<glyph unicode="" d="M1000 300v50q-25 0 -55 32q-14 14 -25 31t-16 27l-4 11l-289 747h-69l-300 -754q-18 -35 -39 -56q-9 -9 -24.5 -18.5t-26.5 -14.5l-11 -5v-50h273v50q-49 0 -78.5 21.5t-11.5 67.5l69 176h293l61 -166q13 -34 -3.5 -66.5t-55.5 -32.5v-50h312zM412 691l134 342l121 -342 h-255zM1100 150v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5z" />
|
||||||
|
<glyph unicode="" d="M50 1200h1100q21 0 35.5 -14.5t14.5 -35.5v-1100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5zM611 1118h-70q-13 0 -18 -12l-299 -753q-17 -32 -35 -51q-18 -18 -56 -34q-12 -5 -12 -18v-50q0 -8 5.5 -14t14.5 -6 h273q8 0 14 6t6 14v50q0 8 -6 14t-14 6q-55 0 -71 23q-10 14 0 39l63 163h266l57 -153q11 -31 -6 -55q-12 -17 -36 -17q-8 0 -14 -6t-6 -14v-50q0 -8 6 -14t14 -6h313q8 0 14 6t6 14v50q0 7 -5.5 13t-13.5 7q-17 0 -42 25q-25 27 -40 63h-1l-288 748q-5 12 -19 12zM639 611 h-197l103 264z" />
|
||||||
|
<glyph unicode="" d="M1200 1100h-1200v100h1200v-100zM50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 1000h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM700 900v-300h300v300h-300z" />
|
||||||
|
<glyph unicode="" d="M50 1200h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 700h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5zM700 600v-300h300v300h-300zM1200 0h-1200v100h1200v-100z" />
|
||||||
|
<glyph unicode="" d="M50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-350h100v150q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-150h100v-100h-100v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v150h-100v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM700 700v-300h300v300h-300z" />
|
||||||
|
<glyph unicode="" d="M100 0h-100v1200h100v-1200zM250 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM300 1000v-300h300v300h-300zM250 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M600 1100h150q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-100h450q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h350v100h-150q-21 0 -35.5 14.5 t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h150v100h100v-100zM400 1000v-300h300v300h-300z" />
|
||||||
|
<glyph unicode="" d="M1200 0h-100v1200h100v-1200zM550 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM600 1000v-300h300v300h-300zM50 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" />
|
||||||
|
<glyph unicode="" d="M865 565l-494 -494q-23 -23 -41 -23q-14 0 -22 13.5t-8 38.5v1000q0 25 8 38.5t22 13.5q18 0 41 -23l494 -494q14 -14 14 -35t-14 -35z" />
|
||||||
|
<glyph unicode="" d="M335 635l494 494q29 29 50 20.5t21 -49.5v-1000q0 -41 -21 -49.5t-50 20.5l-494 494q-14 14 -14 35t14 35z" />
|
||||||
|
<glyph unicode="" d="M100 900h1000q41 0 49.5 -21t-20.5 -50l-494 -494q-14 -14 -35 -14t-35 14l-494 494q-29 29 -20.5 50t49.5 21z" />
|
||||||
|
<glyph unicode="" d="M635 865l494 -494q29 -29 20.5 -50t-49.5 -21h-1000q-41 0 -49.5 21t20.5 50l494 494q14 14 35 14t35 -14z" />
|
||||||
|
<glyph unicode="" d="M700 741v-182l-692 -323v221l413 193l-413 193v221zM1200 0h-800v200h800v-200z" />
|
||||||
|
<glyph unicode="" d="M1200 900h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300zM0 700h50q0 21 4 37t9.5 26.5t18 17.5t22 11t28.5 5.5t31 2t37 0.5h100v-550q0 -22 -25 -34.5t-50 -13.5l-25 -2v-100h400v100q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v550h100q25 0 37 -0.5t31 -2 t28.5 -5.5t22 -11t18 -17.5t9.5 -26.5t4 -37h50v300h-800v-300z" />
|
||||||
|
<glyph unicode="" d="M800 700h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-100v-550q0 -22 25 -34.5t50 -14.5l25 -1v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v550h-100q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h800v-300zM1100 200h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300z" />
|
||||||
|
<glyph unicode="" d="M701 1098h160q16 0 21 -11t-7 -23l-464 -464l464 -464q12 -12 7 -23t-21 -11h-160q-13 0 -23 9l-471 471q-7 8 -7 18t7 18l471 471q10 9 23 9z" />
|
||||||
|
<glyph unicode="" d="M339 1098h160q13 0 23 -9l471 -471q7 -8 7 -18t-7 -18l-471 -471q-10 -9 -23 -9h-160q-16 0 -21 11t7 23l464 464l-464 464q-12 12 -7 23t21 11z" />
|
||||||
|
<glyph unicode="" d="M1087 882q11 -5 11 -21v-160q0 -13 -9 -23l-471 -471q-8 -7 -18 -7t-18 7l-471 471q-9 10 -9 23v160q0 16 11 21t23 -7l464 -464l464 464q12 12 23 7z" />
|
||||||
|
<glyph unicode="" d="M618 993l471 -471q9 -10 9 -23v-160q0 -16 -11 -21t-23 7l-464 464l-464 -464q-12 -12 -23 -7t-11 21v160q0 13 9 23l471 471q8 7 18 7t18 -7z" />
|
||||||
|
<glyph unicode="" d="M1000 1200q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM450 1000h100q21 0 40 -14t26 -33l79 -194q5 1 16 3q34 6 54 9.5t60 7t65.5 1t61 -10t56.5 -23t42.5 -42t29 -64t5 -92t-19.5 -121.5q-1 -7 -3 -19.5t-11 -50t-20.5 -73t-32.5 -81.5t-46.5 -83t-64 -70 t-82.5 -50q-13 -5 -42 -5t-65.5 2.5t-47.5 2.5q-14 0 -49.5 -3.5t-63 -3.5t-43.5 7q-57 25 -104.5 78.5t-75 111.5t-46.5 112t-26 90l-7 35q-15 63 -18 115t4.5 88.5t26 64t39.5 43.5t52 25.5t58.5 13t62.5 2t59.5 -4.5t55.5 -8l-147 192q-12 18 -5.5 30t27.5 12z" />
|
||||||
|
<glyph unicode="🔑" d="M250 1200h600q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-500l-255 -178q-19 -9 -32 -1t-13 29v650h-150q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM400 1100v-100h300v100h-300z" />
|
||||||
|
<glyph unicode="🚪" d="M250 1200h750q39 0 69.5 -40.5t30.5 -84.5v-933l-700 -117v950l600 125h-700v-1000h-100v1025q0 23 15.5 49t34.5 26zM500 525v-100l100 20v100z" />
|
||||||
|
</font>
|
||||||
|
</defs></svg>
|
||||||
|
After Width: | Height: | Size: 106 KiB |
BIN
apidoc/assets/glyphicons-halflings-regular.ttf
Normal file
BIN
apidoc/assets/glyphicons-halflings-regular.woff
Normal file
BIN
apidoc/assets/glyphicons-halflings-regular.woff2
Normal file
92
apidoc/assets/main.bundle.js
Normal file
623
apidoc/assets/main.css
Normal file
@@ -0,0 +1,623 @@
|
|||||||
|
/**
|
||||||
|
* apidoc main css file
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define colors
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--primary: #0088cc;
|
||||||
|
--white: #fff;
|
||||||
|
--light-gray: #ccc;
|
||||||
|
--main-gray: #777;
|
||||||
|
--dark-gray: #2d2d2d;
|
||||||
|
--hover-gray: #666;
|
||||||
|
--meth-get: green;
|
||||||
|
--meth-put: #e5c500;
|
||||||
|
--meth-post: #4070ec;
|
||||||
|
--meth-delete: #ed0039;
|
||||||
|
--red: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-primary {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-primary {
|
||||||
|
background-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-red {
|
||||||
|
color: var(--white);
|
||||||
|
background-color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-danger {
|
||||||
|
border: 1px solid var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** for some reason the iOS safari style is applied on date inputs */
|
||||||
|
input[type="date"] {
|
||||||
|
line-height: 1.4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------
|
||||||
|
* Content
|
||||||
|
* ------------------------------------------------------------------------------------------ */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Glyphicons Halflings';
|
||||||
|
src: url('./glyphicons-halflings-regular.eot');
|
||||||
|
src: url('./glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('./glyphicons-halflings-regular.woff') format('woff'),
|
||||||
|
url('./glyphicons-halflings-regular.woff2') format('woff2'),
|
||||||
|
url('./glyphicons-halflings-regular.ttf') format('truetype'),
|
||||||
|
url('./glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide vertical scrollbar on off canvas animation ("left" positioning) */
|
||||||
|
html {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Source Sans Pro", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:focus {
|
||||||
|
background-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 130%;
|
||||||
|
color: var(--main-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
padding: 30px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
border-top: 1px solid var(--light-gray);
|
||||||
|
padding: 14px 0 30px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: var(--main-gray);
|
||||||
|
color: var(--white);
|
||||||
|
text-align: left;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border: 1px solid var(--main-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid var(--main-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
td.code {
|
||||||
|
font-family: "Source Code Pro", monospace;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
float: right;
|
||||||
|
margin-top: 4px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.optional {
|
||||||
|
background-color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label.required {
|
||||||
|
background-color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-value,
|
||||||
|
.type-size {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.open-left {
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invisible {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-addon.sample-request-select {
|
||||||
|
padding: 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-addon.sample-request-select select {
|
||||||
|
width: auto;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sample-request-input-Boolean-container {
|
||||||
|
width: 40px;
|
||||||
|
height: 34px;
|
||||||
|
background: var(--white);
|
||||||
|
border: 1px solid var(--light-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sample-request-input-Boolean-container > div {
|
||||||
|
margin-top: 7px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sample-request-input-Boolean-container > div input {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------
|
||||||
|
* Request method (HTTP verb)
|
||||||
|
* ------------------------------------------------------------------------------------------ */
|
||||||
|
.method {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
padding: 4px 5px;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
background-color: var(--main-gray);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meth-get {
|
||||||
|
background-color: var(--meth-get);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meth-put {
|
||||||
|
background-color: var(--meth-put);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meth-post {
|
||||||
|
background-color: var(--meth-post);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meth-delete {
|
||||||
|
background-color: var(--meth-delete);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------
|
||||||
|
* Sidenav
|
||||||
|
* ------------------------------------------------------------------------------------------ */
|
||||||
|
#scrollingNav {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10;
|
||||||
|
background-color: var(--dark-gray);
|
||||||
|
box-shadow: 0 2px 5px 0 rgb(0 0 0 / 16%), 0 2px 10px 0 rgb(0 0 0 / 12%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidenav {
|
||||||
|
color: var(--white);
|
||||||
|
position: absolute;
|
||||||
|
top: 50px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidenav:hover {
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidenav > li > a {
|
||||||
|
color: var(--white);
|
||||||
|
display: block;
|
||||||
|
padding: 8px 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* non active sidenav link are highlighted on hover */
|
||||||
|
.sidenav > li:not(.active) > a:hover {
|
||||||
|
background-color: var(--hover-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidenav > li.nav-header {
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidenav > li.nav-header > a {
|
||||||
|
padding: 5px 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: var(--main-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.sidenav > li.active > a {
|
||||||
|
position: relative;
|
||||||
|
background-color: var(--primary);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: commented out for the moment
|
||||||
|
.sidenav > li.has-modifications a {
|
||||||
|
border-right: 4px solid var(--main-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-list-item :not(.is-new) {
|
||||||
|
border-left: 4px solid var(--main-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidenav > li.is-new a {
|
||||||
|
border-left: 4px solid var(--primary);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Off Canvas
|
||||||
|
* --------------------------------------------------
|
||||||
|
*/
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
#content {
|
||||||
|
margin-top: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-offcanvas {
|
||||||
|
position: relative;
|
||||||
|
-webkit-transition: all .25s ease-out;
|
||||||
|
-o-transition: all .25s ease-out;
|
||||||
|
transition: all .25s ease-out;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-offcanvas,
|
||||||
|
.row-offcanvas * {
|
||||||
|
transition: all 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-offcanvas .sidebar-offcanvas {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -200px !important; /* 6 columns */
|
||||||
|
width: 100%; /* 6 columns */
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-toggle {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
background: var(--dark-gray);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-toggle .btn {
|
||||||
|
margin: 10px 14px;
|
||||||
|
}
|
||||||
|
.nav-toggle .icon-bar {
|
||||||
|
display: block;
|
||||||
|
width: 22px;
|
||||||
|
height: 2px;
|
||||||
|
border-radius: 1px;
|
||||||
|
background-color: var(--white);
|
||||||
|
}
|
||||||
|
.nav-toggle .icon-bar + .icon-bar {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-offcanvas.active .sidebar-offcanvas {
|
||||||
|
left: 0 !important; /* 6 columns */
|
||||||
|
}
|
||||||
|
.row-offcanvas.active, .row-offcanvas.active .nav-toggle {
|
||||||
|
left: 200px;
|
||||||
|
}
|
||||||
|
/* Styling the three lines to make it an X */
|
||||||
|
.row-offcanvas.active .nav-toggle .btn > .icon-bar {
|
||||||
|
transform: rotate(45deg) translate(-4px, -4px);
|
||||||
|
}
|
||||||
|
.row-offcanvas.active .nav-toggle .btn .icon-bar:nth-child(2) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.row-offcanvas.active .nav-toggle .btn .icon-bar:nth-child(3) {
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------
|
||||||
|
* Side nav search
|
||||||
|
* ------------------------------------------------------------------------------------------ */
|
||||||
|
.sidenav-search {
|
||||||
|
padding: 16px 10px 10px;
|
||||||
|
background-color: var(--dark-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidenav-search .search {
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-reset {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
right: 28px;
|
||||||
|
top: 18px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------
|
||||||
|
* Prism - Toolbar
|
||||||
|
* ------------------------------------------------------------------------------------------ */
|
||||||
|
div.code-toolbar.code-toolbar > .toolbar {
|
||||||
|
top: .4rem;
|
||||||
|
right: .4rem;
|
||||||
|
}
|
||||||
|
div.code-toolbar.code-toolbar > .toolbar > .toolbar-item > button:hover,
|
||||||
|
div.code-toolbar.code-toolbar > .toolbar > .toolbar-item > button:focus {
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
div.code-toolbar.code-toolbar > .toolbar > .toolbar-item > button {
|
||||||
|
color: var(--light-gray);
|
||||||
|
padding: .5em;
|
||||||
|
background: var(--hover-gray);
|
||||||
|
box-shadow: 0 2px 1px 1px rgba(0,0,0,.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------
|
||||||
|
* Compare
|
||||||
|
* ------------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
ins {
|
||||||
|
background: #60d060;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
del {
|
||||||
|
background: #f05050;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-ins {
|
||||||
|
background-color: #60d060;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-del {
|
||||||
|
background-color: #f05050;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre.ins {
|
||||||
|
background-color: #60d060;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre.del {
|
||||||
|
background-color: #f05050;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.ins th,
|
||||||
|
table.ins td {
|
||||||
|
background-color: #60d060;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.del th,
|
||||||
|
table.del td {
|
||||||
|
background-color: #f05050;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.ins td {
|
||||||
|
background-color: #60d060;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.del td {
|
||||||
|
background-color: #f05050;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------
|
||||||
|
* Spinner
|
||||||
|
* ------------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
#loader {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loader p {
|
||||||
|
padding-top: 80px;
|
||||||
|
margin-left: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
margin: 200px auto;
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container1 > div, .container2 > div, .container3 > div {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
background-color: #0088cc;
|
||||||
|
|
||||||
|
border-radius: 100%;
|
||||||
|
position: absolute;
|
||||||
|
-webkit-animation: bouncedelay 1.2s infinite ease-in-out;
|
||||||
|
animation: bouncedelay 1.2s infinite ease-in-out;
|
||||||
|
/* Prevent first frame from flickering when animation starts */
|
||||||
|
-webkit-animation-fill-mode: both;
|
||||||
|
animation-fill-mode: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner .spinner-container {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container2 {
|
||||||
|
-webkit-transform: rotateZ(45deg);
|
||||||
|
transform: rotateZ(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container3 {
|
||||||
|
-webkit-transform: rotateZ(90deg);
|
||||||
|
transform: rotateZ(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle1 { top: 0; left: 0; }
|
||||||
|
.circle2 { top: 0; right: 0; }
|
||||||
|
.circle3 { right: 0; bottom: 0; }
|
||||||
|
.circle4 { left: 0; bottom: 0; }
|
||||||
|
|
||||||
|
.container2 .circle1 {
|
||||||
|
-webkit-animation-delay: -1.1s;
|
||||||
|
animation-delay: -1.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container3 .circle1 {
|
||||||
|
-webkit-animation-delay: -1.0s;
|
||||||
|
animation-delay: -1.0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container1 .circle2 {
|
||||||
|
-webkit-animation-delay: -0.9s;
|
||||||
|
animation-delay: -0.9s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container2 .circle2 {
|
||||||
|
-webkit-animation-delay: -0.8s;
|
||||||
|
animation-delay: -0.8s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container3 .circle2 {
|
||||||
|
-webkit-animation-delay: -0.7s;
|
||||||
|
animation-delay: -0.7s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container1 .circle3 {
|
||||||
|
-webkit-animation-delay: -0.6s;
|
||||||
|
animation-delay: -0.6s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container2 .circle3 {
|
||||||
|
-webkit-animation-delay: -0.5s;
|
||||||
|
animation-delay: -0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container3 .circle3 {
|
||||||
|
-webkit-animation-delay: -0.4s;
|
||||||
|
animation-delay: -0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container1 .circle4 {
|
||||||
|
-webkit-animation-delay: -0.3s;
|
||||||
|
animation-delay: -0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container2 .circle4 {
|
||||||
|
-webkit-animation-delay: -0.2s;
|
||||||
|
animation-delay: -0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container3 .circle4 {
|
||||||
|
-webkit-animation-delay: -0.1s;
|
||||||
|
animation-delay: -0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes bouncedelay {
|
||||||
|
0%, 80%, 100% { -webkit-transform: scale(0.0) }
|
||||||
|
40% { -webkit-transform: scale(1.0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bouncedelay {
|
||||||
|
0%, 80%, 100% {
|
||||||
|
transform: scale(0.0);
|
||||||
|
-webkit-transform: scale(0.0);
|
||||||
|
} 40% {
|
||||||
|
transform: scale(1.0);
|
||||||
|
-webkit-transform: scale(1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------
|
||||||
|
* Tabs
|
||||||
|
* ------------------------------------------------------------------------------------------ */
|
||||||
|
ul.nav-tabs {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.deprecated span{
|
||||||
|
color: var(--red);
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Footer
|
||||||
|
*/
|
||||||
|
#generator {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------------------------
|
||||||
|
* Print
|
||||||
|
* ------------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
|
||||||
|
#sidenav,
|
||||||
|
#version,
|
||||||
|
#versions,
|
||||||
|
section .version,
|
||||||
|
section .versions {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:after {
|
||||||
|
content: " [" attr(href) "] ";
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #000000
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #000000;
|
||||||
|
padding: 10px;
|
||||||
|
border: #808080 1px solid;
|
||||||
|
border-radius: 6px;
|
||||||
|
position: relative;
|
||||||
|
margin: 10px 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* /@media print */
|
||||||
13
apidoc/assets/prism-diff-highlight.css
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
pre.diff-highlight > code .token.deleted:not(.prefix),
|
||||||
|
pre > code.diff-highlight .token.deleted:not(.prefix) {
|
||||||
|
background-color: rgba(255, 0, 0, .1);
|
||||||
|
color: inherit;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre.diff-highlight > code .token.inserted:not(.prefix),
|
||||||
|
pre > code.diff-highlight .token.inserted:not(.prefix) {
|
||||||
|
background-color: rgba(0, 255, 128, .1);
|
||||||
|
color: inherit;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
65
apidoc/assets/prism-toolbar.css
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
div.code-toolbar {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.code-toolbar > .toolbar {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
top: .3em;
|
||||||
|
right: .2em;
|
||||||
|
transition: opacity 0.3s ease-in-out;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.code-toolbar:hover > .toolbar {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Separate line b/c rules are thrown out if selector is invalid.
|
||||||
|
IE11 and old Edge versions don't support :focus-within. */
|
||||||
|
div.code-toolbar:focus-within > .toolbar {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.code-toolbar > .toolbar > .toolbar-item {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.code-toolbar > .toolbar > .toolbar-item > a {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.code-toolbar > .toolbar > .toolbar-item > button {
|
||||||
|
background: none;
|
||||||
|
border: 0;
|
||||||
|
color: inherit;
|
||||||
|
font: inherit;
|
||||||
|
line-height: normal;
|
||||||
|
overflow: visible;
|
||||||
|
padding: 0;
|
||||||
|
-webkit-user-select: none; /* for button */
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.code-toolbar > .toolbar > .toolbar-item > a,
|
||||||
|
div.code-toolbar > .toolbar > .toolbar-item > button,
|
||||||
|
div.code-toolbar > .toolbar > .toolbar-item > span {
|
||||||
|
color: #bbb;
|
||||||
|
font-size: .8em;
|
||||||
|
padding: 0 .5em;
|
||||||
|
background: #f5f2f0;
|
||||||
|
background: rgba(224, 224, 224, 0.2);
|
||||||
|
box-shadow: 0 2px 0 0 rgba(0,0,0,0.2);
|
||||||
|
border-radius: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.code-toolbar > .toolbar > .toolbar-item > a:hover,
|
||||||
|
div.code-toolbar > .toolbar > .toolbar-item > a:focus,
|
||||||
|
div.code-toolbar > .toolbar > .toolbar-item > button:hover,
|
||||||
|
div.code-toolbar > .toolbar > .toolbar-item > button:focus,
|
||||||
|
div.code-toolbar > .toolbar > .toolbar-item > span:hover,
|
||||||
|
div.code-toolbar > .toolbar > .toolbar-item > span:focus {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
122
apidoc/assets/prism.css
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
/**
|
||||||
|
* prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
|
||||||
|
* Based on https://github.com/chriskempson/tomorrow-theme
|
||||||
|
* @author Rose Pritchard
|
||||||
|
*/
|
||||||
|
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
color: #ccc;
|
||||||
|
background: none;
|
||||||
|
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-moz-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code blocks */
|
||||||
|
pre[class*="language-"] {
|
||||||
|
padding: 1em;
|
||||||
|
margin: .5em 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(pre) > code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
background: #2d2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline code */
|
||||||
|
:not(pre) > code[class*="language-"] {
|
||||||
|
padding: .1em;
|
||||||
|
border-radius: .3em;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.comment,
|
||||||
|
.token.block-comment,
|
||||||
|
.token.prolog,
|
||||||
|
.token.doctype,
|
||||||
|
.token.cdata {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.punctuation {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.tag,
|
||||||
|
.token.attr-name,
|
||||||
|
.token.namespace,
|
||||||
|
.token.deleted {
|
||||||
|
color: #e2777a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.function-name {
|
||||||
|
color: #6196cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.boolean,
|
||||||
|
.token.number,
|
||||||
|
.token.function {
|
||||||
|
color: #f08d49;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.property,
|
||||||
|
.token.class-name,
|
||||||
|
.token.constant,
|
||||||
|
.token.symbol {
|
||||||
|
color: #f8c555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.selector,
|
||||||
|
.token.important,
|
||||||
|
.token.atrule,
|
||||||
|
.token.keyword,
|
||||||
|
.token.builtin {
|
||||||
|
color: #cc99cd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.string,
|
||||||
|
.token.char,
|
||||||
|
.token.attr-value,
|
||||||
|
.token.regex,
|
||||||
|
.token.variable {
|
||||||
|
color: #7ec699;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.operator,
|
||||||
|
.token.entity,
|
||||||
|
.token.url {
|
||||||
|
color: #67cdcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.important,
|
||||||
|
.token.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.token.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.entity {
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.inserted {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
1047
apidoc/index.html
Normal file
36
app.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* 前后端模板 — Node 入口(node-core-framework)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Framework = require("./framework/node-core-framework.js");
|
||||||
|
const config = require("./config/framework.config.js");
|
||||||
|
const businessAssociations = require("./config/model.associations.js");
|
||||||
|
|
||||||
|
async function start() {
|
||||||
|
try {
|
||||||
|
console.log("正在启动应用...\n");
|
||||||
|
console.log("正在初始化框架...");
|
||||||
|
|
||||||
|
const framework = await Framework.init({
|
||||||
|
...config,
|
||||||
|
businessAssociations,
|
||||||
|
});
|
||||||
|
|
||||||
|
await framework.start(config.port.node);
|
||||||
|
|
||||||
|
const schedule = require("./middleware/schedule.js");
|
||||||
|
await schedule.init();
|
||||||
|
|
||||||
|
console.log(`\nAPI 文档: http://localhost:${config.port.node}/api/docs`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("\n" + "=".repeat(60));
|
||||||
|
console.error("应用启动失败");
|
||||||
|
console.error("=".repeat(60));
|
||||||
|
console.error("错误信息:", error.message);
|
||||||
|
console.error("错误堆栈:", error.stack);
|
||||||
|
console.error("=".repeat(60) + "\n");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
||||||
21
config/config.development.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* 本地开发配置(请按实际环境修改数据库等)
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
db: {
|
||||||
|
username: "root",
|
||||||
|
password: "your_password",
|
||||||
|
database: "fullstack_template",
|
||||||
|
host: "127.0.0.1",
|
||||||
|
port: 3306,
|
||||||
|
dialect: "mysql",
|
||||||
|
},
|
||||||
|
aliyun: {
|
||||||
|
accessKeyId: "",
|
||||||
|
accessKeySecret: "",
|
||||||
|
ossUrl: "",
|
||||||
|
bucket: "",
|
||||||
|
},
|
||||||
|
redis: null,
|
||||||
|
};
|
||||||
41
config/config.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// 共享配置;环境相关变量放在 config.development.js / config.production.js
|
||||||
|
|
||||||
|
const env = process.env.NODE_ENV || "development";
|
||||||
|
|
||||||
|
let env_config = {};
|
||||||
|
try {
|
||||||
|
if (env === "production") {
|
||||||
|
env_config = require("./config.production.js");
|
||||||
|
} else {
|
||||||
|
env_config = require("./config.development.js");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Warning: failed to load ${env} config, using defaults.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const base_config = {
|
||||||
|
port: {
|
||||||
|
node: 9098,
|
||||||
|
web: 9090,
|
||||||
|
},
|
||||||
|
allowUrls: [
|
||||||
|
"/admin_api/sys_user/login",
|
||||||
|
"/admin_api/sys_user/authorityMenus",
|
||||||
|
"/admin_api/sys_user/register",
|
||||||
|
"/file/",
|
||||||
|
"/sys_file/",
|
||||||
|
"/api/docs",
|
||||||
|
"/api/swagger.json",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...base_config,
|
||||||
|
...env_config,
|
||||||
|
wechat: {
|
||||||
|
...(base_config.wechat || {}),
|
||||||
|
...(env_config.wechat || {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(`Config env: ${env}`);
|
||||||
21
config/config.production.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* 生产环境配置(部署时填写真实连接信息)
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
db: {
|
||||||
|
username: "root",
|
||||||
|
password: "your_password",
|
||||||
|
database: "fullstack_template",
|
||||||
|
host: "127.0.0.1",
|
||||||
|
port: 3306,
|
||||||
|
dialect: "mysql",
|
||||||
|
},
|
||||||
|
aliyun: {
|
||||||
|
accessKeyId: "",
|
||||||
|
accessKeySecret: "",
|
||||||
|
ossUrl: "",
|
||||||
|
bucket: "",
|
||||||
|
},
|
||||||
|
redis: null,
|
||||||
|
};
|
||||||
11
config/custom.schemas.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* 自定义 Swagger Schemas
|
||||||
|
* 用于定义复合响应结构和业务特定的 Schema
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
129
config/framework.config.js
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
/**
|
||||||
|
* Framework 配置文件
|
||||||
|
* 基于 node-core-framework 的配置
|
||||||
|
*/
|
||||||
|
|
||||||
|
const path = require("path");
|
||||||
|
const baseConfig = require("./config.js");
|
||||||
|
const customSchemas = require("./custom.schemas.js");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// ===== 必需配置 =====
|
||||||
|
env: process.env.NODE_ENV || 'development',
|
||||||
|
"project_key": "fullstack_template",
|
||||||
|
// 数据库配置(必需)
|
||||||
|
db: {
|
||||||
|
username: baseConfig.db.username,
|
||||||
|
password: baseConfig.db.password,
|
||||||
|
database: baseConfig.db.database,
|
||||||
|
host: baseConfig.db.host,
|
||||||
|
port: baseConfig.db.port || 3306,
|
||||||
|
dialect: baseConfig.db.dialect || 'mysql',
|
||||||
|
timezone: '+08:00',
|
||||||
|
// 远程 MySQL 常见:wait_timeout / 网络设备掐断空闲连接 → 池中连接已死仍被复用 → SequelizeConnectionError
|
||||||
|
dialectOptions: {
|
||||||
|
connectTimeout: 60000,
|
||||||
|
// mysql2@2+ 有效;旧版会忽略未知项
|
||||||
|
enableKeepAlive: true,
|
||||||
|
keepAliveInitialDelay: 10000,
|
||||||
|
},
|
||||||
|
pool: {
|
||||||
|
max: 50,
|
||||||
|
// min>0 会长期占着连接,服务端先断开后易 “Connection lost”;开发/低频接口建议 0
|
||||||
|
min: 0,
|
||||||
|
acquire: 60000,
|
||||||
|
// 空闲超过 idle(ms) 释放回池,应小于 MySQL wait_timeout(常见 600~28800s)
|
||||||
|
idle: 10000,
|
||||||
|
// 定期清理池中空闲连接,降低复用到已被服务端关闭的 TCP
|
||||||
|
evict: 15000,
|
||||||
|
},
|
||||||
|
slowSQLThreshold: 1000,
|
||||||
|
logging: (() => {
|
||||||
|
// 启用SQL监控
|
||||||
|
|
||||||
|
return (sql, timing) => {
|
||||||
|
// 调用监控函数
|
||||||
|
|
||||||
|
// 开发环境仍然输出日志
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(`[SQL] ${timing}ms - ${typeof sql === 'string' ? sql : (sql.sql || JSON.stringify(sql))}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
|
||||||
|
// API 路径配置(必需):模板仅保留管理端业务控制器目录
|
||||||
|
apiPaths: [
|
||||||
|
{
|
||||||
|
path: "./api/controller_admin",
|
||||||
|
prefix: "/admin_api",
|
||||||
|
authType: "admin",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
"fileConifg": {
|
||||||
|
"vue": {
|
||||||
|
"api": "../../admin/src/api/",
|
||||||
|
"view": "../../admin/src/view/"
|
||||||
|
},
|
||||||
|
"node": {
|
||||||
|
"controller": "../controller_admin/",
|
||||||
|
"model": "../model/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// API文档配置
|
||||||
|
swagger: {
|
||||||
|
title: "Fullstack Template API",
|
||||||
|
version: "1.0.0",
|
||||||
|
description: "admin-framework + node-core-framework 模板项目 API",
|
||||||
|
contact: {
|
||||||
|
name: '开发团队',
|
||||||
|
email: 'dev@example.com'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ===== 建议配置 =====
|
||||||
|
|
||||||
|
// 基础 URL(根据环境区分)
|
||||||
|
baseUrl: (() => {
|
||||||
|
const env = process.env.NODE_ENV || 'production';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
switch (env) {
|
||||||
|
case "production":
|
||||||
|
return "http://localhost:9098";
|
||||||
|
case "development":
|
||||||
|
default:
|
||||||
|
return "http://localhost:9098";
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
|
||||||
|
// 日志路径
|
||||||
|
logPath: './logs',
|
||||||
|
|
||||||
|
// 白名单 URL(不需要认证的接口)
|
||||||
|
allowUrls: baseConfig.allowUrls,
|
||||||
|
|
||||||
|
// 授权文件路径(可选,如果不需要授权验证可以设置为 null)
|
||||||
|
// node-core-framework 读取顶层字段 licensePath
|
||||||
|
licensePath: path.join(__dirname, "..", "_license", "license.lic"),
|
||||||
|
|
||||||
|
|
||||||
|
// Redis 配置
|
||||||
|
redis: baseConfig.redis || null,
|
||||||
|
|
||||||
|
// 模型路径
|
||||||
|
modelPaths: './api/model',
|
||||||
|
|
||||||
|
// ===== 业务配置(从原 config.js 继承)=====
|
||||||
|
|
||||||
|
// 端口配置
|
||||||
|
port: baseConfig.port,
|
||||||
|
|
||||||
|
|
||||||
|
// 自定义 Swagger Schemas
|
||||||
|
customSchemas: customSchemas
|
||||||
|
};
|
||||||
|
|
||||||
5
config/model.associations.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/**
|
||||||
|
* 业务模型关联(模板项目无额外 Sequelize 关联,在此按需补充)
|
||||||
|
* @param {Object} models - 所有模型对象
|
||||||
|
*/
|
||||||
|
module.exports = () => {};
|
||||||
9
framework/node-core-framework.js
Normal file
1392
framework/node-core-framework.md
Normal file
39
middleware/baseModel.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 模型导出模块(代理模式)
|
||||||
|
* 从 Framework 动态获取已初始化的模型
|
||||||
|
* 使用 Proxy 延迟获取,确保 Framework 已初始化
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Sequelize = require("sequelize");
|
||||||
|
const Framework = require("../framework/node-core-framework.js");
|
||||||
|
const db = require("./db_proxy");
|
||||||
|
|
||||||
|
module.exports = new Proxy({}, {
|
||||||
|
get(_, prop) {
|
||||||
|
// 获取 Framework 初始化的模型
|
||||||
|
const models = Framework.getModels();
|
||||||
|
|
||||||
|
if (!models) {
|
||||||
|
throw new Error('Framework not initialized. Please call Framework.init() first.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特殊属性处理
|
||||||
|
if (prop === 'Sequelize') {
|
||||||
|
return Sequelize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop === 'op') {
|
||||||
|
return Sequelize.Op;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop === 'querySql') {
|
||||||
|
return async (sql) => {
|
||||||
|
return await db.sequelize.query(sql, { type: Sequelize.QueryTypes.SELECT });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回对应的模型
|
||||||
|
return models[prop];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
7
middleware/base_controller.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* 基础控制器中间件(兼容层)
|
||||||
|
* Framework 已自动处理路由注册,此文件仅用于向后兼容
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = () => async (ctx, next) => await next();
|
||||||
|
|
||||||
24
middleware/db_proxy.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 数据库连接代理
|
||||||
|
* 从 Framework 获取 Sequelize 实例
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Framework = require("../framework/node-core-framework.js");
|
||||||
|
|
||||||
|
module.exports = new Proxy({}, {
|
||||||
|
get(_, prop) {
|
||||||
|
const models = Framework.getModels();
|
||||||
|
|
||||||
|
if (!models) {
|
||||||
|
throw new Error('Framework not initialized. Please call Framework.init() first.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = {
|
||||||
|
sequelize: models.sequelize || global.sequelize,
|
||||||
|
Sequelize: models.Sequelize
|
||||||
|
};
|
||||||
|
|
||||||
|
return db[prop];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
22
middleware/redis_proxy.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Redis 服务代理
|
||||||
|
* 从 Framework 获取 redisService
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Framework = require("../framework/node-core-framework.js");
|
||||||
|
|
||||||
|
module.exports = new Proxy({}, {
|
||||||
|
get(_, prop) {
|
||||||
|
const services = Framework.getServices();
|
||||||
|
const redisService = services?.redisService;
|
||||||
|
|
||||||
|
if (!redisService) {
|
||||||
|
throw new Error('Redis service not available. Framework may not be initialized.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof redisService[prop] === 'function'
|
||||||
|
? redisService[prop].bind(redisService)
|
||||||
|
: redisService[prop];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
8
middleware/schedule.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* 定时任务占位:按需在此注册 node-schedule 任务
|
||||||
|
*/
|
||||||
|
class Schedule {
|
||||||
|
async init() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new Schedule();
|
||||||
15
middleware/sqlUpdate.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
const { querySql, op } = require("./baseModel");
|
||||||
|
class SqlUpdate {
|
||||||
|
async init() {
|
||||||
|
// await this.initDb()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 测试数据库专用用于 初始化db
|
||||||
|
async initDb() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sqlUpdate = new SqlUpdate();
|
||||||
|
module.exports = sqlUpdate;
|
||||||
0
migrations/init.sql
Normal file
27
nodemon.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"watch": ["app.js", "config", "middleware", "api", "tool", "framework"],
|
||||||
|
"ext": "js,mjs,cjs,json",
|
||||||
|
"delay": 2000,
|
||||||
|
"legacyWatch": true,
|
||||||
|
"ignore": [
|
||||||
|
"node_modules/**",
|
||||||
|
"admin/**",
|
||||||
|
"logs/**",
|
||||||
|
"temp/**",
|
||||||
|
".git/**",
|
||||||
|
"*.log",
|
||||||
|
"*.xlsx",
|
||||||
|
"**/*.test.js",
|
||||||
|
"_doc/**",
|
||||||
|
"scripts/**",
|
||||||
|
"__tests__/**"
|
||||||
|
],
|
||||||
|
"watchOptions": {
|
||||||
|
"usePolling": true,
|
||||||
|
"interval": 1500,
|
||||||
|
"awaitWriteFinish": {
|
||||||
|
"stabilityThreshold": 1500,
|
||||||
|
"pollInterval": 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16865
package-lock.json
generated
Normal file
73
package.json
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"name": "frameapi",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "light22600",
|
||||||
|
"description": "a node webApi",
|
||||||
|
"main": "app.js",
|
||||||
|
"type": "commonjs",
|
||||||
|
"dependencies": {
|
||||||
|
"@alicloud/dysmsapi20170525": "^4.1.2",
|
||||||
|
"@alicloud/green20220302": "^3.2.1",
|
||||||
|
"@alicloud/nls-filetrans-2018-08-17": "^1.0.0",
|
||||||
|
"@alicloud/openapi-client": "^0.4.15",
|
||||||
|
"@alicloud/pop-core": "^1.7.10",
|
||||||
|
"@alicloud/tea-util": "^1.4.10",
|
||||||
|
"@koa/cors": "^5.0.0",
|
||||||
|
"ali-oss": "^6.3.1",
|
||||||
|
"art-template": "^4.13.2",
|
||||||
|
"axios": "^0.24.0",
|
||||||
|
"cli-progress": "^3.11.2",
|
||||||
|
"crypto": "^1.0.1",
|
||||||
|
"dayjs": "1.8.24",
|
||||||
|
"echarts": "^5.1.2",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"fs": "0.0.1-security",
|
||||||
|
"gsap": "^3.12.5",
|
||||||
|
"https": "^1.0.0",
|
||||||
|
"ioredis": "^5.4.1",
|
||||||
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"koa": "^2.8.1",
|
||||||
|
"koa-body": "^6.0.1",
|
||||||
|
"koa-router": "^7.4.0",
|
||||||
|
"koa-sslify": "^4.0.3",
|
||||||
|
"koa-static": "^5.0.0",
|
||||||
|
"koa2-cors": "^2.0.6",
|
||||||
|
"koa2-swagger-ui": "^5.11.0",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"md5": "^2.2.1",
|
||||||
|
"mysql2": "^1.7.0",
|
||||||
|
"node-schedule": "^1.3.2",
|
||||||
|
"node-uuid": "^1.4.8",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
|
"redis": "^3.0.2",
|
||||||
|
"sequelize": "^5.19.0",
|
||||||
|
"string-random": "^0.1.3",
|
||||||
|
"superagent": "^10.2.3",
|
||||||
|
"swagger-jsdoc": "^6.2.8",
|
||||||
|
"threads": "^1.7.0",
|
||||||
|
"urlencode": "^1.1.0",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
|
"wechatpay-node-v3": "^2.2.1",
|
||||||
|
"xlsx": "^0.18.5",
|
||||||
|
"xml2js": "^0.4.23"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.58.2",
|
||||||
|
"@types/jest": "^30.0.0",
|
||||||
|
"assert": "^2.0.0",
|
||||||
|
"cookie": "^0.5.0",
|
||||||
|
"istanbul": "^0.4.5",
|
||||||
|
"jest": "^30.0.5",
|
||||||
|
"mocha": "^10.1.0",
|
||||||
|
"nodemon": "^2.0.15",
|
||||||
|
"nyc": "^17.1.0",
|
||||||
|
"supertest": "^7.1.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "node app.js",
|
||||||
|
"api": "nodemon --config nodemon.json app.js",
|
||||||
|
"dev": "cd ./admin&&npm run dev -- --port 9000 ",
|
||||||
|
"build": "cd ./admin&&npm run build "
|
||||||
|
},
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
12
sql/tpl_demo.sql
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
-- 模板演示表(与 api/model/tpl_demo.js 一致;无 createdAt/updatedAt)
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `tpl_demo` (
|
||||||
|
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`title` varchar(200) NOT NULL COMMENT '标题',
|
||||||
|
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||||
|
`create_time` datetime NOT NULL COMMENT '创建时间',
|
||||||
|
`last_modify_time` datetime NOT NULL COMMENT '最后修改时间',
|
||||||
|
`is_delete` tinyint NOT NULL DEFAULT '0' COMMENT '0 正常 1 已删除',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_tpl_demo_is_delete` (`is_delete`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='前后端模板演示表';
|
||||||
105
tool/funTool.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const { sys_parameter } = require("../middleware/baseModel");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
delay(seconds = 1, callback) {
|
||||||
|
let promise = new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
resolve(true);
|
||||||
|
}, seconds * 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
mkdirsSync(dirname) {
|
||||||
|
if (fs.existsSync(dirname)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (this.mkdirsSync(path.dirname(dirname))) {
|
||||||
|
fs.mkdirSync(dirname);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
isExist(path) {
|
||||||
|
let promise = new Promise((resolve, reject) => {
|
||||||
|
fs.access(path, function (err) {
|
||||||
|
if (err && err.code == "ENOENT") {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算两个地理坐标点之间的距离(Haversine公式)
|
||||||
|
* @param {number} lat1 起点纬度
|
||||||
|
* @param {number} lng1 起点经度
|
||||||
|
* @param {number} lat2 终点纬度
|
||||||
|
* @param {number} lng2 终点经度
|
||||||
|
* @returns {number|null} 距离(公里),如果坐标无效则返回null
|
||||||
|
*/
|
||||||
|
calculateDistance(lat1, lng1, lat2, lng2) {
|
||||||
|
// 验证坐标参数
|
||||||
|
if (!lat1 || !lng1 || !lat2 || !lng2 ||
|
||||||
|
isNaN(lat1) || isNaN(lng1) || isNaN(lat2) || isNaN(lng2)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const R = 6371; // 地球半径(公里)
|
||||||
|
const dLat = (lat2 - lat1) * Math.PI / 180;
|
||||||
|
const dLng = (lng2 - lng1) * Math.PI / 180;
|
||||||
|
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||||
|
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
|
||||||
|
Math.sin(dLng / 2) * Math.sin(dLng / 2);
|
||||||
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
|
const distance = R * c;
|
||||||
|
|
||||||
|
// 保留两位小数
|
||||||
|
return Math.round(distance * 100) / 100;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户位置信息
|
||||||
|
* @param {number} userId 用户ID
|
||||||
|
* @returns {Object|null} 用户位置信息 {latitude, longitude, hasLocation}
|
||||||
|
*/
|
||||||
|
async getUserLocation(userId) {
|
||||||
|
try {
|
||||||
|
const { wch_users } = require("../middleware/baseModel");
|
||||||
|
const user = await wch_users.findOne({
|
||||||
|
where: { id: userId, is_delete: 0 },
|
||||||
|
attributes: ['id', 'latitude', 'longitude']
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) return null;
|
||||||
|
|
||||||
|
const userLat = parseFloat(user.latitude);
|
||||||
|
const userLng = parseFloat(user.longitude);
|
||||||
|
const hasLocation = userLat && userLng && !isNaN(userLat) && !isNaN(userLng);
|
||||||
|
|
||||||
|
return {
|
||||||
|
latitude: userLat,
|
||||||
|
longitude: userLng,
|
||||||
|
hasLocation
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取用户位置失败:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
25
tool/logs_proxy.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 日志服务代理
|
||||||
|
* 从 Framework 获取 logsService
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Framework = require('../framework/node-core-framework.js');
|
||||||
|
|
||||||
|
// 控制台日志备用方案
|
||||||
|
const consoleLogs = {
|
||||||
|
log: (message, level = 'info') => console.log(`[${level.toUpperCase()}] ${message}`),
|
||||||
|
error: (message) => console.error(`[ERROR] ${message}`),
|
||||||
|
warn: (message) => console.warn(`[WARN] ${message}`)
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = new Proxy({}, {
|
||||||
|
get(_, prop) {
|
||||||
|
const services = Framework.getServices();
|
||||||
|
const logsService = services?.logsService || consoleLogs;
|
||||||
|
|
||||||
|
return typeof logsService[prop] === 'function'
|
||||||
|
? logsService[prop].bind(logsService)
|
||||||
|
: logsService[prop];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||